Release Notes#
Version 0.2.4#
This release makes eligibility-trace online learning work through JAX control
flow. A new compiler canonicalization + descent pipeline lets ETP operations
inside vmap, cond, scan / for_loop, and weight-free while bodies
participate in online learning (Phases 0–4), so recurrent cells built with
control flow no longer silently drop parameters from the trace graph. The
operator layer gains three new ETP ops — grouped_matmul, embedding, and
einsum — each with a matching braintrace.nn layer; the D-RTRL multi-step
trace update is chunk-factorized for a 2.4–4.5× speedup on multi-step windows;
and a full _op / _algorithm audit closes 24 correctness findings. The
compiler itself is now deterministic across processes and transparently inlines
user jax.jit bodies. One internal ETP rule is renamed (see Breaking changes).
Highlights#
New: grouped_matmul, embedding, and einsum ETP operators#
Three new ETP operators join the operator layer, each with hand-written ETP rules (
dt_to_t,xy_to_dw, trace initializers), a closed-form D-RTRL fast path where applicable, public exports, and single-step BPTT-oracle coverage:braintrace.grouped_matmul— a grouped matmul exposing both batched and unbatched primitives (etp_gmm/etp_gmv) and a closed-form D-RTRL fast path; D-RTRL matches BPTT element-wise andpp_propis directionally aligned. Wrapped by the newbraintrace.nn.GroupedLinearlayer.braintrace.embedding— an ETP embedding lookup with a broadcastdt_to_tand a scatter-addxy_to_dw. Because the input is integer token indices, the IO-dim (pp_prop/ES_D_RTRL) input trace cannot low-pass the raw indices; a new optional per-primitiveETP_RULES_PP_X_REPRregistry letsembeddingfilter the linear one-hot representation (y = onehot(idx) @ T) instead, andxy_to_dwdispatches on the x dtype (integer indices → gather-VJP scatter-add; float one-hot → contraction VJP). Wrapped by the newbraintrace.nn.Embeddinglayer.braintrace.einsum— an equation-parsed ETP einsum with axis classification; diagonal-class and shared-axis equations are D-RTRL BPTT-exact (maxdiff 0.0), while the genuinely lossy regime (output positions collapsing into a smaller hidden state) fails loudly at compile time with a cotangent-shape error rather than silently emitting wrong gradients.
New: structured scan descent — long ETP scans compile and learn online (Phase 4)#
A third compile path for ETP-relevant
scans above the unroll limit. Previously an ETP-relevantlax.scan/for_loopwhose static length exceededControlFlowPolicy.scan_unroll_limitwas a dead end (NotImplementedError). Under the new defaultControlFlowPolicy(scan_descent='auto'), the compiler descends such a scan: relations and hidden groups are discovered inside the scan body with the same flat finders, the equation is rewritten to emit stacked per-substep values as extra ys (leading substep axisL), and the graph executor computes stacked per-substep Jacobians by vmapping over that axis — the compiled program stays a single scan equation, so compile size is independent of the loop length (anL=100inner loop compiles in under 60 equations). ASCAN_DESCENT_APPLIEDINFO diagnostic records each descent; blocked scans getSCAN_DESCENT_SKIPPED. SetControlFlowPolicy(scan_descent='off')to restore the pre-Phase-4 error.Param-dim algorithms fold the eligibility trace over the substep axis.
D_RTRL(theParamDimVjpAlgorithmfamily) applies its trace update per substep with an innerjax.lax.scan(eps <- D_tau * eps + x_tau (x) df_tau), declaring_supports_scan_descent = True. The fold is values-only and stop-gradient’d — never differentiated, so no checkpointing is needed. The learning signal stays one-per-outer-step ((*varshape, num_state); the SNN learning-signal axis contract is unchanged). The io-dim family (pp_prop/ES_D_RTRL) and other algorithms without the flag reject descended graphs with an actionableNotImplementedErroratcompile_graph.Exactness contract (pinned by oracle tests). For diagonal-recurrence bodies (elementwise hidden-to-hidden substep path — the SNN class), descended D-RTRL is exact: whole-sequence, chunked (3-step and 1-step chunks, where the gradient depends on the folded trace at every chunk boundary), and one-step single-step gradients all match BPTT / the unrolled twin element-wise, including a two-hidden-state (
num_state == 2) group through the fold. For bodies that mix the hidden state through an ETP matmul, whole-sequence multi-step gradients remain BPTT-exact; chunked gradients approximate cross-substep credit (the same approximation class as the unroll path — documented divergence).Algorithm-level
control_flowkwarg.D_RTRL,pp_prop(IODimVjpAlgorithm),OTPE, andOTTTnow acceptcontrol_flow=ControlFlowPolicy(...)and thread it through their graph executors into compilation.v1 restrictions (each blocks descent for that scan, with a diagnostic): reverse scans, nested control flow inside the body, trainable weights scanned over as xs, and an outer ETP relation targeting a hidden state carried by a descended scan (raises with restructuring guidance).
jitbodies nested inside control-flow equations are now inlined during extraction so descent sees a flat body.Single-step readout limitation. The per-step hidden perturbation is added to a descended scan’s carry outvar; a loss that reads the hidden state through the scan’s stacked ys (e.g.
for_loop(...)[-1]) bypasses it, dropping the same-step learning signal (pinned by test; parallels the Phase 3 while-hidden limitation). Read the state after the loop (self.h.value) instead — multi-step VJP is unaffected either way.
New: while-loop policy — weight-free opaque-forward support (Phase 3)#
Weight-free
lax.while_loops that read/update hidden state now compile. Under the new default policy knobControlFlowPolicy(while_hidden='opaque-fwd'), awhilewhose inputs carry no trainable ETP weight is kept as an opaque forward node: the compiler registers relations whosey→hidden tail crosses the loop, emits aCONTROL_FLOW_OPAQUE_FWDINFO diagnostic, and extracts hidden-to-hidden Jacobians for any hidden group whose transition contains awhilein forward mode (jax.jvp-basedjacfwd_last_dim/jacfwdblock extraction) — reverse mode throughwhile_loopis structurally unsupported by JAX. SetControlFlowPolicy(while_hidden='error')to reject such loops instead.Perturbation detach keeps the VJP reverse-traceable. The hidden perturbation pass rewires every hidden-producing
whileto consumestop_gradientcopies of its inputs in the perturbed jaxpr only; theh = fresh + εadd stays outside the detach, so the single-step learning signal of the loop’s own hidden group (taken exclusively from the perturbation cotangents) is exact. Verified: D-RTRL single-step gradients on a while-settle model match its hand-composed no-whiletwin element-wise, and the twin matches the BPTT oracle. Limitation: the detach zeroes every same-step reverse path through the loop, so a parameter or other hidden group whose only same-step path to the loss crosses the loop — e.g. the weights of an upstream layer feeding a while-hidden layer — receives a zero learning signal (a WARNING-levelCONTROL_FLOW_OPAQUE_FWDdiagnostic records each detach; the zero-upstream-gradient behavior is pinned by test).vjp_method='multi-step'on awhile-hidden model still raises JAX’s reverse-through-while_loopValueError(documented limitation — use the default single-step path).A weight used inside a
whileis now a hard, actionable error (WEIGHT_IN_WHILEERROR diagnostic +NotImplementedError): move the weight application outside the loop so the loop consumes only its result (subject to the same-step limitation above), or use a fixed-length scan/for_loop(which the compiler unrolls).Breaking: ETP primitives left inside an un-flattened
scan/while/condbody now raise instead of being silently warned-and-excluded (etp_in_control_flow='error', the new default). PassControlFlowPolicy(etp_in_control_flow='exclude')to restore the old warn-and-exclude behavior.Position-mixing guard: a
while/opaque control-flow body that appliesdot_general/conv_general_dilatedto hidden-derived values (recurrent weight mixing inside the loop) cannot be expressed as a per-position Jacobian; the compiler treats it as a boundary, emits aCONTROL_FLOW_RECURRENT_MIXINGWARNING, and falls back to the zero-recurrence (e-prop-style) group.
New: inner-scan unrolling (compiler canonicalization, Phase 2)#
ETP operations inside
lax.scan/brainstate.transform.for_loopbodies now participate in online learning. A new canonicalization pass (unroll_inner_scansin_compiler/canonicalize.py) runs at extraction time and replaces every ETP-relevant, statically short scan with its unrolled body: one cloned copy per iteration with fresh variables,xssliced per step, consumedysre-stacked viabroadcast_in_dim+concatenate, andreverse=Truerespected. The unrolled program is value- and Jacobian-identical to the scan, so exact algorithms (D-RTRL, full-rank pp_prop, EProp(k=0), OSTLRecurrent) match BPTT element-wise on scan-body models — verified against hand-flattened twins and the BPTT oracle. Cond and scan canonicalization now run as a joint fixpoint (canonicalize_control_flow), so acondinside a scan body (and an eligible scan inside acondbranch) both flatten.Relation counts follow the weight→weight→hidden invariant: in an unrolled inner loop only the last sub-step’s ETP ops become relations — earlier sub-steps reach the hidden state through another trainable ETP op and are excluded (with the usual no-relation warning).
Eligibility gates: only scans whose static
lengthis ≤ControlFlowPolicy.scan_unroll_limit(default 16) and that carry no effects and contain nowhileare unrolled. An ETP-relevant scan that fails a gate emits aSCAN_UNROLL_SKIPPEDwarning and keeps today’s hard-error behavior; unrolls are recorded asSCAN_UNROLLEDINFO diagnostics onETraceGraph.diagnostics. Scans that scan over a trainable weight (weights asxs) are never unrolled (RELATION_EXCLUDED_SLICED_WEIGHTwarning) — per-slice trace lineage is deferred.Cond gate revision: a branch containing a scan no longer blocks if-conversion when that scan is itself unrollable;
scan_unroll_limit=0disables unrolling and restores the exact Phase 1 gating.Tied-weight invariant locked: one
ParamStateconsumed by several ETP call sites (which unrolling multiplies) is keyed per relation instance with per-path gradient accumulation — verified BPTT-exact and now covered by regression tests.
New: cond if-conversion (compiler canonicalization, Phase 1)#
ETP operations inside
lax.condbranches now participate in online learning. A new canonicalization pass (_compiler/canonicalize.py) runs at extraction time (after user-jitinlining) and rewrites every ETP-relevantcondequation into the inlined bodies of all branches followed by oneselect_nper output.select_n’s index semantics and JVP matchcondexactly, so for finite branches values and Jacobians — and therefore exact algorithms such as D-RTRL — are unchanged. Weights used insidecondbranches previously raisedNotImplementedError(or were silently excluded when only ETP primitives appeared inside).Semantics note: on the canonicalized graph both branches execute every step and the dead branch’s value is discarded by
select_n. Values and forward-mode derivatives are unaffected by dead-branch NaN/Inf. Reverse-mode gradients are not: if the dead branch’s local Jacobian is NaN/Inf (e.g. acondprotecting asqrtdomain), its VJP multiplies the exact-zero cotangent by that Jacobian (0 * nan = nan) and contaminates gradients of shared inputs — the classic single-wherepitfall. Keep such domain-guard conds opaque (ControlFlowPolicy(cond='opaque')) or guard the operand inside the branch.Gates: conds that touch no ETP primitive, weight, or hidden state stay opaque at zero cost. Conds with effects or containing
while/scanin a branch are never converted; an ETP-relevant one that is skipped this way emits aCOND_CONVERSION_SKIPPEDwarning and keeps today’s behavior. Conversions are recorded asCOND_IF_CONVERTEDINFO diagnostics onETraceGraph.diagnostics.Opt-out:
braintrace.ControlFlowPolicy(cond='opaque')via the newcontrol_flowkeyword oncompile_etrace_graph/extract_module_inforestores the previous behavior.
New: vmap identity preservation (operator layer)#
vmap identity preservation (operator layer):
jax.vmapover an unbatched ETP op (matmul,lora_matmul,sparse_matmulwith vector input) now re-binds the batched ETP primitive (etp_mm/etp_lora_mm/etp_sp_mm) instead of decomposing into standard JAX ops. Models that vmap per-sample ETP operations insideupdate()now compile with full eligibility-trace relations. When promotion is impossible (batched weights,etp_conv, nested vmap), the op decomposes as before but emits aUserWarninginstead of silently dropping the parameter from online learning. Note: when this warning appears from acompile(..., vmap=True)learner’s execution trace (e.g. conv models), it is expected and benign — the eligibility-trace graph was already compiled per-sample before the learner was vmapped, so no parameter is dropped.
New: user-jit inlining and a deterministic compiler#
ETP operations inside a user
jax.jitnow compile.extract_module_infoinlines userjax.jitbodies before any analysis, sojitboundaries are transparent to hidden-group discovery and relation finding. Previously a weight used inside ajitraisedNotImplementedErrorand bare ETP primitives were silently skipped (#123).Deterministic, reproducible compilation. Hidden-group discovery, transition bookkeeping, and group merging now use insertion-ordered maps and a canonical compiled-state ordering instead of
sets keyed by object identity, so group membership and ordering are stable across processes. Every built group is validated withcheck_consistent_varshape, and merges emit an INFO-levelHIDDEN_GROUP_MERGEDdiagnostic (#123).Directly-fed fan-out fix. A single ETP op feeding two independent recurrent states now registers relations to both groups — the forward BFS previously locked onto whichever hidden state it reached first and dropped the rest. Relation gating also resolves all keys before excluding a relation, so a constant-weight /
ParamState-bias matmul still registers with the bias as its trainable key (#123).Robust perturbation pass. The single-step perturbation now handles multi-output equations and read-only hidden states (synthesizing the
h^t = h^{t-1} + ppassthrough) and preserves the source jaxpr’s effect set, instead of falling through to an unexplained-hidden error (#123).
Performance#
Chunk-factorized multi-step D-RTRL trace update. Multi-step trace updates for
D_RTRLnow factor the per-step decay into suffix products and apply the trace update per chunk instead of step-by-step, giving a 2.4–4.5× speedup on multi-step windows for the dense (etp_mm/etp_mv) and elementwise (etp_elemwise) kernels. Exposed as achunked_traceknob onD_RTRL/braintrace.compile(#132).
Correctness#
ETP
_op/_algorithmaudit — 24 findings closed (4 Critical, 6 High, 6 Medium, 8 Minor). Highlights: exactconv(C1) andlora_matmul(C2) gradients under param-dim D-RTRL (per-position kernel trace + effective-weight trace, backed by new optional instant / solve D-RTRL rule registries); a fix for the batched sparse D-RTRL crash (C3) via a hashable CSR wrapper; and OSTTP’s always-zero learning signal (C4) viacustom_vjpresidual threading. Also resolved:trace_dtypegate mismatch, conv bias broadcast, EProp kappa-filter cross-state contamination and random-feedback scale invariance, OTTT / OTPE dropped bias gradients and missing guards, int/bool autodiff guards, rank guards with nn-layer axis folding, and a corrected OSTL exactness claim. Adds a cross-family single-step BPTT oracle suite and first-principles rule tests (6c7796a).
Breaking changes#
ETP rule rename:
YW_TO_W→DT_TO_T. The recurrent trace-propagation rule computesD^t * ε^{t-1}(theDᵗ-times-previous-trace term of the D-RTRL update), soDT_TO_Tnames it accurately;YW_TO_Wnever matched what the rule computes. Custom primitives that register this rule (viaregister_etp_rules/register_primitive) must use the new name. This is unrelated tobrainevent’s externalDataRepresentation.yw_to_w/yw_to_w_transposedprotocol methods, which are untouched (#130).
Internal#
mypyCI gate repaired. Cleared 40 accumulated type errors across 8 files (annotation-only, no behavior change), restoring a greentypecheck_and_buildjob (#133).Signature cleanups: a readability pass on
_etp_sp_matmul_impland removal of unused keyword arguments across several modules.
Version 0.2.3#
This release adds optional, shape-preserving parameter-transform hooks to the
eligibility-trace (ETP) operators, so a trainable weight (or bias) can be passed
through an elementwise / standardizing function before it enters the operation
while the eligibility trace and gradient remain with respect to the raw
stored parameter. These hooks are threaded through the braintrace.nn linear
layers and demonstrated in a new tutorial. The release also hardens the public
API with inline type annotations behind an enforced mypy gate, corrects the
weight_fn / bias_fn gradients on the closed-form fast path, relocates the
fast-path kernels into the operator layer, and tightens the sparse_matmul
input contract. Two public APIs are renamed and one operand type is now required
(see Breaking changes).
Highlights#
New: parameter-transform hooks on ETP operators#
Add transform hooks to the ETP ops, computing
y = x @ weight_fn(w) (+ bias_fn(b))(and per-op equivalents), with the eligibility trace and gradient kept with respect to the raw parameter:braintrace.matmul/braintrace.sparse_matmul—weight_fn,bias_fn.braintrace.conv—kernel_fn,bias_fn.braintrace.lora_matmul—b_fn,a_fn,bias_fn.braintrace.element_wise—weight_fn(see Breaking changes).
Each transform is applied inside the ETP primitive; the per-parameter Jacobian is recovered exactly once (via
jax.vjp) in the weight-gradient rule, while the trace-propagation rule is unchanged — so the forward-mode eligibility trace stays exact and is never double-counted. D-RTRL matches backprop-through-time element-wise for non-identity transforms (verified withtanh,w**2, andabs). Omitting a transform is bit-identical to the previous behavior.
New / Improved: braintrace.nn linear layers#
braintrace.nn.Linear(withw_mask),braintrace.nn.SignedWLinear, andbraintrace.nn.ScaledWSLinearnow route their weight masking / sign / standardization through the newmatmul(weight_fn=...)hook, so the masked / signed / standardized weight participates in eligibility-trace learning with the gradient kept w.r.t. the raw weight leaf. (ForScaledWSLinear,gainandbiasare applied as post-operations and are therefore non-temporal for the online trace, though still recovered exactly by the multi-step VJP oracle.)Export
braintrace.nn.ScaledWSLinear(previously importable only by its fully-qualified module path).
New: typed public API with an enforced mypy gate#
Inline type annotations now cover the public surface — ETP operators and their rule functions,
ETPPrimitive/register_primitive, thebraintrace.compileentry point and package accessors, input-data containers, thebraintrace.nnlinear / conv / recurrent cells, and the algorithm base classes, executors, and concrete algorithms. A newWeightFnalias names the transform-hook signature.An enforced
mypygate guards the public API, so type regressions fail the build (#119).
Improvements#
Correct
weight_fn/bias_fngradients on the fast path. The transform Jacobianf'(W)is now applied on the param-dim D-RTRL closed-form fast path (it lives solely inxy_to_dw;dt_to_tstays transform-free), so transformed-parameter gradients match the slow path. Also fixes anelement_wiseslow-path batched-cotangent crash (#120).Operator-layer fast-path kernels. The closed-form fast-path kernels (instant / recurrent / solve) move into the operator layer as a per-primitive
FastPathRulesbundle behind anETP_FAST_PATH_RULESregistry, and the algorithm-layer string-match gate is replaced by a per-primitiveapplicable()predicate — keeping primitive knowledge in the operator layer per the layered design (#120).
Documentation#
New tutorial: customizing primitive transforms (
docs/tutorials/customizing_primitive_transforms.ipynb), plus transform-hook docstrings on the ETP operators (#120).
Breaking changes#
braintrace.element_wise: thefnparameter is renamed toweight_fnand is now keyword-only, and the transform is applied inside the ETP primitive (previously it was applied to the weight outside the primitive). Migrateelement_wise(w, fn=g)toelement_wise(w, weight_fn=g). Forward results are unchanged; only the call signature and the internal trace-factorization point differ.braintrace.sparse_matmul: the weight parameter is renamed fromweight_datatoweightfor a cleaner, more consistent API. All in-tree call sites pass it positionally and are unaffected; update any keyword callers (#116).braintrace.sparse_matmul: the sparse operand (sparse_mat) must now be abrainevent.DataRepresentationand is enforced with a strict runtimeisinstancecheck (raisingTypeError).DataRepresentationsupplies the ETP online-learning protocol the compiler / executor require (with_data,yw_to_w,yw_to_w_transposed);brainunitsparse types (u.sparse) lack these and are no longer accepted.braineventis now a runtime dependency. Migrate sparse weights tobrainevent(e.g.brainevent.CSR) (#121).
Dependencies#
Add
braineventas a runtime dependency (pyproject.toml,requirements.txt) (#121).Bump
codecov/codecov-actionfrom 5 to 7 (#117).
Version 0.2.2#
This release introduces a unified braintrace.compile entry point for building
eligibility-trace online learners, adds a recurrent mixing mode to the
graph-construction compiler, and fixes eligibility-trace convergence under
vmap / brainstate.mixin.Batching(). It also migrates unit handling from
saiunit to brainunit, modernizes the toolchain (Python 3.14,
brainstate >= 0.5.2, Codecov), and ships broad documentation, example, and
test improvements. Internal modules were renamed for brevity; no documented
0.2.x public API is removed.
Highlights#
New: unified braintrace.compile entry point#
braintrace.compile(model, algorithm, example_input, ...)is now the canonical, single-call way to build a compiled online learner. It always initializes states, accepts aseed, applies model guardrails, and can emit a verbose compilation report — replacing the manualinit_states/learner.compile_graph(x0)triad.vmap=parameter for per-sample vmap state initialization. Withvmap=True, states are built viavmap_new_states(state_tag='new', ...)and the learner is wrapped inbrainstate.nn.Vmap(vmap_states='new'), so eligibility-trace models compose with brainstate’s per-sample vmap scheme.CompilationReport, a structured view over the eligibility-trace graph (relation/weight counts,etrace_weights,excluded_weights,report.show()with verbosity levels). It is exposed viaETraceAlgorithm.reportand now backsshow_graph.
New: recurrent mixing mode for graph construction#
Add a recurrent mixing mode to eligibility-trace graph construction, broadening the set of cell topologies the compiler can connect (#108).
Improvements#
Dependencies and toolchain#
Replace
saiunitwithbrainunitfor all unit handling across source, tests, examples, and docs.brainunitre-exportssaiunitinternally, so this is a drop-in change (#106).Raise the
brainstatefloor to >= 0.5.2, required by thecompile(vmap=True)path, and drop a duplicate dependency declaration.Update the supported Python version to 3.14 and adjust the CI JAX version matrix.
Add Codecov coverage reporting and raise source coverage to 93%, with new tests for previously-untested modules (#109).
Refactoring#
Rename internal module packages for brevity:
_etrace_op→_op,_etrace_compiler→_compiler, and_etrace_algorithms→_algorithm. These are private modules; imports were updated package-wide with word-boundary-anchored replacement (#111).Remove the unused
ParamStatefrom state management.Remove the per-step spectral-normalization path (
normalize_matrix_spectrum) from D-RTRL, E-Prop, and the OSTL trace scan; it ranjnp.linalg.eigvalson every hidden-group Jacobian, was off by default, and was costly.
Fixes#
Eligibility-trace convergence under
vmapbatching. Defer graph compilation during thevmap_new_statesdiscovery probe so the executor binds to the real batched states (fixes aBatchAxisErrorwhen writing batched values), correctly handle models that mix batched and unbatched ETP primitives in the param-dim VJP solve, and align convolution eligibility traces underbrainstate.nn.Vmap(vmap_states='new'). Restores convergence for the conv-based SNN/RNN training examples.Element-wise eligibility traces under
brainstate.mixin.Batching(). Size the trace from the (batch-aware) hidden group and sum out the leading batch axis in the solver, fixing a scan-carry type mismatch and a custom-VJP backward shape mismatch. This unblocks the default SHD batch trainer, where every LIF leak is an element-wise weight.braintrace.nn.LoRAnow routes its forward through the ETPlora_matmulprimitive, so LoRA factors participate in eligibility-trace learning (fixes the zero-relations bug) and the factor order is corrected.Resolve pre-existing
mypyerrors in the compiler’sreport.py(#112) and treatbrainunit/saiunitas untyped formypyto clear spuriousattr-definederrors from their re-export chain.Convert legacy
xfailtests to positive assertions, silence thecore.JaxprDebugInfodeprecation warning, and migrate deprecatedbrainstateAPIs (brainstate.augment→brainstate.transform,brainstate.functional→brainstate.nn) (#113).
Documentation and examples#
Make
braintrace.compilethe canonical entry point in every docstring, tutorial, notebook, and example, and fix broken examples (e.g. self-contained RNNs, consistent batch axes); each documented example is now backed by an executable test (#114).Document
CompilationReportin the API reference and migrate the onboarding guides, quickstart, and tutorials to the unified compile flow.Add a smoke-test harness and a testable
main()entry point to the standalone examples; repair all docs notebooks so they execute cleanly.
Notes#
The internal module renames (
_etrace_*→_*), the removal ofParamState, and the removal ofnormalize_matrix_spectrumtouch private/internal surfaces only; the documented 0.2.x public API is unchanged.Verified locally: the full CPU test suite is green (1604 passed, 3 skipped).
Version 0.2.1#
This is a maintenance release that restores compatibility with the latest
brain-ecosystem dependencies and toolchain. It contains no functional or
public-API changes — code written against 0.2.0 continues to work unchanged —
and exists to keep BrainTrace green against brainstate 0.5, saiunit/
brainunit 0.5.1, and pytest 9.1.
Fixes#
Dependency Compatibility#
brainstate0.5 typed API: adoptedbrainstate’s PEP 561py.typedsurface throughout the source — routedPyTreethrough BrainTrace’s existing type alias, centralized anas_size_tuple()helper in_typing, droppedFlattedDictsubscripts, and added boundary asserts/casts. This clears the 154 mypy errors newly exposed by the upstream typing, with minimal# type: ignoreonly wherebrainstate’s typing makes it unavoidable.brainstate0.5.0 convolution validation: updated convolution test expectations for the hardened validation (bareassert→ValueError) and the new one-value-per-spatial-dimension padding-tuple semantics.pytest9.1.0 collection: removed trailing commas in single-argumentparametrizeids thatpytest9.1.0 mis-parses as two values, fixing a collection-timeGraphNodeMeta has no len()error.
Notes#
All changes are BrainTrace-side. A related upstream
saiunitissue is resolved insaiunit/brainunit0.5.1 and requires no change here.Verified locally: full suite 1367 passed (2 xfailed), mypy clean across 51 files, and wheel + sdist build with
py.typedshipped (PEP 561).
Version 0.2.0#
This release is a major step for BrainTrace. It adds a family of spiking neural network (SNN) online-learning algorithms, rewrites the eligibility-trace compiler around primitive-type dispatch, generalizes every ETP primitive to support multiple trainable inputs (fixing a silent bias-gradient drop), delivers substantial performance gains for D-RTRL and multi-step rollouts, and hardens the package with PEP 561 typing and a BPTT-oracle-backed test suite.
Major Changes#
New: SNN Online-Learning Algorithms#
Added five SNN online-learning algorithms as flat
ETraceVjpAlgorithmsubclasses:EProp,OSTL(OSTLRecurrent/OSTLFeedforward),OTPE,OTTT, andOSTTP. All are exported at the top level.Added a
_compute_learning_signalhook toETraceVjpAlgorithmto support target-projection algorithms (OSTTP) without disrupting the existing D-RTRL and pp-prop paths.Added supporting trace helpers:
PresynapticTrace,KappaFilter,FixedRandomFeedback, and target-signal extraction utilities.Algorithms are cross-checked for regime equivalence and verified to decrease loss in integration smoke tests.
ETP Compiler Rewrite#
Rewrote the eligibility-trace compiler to dispatch on primitive-type identity rather than string-matching op or trace names, with structured, leveled diagnostics (
DiagnosticKind,DiagnosticLevel,CompilationRecord) replacing ad-hoc warnings.Added compile-time diagnostics that surface previously silent issues — e.g.
TRAINABLE_INVAR_NOT_PARAMSTATEflags a trainable input (such as a constant bias) that does not trace to aParamState, so users can wrap it intentionally instead of silently losing its gradient.
Multi-Trainable-Input ETP Primitives (Bias Gradients)#
Generalized every ETP primitive from a single-“weight” assumption to an arbitrary named dict of trainable inputs. This fixes a silent bias-gradient drop and a LoRA executor signature mismatch in one coherent refactor.
Migrated all built-in primitives (
elemwise, densemm/mv,conv,sparsemm/mv, andlora) to the dict-based rule API with first-class bias gradient support, each verified element-wise against a BPTT oracle.Fixed layout-aware axis handling in conv primitives (1D/2D, NHWC/NCHW, OIHW/HWIO kernel layouts) that previously corrupted gradients on non-default layouts, and fixed non-square dense weight broadcasting in
_mm_dt_to_t.Eligibility traces are now stored as per-key dicts; the transitional legacy-array adapter has been fully removed.
Performance#
D-RTRL einsum fast path (
fast_solve=True, default on): replaces nestedvmap-of-vjpand per-steplax.condoverhead with direct einsum kernels formm/mv/elemwise; conv/sparse/LoRA fall back to the legacy path.Reduced-precision trace storage (
trace_dtype, e.g. bf16/fp16) halves the dominantB*N^2trace bandwidth on GPU/TPU while keeping Jacobians, learning signals, and final gradients in fp32. DefaultNonepreserves exact behavior.Multi-step trace fusion: the per-step eligibility-trace roll for exact algorithms (D-RTRL, pp-prop) is now threaded into the graph executor’s forward scan, eliminating an
O(T × Jacobian)HBM round-trip (traced scan count drops 3 → 2). Opt-in and multi-step-only; single-step/SNN paths are unchanged.Branch-free spectrum/vector normalization to restore XLA fusion across steps.
Primitive Registration Simplification#
Removed
ETPPrimitiveSpecand the spec-based registration layer; invar/ outvar layout metadata (trainable_invars_fn,x_invar_index,y_outvar_index) now lives in internal registries populated directly throughregister_primitivekeyword arguments.
Package Restructuring#
Consolidated the eligibility-trace code into a single flat
_etrace_algorithmspackage, merging the former_etrace_vjp/,_etrace_algorithms.py,_etrace_graph_executor.py, and_snn_algorithms/modules. The top-level public API is unchanged.Split the algorithm base hierarchy into dedicated modules:
ParamDimVjpAlgorithm(D-RTRL) andIODimVjpAlgorithm(pp-prop) now live in their own files, withD_RTRL/pp_propas thin subclasses.Removed the experimental hybrid online-learning method.
Typing & Packaging#
The package is now PEP 561 compliant: ships a
py.typedmarker so downstream users receive inline type hints.Added a pragmatic
mypyconfiguration and wired type checking plus packaging verification (python -m build,py.typedpresence) into CI.
Testing#
Added a BPTT gradient oracle and a layered correctness test suite (P2–P8): per-operator rule oracles, public-API contract tests, exact-class element-wise equivalence with BPTT, approximate-class direction-alignment checks, transform/integration invariance, and per-cell compiler relation guardrails tied to the cell registry.
Documentation#
Converted all public-API docstrings to NumPy-doc style with math, references, and runnable examples.
Documentation is now self-hosted at
brainx.chaobrain.com/braintrace/, with refreshed RTD links and a WebP logo.
Dependencies & Tooling#
Replaced
brainunitwithsaiunitthroughout for unit handling.Numerous CI/CD upgrades (checkout, setup-python, artifact actions, sphinx and theme requirements); docs deploy on release publication.
Deprecations#
The entire v0.1.x class-based operator/parameter API is deprecated in favor
of the new primitive-based ETP user-API. The legacy classes still work —
they are thin back-compatibility shims that route through the new primitives —
but each emits a DeprecationWarning (once per class, per process) on first
use, and they will be removed in a future release. Migrate at your convenience.
Deprecated operator classes → new primitive functions:
Deprecated (v0.1.x) |
Use instead (v0.2.0) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
the ETP primitive functions above |
Deprecated parameter classes → brainstate.ParamState + a primitive:
Deprecated (v0.1.x) |
Use instead (v0.2.0) |
|---|---|
|
|
|
|
|
|
|
plain objects with plain JAX ops |
The stop_param_gradients context manager and the general_y2w helper are kept
as no-op compatibility shims and have no effect on the new primitive path.
Breaking Changes#
OSTL factory removed — use
OSTLRecurrentorOSTLFeedforwarddirectly instead of the formerOSTLfactory function.OTTTandOTPErequire an explicitleak— the membrane leak is no longer inferred frommodel.states()(it silently picked a wrong value on heterogeneous/multi-population models). Both now also reject hidden groups withnum_state > 1at compile time, as collapsing thenum_stateaxis has no theoretical basis for these LIF-derived rules.OTPEadditionally documents a narrower feed-forward / single-layer / global-scalar-leak regime.Unit dependency change — code relying on
brainunitinternals should migrate tosaiunit.ETPPrimitiveSpecremoved — custom primitives must register layout metadata viaregister_primitivekeyword arguments (trainable_invars_fn,x_invar_index,y_outvar_index).
Migration Guide#
OSTL#
# Old
algo = OSTL(model, ...) # factory
# New — choose the regime explicitly
algo = OSTLRecurrent(model, ...)
# or
algo = OSTLFeedforward(model, ...)
OTTT / OTPE#
# Old
algo = OTTT(model, ...) # leak inferred from model.states()
# New — pass the postsynaptic membrane leak explicitly
algo = OTTT(model, leak=0.9, ...)
Custom ETP primitives#
# Old: register_primitive_spec(ETPPrimitiveSpec(...))
# New: pass layout metadata directly
register_primitive(
prim,
trainable_invars_fn=...,
x_invar_index=...,
y_outvar_index=...,
)
Deprecated class-based API → primitive-based API#
# Old (v0.1.x): wrap the weight in an ETraceParam bound to an op
self.w = braintrace.ETraceParam({'weight': w}, braintrace.MatMulOp())
y = self.w.execute(x)
# New (v0.2.0): a plain ParamState + the ETP primitive function
self.w = brainstate.ParamState({'weight': w})
y = braintrace.matmul(x, self.w.value)
The element-wise case is analogous (ElemWiseParam/ElemWiseOp →
brainstate.ParamState + braintrace.element_wise); to keep a weight out of
the eligibility-trace graph, use a plain brainstate.ParamState with ordinary
JAX ops instead of NonTempParam / FakeETraceParam.
Version#
Bumped version from
0.1.3to0.2.0
Version 0.1.2#
Major Changes#
Import Path Migration#
Updated dependency from
brainpytobrainpy.state: Migrated all imports to use the more specificbrainpy.statemoduleUpdated
braintrace/nn/_readout.py: Changed neuron model imports frombrainpytobrainpy.stateUpdated all documentation notebooks (12 files): Concepts, RNN/SNN online learning, batching, state management, and graph visualization tutorials
Updated example scripts (4 files): COBA EI RSNN, SNN evaluation, feedforward conv SNN, and SNN models
Updated
requirements.txtandpyproject.tomlto specifybrainpy-stateas dependencyTotal: 19 files changed with improved module structure and consistency
New Algorithms#
Added PP-Prop (Pseudo-Prospective Propagation) algorithm: New eligibility trace algorithm in VJP-based methods
Added
pp_proptobraintrace/_etrace_vjp/esd_rtrl.pyUpdated
docs/apis/algorithms.rstto include PP-Prop in algorithm documentation
Python 3.14 Support#
Added Python 3.14 compatibility: Updated project metadata to officially support Python 3.14
Updated
pyproject.tomlclassifiers to include Python 3.14
Bug Fixes#
Fixed version info tuple creation: Corrected the version info structure in
braintrace/__init__.pyEnsures proper version tuple formatting for compatibility checks
CI/CD Improvements#
Updated GitHub Actions workflow: Bumped
actions/upload-artifactfrom v5 to v6Modernized CI/CD pipeline with latest GitHub Actions versions
Improved artifact upload reliability and performance
Documentation Updates#
Updated documentation links: Refreshed links in concept documentation for better navigation
Updated
docs/quickstart/concepts-en.ipynb(116 lines modified)Updated
docs/quickstart/concepts-zh.ipynb(104 lines modified)
Breaking Changes#
Dependency Change:
Dependency name change: The project now requires
brainpy-stateinstead ofbrainpyUpdate your
requirements.txtor installation commands accordingly
# Old (0.1.1)
pip install brainpy
# New (0.1.2)
pip install brainpy-state
Import path update: Update neuron model imports to use
brainpy.state
# New (0.1.2)
from brainpy.state import IF, LIF, ALIF
Migration Guide#
Update Dependencies#
Replace brainpy with brainpy-state in your project dependencies:
pip uninstall brainpy
pip install brainpy-state
Update Import Statements#
If you have custom code importing neuron models, update to use brainpy.state:
# Find and replace in your codebase
# from brainpy import → from brainpy.state import
Version#
Bumped version from
0.1.1to0.1.2
Version 0.1.1#
Major Changes#
Project Rename: BrainScale → BrainTrace#
Renamed the entire project from
brainscaletobraintrace: This change reflects the project’s focus on eligibility trace-based learning algorithmsPackage directory renamed from
brainscale/tobraintrace/All internal imports updated from
brainscaletobraintraceUpdated all 95 files including source code, tests, documentation, and examples
Updated
pyproject.tomlwith new project name and metadataUpdated README with new project branding and citation information
VJP-Based Eligibility Trace Algorithms#
Added new VJP-based eligibility trace module (
_etrace_vjp/): Comprehensive implementation of vector-Jacobian product based algorithmsbase.py: Core base classes and utilities for VJP operations (671 lines)d_rtrl.py: Diagonal Real-Time Recurrent Learning implementation (756 lines)esd_rtrl.py: Efficient Sparse Diagonal RTRL implementation (847 lines)hybrid.py: Hybrid approaches combining multiple techniques (604 lines)graph_executor.py: Graph-based execution for VJP computationsmisc.py: Miscellaneous utilities including matrix spectrum normalization
Refactored VJP algorithm structure: Migrated from monolithic
_etrace_vjp_algorithms.py(2,888 lines) to modular architectureBetter separation of concerns
Improved testability with dedicated test files (
d_rtrl_test.py,esd_rtrl_test.py,graph_executor_test.py)
Logo and Branding#
Updated logo format from JPG to PNG for consistency
Updated logo across documentation
Breaking Changes#
Package Rename:
Import path change: All imports must now use
braintraceinstead ofbrainscale
# Old (0.1.0)
import brainscale
from brainscale import EligibilityTrace
from brainscale.nn import Linear, GRUCell
# New (0.1.1)
import braintrace
from braintrace import EligibilityTrace
from braintrace.nn import Linear, GRUCell
Installation: Package name changed from
brainscaletobraintrace
# Old
pip install brainscale
# New
pip install braintrace
Migration Guide#
Update Import Statements#
Replace all occurrences of brainscale with braintrace:
# Find and replace in your codebase
# brainscale → braintrace
VJP Algorithm Usage#
The new VJP-based algorithms are now available through the modular interface:
Version#
Bumped version from
0.1.0to0.1.1
Version 0.1.0#
Major Changes#
State Management Refactoring#
Renamed
ETraceStatetoHiddenState: All eligibility trace state management now uses the more generalHiddenStatenaming conventionUpdated across
_etrace_algorithms.py,_etrace_concepts.py,_state_managment.pyAdded deprecation warnings for
ETraceStateto guide users tobrainstate.HiddenStateUpdated all documentation and examples to reflect the new naming
Renamed
ETraceGroupStatetoHiddenGroupState: Improved consistency in hidden state handlingUpdated in
_etrace_compiler_hidden_group.pyAdded deprecation warnings for backward compatibility
Added deprecation handling: Implemented
__getattr__in main__init__.pyto provide helpful warnings when using deprecated names:ETraceState→brainstate.HiddenStateETraceGroupState→brainstate.HiddenGroupStateETraceTreeState→brainstate.HiddenTreeState
Neural Network Module Reorganization#
Consolidated neural network modules: Removed standalone neuron, synapse, and activation modules, migrating them to
brainstateandbrainpyecosystemsDeleted files:
brainscale/nn/_neurons.py(IF, LIF, ALIF now inbrainpy.state)brainscale/nn/_synapses.py(Expon, Alpha, DualExpon, STP, STD now inbrainpy.state)brainscale/nn/_elementwise.py(activation functions now inbrainstate.nn)brainscale/nn/_poolings.py(pooling layers now inbrainstate.nn)
Renamed
_rate_rnns.pyto_rnn.py: Simplified module naming for better clarityAdded comprehensive deprecation warnings in
nn.__getattr__: Automatically redirects users to the correct modules:Neuron models (IF, LIF, ALIF) →
brainpy.stateSynapse models (Expon, Alpha, DualExpon, STP, STD) →
brainpy.stateActivation functions (ReLU, Sigmoid, etc.) →
brainstate.nnPooling layers (MaxPool, AvgPool, etc.) →
brainstate.nnDropout layers →
brainstate.nn
API Improvements#
Normalization parameter standardization: Renamed
normalized_shapetoin_sizeacross all normalization layers for consistencyUpdated in
_normalizations.pyfor LayerNorm, GroupNorm, InstanceNorm, etc.Improved clarity and consistency with other layer APIs
Enhanced input dimension validation: Improved error checking in convolutional layers to catch dimension mismatches early
Refactored imports for consistency: Updated all internal imports to use
braintoolsfor optimization and initialization utilities consistently across the codebase
Testing Infrastructure#
Added comprehensive unit tests for neural network modules:
_conv_test.py: 868 lines of tests for convolutional layers (Conv1d, Conv2d, Conv3d, ConvTranspose)_linear_test.py: 658 lines of tests for linear layers (Linear, Identity)_normalizations_test.py: 695 lines of tests for normalization layers (LayerNorm, BatchNorm, GroupNorm, etc.)_readout_test.py: 763 lines of tests for readout layers (LeakyRateReadout, LeakySpikeReadout)_rnn_test.py: 710 lines of tests for RNN cells (VanillaRNNCell, GRUCell, LSTMCell, MGUCell, etc.)Total: 3,694 lines of new test coverage
Documentation Updates#
Streamlined API documentation: Updated
docs/apis/nn.rstto remove redundant sections and enhance RNN overviewUpdated tutorials and examples: All 16 tutorial notebooks and 11 example scripts updated to reflect new APIs:
Concepts tutorials (en/zh)
RNN and SNN online learning guides
Batching strategies documentation
ETrace state management examples
Graph visualization tutorials
Code Quality Improvements#
Removed redundant docstrings: Cleaned up duplicate documentation in
LeakyRateReadoutandLeakySpikeReadoutImproved code organization: Streamlined
__all__definitions across all modulesEnhanced readability: Consistent import structure and better code formatting throughout
Dependency Updates#
Updated
requirements.txt: Refined dependency specifications to ensure compatibility with latestbrainstateandbrainpyversionsUpdated
pyproject.toml: Bumped version to 0.1.0 and updated project metadata
Breaking Changes#
API Changes:
State class renaming (with deprecation warnings):
ETraceState→ Usebrainstate.HiddenStateinsteadETraceGroupState→ Usebrainstate.HiddenGroupStateinsteadETraceTreeState→ Usebrainstate.HiddenTreeStateinstead
Neural network component migration (with deprecation warnings):
Neuron models (IF, LIF, ALIF) → Use
brainpy.statemoduleSynapse models (Expon, Alpha, etc.) → Use
brainpy.statemoduleActivation functions → Use
brainstate.nnmodulePooling layers → Use
brainstate.nnmodule
Normalization parameter rename:
normalized_shape→in_size(for LayerNorm, GroupNorm, etc.)
Module file reorganization:
nn/_rate_rnns.py→nn/_rnn.pyRemoved:
_neurons.py,_synapses.py,_elementwise.py,_poolings.py
Migration Guide#
For State Management:#
# Old (0.0.11)
from brainscale import ETraceState, ETraceGroupState
# New (0.1.0)
from brainstate import HiddenState, HiddenGroupState
For Neural Network Components:#
# Old (0.0.11)
from brainscale.nn import IF, LIF, Expon, ReLU, MaxPool2d
# New (0.1.0)
from brainpy.state import IF, LIF, Expon
from brainstate.nn import ReLU, MaxPool2d
For Normalization Layers:#
# Old (0.0.11)
norm = LayerNorm(normalized_shape=(128,))
# New (0.1.0)
norm = LayerNorm(in_size=128)
Note: All deprecated APIs include automatic warnings that will guide you to the correct replacements. The old APIs will continue to work in 0.1.0 but will be removed in a future release.
Version#
Bumped version from
0.0.11to0.1.0
Version 0.0.11#
Major Changes#
Import Refactoring#
Migrated imports from
brainstatetobraintools: All initialization-related imports now usebraintools.initinstead ofbrainstate.initUpdated imports in:
brainscale/nn/_neurons.py: Changedfrom brainstate import inittofrom braintools import initbrainscale/nn/_linear.py: Changedfrom brainstate import inittofrom braintools import initbrainscale/nn/_conv.py: Updated initialization importsbrainscale/nn/_synapses.py: Updated initialization importsbrainscale/nn/_readout.py: Updated initialization imports
Migrated neural network model imports from
brainstate.nntobrainpy: Updated base classes for neuron modelsIF,LIF,ALIFnow inherit frombrainpyinstead ofbrainstate.nnMaintained API compatibility while using the new
brainpybackend
Updated functional API calls: Changed from
brainstate.functional.sigmoidtobrainstate.nn.sigmoidin RNN cells
Dependency Updates#
Added
brainpyas a required dependency inrequirements.txt
Documentation Enhancements#
Improved docstring formatting across the codebase:
Enhanced parameter documentation with proper type annotations using NumPy-style docstrings
Added missing “Returns” sections to property and method docstrings
Converted inline examples to proper “Examples” sections with code blocks
Updated documentation in:
brainscale/_etrace_algorithms.py: EnhancedEligibilityTraceandETraceAlgorithmdocumentationbrainscale/_etrace_compiler_base.py: Improved parameter and return type documentationbrainscale/_etrace_compiler_module_info.py: Enhanced module documentation
Core Algorithm Updates#
RNN State Management: Updated all RNN cells to use
braintools.init.paramfor state initialization and resetValinaRNNCell: Updatedinit_state()andreset_state()methodsGRUCell: Updated state management and activation functionsCFNCell: Updated forget and input gate implementationsMGUCell: Updated minimal gated unit state handling
Test Updates#
Refactored test imports: Updated test files to use new import paths
brainscale/_etrace_model_test.py: Updated with new import structurebrainscale/_etrace_vjp_algorithms_test.py: Aligned with new API
Version#
Bumped version from
0.0.10to0.0.11
Files Changed (17 files)#
.gitignore: Added new patternsbrainscale/__init__.py: Updated version numberbrainscale/_etrace_algorithms.py: Enhanced documentation and importsbrainscale/_etrace_compiler_base.py: Improved documentationbrainscale/_etrace_compiler_graph.py: Minor updatesbrainscale/_etrace_compiler_hidden_group.py: Minor updatesbrainscale/_etrace_compiler_module_info.py: Enhanced documentationbrainscale/_etrace_model_test.py: Updated test importsbrainscale/_etrace_vjp_algorithms_test.py: Updated test importsbrainscale/_etrace_vjp_graph_executor.py: Updated importsbrainscale/nn/_conv.py: Migrated to braintools importsbrainscale/nn/_linear.py: Migrated to braintools importsbrainscale/nn/_neurons.py: Migrated to brainpy and braintoolsbrainscale/nn/_rate_rnns.py: Migrated to braintools and updated functional APIsbrainscale/nn/_readout.py: Updated importsbrainscale/nn/_synapses.py: Updated importsrequirements.txt: Added brainpy dependency
Breaking Changes#
None. All changes maintain backward compatibility at the API level.
Migration Guide#
If you have custom code using brainscale:
No changes required for end users
If extending brainscale internally, note that initialization utilities now come from
braintoolsinstead ofbrainstate