AI Grimoire
Sheet
statusstandard
difficultyintermediate
timeO(n·log n)
memoryO(n)
described1980
revisedtoday

Parallel Scan

A recurrence looks inherently sequential. If the update is associative it is not — the same result is reachable in log n dependent steps, which is what lets a recurrent model train on a GPU.

Standing

Current standardPart of the default recipe. Present in most frontier models trained today, and the thing a new design departs from rather than argues for.

The algorithm that makes every modern recurrent model trainable. Not new, not from machine learning, and the reason Mamba exists at all.

judged as of 2026-09 · what the labels mean

Theory

An RNN trains slowly because hth_t needs ht1h_{t-1}. That dependency looks like a law of nature. For a linear recurrence it is not.

ht=atht1+bth_t = a_t \odot h_{t-1} + b_t
eq. 1 — the form that admits a scan

with ata_t and btb_t computed from the input alone — not from hh. Every element of the state evolves independently, and the update is affine.

Composition

Two consecutive steps compose into a single step of the same shape.

(a2,b2)(a1,b1)=(a2a1,  a2b1+b2)(a_2, b_2) \bullet (a_1, b_1) = \bigl(a_2 a_1,\; a_2 b_1 + b_2\bigr)
eq. 2 — the associative operator

Substituting confirms it: applying (a1,b1)(a_1,b_1) then (a2,b2)(a_2,b_2) gives a2(a1h+b1)+b2a_2(a_1 h + b_1) + b_2, which is the composed pair applied once. And the operator is associative, because function composition always is.

That is the whole trick. Associativity means the operations can be grouped however you like, and a different grouping is a different schedule — a balanced binary tree instead of a chain.

Blelloch’s two sweeps

Up-sweep. Pair adjacent elements and compose, giving n/2n/2 pairs each representing two steps. Repeat. After logn\log n rounds the root holds the composition of the entire sequence. This is a reduction tree.

Down-sweep. Walk back down, pushing each node’s accumulated prefix into its children, so every leaf ends up holding the composition of everything to its left. Another logn\log n rounds.

depth=2log2nwork=O(n)\text{depth} = 2\log_2 n \qquad \text{work} = O(n)
eq. 3 — depth against work

For n=4096n = 4096: 24 dependent steps rather than 4096. The total arithmetic is about twice the sequential version, which is an excellent trade on hardware with thousands of idle cores.

Why every recurrent model since 2021 is linear

The scan requires eq. 1’s form. A tanh\tanh inside the recurrence destroys associativity — tanh(a2tanh(a1h+b1)+b2)\tanh(a_2\tanh(a_1 h + b_1) + b_2) does not collapse into one affine step — and no amount of engineering recovers it.

So the design question inverted. Rather than asking what recurrence is most expressive and accepting slow training, the field asked what can be trained in parallel and found expressiveness elsewhere: nonlinearities between layers, and input-dependent ata_t within them. Selective SSMs, RWKV and gated linear attention are all eq. 1 with different parameterisations of aa and bb, and they are all trained by this algorithm.

Implementation

python · torch · Hillis–Steele, the simpler variant
import torch
from torch import Tensor


def scan(a: Tensor, b: Tensor) -> Tensor:
    """h_t = a_t * h_{t-1} + b_t, over dim 1. a, b: [B, T, D]."""
    a, b = a.clone(), b.clone()
    t, shift = a.size(1), 1

    while shift < t:
        # Compose each element with the one `shift` places behind it. After
        # round k every element holds the composition of 2^k steps.
        a_prev = torch.nn.functional.pad(a[:, :-shift], (0, 0, shift, 0), value=1.0)
        b_prev = torch.nn.functional.pad(b[:, :-shift], (0, 0, shift, 0), value=0.0)

        b = a * b_prev + b
        a = a * a_prev
        shift *= 2

    return b


def scan_reference(a: Tensor, b: Tensor) -> Tensor:
    """The loop it replaces. Same answer, 4096 launches instead of 12."""
    h = torch.zeros_like(b[:, 0])
    return torch.stack([(h := a[:, t] * h + b[:, t]) for t in range(b.size(1))], 1)

Hillis–Steele is O(nlogn)O(n\log n) work against Blelloch’s O(n)O(n), and is nonetheless what most implementations use: it has half the depth, no down-sweep, and a memory access pattern the hardware likes. Asymptotically worse and reliably faster at the sizes that occur.

The part the papers do not put in the equations

The real kernels do not run the scan on the full sequence in one go. They tile it: load a chunk into SRAM, scan it there, carry one composed pair across the chunk boundary, and never write intermediate states to HBM. That is the same IO-aware argument as FlashAttention, and it is why Mamba’s paper spends as much space on the memory hierarchy as on the model — the algorithm was settled in 1980, and what was new was making it bandwidth-efficient.

Sequential depth
4 096
Scan depth
24
Total work
≈ 2n
Sequence of 4096

Related

References

[1]Blelloch — Prefix Sums and Their Applications (1990)CMU-CS-90-190
[2]Martin & Cundy — Parallelizing Linear Recurrent Neural Nets Over Sequence Length (2017)arXiv:1709.04057
[3]Smith et al. — Simplified State Space Layers for Sequence Modeling (2022)arXiv:2208.04933