tenseleyflow/sway / fe19524

Browse files

README + CHANGELOG: training_drift paragraph + Sprint 33 block

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
fe19524c3488198d7787cf927f0fcb08f19fa905
Parents
73d923c
Tree
5205499

2 changed files

StatusFile+-
M CHANGELOG.md 88 0
M README.md 18 2
CHANGELOG.mdmodified
@@ -2,6 +2,94 @@
22
 
33
 ## Unreleased
44
 
5
+### Sprint 33 — `training_drift` probe (cross-repo, reads dlm loss curves)
6
+
7
+Closes the X2 "training_drift probe" backlog item. Sister to S25
8
+`gradient_ghost`: where the ghost reads optimizer state at
9
+end-of-training, `training_drift` reads the loss *curve* during
10
+training. Both are pre-run, no model load, no backend required.
11
+
12
+**New probe (`kind: training_drift`, category: calibration).**
13
+
14
+For a dlm store, the probe parses every `train-*.jsonl` under
15
+`<store_path>/logs/`, dedupes resumed runs (latest occurrence wins),
16
+and computes four metrics:
17
+
18
+- `final_loss` — last recorded step's loss.
19
+- `convergence_ratio` — `final_loss / initial_loss`.
20
+- `smoothness` — `1 − var(Δloss) / var(loss)`, clipped to `[0, 1]`.
21
+- `instability_events` — count of loss-*increase* events whose
22
+  magnitude exceeds the local typical movement scale (median
23
+  absolute delta in a centered window). NaN losses count as one
24
+  instability each, then forward-fill so downstream stats stay
25
+  finite.
26
+
27
+Verdict PASS when all three thresholds clear (smoothness ≥ 0.7,
28
+convergence_ratio ≤ 0.7, instability_events ≤ 0). Otherwise WARN
29
+with each failed threshold listed in the message.
30
+
31
+**Spike heuristic note.** Sprint plan called for `|Δloss| > 3 ·
32
+rolling_std`. Implementation rejects that: on a smooth exponential
33
+decay, within-window std-of-deltas stays tiny while absolute deltas
34
+are large — every step trips the threshold (verified during dev:
35
+60-step smooth curve flagged 59 false-positive spikes). Replaced
36
+with: count *positive* deltas (loss going up — the semantically
37
+meaningful instability) that exceed `sigma · median(|Δ|)` in a
38
+centered window. Robust to scale changes across training; loss
39
+going down faster than usual is no longer mistaken for instability.
40
+
41
+**No null calibration.** Mirrors `prompt_collapse` and
42
+`multi_turn_coherence_decay`: a null adapter has no loss curve,
43
+the null distribution of "smoothness on a noise adapter" is
44
+undefined. Fixed-threshold verdicts; users override per-spec.
45
+
46
+**Log-format note.** Sprint plan said dlm writes per-step JSONs at
47
+`logs/train_step_*.json`. Reality (verified against
48
+`~/.dlm/store/`): one mixed JSONL per run at
49
+`logs/train-NNNNNN-YYYYMMDDTHHMMSS.jsonl` containing banner +
50
+delta + step + run_complete records. The probe filters for
51
+`{"type": "step"}` lines and reads `step` + `loss`. Sibling
52
+`*.summary.json` carries run aggregates we don't consume — the
53
+curve is richer.
54
+
55
+**Robustness:**
56
+- Resumed runs (overlapping step numbers across multiple jsonls):
57
+  dedupe-by-keep-latest mirrors `dlm metrics` semantics.
58
+- Truncated JSONL tail (crashed-mid-line trainer): partial line
59
+  is skipped; valid lines still consumed.
60
+- NaN losses are recorded as `+inf` so the spike detector flags
61
+  them without numpy NaN poisoning the rest of the pipeline.
62
+- 1500-step run downsamples to ≤ 512 evidence points (uniform
63
+  stride; first + last always preserved).
64
+- Pathological: every step recorded NaN → `smoothness = 0.0`,
65
+  `instability_events = num_steps`, verdict WARN.
66
+
67
+**Implementation:**
68
+- `probes/training_drift.py` — spec, probe, JSONL parser,
69
+  metric helpers, verdict mapping, downsampler. 365 LOC.
70
+- `probes/__init__.py` — registers the new probe.
71
+
72
+**Test surface:**
73
+- `tests/unit/test_probe_training_drift.py` — 30 unit tests
74
+  covering: skip paths (no store_path / no logs dir / no jsonl /
75
+  too few steps), end-to-end with smooth & spiky curves,
76
+  resume-deduplication, downsampling, corrupt-first-line ERROR,
77
+  truncated-tail tolerance, pure-math metric helpers
78
+  (smooth/constant/NaN/all-NaN/zero-initial), spike-detector
79
+  heuristic (loss-up vs loss-down semantics, short curves,
80
+  empty), downsampling, verdict mapping, and JSONL parsing
81
+  (filter non-step records, missing keys, NaN encoding,
82
+  missing files).
83
+- `tests/fixtures/dlm_train_log_fixture.jsonl` — captured-from-disk
84
+  shape: banner + delta record + 30 step records + run_complete.
85
+  If this fixture's parse breaks, dlm's log format has shifted and
86
+  the probe needs an update — the test catches that explicitly.
87
+
88
+**README** gains a `training_drift` paragraph in "Pre-run
89
+diagnostics" alongside `gradient_ghost`. The probe table at "Why
90
+it exists" picks up `training_drift` and the previously-missed
91
+`gradient_ghost` entry under Calibration.
92
+
593
 ### Sprint 30 — `multi_turn_coherence_decay` probe
694
 
795
 Closes the P2 "multi_turn_coherence_decay probe" backlog item. Sway
README.mdmodified
@@ -124,6 +124,22 @@ spec doesn't load a model, doesn't allocate GPU memory, doesn't pay
124124
 the cold-start tax. Useful for `pre-commit` gates that should fast-
125125
 path adapter sanity before authorizing the full battery.
126126
 
127
+The **`training_drift`** probe pairs with `gradient_ghost`: where the
128
+ghost reads optimizer state at end-of-training, drift reads the loss
129
+*curve* during training (from dlm's per-step JSONL logs):
130
+
131
+```
132
+⚠️  training_drift flagged unstable training
133
+    smoothness=0.42, convergence_ratio=0.91, instability_events=4
134
+```
135
+
136
+Four metrics: `final_loss`, `convergence_ratio`, `smoothness`
137
+(1 − var(Δloss)/var(loss)), and `instability_events` (loss-increase
138
+spikes that exceed local typical movement). Verdict PASS when all
139
+three of (smoothness ≥ 0.7, instability_events == 0,
140
+convergence_ratio ≤ 0.7); else WARN. Like `gradient_ghost`, runs in
141
+~10 ms with no model load.
142
+
127143
 ## Full suite
128144
 
129145
 ```yaml
@@ -169,14 +185,14 @@ user-authored document. The right question is *"did the adapter actually
169185
 move the model toward what I wrote?"* — and existing tools answer this
170186
 poorly.
171187
 
172
-`sway` answers it directly via fourteen primitives across four
188
+`sway` answers it directly via fifteen primitives across four
173189
 categories, plus a baseline-calibration primitive:
174190
 
175191
 | Category      | Primitives                                            |
176192
 |---------------|-------------------------------------------------------|
177193
 | Adherence     | `delta_kl`, `adapter_revert`, `prompt_collapse`, `cluster_kl`, `multi_turn_coherence_decay` |
178194
 | Attribution   | `section_internalization`, `paraphrase_invariance`, `preference_flip`, `tool_use_fidelity` |
179
-| Calibration   | `style_fingerprint`, `calibration_drift`, `leakage`, `external_perplexity` |
195
+| Calibration   | `style_fingerprint`, `calibration_drift`, `leakage`, `external_perplexity`, `gradient_ghost`, `training_drift` |
180196
 | Ablation      | `adapter_ablation` ← the signature primitive          |
181197
 | Baseline      | `null_adapter` (powers every z-score in the report)   |
182198