Skip to content

feat(train): stale-CI replay (S34) — repeat steps reuse the window-first CI envelope#953

Open
danbraunai-goodfire wants to merge 2 commits into
feature/train-batch-replayfrom
feature/replay-stale-ci
Open

feat(train): stale-CI replay (S34) — repeat steps reuse the window-first CI envelope#953
danbraunai-goodfire wants to merge 2 commits into
feature/train-batch-replayfrom
feature/replay-stale-ci

Conversation

@danbraunai-goodfire

Copy link
Copy Markdown
Collaborator

Description

Config-gated data.replay_stale_ci (SPEC S34, off by default): under free-AT batch replay, the repeat steps of a window reuse the window-first step's CI envelope as a constant — no taps read, no CI-fn forward or backward, and the ci_fn + its optimizer state pass through untouched. The ci_fn trains only on window-first steps, matching the 1-per-window unique-data rate; its LR still follows the run's global-step schedule (unit-LR optimizer + in-step scheduled_value_traced, since optax's count-driven cosine would stretch by the replay factor).

Mechanically: make_train_step's internals become a shared _build_step_impl(cached: CIEnvelope | None); flag-off traces the pre-S34 step unchanged (op order preserved for compile-cache stability). make_stale_ci_train_steps returns fresh/repeat/compute_ci; repeat's first arg bundles (model, envelope) so all-except-first donation spares both. The engine's do_step dispatches on step % train_batch_replay; a mid-window requeue-resume rebuilds the envelope via compute_ci (one ci_fn update fresher than the interrupted window's — a preemption-only wobble, noted in S34).

Also ships the sweep arms: pile_ppgd_bsc_nw0_lr0p01_replay{2,4,8,16}_staleci_400k.yaml (config-twins of the running replay-only arms, differing only in the flag) plus replay1_staleci_base_400k.yaml, a flag-off replay=1 baseline from this branch to pin that the refactored flag-off path reproduces the parent branch's trajectory at scale.

Related Issue

Motivation and Context

The CI fn is ~10x the target and its fwd+vjp is the dominant step slab in the pile-4L runs; within a replay window its inputs (taps) are bit-identical across steps, so repeats recompute it for one Adam step of param freshness. Skipping it on repeats is the same staleness bet free-AT itself makes, and V/U + adversary gradients are provably unchanged by CI being live vs constant. Expected step-time cut on repeats is large at 4L; the replay-scaling sweep (2/4/8/16, group ppgd-replay-staleci-400k on cw-east, A/B against the running ppgd-replay-scaling-400k) measures both the speedup and whether decomposition quality survives.

How Has This Been Tested?

  • New test_stale_ci_repeat_step_semantics: repeat step with the fresh step's envelope produces the same components + adversary updates as the fresh step (allclose), leaves ci_fn + optimizer state bit-untouched, compute_ci reproduces the fresh step's envelope, and the metric families differ only by the absent ci_fns grad norms.
  • make test: all pass except 4 test_slow_eval renderer tests that fail locally on the Mac GUI matplotlib backend (pass under MPLBACKEND=Agg; unrelated to this diff).
  • Core tests at XLA_FLAGS=--xla_force_host_platform_device_count=4 (llama_simple_mlp / checkpoint / sharding): pass.
  • Equivalence suite: pass (one source-inspection test retargeted make_train_step_build_step_impl).
  • make check / make type: clean.

Does this PR introduce a breaking change?

No. Flag off (the default) emits the unchanged step; DataConfig gains a required replay_stale_ci field with a single lab-side construction site.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QKw3iikejknWeF4ZQDFZbL

danbraunai-goodfire and others added 2 commits July 6, 2026 12:05
…rst CI envelope

Under free-AT batch replay, a window's taps are identical across its steps, and
the (~10x-the-target) CI fn fwd+vjp is the dominant step slab at 4L. New
config-gated data.replay_stale_ci: repeat steps consume the window-first step's
CI envelope as a constant — no taps read, no CI-fn forward/backward, ci_fn +
optimizer state pass through untouched (it trains only on window-first steps,
matching the 1-per-window unique-data rate). V/U + both adversaries see
identical gradients either way (CI live vs constant doesn't change their
backward), pinned by test_stale_ci_repeat_step_semantics.

- train.py: make_train_step internals -> shared _build_step_impl(cached=None |
  CIEnvelope); flag-off traces the pre-S34 step unchanged (op order preserved).
  make_stale_ci_train_steps returns fresh/repeat/compute_ci; repeat's first arg
  bundles (model, envelope) so all-except-first donation spares both.
- ci_fn LR under stale CI: unit-LR optimizer (run_state.unit_lr_ci_fn_optimizer)
  + in-step scheduled_value_traced at the GLOBAL step — optax's count advances
  only on window-first steps, so a count-driven cosine would stretch by the
  replay factor; Adam bias correction correctly tracks the update count.
- run.py: do_step dispatches fresh/repeat on step % train_batch_replay; a
  mid-window requeue-resume rebuilds the envelope via compute_ci (one ci_fn
  update fresher than the interrupted window's — preemption-only wobble, noted
  in S34). Grad-norm window stats take the key union (repeats carry no ci_fns
  family).
- configs: staleci 400k arms (replay 2/4/8/16, group-twin the running
  replay-only sweep) + a replay1 flag-off code baseline from this branch.

SPEC S34 added (experimental, off by default; flag-off unamended).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKw3iikejknWeF4ZQDFZbL
Twin of the scaling sweep's nw2 control (only run_name differs), run from this
branch's codebase with replay_stale_ci off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKw3iikejknWeF4ZQDFZbL
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant