๐Ÿ”ฌ Trainsect โ€” git bisect for training runs

Your training run collapsed. Which exact optimizer step, data shard, and batch caused it? Trainsect answers with a causal proof, not an attribution score: it reloads optimizer state at a checkpoint, replays forward deterministically, binary-searches the checkpoint timeline, then counterfactually replays with each data shard excluded until only the guilty shard remains.

How the search narrows to the exact shard

full run โ€” 120 steps, 6 shards ร— 20 batches
Stage A โ€” symptom: eval-loss change point at step 80
Stage B โ€” bisect: checkpoint binary search โ†’ bracket (60, 90)
Stage C โ€” pinpoint: trigger step 60 (first train spike inside bracket)
Stage D โ€” shard: offending shard = shard-3
Stage E โ€” proof: necessity + sufficiency โœ“

Necessity = replay the trigger interval without shard-3 and the collapse vanishes. Sufficiency = replay it with only shard-3 and the collapse reproduces. That pair is a causal proof for this trajectory โ€” not an attribution score.

Evidence report (preloaded)

VerdictCONFIRMED (causal)
Provenanceinstrumented
Confidencecausal by counterfactual replay (necessity + sufficiency)
FidelityBIT-EXACT
Change pointstep 80 (threshold 3.0, baseline 0.0019, eval at change point 4.082)
Step range[60, 90]
Trigger step60
Offending shardshard-3
Batch fingerprint639a024b373ce19a
NecessityTrue
SufficiencyTrue

Minimal repro

trainsect replay --checkpoints demos/demo_run (preloaded) --from-step 60 --steps 30 --exclude-shard 3 # collapse vanishes (necessity)
trainsect replay --checkpoints demos/demo_run (preloaded) --from-step 60 --steps 30 --only-shard 3 # collapse reproduces (sufficiency)
trainsect replay --checkpoints demos/demo_run (preloaded) --from-step 60 --steps 30 --only-batch 639a024b373ce19a # trigger batch alone

Full terminal transcript (preloaded, verbatim)

=== TRAINSECT DEMO: nanoGPT-style tiny transformer + poison shard ===
training 120 steps, poison shard=3 (steps 60-79)
  step   0 shard 0 train_loss=  4.3811
  step  20 shard 1 train_loss=  0.1188
  step  40 shard 2 train_loss=  0.0027
  step  60 shard 3 train_loss= 12.4577
  step  80 shard 4 train_loss=  4.1259
  step 100 shard 5 train_loss=  4.0403
eval loss log: ['0:4.298', '10:1.749', '20:0.072', '30:0.006', '40:0.002', '50:0.002', '60:0.002', '70:3.871', '80:4.082', '90:4.067', '100:4.055', '110:4.026', '119:3.997']
demo run saved to demos/demo_run (preloaded)

=== TRAINSECT DIAGNOSE (live) ===
[fidelity] replay 7622e7196398a345 vs original 7622e7196398a345 -> BIT-EXACT
[Stage A] change point: {'change_point_step': 80, 'threshold': 3.0, 'baseline': 0.001927278353832662, 'eval_loss_at_cp': 4.0823655128479}
[thresholds] train-spike=3.035 eval-health=3.000
[Stage B] checkpoint binary search -> {'ok': True, 'bracket': (60, 90)}
[Stage C] pinpoint: first train spike at step 60 (loss=12.457655906677246); first eval crossing at 70 inside bracket [60, 90)
[Stage D] offending shard = shard-3
[Stage E] necessity=True sufficiency=True
[Stage E] trigger batch: step 60, shard 3, fingerprint 639a024b373ce19a
[Stage E] offending sample: {'sample_index_in_batch': 3, 'sample_loss': 13.739205360412598, 'batch_mean_loss': 12.457656860351562}

=== TRAINSECT EVIDENCE REPORT ===
run:                 demos/demo_run (preloaded)
verdict:             CONFIRMED (causal)
provenance:          instrumented
confidence:          causal by counterfactual replay (necessity + sufficiency)
fidelity:            BIT-EXACT
change point:        80 (threshold=3.0)
step range:          [60, 90]
trigger step:        60
offending shard:     shard-3
batch fingerprint:   639a024b373ce19a
offending sample:    {'sample_index_in_batch': 3, 'sample_loss': 13.739205360412598, 'batch_mean_loss': 12.457656860351562}
necessity:           True
sufficiency:         True
replays:             8 (540 replayed steps)
wall time:           52.2s

minimal repro:
  trainsect replay --checkpoints demos/demo_run (preloaded) --from-step 60 --steps 30 --exclude-shard 3 # collapse vanishes (necessity)
  trainsect replay --checkpoints demos/demo_run (preloaded) --from-step 60 --steps 30 --only-shard 3 # collapse reproduces (sufficiency)
  trainsect replay --checkpoints demos/demo_run (preloaded) --from-step 60 --steps 30 --only-batch 639a024b373ce19a # trigger batch alone

How it works โ€” causal replay-bisection

  1. Deterministic replay. A training step is a pure function of (model, optimizer, scheduler, batch, RNG) state. Checkpoints store all of it with pre-update semantics. Before trusting any bisection, Trainsect replays a segment twice and compares SHA-256 parameter hashes โ€” if they disagree, bisection halts.
  2. Collapse predicate (eval-based, never train-spike). A checkpoint counts as collapsed only by its eval loss. Transient train-loss spikes on a healthy model never count.
  3. Checkpoint binary search. Predicate P(c) = "checkpoint c is healthy AND replaying forward reproduces the collapse" is monotone, so binary search brackets the trigger to one checkpoint interval in O(log n) replays.
  4. Counterfactual shard bisection. Replay the trigger interval with each candidate shard's batches replaced by benign substitutes. The survivor must pass necessity (collapse vanishes without it) and sufficiency (collapse reproduces with only it).
  5. Evidence report. JSON + human-readable report with minimal-repro commands, so anyone can re-run the proof.

Honest scope + limitations

Forensic mode โ€” checkpoints alone

diagnose takes the forensic path automatically when a directory lacks recorder data. It reconstructs batch orders from the known pool under multiple seeds (3 for the bracket search, 8 for confirmation) and names a shard only if necessity + sufficiency + specificity all hold and frozen-weight loss ranking agrees. Otherwise it reports UNPROVEN with what evidence would settle it. Forensic mode never sets a batch fingerprint or trigger step, and its verdict is never CONFIRMED (causal).

Measured on the two demo runs, stripped of recorder data: the P0 validation run (MLP) diagnoses FORENSIC (probable), shard-2, bracket [40, 80) โ€” collapse reproduces under 8/8 reconstructed orders. The transformer demo diagnoses UNPROVEN, no shard named โ€” its collapse is concentration-dependent and reconstructed orders cannot reproduce it, so no claim is made.

Methodology โ€” the novel claim

Every existing training-debugger bisects the wrong axis:

Trainsect bisects the training-time axis itself: it treats the optimizer trajectory as a deterministic, replayable computation, reloads optimizer state at step t, replays forward, and binary-searches the exact step-range where the collapse triggers. Then it bisects the data-shard axis with counterfactual replay (collapse disappears iff shard S is excluded) to name the exact shard, batch, and sample.

The output is not a ranked hypothesis. It is a falsifiable, re-runnable minimal repro. No existing tool combines (a) deterministic optimizer-step replay across checkpoints, (b) binary search over the checkpoint timeline, and (c) causal data-shard bisection.

Deterministic replay

A training step is deterministic given complete state: ฮธt+1, optt+1, rngt+1 = STEP(ฮธt, optt, batcht, rngt). Checkpoints capture model + optimizer + scheduler state; the one-line recorder hook captures dataloader cursor, batch sequence, RNG states, worker seeds, and determinism flags. Before trusting any bisection, Trainsect replays t โ†’ t+k twice and compares SHA-256 parameter hashes โ€” if they disagree, bisection halts and reports the divergence.

Collapse detection

Two stages. Stage A is cheap and runs on logs: change-point detection on the eval-loss curve (metric predicate + binary segmentation). Stage B is causal and runs on replay: P(c) = checkpoint c is healthy (its own eval loss below threshold) AND replaying forward c โ†’ B reproduces the collapse. The predicate is eval-based, not train-loss-spike-based โ€” a healthy model can show transient train-loss spikes that a spike-based predicate mistakes for collapses. Eval loss is the collapse signal; per-step train loss pinpoints the trigger batch inside an already-established collapse bracket.

Checkpoint binary search + shard bisect

Checkpoints at steps {c0 < c1 < โ€ฆ < cn}, known-bad point B. P(c) is monotone in c, so binary search brackets the trigger to one checkpoint interval in O(log n) replays. Then counterfactual shard bisection: replay the trigger interval with each candidate shard's batches replaced by benign substitutes from a known-clean shard. The survivor must pass necessity and sufficiency. Batch โ†’ sample: within the trigger batch, the offending sample is the one whose per-sample loss dominates the batch. The evidence report ships minimal-repro commands for all three counterfactuals.

Instrumented mode โ€” the one-line recorder hook

start_recording(...) / record(step, batch, ...) / stop_recording(...) โ€” record() is called after the step's update, preserving pre-update checkpoint semantics. Measured overhead 0.7โ€“1.0% of step time. A hook-recorded run replays bit-exact.

Gap verification (2026-10-14)

Checked against rldk (bisects code commits), culprit (bisects checkpoint releases across runs, robotics), merriv (bisects precomputed labels), NeuralDBG (in-loop hypotheses without replay), and TRAK/influence functions (approximate scores, not causal replay). Manual data bisection exists only as practitioner folklore. Nothing does deterministic optimizer-step replay + checkpoint binary search + data-shard bisect.

Full methodology: docs/P0-methodology.md in the repo.