perf(glu): emit residual taps from the clean-forward scan; one frozen forward per step#972
perf(glu): emit residual taps from the clean-forward scan; one frozen forward per step#972claude-spd1 wants to merge 1 commit into
Conversation
… forward per step GLUDecomposedModel.read_activations was a Python loop over all blocks — the largest unrolled region left in the step HLO, present in the train step and all seven jitted eval/metric steps — and a duplicate of the scanned clean_output forward on the same batch two lines earlier (XLA does not CSE scan vs unroll). (Authored against llama8b.py pre-#987; the machinery now lives in glu_transformer.py, so the fix covers llama8b AND qwen3.) Now clean_output / read_activations / the new clean_output_and_activations all route through one frozen-path lax.scan (S3/S4 semantics unchanged): taps ride out as scan ys, one stacked [tapped_depth,b,t,d] array per block intermediate (resid / post-LN1 / attn-out / post-LN2 / MLP-inner), indexed into the flat tap dict after the scan — the _run_masked_forward per-kind-ys pattern, no per-layer lax.cond. The tapped prefix alone emits ys, so the old early-break compute property is preserved statically. Family behavior (Qwen3 QK-norm) rides in attn.core exactly as the unrolled version had it. clean_output_and_activations (DecomposedModel protocol; trivial two-call impl on the unscanned targets) serves the two steps that need the clean logits AND the taps on the same batch — train and fast eval — from ONE forward; harvest's load_run forward folds three frozen passes into it likewise. Numerics (pinned pre-refactor CPU fixtures, 557 arrays): clean_output logits BIT-identical; taps vs the old unrolled forward differ within fp32 reassociation (<=1.7e-5 abs — the scan-vs-unroll class clean_output's docstring already documents; same order as SPEC D4's accepted 2.2e-6), propagating to <=3.9e-6 over a 3-step trajectory in both remat modes. The fused accessor is exact vs the separate methods (pinned by the new test); taps are now bit-identical to the residual the recon target actually threads. Crew-Address: task/scan-read-activations-taps
7f93825 to
0529fb8
Compare
|
GPU memory A/B done — the task brief's remaining verification (taps-as-scan-ys are saved outputs; confirm peak memory). Setup: two XLA static
No regression — temp down 0.10 GiB. As the brief anticipated, the (Runtime 2-step peaks weren't obtainable on this hardware: argument+temp = 75.4 GiB exceeds the 73.6 GiB BFC pool at the default 0.92 mem fraction, so BOTH arms OOM identically at step 1 — b32/dp32 is the minimum per-rank batch, and the b128 config of the 2026-06-29 memory canon is further out of reach on 80GB parts. The static analysis is XLA's own accounting of the same executables and is the apples-to-apples number.) Together with the earlier CPU A/B (HLO text −4%, compile 55.5→49.7s at the canonical 4-chunk 32L structure), all verification from the task brief is complete. Crew-Address: task/scan-read-activations-taps |
Description
Bridge task: scan-read-activations-taps (2026-07-10 JAX compile-hygiene audit finding).
read_activationswas a Pythonfor layer in range(n_layer)— the largest unrolled region left in the step HLO, inlined into the train step and all seven jitted eval/metric steps — and a duplicate of the scannedclean_outputforward on the same batch two lines earlier in the train step (XLA won't CSE scan vs unroll). Authored againstllama8b.py; after #987 the machinery lives intargets/glu_transformer.py, so the fix now covers llama8b and qwen3 at once (rebased onto a2c297b).Now
clean_output/read_activations/ the newclean_output_and_activationsall route through one private_clean_forwardlax.scan(SPEC S3/S4 semantics unchanged):[tapped_depth, b, t, d]array per block intermediate (resid/ post-LN1 / attn-out / post-LN2 / MLP-inner), indexed into the flat tap dict after the scan (the_run_masked_forwardper-kind-ys pattern). No per-layerlax.cond(the scan-recon-chunks lesson). Family behavior (Qwen3 QK-norm) rides inattn.coreexactly as the unrolled version had it.breakcompute property, preserved statically.clean_output_and_activations(newDecomposedModelprotocol method; trivial two-call impl on SimpleMLP/TMS/ResidMLP) serves the two steps that need clean logits AND taps on the same batch — train and fast eval — from ONE frozen forward. Harvest'sload_runforward folds its three frozen passes (clean + CI taps + site inputs) into one fused call likewise.Measured (32L/224-site structure at toy width, canonical 4-chunk recon, both arms one CPU node): HLO text 9.91→9.49 MB (−4%), CPU compile 54.2→49.9s. Per the 2026-06-30 review doc the step-time win is small (~0.3%:
read_activationsgathers no V/U and the step is gather-bound) — the payoff is HLO/compile across all eight jitted steps plus one whole frozen forward removed per train/eval step. A 4-node dp32 GPUmem_profileA/B (compile time + peak memory, per the task brief's memory caveat) is in flight; numbers land on the bridge thread and here.Related Issue
Bridge thread
scan-read-activations-taps. Coordinated withtargets-refactor(#966): agreed there that this PR's scan shape is what their remaining carve builds on.Motivation and Context
Compile hygiene: everything else on the layer axis is already
lax.scan'd (clean_output, masked forwards, chunkwise CI fn). This removes the last per-layer unroll and a redundant frozen forward per jitted step.How Has This Been Tested?
wantedshapes, 3-step train trajectories in BOTH remat modes, eval-step metrics), re-pinned at the rebased base a2c297b:clean_outputlogits bit-identical; taps vs the old unrolled forward within fp32 reassociation (≤1.7e-5 abs — the scan-vs-unroll classclean_output's docstring already documents; same order as SPEC D4's accepted 2.2e-6), propagating to ≤3.9e-6 abs over the 3-step trajectory. Taps are now bit-identical to the residual the recon target actually threads (previously they came from a separately-compiled forward).test_clean_output_and_activations_shares_the_forward: fused accessor EXACTLY equals the separate methods (and taps are invariant to thewantedset); at full tapped depth (every production config) fused clean logits are bit-equal toclean_output.--xla_force_host_platform_device_count=4, basedpyright 0 errors, ruff clean (re-running post-rebase; will confirm on the thread).Does this PR introduce a breaking change?
DecomposedModelgains one protocol method (clean_output_and_activations); all in-repo targets + test stubs implement it.read_activationsnow assertswantedis non-empty (previously crashed onmax()of empty). No config or checkpoint format changes.🤖 Generated with Claude Code
Crew-Address: task/scan-read-activations-taps