This repository was archived by the owner on Jul 17, 2026. It is now read-only.
perf(recon): prefix-shared masked forwards — one clean pass seeds every recon forward#965
Open
danbraunai-goodfire wants to merge 2 commits into
Open
perf(recon): prefix-shared masked forwards — one clean pass seeds every recon forward#965danbraunai-goodfire wants to merge 2 commits into
danbraunai-goodfire wants to merge 2 commits into
Conversation
…ry recon forward (S18 amended, new D5)
Masked forwards take a FORWARD START instead of the token batch:
start_from_inputs (input edge; eval tiers) or masked_start (the clean pass's
resid.{first live block} boundary; every recon/ascent forward). The step's one
clean_output_and_taps segmented scan emits the CI taps AND the start seeds, so
the frozen prefix below the earliest live site is never recomputed — measured
~1.32x fleet-avg step time on single-block dp8 runs (up to ~2.6x for late
blocks), and each chunk pass of a chunkwise run truncates at its own start.
S2/S3/S10'/S24 untouched; S18 amended (clean path still token-fed and
whole-model, starts are model-opaque and in-graph — none of the removed
residual-start engine machinery returns); new D5 pins tap-start == input-start
equivalence up to reassociation, guarded by tests/test_prefix_start.py
(deterministic + stochastic paths, both LM targets, remat on/off).
Also un-bitrots experiments/invariance_check.py (fed a float residual batch —
stale since the 2026-06-24 token contract); D4 passes on the new step.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011F8uqqWqnkC48Y8G4QCKsk
…unused test default Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011F8uqqWqnkC48Y8G4QCKsk
claude-spd1
reviewed
Jul 9, 2026
claude-spd1
left a comment
Collaborator
There was a problem hiding this comment.
Crew review (task review-pr-965-prefix-shared-recon). Recommend merge once Oli signs off the S18/D5 amendment — that gate is the only blocker I see.
Verified:
- Read the full diff; re-ran the D5 gate (
test_prefix_start.py) plustest_llama8b/test_llama_simple_mlp/test_eval/test_generic_model_io/test_attn_patterns_eval/test_hidden_acts_eval/test_source_grad_meanatcfc6f65c9locally: 53 passed. CI green. - The seam respects the 2026-06-24 removal's substance: starts are built and consumed target-side, the engine never inspects one (S4-style opacity), no
Prefix/first_layer/harvest machinery returns.clean_outputstays the whole-model frozen forward (S3), andwanted=()makesclean_output_and_tapsliterally the old unsplit scan. _run_masked_forward'sstart_block <= first_liveassert + the contiguous-live assert cover the seeding contract; live=() correctly degrades to a frozen scan fromstart_block.- Stochastic start-invariance rests on absolute-layer key folding and is pinned by test.
Findings (all minor, none blocking):
targets/llama8b.pyclass docstring (~line 512) namesstart_from_taps— the method ismasked_start.eval.pybuildsmasked_start(token_ids, taps, site_names)from taps requested as bareci_fn.input_names, relying implicitly on the chunkwise builder's guarantee that the first chunk tapsresid.{earliest decomposed block}. True today (_resolved_chunks), and a violation fails loud (KeyError at trace), but train.py unionsstart_tapsintowanted— eval.py doing the same one-line union would make the invariant structural instead of coincidental.- Config-space narrowing: an LM target +
layerwise_mlp/global_mlpCI fn (site-nameinput_names) was previously traceable and now trips theresid.*-only assert inclean_output_and_tapsat trace time. The config union permits the combo though the docstrings scope MLP CI fns to positionless toys — if it's meant to be illegal, a guard at config resolution would say so sooner and clearer. - The chunkwise multi-block claim ("each chunk pass truncated at its own chunk start") is covered by tests but not by the live A/B (both A/B runs are single-block). Worth one chunkwise smoke before leaning on the number for chunk-run capacity planning.
On the two flagged veto-able choices, my recommendation is to keep the PR's current shape on both:
- Keep the
clean_outputone-line delegator.clean_outputis S3's name for a real concept;clean_output_and_taps(x, ())[0]at 15 call sites trades a one-liner for noise everywhere. - Keep
start_from_inputs+masked_startseparate. They have different signatures, different call sites (eval tiers vs recon/ascent), and collapsing them means an optional-tapsmega-constructor — worse, not simpler.
Crew-Address: task/review-pr-965-prefix-shared-recon
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Measured speedup (dp8 single-block smokes, B200, 300-step A/B at identical config + seed):
The win scales with block depth (fleet average over 32 per-block runs ≈ 1.20×), and chunkwise multi-block runs get each chunk pass truncated at its own chunk start. Losses match the baseline runs to 4 significant figures in early steps; the layer-31 trajectory is identical through all 300 steps, layer-18 within 0.5% at step 300 (reassociation amplification, D5).
Where the speedup comes from: a step previously ran the frozen prefix of the target ~6× — once in
clean_output, once inread_activations, and once inside each masked forward (2 PPGD warmup ascents + stochastic recon + PPGD main), which all re-embedded the batch and re-ran every block below the first live site. Now the step runs ONE clean pass (clean_output_and_taps, a segmented scan) that emits the recon target, the CI taps, and the residual boundaries; every masked/ascent forward starts from its entry's boundary (masked_start→(first_live_block, resid)), so the frozen prefix below the earliest live site is never recomputed. For a single-block-18 decomposition that deletes 4–5 × 18 frozen-block forwards per step.Mechanically: the
DecomposedModelmasked family (masked_output/masked_output_stochastic/masked_site_outputs/masked_component_activations) takes a model-opaque forward start instead of the token batch, built via two constructors —start_from_inputs(input edge; eval tiers) andmasked_start(tap-seeded; all recon/ascent forwards). The engine stays d-blind: starts are built and consumed target-side (the S4 opacity rule),start_blockis static per recon-plan entry, and all-sites passes on a full-model run degrade gracefully tostart_block = 0. All sevenDecomposedModelimpls migrated (toys: identity — their start IS their input). llama8b's already-segmented[prefix][live][suffix]scan just gets its prefix sliced[start_block, first_live);llama_simple_mlpdestructures the start's static int OUTSIDE itsjax.checkpoint(tracer hazard, pinned by test).This seam is also the substrate for later activation caching (
residfrom disk instead of the clean pass) and adjacent to block-local recon experiments.Related Issue
None — motivated by the per-block (two-stage) training investigation; step-cost model measured in wandb group
l18only(runs 172591/172594-172596, A/B 172602/172603).Motivation and Context
Per-block VPD runs (the l18-style runs and the planned per-block stage-1 sweep) and mid-stack chunkwise runs (l18-26) spend a large share of each step recomputing a frozen prefix whose value the clean forward already produced.
SPEC (needs @oli sign-off per the one rule): S18 amended — masked forwards take a forward start; the clean path still takes the token batch and embeds internally,
clean_outputstays the whole-model frozen forward (S3 untouched), and NONE of the removed residual-start engine machinery returns (noPrefixobject, nofirst_layeroffsets, no out-of-graph harvest, no tap ceiling — cf. the 2026-06-24 removal). New D5: tap-start ≡ input-start up to float reassociation. S2/S3/S10′/S24 untouched. Glossary + §4.1/§4.5 pseudocode updated.One drive-by fix:
experiments/invariance_check.pyhad been bitrotted since the 2026-06-24 token contract (fed a float residual batch intoembed[tokens]) — now feeds tokens and passes.How Has This Been Tested?
tests/test_prefix_start.py(the D5 gate): tap-start vs input-start equivalence on the deterministic AND stochastic mask paths (stochastic RNG keys fold over absolute layer indices, so draws are start-invariant), both LM targets, remat on/off;clean_output_and_tapsvs separate calls (clean bit-level, taps at D4 tolerance — scan vs unrolled loop).param_decomp/tests/+ toy suites: 336 passed at default AND--xla_force_host_platform_device_count=4(skips/xfails pre-existing).experiments/invariance_check.py(D4): 3-step trajectory matches 1-layout vs 4-device GSPMD, reassociation-only.make type/make checkclean.perf/prefix-shared-recon-ab= this + the smoke configs); losses vs baseline as described.Does this PR introduce a breaking change?
Yes, two contract-level notes:
start_from_inputs/masked_start. All in-repo call sites are migrated; out-of-tree consumers ofmasked_outputmust wrap their batch inmodel.start_from_inputs(batch).read_activationspass to the clean scan's boundary carries), and the persistent compile cache misses once (new HLO).🤖 Generated with Claude Code
https://claude.ai/code/session_011F8uqqWqnkC48Y8G4QCKsk