Compile train_synthetic_gradient's window loop instead of tracing it per window#145
Merged
Merged
Conversation
…per window Acts on the one codex finding I pushed back on and was wrong about. The argument was that AGENTS.md rule 10 targets driving the *model* over time, while this is a grad-plus-optimiser-step loop like any training loop. Both halves are true and the conclusion still does not follow: the body drives the learner — `learner(_as_window(...))` under `grad` — so a Python loop re-traces the whole custom_vjp machinery every window. Measured with a trace counter on the synthesiser's `apply`: 14 traces for 4 windows and 38 for 12, against a count that does not move with window count under `for_loop`. The repo's own quickstart already had the right shape — Python over epochs, `scan` over steps, optimiser inside — so the idiom argument pointed this way too. - The window loop is now `brainstate.transform.for_loop`. Everything that varies across windows is `State` and threads through automatically: hidden states, the trace, the synthesiser's parameters, the optimiser's moments. Verified bit-comparable against the previous implementation for both the built-in SGD step and `braintools.optim.Adam` — identical per-window errors, parameters within float32 rounding (1.5e-8). - Epochs stay a Python loop: `reset` calls `init_all_states`, which reallocates state and cannot run under a trace. - `_fit_one` returns the traced error instead of calling `float()` on it. That call was forcing a device sync per window to build a list averaged one line later; concretisation now happens once, after the loop. - A sequence length that does not divide `chunk_size` is refused rather than truncated. `for_loop` needs uniform windows, but the real reason is that a short final window fits the synthesiser against a shorter future than any window the learner will be driven with — the mismatch F-35 documents, introduced by the helper rather than the caller. F-35 narrowed accordingly. Tests: the trace-count pin (verified to fail on the Python loop before being committed), ragged-tail refusal over three (T, chunk) pairs, and a chunk_size floor. Roadmap lesson 48. dni_test 30 passed (B4 included); 2526 passed across the rest of the suite; mypy clean. `uoro_test.py` not re-run — `dni.py` is the only source file touched and nothing in the UORO path references it.
|
🧙 Sourcery has finished reviewing your pull request! Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Acts on the one codex finding from the P4 review that I pushed back on and was
wrong about.
My argument was that AGENTS.md rule 10 targets driving the model over time,
while
train_synthetic_gradient's per-window loop is a grad-plus-optimiser-steploop like any training loop. Both halves are true and the conclusion still does
not follow: the body drives the learner —
learner(_as_window(...))undergrad— so a Python loop re-traces the wholecustom_vjpmachinery everywindow. The repo's own quickstart already had the right shape (Python over
epochs,
scanover steps, optimiser inside), so the idiom argument pointed thisway too.
Measured with a trace counter on the synthesiser's
apply:for_loopChanges
brainstate.transform.for_loop. Everything that variesacross windows is
Stateand threads through automatically: the model'shidden states, the learner's trace, the synthesiser's parameters, and the
optimiser's moments. Verified against the previous implementation for both the
built-in SGD step and
braintools.optim.Adam— identical per-window errors,parameters within float32 rounding (1.5e-8).
resetcallsinit_all_states, which reallocatesstate and cannot run under a trace.
_fit_onereturns the traced error rather than callingfloat()on it. Thatcall forced a device sync per window to build a list that was averaged one line
later; concretisation now happens once, after the loop.
for_loopneedsuniform windows, but the substantive reason is that a short last window fits
the synthesiser against a shorter future than any window the learner will ever
be driven with — the same mismatch F-35 documents, introduced by the helper
instead of by the caller. F-35's entry is narrowed accordingly. No in-tree
caller was affected; all use divisible lengths.
Tests
test_the_window_body_is_traced_once_not_once_per_window— compares tracecounts at two sequence lengths rather than asserting an absolute number.
Verified to fail on the Python loop (14 vs 38) before being committed.
test_a_ragged_final_window_is_refused_rather_than_truncated, over three(T, chunk)pairs.test_a_chunk_size_below_one_is_refused.Roadmap lesson 48 records the reasoning error: I was arguing about which
category the loop belonged to instead of asking what the body actually did.
Verification
dni_test30 passed (B4 included), 2526 passed across the rest of the suite,mypy clean.
uoro_test.pywas not re-run —dni.pyis the only source filetouched and nothing in the UORO path references it.
Summary by Sourcery
Compile train_synthetic_gradient’s per-window loop with a transform-based for_loop, enforce window size consistency, and adjust error handling and documentation around synthetic gradient training.
Bug Fixes:
Enhancements:
Documentation:
Tests: