P2: decompose learning rules into explicit axis coordinates#142
Merged
Conversation
Named algorithms become coordinates in a six-axis space rather than separate implementations. `ETraceConfig` (`braintrace/_algorithm/axes.py`) names the space -- trace_factorization, temporal_recursion, recurrence_scope, learning_signal, trace_filter, update_schedule -- validates it against an eight-rule compatibility matrix, and canonicalises coordinates that denote the same rule. `braintrace.compile` accepts a config wherever it accepts a name, so a rule with no preset name is as constructible as one with a name. `D_RTRL`, `pp_prop`, `EProp`, `OSTLRecurrent` and `OSTLFeedforward` are now thin factories that build a config and delegate; `EProp` loses all four of its hook overrides. Their gradients are unchanged, pinned by 24 frozen goldens spanning all three trace paths (chunked, fused multi-step, single-step). Mechanism: `temporal_recursion` is realised by substituting the executor's per-hidden-group hidden->hidden Jacobian (lambda*I for scalar_leak, zeros for none) rather than by special-casing operators, so it holds for dense, conv, sparse, lora and element-wise relations alike. The substitution is applied exactly once on each of the three paths. Two features lift off `EProp` onto the base engine: random feedback and the kappa-filter now compose with the O(I+O) io_factorized trace, not just O(P*H). A random-feedback request that cannot be honoured raises instead of silently computing the symmetric rule. Behaviour changes: `IODimVjpAlgorithm`'s x- and f-side decays are independent, so `decay` is a read-only property that raises when the sides differ (`decay_or_rank=0.9` stays element-wise identical to `(0.9, 0.9)`); and `decay_or_rank=0.0` is now admitted, canonicalising to `temporal_recursion= 'none'`. The private `_include_recurrent_mixing` becomes the public `recurrence_scope`, and asking for 'coupled' on a model whose compilation descends into a scan raises instead of degrading to 'diagonal'. F-30 (the IO-dim bias correction is indexed by update() call count, not trace-step count) was found during review, reproduced at 6.8e-04 on the finite-window path, and deliberately left unfixed -- P2's acceptance criterion is that gradients do not move. It is recorded in the known-limitations list and pinned by a test asserting the current behaviour. Verified: pytest braintrace/ -> 2240 passed, 4 deselected; mypy braintrace clean. Spec: docs/specs/2026-07-25-p2-axis-decomposition.md
There was a problem hiding this comment.
Sorry @chaoming0625, your pull request is larger than the review limit of 150000 diff characters
Codecov Report❌ Patch coverage is 📢 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.
Second phase of
docs/specs/2026-07-25-algorithm-axes-roadmap.md. Spec:docs/specs/2026-07-25-p2-axis-decomposition.md.What this changes
Named algorithms become coordinates in a six-axis space rather than separate implementations. The new
braintrace.ETraceConfignames that space:trace_factorizationper_param(O(P·H)),io_factorized(O(I+O))temporal_recursionjacobian,scalar_leak,none— an(x, f)pair underio_factorizedrecurrence_scopediagonal,coupledlearning_signalsymmetric,random_feedbacktrace_filternone,kappaupdate_scheduleper_stepIllegal combinations are rejected at construction by an eight-rule compatibility matrix with an error naming the legal pairings; coordinates that denote the same rule canonicalise to one form.
braintrace.compileaccepts a config wherever it accepts an algorithm name, so a rule with no preset name is as constructible as one with a name.D_RTRL,pp_prop,EProp,OSTLRecurrentandOSTLFeedforwardare now thin factories over coordinates —EProploses all four of its hook overrides.Mechanism
temporal_recursionis realised by substituting the executor's per-hidden-group hidden→hidden Jacobian (λ·Iforscalar_leak, zeros fornone) rather than by special-casing operators, so it holds for dense, conv, sparse, lora and element-wise relations alike. The substitution is applied exactly once on each of the three trace paths (chunked, fused multi-step, single-step), which is asserted directly.Random feedback and the κ-filter lift off
EProponto the base engine, so both compose with the O(I+O) trace and not just O(P·H).Behaviour changes
IODimVjpAlgorithm's x- and f-side decays are now independent.decaybecame a read-only property that raisesAttributeError(namingdecay_x/decay_f) when the sides differ.decay_or_rank=0.9remains element-wise identical to the new(0.9, 0.9).decay_or_rank=0.0is now admitted (bound relaxed from0 < decay < 1to0 <= decay < 1); it canonicalises totemporal_recursion='none'._include_recurrent_mixingbecame the publicrecurrence_scope, and asking forcoupledon a model whose compilation descends into ascannow raises instead of silently degrading todiagonal.Not fixed here, deliberately
F-30 — the IO-dim f-side bias correction is indexed by
update()call count, not trace-step count. Found during the design review, reproduced at 6.8e-04 on the finite-window path (it is invisible on the full-window path, per F-23). Left unfixed because P2's entire acceptance criterion is that gradients do not move; a numerical correction and a refactor cannot land together or neither can be verified. Recorded indocs/specs/2026-07-25-known-limitations.mdwith a reproduction and pinned by a test asserting the current, biased behaviour.Tests
axes_test.py(64) — vocabulary, canonicalisation, each matrix rule paired with its nearest legal neighbour, coefficient bounds, derived views.tests/axis_golden_test.py(24 frozen gradients, 8 cases × 2 models) — covers all three trace paths. Values were captured on a finite window (chunked_online_param_gradients,chunk_size < T), because a full-sequence multi-step VJP is BPTT and would be identical across every coordinate (F-23).tests/axis_acceptance_test.py(29) — includes a two-sided positive control onleaky_linear, whose hidden→hidden Jacobian is0.9·Iexactly, so substituting the model's own leak must reproduce it bitwise while a mismatched leak moves the gradient.Verified:
pytest braintrace/→ 2240 passed, 4 deselected (diagnostic);mypy braintraceclean.