Skip to main content

somatize_health/
seen.rs

1//! What was measured about one node over one window.
2
3/// The numbers a verdict is taken over.
4///
5/// **`None` means nobody measured it**, which is not zero and not healthy. A
6/// node with no parameters has no gradient norm; a window with no snapshot in
7/// it has no effective rank; per-channel statistics are opt-in because they
8/// cost a reduction per step. A metric that was not taken cannot raise a flag,
9/// and it must not silently pass for one that was taken and came out fine.
10///
11/// It is `Option<f64>` and not a `NaN` sentinel, which is what the original
12/// used. `NaN < x` being false does make an unobserved metric quietly not
13/// flag — elegant, and it works right up until somebody sums a column.
14///
15/// # A window, not a step
16///
17/// Every field here is already reduced over however many steps the caller
18/// decided to look at. Which steps those were, and how many, is the caller's:
19/// a verdict that quietly needed a particular cadence would be a threshold
20/// hiding in a schedule.
21#[derive(Debug, Clone, Default, PartialEq)]
22pub struct Seen {
23    /// Anything in the window was not a number.
24    pub nan: bool,
25    /// Anything in the window was not finite.
26    pub inf: bool,
27    /// The mean L2 norm of this node's parameter gradients.
28    pub grad_norm: Option<f64>,
29    /// The **largest** fraction of the output that was off, over the window.
30    pub zero_frac_max: Option<f64>,
31    /// The **largest** fraction that was pinned, over the window.
32    pub sat_frac_max: Option<f64>,
33    /// The mean ratio of the size of a step to the size of the weights it
34    /// moved.
35    pub update_ratio: Option<f64>,
36    /// How many channels were off across the whole window.
37    pub dead_channels: usize,
38    /// How many were alive and never asked for.
39    pub ignored_channels: usize,
40    /// What fraction of the channels are dormant on Sokar's normalised score.
41    pub dormancy_frac: Option<f64>,
42    /// The largest linear CKA between two groups the caller declared separate.
43    pub group_cka: Option<f64>,
44    /// The effective rank of the representation at the end of the window.
45    pub eff_rank: Option<f64>,
46    /// How the effective rank is moving, per step and relative to itself.
47    pub eff_rank_slope: Option<f64>,
48    /// How the norm of the parameters is moving, likewise.
49    pub param_norm_slope: Option<f64>,
50    /// The stable rank of the update over the window: how many directions this
51    /// node actually moved in.
52    pub update_rank: Option<f64>,
53    /// And what that usually was for this run, which is the only reference a
54    /// single training run has.
55    pub update_rank_usual: Option<f64>,
56    /// The scale of the signal here against where the last normalisation
57    /// upstream left it.
58    ///
59    /// Measured before a step is taken, by a probe rather than by an audit: a
60    /// probe knows what feeds what, because it traced it. Where the last
61    /// normalisation **is** is structure and not a bound, which is why it is
62    /// baked into the number and the threshold stays on this side of the wall.
63    pub signal_gain: Option<f64>,
64    /// The factor a gradient at the output arrives here by:
65    /// `sqrt(E||J^T v||^2)` over random probes, from this layer to the output.
66    ///
67    /// Deliberately not a parameter-gradient norm. At initialisation there is
68    /// no loss, so a gradient norm would be taken against a target somebody
69    /// made up and would land in [`Seen::grad_norm`] at a different scale, to
70    /// be judged by the same bound. This one is a ratio and needs no target.
71    ///
72    /// **It raises nothing, and that was measured.** Walked across criticality,
73    /// the worst network that still trained read 1.41 and the best one that did
74    /// not read 1.95: a factor of 1.4 is where the sampling landed and not a
75    /// bound. See `health/tests/isometry.py`. The number is recorded and drawn,
76    /// because its profile over depth is the vanishing picture and a person can
77    /// read one.
78    pub jacobian_gain: Option<f64>,
79    /// How spread that Jacobian's spectrum is: `s_max / s_rms` of a random
80    /// sketch of it.
81    ///
82    /// Dynamical isometry (Pennington et al., 2017) is a claim about the
83    /// spectrum's **shape** and not its size — a flat one trains dramatically
84    /// faster than one with the same mean and a long tail — and a mean cannot
85    /// see the difference.
86    ///
87    /// **It raises nothing either, and the inversion is the reason**: a network
88    /// reading 1.87 trained and one reading 1.76 did not, so the failing one
89    /// had the tighter spectrum.
90    ///
91    /// There is a rule underneath both of these and it is worth more than
92    /// either. [`Flag::MissingNormalisation`](crate::Flag::MissingNormalisation)
93    /// separates because the forward scale is a **runaway** — a geometric
94    /// process either stays put or leaves by decades, and there is nothing in
95    /// between to be wrong about. These two vary **continuously** with how well
96    /// a network turns out, and something continuous is a ranking. A ranking
97    /// belongs at level 3, beside the proxies, where a number only ever means
98    /// something next to another candidate's.
99    pub jacobian_spread: Option<f64>,
100}