Skip to main content

Execution API

Conceptual treatment and the measured numbers are in Execution and parallel-in-time; this page is the signatures.

unroll

jp.unroll(net, xs, state=None) -> (outputs, final_state)

Runs net over the leading (time) axis of xs, which is (T, batch, ...). Returns the per-timestep outputs stacked on the leading axis plus the final state, so a long sequence can be processed in chunks by feeding the returned state back in. If state is None, it calls net.init_state(xs.shape[1:]).

One lax.scan step per timestep, with membrane state materialized at every step so BPTT can reach it: O(T·B·N) memory. This is the reference path and supports every neuron model.

unroll_checkpointed

jp.unroll_checkpointed(net, xs, state=None, chunk_size=None) -> (outputs, final_state)

Identical results to unroll; the backward pass re-runs each chunk's forward instead of keeping every timestep's residuals live. Peak scratch drops from O(T·B·N) to roughly O(sqrt(T)·B·N) for one extra forward pass — 67× less memory at T=5000 for a 1.09× time cost on a T4.

chunk_size defaults to the divisor of T closest to sqrt(T), and must divide T. Padding is refused rather than supported: padded timesteps would still advance the recurrence and silently corrupt the returned final state.

unroll_parallel

jp.unroll_parallel(net, xs, state=None) -> (outputs, final_state)

Solves the whole time axis with an associative scan over the affine recurrence. Requires every layer to be stateless or reset-free; raises by name for anything else, including recurrent Graphs.

Results are bit-identical to unroll in the spike train (membrane values differ by at most 6e-07 from accumulation order). Costs memory: it materializes the full [T, B, N] activation tensor.

jp.parallel.supports_parallel(layer) -> bool

Whether a single layer can take the parallel path — true if it implements parallel_apply.

jp.parallel.scan_linear_recurrence(a, b, v0) -> v

Solves v[t] = a[t]*v[t-1] + b[t] for all t with an associative scan. This is the primitive to use when adding parallel_apply to your own neuron.

Readouts

jp.spike_rate(spikes) # mean over time
jp.spike_count(spikes) # sum over time
jp.density(spikes) # fraction of (timestep, neuron) slots that spiked
jp.max_membrane_logits(membrane) # max over time -- pair with a LeakyIntegrator readout
jp.count_logits(spikes) # alias for the spike-count readout

density is worth watching during development: it catches a layer that has gone silent, and below roughly 10% a sparse gather-and-accumulate path would beat a dense matmul.