SequenceDriverMixin#
- class braintrace.SequenceDriverMixin#
etrace_grad/etrace_evolve, written once for both hosts.Hosts supply three hooks:
_seq_callThe callable that drives one step or window. Defaults to
self._seq_param_statesThe default set of
brainstate.ParamStateto differentiate._seq_vjp_methodThe learner’s
vjp_method, orNoneif it has none.
_seq_vjp_methodis a hook rather than agetattron the driver becausebrainstate.nn.Vmapdefines no__getattr__and so does not forwardvjp_methodfrom.module. Reading the attribute off the driver object would silently yieldNonefor every vmapped learner and bypass the window-mode validation entirely.- etrace_evolve(*sequences, step_fn=None, chunk_size=None, return_outputs=False)#
Drive the model and the eligibility trace forward, computing no gradient.
Hidden states and eligibility traces advance exactly as they do inside
etrace_grad().- Parameters:
*sequences – As in
etrace_grad().step_fn (callable, optional) –
None(default) calls the learner directly with the slices – and, under window mode, wraps them inMultiStepData, since with nostep_fnevery sequence is by definition a model input. Supplying astep_fnopts out of that wrapping entirely.chunk_size (int, optional) – As in
etrace_grad(), except that windows are legal on eithervjp_method: this method runs no loss VJP, which is the only thing single-step learners refuse.return_outputs (bool, optional) –
False(default) returnsNoneand stacks nothing, so a long warm-up costs no output memory.Truestacks whatever the call returned, with leading axisT // chunk_size.
- Returns:
None or stacked outputs –
Nonewhenreturn_outputs=False; otherwise the stacked per-step (or per-window) return values of the driven call.- Raises:
TypeError – As in
etrace_grad(), for chunk_size and for wrapped sequences.ValueError – As in
etrace_grad(), except that a window is not refused for being on a single-step learner – no loss VJP is taken here, so the restriction does not apply. Windows are still refused under a vmapped learner, andT % chunk_size == 0still holds.
Examples
>>> import braintrace >>> learner = braintrace.compile(model, 'D_RTRL', xs[0], batch_size=1) >>> warmup_inputs, xs, ys = inputs[:20], inputs[20:], targets[20:] >>> >>> learner.etrace_evolve(warmup_inputs) # free-running prefix >>> grads = learner.etrace_grad(xs, ys, step_fn=step_loss)
- etrace_grad(*sequences, step_fn, mask=None, chunk_size=None, weights=None, reduction='mean', loss_output='per_step', has_aux=False, return_value=False)#
Accumulate online gradients over a sequence.
- Parameters:
*sequences – One or more pytrees whose leaves share a leading length
T, sliced in lockstep and passed tostep_fnpositionally. There is no distinguishedtargetsargument. May not be aSingleStepData/MultiStepDatawrapper – wrap insidestep_fninstead.step_fn (callable) – The user’s step function, which runs the model itself and returns the loss. Keyword-only and required. It must call this learner exactly once per invocation: zero calls leave the trace un-advanced for that step, two advance it twice, and neither is detectable from the returned gradient.
mask (array, optional) –
(T,)per-step loss weights;Nonemeans all-ones. Values need not be binary. Gates only the loss – the model and the eligibility trace are driven at every step regardless.chunk_size (int, optional) –
None(default) or1drive step-by-step, handingseq[t]tostep_fn, which returns a scalar.k >= 2drives in windows, handingseq[t:t+k]of shape(k, ...), andstep_fnmust return a(k,)vector of per-step losses and wrap its model inputs inMultiStepData. Window mode requiresvjp_method='multi-step'andT % k == 0, and is not available under a vmapped learner.weights (dict, optional) – The
brainstate.ParamStateto differentiate. Defaults to the learner’s ownparam_states.reduction ({‘mean’, ‘sum’}, optional) –
'mean'(default) divides by the total mask weight,max(sum_t mask_t, 1), not byT.loss_output ({‘per_step’, ‘masked’, ‘scalar’}, optional) – What
return_value=Truehands back: the raw pre-mask losses(T,), the masked losses(T,), or the reduced objective (scalar). Ignored whenreturn_value=False.has_aux (bool, optional) – Whether
step_fnreturns(loss, aux).return_value (bool, optional) – Whether to return the losses alongside the gradients.
- Returns:
grads or tuple – Mirrors
brainstate.transform.grad:grads,(grads, losses),(grads, aux)or(grads, losses, aux).- Raises:
TypeError – If
step_fnis not given; if chunk_size is notNoneor a Pythonint(a traced or numpy value is refused, since the value has to be known at trace time); or if a sequence is aSingleStepData/MultiStepDatawrapper. The wrappers are registered pytree nodes, so slicing one would decompose the wrapper rather than the data.ValueError – If no sequences are given, if their leading lengths disagree, or if
T == 0– there is nothing to slice. If chunk_size is below1; ifk >= 2but the learner’svjp_methodis not'multi-step'(the executor would raise three frames down), the learner is vmapped (in_axes=0would map time as the batch axis), orT % k != 0. If mask is not shape(T,). If reduction or loss_output is not one of its legal values. Ifstep_fnreturns a non-scalar in plain mode, or anything but shape(k,)in window mode. If the learner has not been compiled, the existing guard raises before any of these.
Warning
Two known limitations are reachable through this method. Neither is a driver defect – both belong to the engines it drives – but
chunk_sizeis what makes them easy to hit by accident, so they are named here rather than only in the limitations document.F-30, window mode on an IO-factorized engine.
pp_prop/ES_D_RTRL/OSTLFeedforward, and anytrace_factorization='io_factorized'config, undo the warm-up bias of their f-side smoothing with a factor indexed byrunning_index, which countsupdate()calls. Ak-step window advances the tracektimes butrunning_indexonce, so the correction lags the trace by a factor ofk. Measured atT=6, k=2, decay=0.9: 2.1e-03 relative against the same run indexed by true trace steps. Driving withchunk_size=Noneavoids it entirely.F-35, DNI and the synthesizer’s deployment contract. A
train_synthetic_gradient()fit is valid only for the exact(loss_fn, chunk_size)pair it was trained on, and nothing checks that deployment matches. Fit and drive at the samechunk_size– note that1andNoneare the same path on both sides. A mismatch is not degraded DNI but noise shaped like a cotangent: fitting at1and deploying at2moves the gradient 2.0e-02 relative.maskis the other half of the contract, since it changes the objective the synthesizer is predicting the future of.reductionis not: it divides once after the loop and never enters the differentiated objective.Notes
gradsis the learner’s online-gradient estimate of the reduced objective, not in general its mathematical derivative. For an exact algorithm inside its valid regime the two coincide; for every approximate rule they deliberately do not, and that difference is the algorithm’s content.reductionandmaskdefine the objective the estimate is aimed at.Examples
>>> import braintools >>> import braintrace >>> learner = braintrace.compile(model, 'D_RTRL', inputs[0], batch_size=1) >>> opt = braintools.optim.Adam(1e-3) >>> opt.register_trainable_weights(learner.param_states) >>> >>> def step_loss(inp, tar): ... out = learner(inp) ... return braintools.metric.squared_error(out, tar).mean() >>> >>> grads, loss = learner.etrace_grad( ... inputs, targets, step_fn=step_loss, ... loss_output='scalar', return_value=True) >>> opt.update(grads)
- SequenceDriverMixin.__init__()#