A state-space model is a sequence layer that keeps a fixed-size summary of everything it has read, called its state, and updates that state with each new token. The update is linear in the state and the same at every step, so the same layer can run one token at a time like a recurrent network or over a whole sequence at once like a convolution. This section builds such a layer from a continuous-time system, the kind of equation engineers use for circuits and springs, and then chooses the system so that the state is a faithful record of the distant past. The result, called S4, was the first model to solve a task whose answer depends on inputs 16,000 steps apart, and the state-space layers inside today's hybrid language models descend from it.
Here is the task. The Long Range Arena benchmark (Tay et al. 2021) collects tasks built to break efficient sequence models. The hardest, Path-X, shows a model a 128 by 128 black-and-white image one pixel at a time, 16,384 steps in all, and asks whether two marked dots are joined by a dashed path. The Transformer and every faster variant of it that the benchmark tested stayed at 50%, the accuracy of a coin flip. The answer depends on pixels thousands of steps apart, and nothing carried the signal that far. A year later S4, the structured state-space sequence model, reached 88% (Gu, Goel & Ré 2021). The same model classified raw one-second recordings of ten spoken words, 16,000 numbers per clip with no hand-made audio features, at 98.3% accuracy.
Two earlier sections set up what such a layer must look like. A recurrence is linear in its state when each step multiplies the old state by a number and adds a weighted input, ht=aht−1+uxt, with a decaya and an input weightu. §21.1 showed that such a recurrence unrolls into a lower-triangular matrix, so it trains in parallel, and that its memory is set by the decay. An input's weight halves every t1/2=ln2/(−lna) steps, its half-life. §21.2 showed what happens with no decay at all. A state that only adds is crowded out by its own past.
Neither section said how to choose the decays. A half-life of 16,000 steps needs a decay within about 4×10−5 of 1. Noticing what happened three steps ago needs fast decays too. And a few fading sums of the inputs are a crude record of the past. We would rather have a state from which the whole history could be redrawn.
This section gets all of that from the continuous-time linear system of physics and control theory. We solve it exactly and turn it into a recurrence over tokens in three ways, two of which stay stable at any step size. We show that the same layer is a long convolution, which the fast Fourier transform computes in O(TlogT) time. We derive a state matrix, called HiPPO, that makes the state the best polynomial summary of the past. We see why S4 needed a structured form of it, and why its successors got away with a plain diagonal matrix. The section ends with the one thing this whole family cannot do, which §21.4 fixes.
A system that integrates its input
Fill a leaky bucket from a tap. The water level rises with the inflow and drains in proportion to how full the bucket is. Write h(t) for the level at time t, x(t) for the inflow, and h˙(t) for the rate at which the level changes. Then h˙(t)=−h(t)+x(t). Start empty and pour at a constant rate of 1. The level is h(t)=1−e−t. It is 0.63 after one unit of time and 0.95 after three, and it approaches 1, the level at which drain balances inflow.
The level at any moment is a weighted record of all past inflow, with recent inflow weighted most. A resistor charging a capacitor, a mass on a damped spring, and a thermometer obey the same kind of equation. Each one integrates its input and forgets at a rate set by its own constants. Each one is also linear. Twice the input gives twice the response, and the response to a sum of inputs is the sum of the responses.
Now give the system N internal quantities that feed into each other, like N connected tanks. The state becomes a vector, and the rates become a matrix.
In plain words, the state changes at a rate that is a fixed linear function of the state and the current input, and the output is a fixed linear readout of the state, plus a direct path from the input.
A few notation rules hold for the whole chapter. A is always a state matrix, never a matrix of attention weights. N is the state size, never a parameter count. Bold h(t) with parentheses is the continuous state, and ht with a subscript will be its value at token t. SSM is short for state-space model.
Solving it exactly
For a single number a, the equation h˙=ah is solved by h(t)=eath(0). A square matrix has an exponential too, defined by the same power series, and it solves the matrix equation in the same way.
For a diagonal matrix the third property is all there is. With A=diag(−1,−2), eAt=diag(e−t,e−2t), two leaky buckets that drain at different rates.
In plain words, the state now is what is left of the old state, plus every past input pushed through the system's response eA(t−s)B for the time t−s since it arrived. Check it on the bucket with constant inflow, where A=−1, B=1, and h(0)=0. The integral is ∫0te−(t−s)ds=1−e−t, the curve above.
∂ Proof of the solution formula
Multiply the equation by e−At and move the Ah term to the left, e−Ath˙−e−AtAh=e−AtBx. By property 1 and the product rule, the left side is dtd(e−Ath(t)). Integrate both sides from t0 to t,
e−Ath(t)−e−At0h(t0)=∫t0te−AsBx(s)ds,
then multiply on the left by eAt and use property 2. For stability, property 3 gives eAt=Vdiag(eλnt)V−1, and ∣eλnt∣=etReλn→0 when Reλn<0. ■
In the basis of eigenvectors, each coordinate of the state is its own leaky bucket with rate λn. We call such a coordinate a mode. An eigenvalue does not have to be real. A real matrix can have complex eigenvalues, in conjugate pairs λ=σ±iω. Then eλt=eσt(cosωt+isinωt) is a rotation at frequency ω whose radius shrinks at rate ∣σ∣. It is the complex rotation of rotary position embeddings (§5.5), now with a decay.
Real eigenvalues give a memory that fades smoothly. Complex ones give a memory that oscillates as it fades, which lets a few state numbers represent wiggly patterns in the past. This is the eigenvalue analysis of a linear RNN from §3.3, carried into continuous time. The powers λk of a discrete step have become exponentials eλt, and the condition ∣λ∣<1 has become Reλ<0. In practice the memory of a state-space layer is set by the eigenvalues of A, and an interviewer who asks how long such a layer remembers wants to hear about their real parts.
From continuous time to tokens
A sequence of tokens is not a function of time. To use the equation we pretend that it is a signal sampled every Δ units of time. Token xt is held constant over the interval ((t−1)Δ,tΔ] that ends at step t, and the state after step t is ht=h(tΔ). So input t affects output t, the update-then-read convention of this chapter. The step size Δ>0 is a learned parameter, and we will see that it is the most important one.
Turning one step of the equation into an update rule is called discretizing it. There are three standard ways.
Zero-order hold (ZOH). Hold the input constant over the step and solve the equation exactly. Apply the solution formula from t0=(t−1)Δ to tΔ with x(s)=xt,
where the second form substitutes τ=tΔ−s. By property 1, dτd(A−1eAτ)=eAτ when A is invertible, so the integral is A−1(eΔA−I). Nothing is approximated. The ZOH update is exact for an input that really is held constant over each step.
Euler. Replace the derivative by a forward difference, ht−ht−1≈Δ(Aht−1+Bxt). This is the first thing anyone would try, and we will see that it is the worst.
Bilinear, also called the trapezoid rule. Average the state term over the two ends of the step, ht−ht−1≈2ΔA(ht+ht−1)+ΔBxt, and solve for ht. That gives (I−2ΔA)ht=(I+2ΔA)ht−1+ΔBxt. This is the rule S4 used.
In plain words, the discrete decay of each mode depends only on Δ times its eigenvalue. A discretized SSM is the linear recurrence of §21.1, with a decay Aˉ and an input weight Bˉ. They are no longer free numbers. They are functions of a continuous system and a step size.
The cell repeats the example for all three rules.
Python · cell 1
Which rules are stable at every step size
The step size is learned, so it moves during training. A recurrence run for 16,000 steps turns any discrete eigenvalue of size above 1 into overflow. So we need a stable continuous system to stay stable after discretization, whatever Δ is. In the complex plane, the stable continuous eigenvalues fill the left half-plane (Reλ<0). The stable discrete ones fill the unit disk, the complex numbers of size less than 1.
In plain words, ZOH and bilinear map the whole left half-plane into the unit disk, so you can train Δ freely and never leave the stable region. Euler scales the plane by Δ and then shifts it right by 1. Only the disk of radius 1 around −1 lands inside the unit disk, so a large step pushes fast modes out.
∂ Proof of the stability theorem
With A=VΛV−1, each of the three Aˉ equals Vdiag(λˉn)V−1, where λˉn is the scalar rule applied to λn. For ZOH this is property 3. For the other two it holds because sums, products, and inverses of matrices of the form V(diagonal)V−1 act entry by entry on the diagonal. So it is enough to check scalars.
Write z=Δλ, so Rez<0. ZOH gives ∣ez∣=eRez<1. For bilinear, ∣1+z/2∣2−∣1−z/2∣2=2Rez<0, so ∣(1+z/2)/(1−z/2)∣<1. Euler's 1+z lies in the unit disk only if z lies in the disk of radius 1 around −1. For a real z=−Δ∣λ∣ that means Δ∣λ∣<2. ■
Stability is one property and accuracy is another. Compare each rule with the exact factor ez=1+z+z2/2+z3/6+⋯, where z=Δλ.
Euler's 1+z is already wrong at the z2 term. Its error per step is O(Δ2), and over the t/Δ steps needed to reach a fixed time t the errors add up to O(Δ). A rule like this is called first order.
Bilinear agrees with ez up to the z2 term and is first wrong at z3, so its error over a fixed time is O(Δ2). It is second order.
ZOH is exact for the state.
∂ Expanding the bilinear rule
Expand 1/(1−z/2) as a geometric series, 1+z/2+z2/4+z3/8+⋯, and multiply by 1+z/2,
1−z/21+z/2=1+z+2z2+4z3+⋯.
The first three terms match ez. The cubic term is z3/4 against the exact z3/6, an error of z3(1/4−1/6)=z3/12 per step. Over t/Δ steps that adds up to O(Δ2). ■
The next cell measures all three on a decaying rotation with no input, and then looks at a fast mode.
Python · cell 2
Halving Δ divides the bilinear error by 4, the signature of a second-order rule. It divides the Euler error by 3.4, then 2.5, then 2.2, approaching the factor 2 of a first-order rule. The ZOH error is at rounding level.
The fast mode shows something the orders do not. At Δ∣λ∣=5, ZOH decays it almost completely (e−5=0.0067). Euler explodes, with aˉ=−4. Bilinear keeps it stable but sends it to aˉ=−0.43, a mode that flips sign every step. As Δ∣λ∣→∞ the bilinear aˉ tends to −1, so fast modes ring instead of dying. That is one reason the later diagonal models and Mamba prefer ZOH.
rule
λˉ for one eigenvalue
accuracy
stable for every Δ when Reλ<0
used by
Euler
1+Δλ
first order
no, needs ∣1+Δλ∣<1
the update printed in the HiPPO paper, rarely in layers
bilinear
1−Δλ/21+Δλ/2
second order
yes, fast modes tend to −1
S4, LSSL
ZOH
eΔλ
exact for held inputs
yes, fast modes tend to 0
DSS, S4D, S5, Mamba (Aˉ by ZOH, Bˉ≈ΔB)
exponential-trapezoidal
eΔλ, inputs blended across the step
second order in the input
yes
Mamba-3
The last column names the models of this chapter, each introduced where it first matters. S4, its predecessor LSSL, and the diagonal models DSS, S4D, and S5 appear later in this section. Mamba (§21.4) keeps the exact Aˉ=eΔA but uses Bˉ=ΔB, the first term of (ez−1)/z=1+z/2+⋯. Mamba-3 (Lahoti et al. 2026) later named that rule exponential-Euler. It replaced it with the exponential-trapezoidal rule of the last row, which blends the current and previous inputs and which §21.6 describes.
In practice this theorem is why every modern SSM learns a continuous eigenvalue and a step size, and never the discrete decay directly. An interviewer will ask you to derive ZOH, and then to explain why Euler is unsafe at a large step.
The step size is a timescale
Under ZOH, a continuous mode with rate Reλ<0 becomes a decay of size ∣λˉ∣=eΔReλ per step. Put that into the half-life formula t1/2=ln2/(−ln∣λˉ∣) of §21.1, and it becomes
t1/2=Δ∣Reλ∣ln2steps.
In plain words,Δ converts the system's own clock into token steps. A small Δ means that many tokens pass per unit of the system's time, so the state remembers for many tokens. A large Δ means that each token is a big jump in time, so the state forgets fast and tracks the latest input.
The step size gives these models a property that attention and discrete RNNs lack. The continuous system does not depend on Δ. If the data are recorded at a different rate, changing Δ in proportion gives the same continuous model. S4 was trained on speech sampled 16,000 times a second. On the same speech sampled at half that rate, with its Δ doubled and no retraining, it still scored 96.3% (Gu, Goel & Ré 2021).
The widget shows all of this on one eigenvalue. The left panel is the continuous plane, where you drag λ (its conjugate moves with it) inside the stable left half. The right panel is the discrete plane with the unit circle, and it marks where each rule sends λ for the current Δ. A faint trail shows where each rule sends λ as Δ grows, and Sweep Δ runs the points along it. Below, the chosen rule's mode is started at 1 and drawn solid (its real part) against the exact continuous mode, dashed. Up to a constant factor this is the mode's impulse response, what it does after a single unit input. Watch it decay, ring, or blow up.
Where does each discretization send a continuous eigenvalue, and what does that do to memory?
Drag λ on the left. The faint trails show where each rule sends it as Δ grows.
Loading interactive figure…
Warm-up
Discretize a diagonal SSM three ways
Implement discretize(lam, B, delta, method), which turns a diagonal SSM into its discrete recurrence with the formulas of the definition box.
lam holds the eigenvalues λn and B the input weights Bn, both complex arrays of shape (N,).
delta is a positive float, or an array that broadcasts to (N,) (one step size per mode).
method is 'zoh', 'bilinear', or 'euler'.
Return (Abar, Bbar), both of shape (N,). The tests check ZOH against the exact solution for a held input, check that the three rules agree as Δ→0, and check which rules stay stable at Δ=10.
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.
The same layer is a convolution
A recurrence with fixed parameters weighs each past input by how long ago it arrived and by nothing else. That is the definition of a convolution, and it gives a second way to compute the layer. Start with the bucket.
The same is true in general. Unroll the discrete recurrence from h0=0. The first state is Bˉx1, the second AˉBˉx1+Bˉx2, and in general ht=∑s=1tAˉt−sBˉxs. That is the unrolled form of §21.1 with a constant decay. Multiply by C and write j=t−s for the lag.
Proof. The unrolled state follows by induction. It holds at t=1. If it holds at t−1, then ht=Aˉ∑s=1t−1Aˉt−1−sBˉxs+Bˉxt=∑s=1tAˉt−sBˉxs. Multiplying by C and substituting j=t−s gives the sum. For a diagonal Aˉ, CAˉjBˉ=∑nCnλˉnjBˉn. ■
In plain words, the whole layer is one fixed filter slid along the sequence. The weight Kˉj says how much a token j steps ago contributes to the output now, and it is the same at every position. The kernel is also the layer's impulse response. Feed it a single 1 followed by zeros, and the outputs are Kˉ0,Kˉ1,… (plus D at the first step).
So a time-invariant SSM has two faces. As a recurrence it decodes one token at a time with O(N) work and a state of N numbers, like the RNNs of §3.3. As a convolution it trains on a whole sequence at once, like the convolutional networks of §0.8, but with a filter as long as the sequence. A CNN learns each filter weight separately, and a filter of 16,000 free weights would be far too many to learn well. Here the filter is generated by O(N) parameters, however long T is. For a diagonal A they are the N eigenvalues, B, C, Δ, and D. Fast parallel training and cheap decoding in one layer is the reason this line of work existed.
Conjugate pairs make the kernel real and halve its cost. A real A has complex eigenvalues in conjugate pairs. After diagonalizing, its B and C come in matching conjugate pairs too. So the kernel's terms pair up as q+q∗=2Req, where q∗ is the complex conjugate (we avoid the overbar, which here means discretized), and the kernel is real. Implementations therefore store only one mode of each pair, N/2 complex modes, and take twice the real part of their sum (Gu et al. 2022, S4D).
Convolution by FFT, without wraparound
Computed directly, the convolution of two sequences of length T costs O(T2), no better than attention. The Fourier transform turns a convolution into an entrywise product, and a fast algorithm computes the transform in O(TlogT). There is one catch. The transform's convolution wraps around the end of the sequence, and a causal model must not.
The definitions come first. For a sequence f of length L, indexed from 0, the discrete Fourier transform (DFT) is f^m=∑n=0L−1fne−2πimn/L. It is invertible, fn=L1∑m=0L−1f^me2πimn/L. The circular convolution of f and g is (f⊛g)n=∑j=0L−1fjg(n−j)modL, in which an index that runs off the start wraps around to the end. The convolution theorem says that the DFT of a circular convolution is the entrywise product f^mg^m. So a convolution costs two forward transforms, a product, and one inverse transform.
∂ The circular convolution theorem
Write ω=e−2πi/L, so f^m=∑nfnωmn and ωL=1. Substitute the circular sum into the DFT and swap the two finite sums,
using ωmn=ωmjωm(n−j). Because ωL=1, the power ωm(n−j) depends on n−j only modulo L, so it equals ωmk with k=(n−j)modL. As n runs over 0,…,L−1 with j fixed, k runs over the same values once each, and the inner sum is ∑kgkωmk=g^m. What is left is ∑jfjωmjg^m=f^mg^m.
For the inverse, L1∑mf^mω−mn=∑kfkL1∑mωm(k−n). The geometric sum ∑m=0L−1ωm(k−n) is L when k=n and (1−ωL(k−n))/(1−ωk−n)=0 otherwise, which leaves fn. ■
Computed from its definition, a DFT costs O(L2). The fast Fourier transform (FFT) computes a DFT or its inverse in O(LlogL) operations, and §35.2 derives the algorithm.
Now the wraparound. Take T=2, a kernel (K0,K1), and inputs (x0,x1). The causal output is y0=K0x0. A circular convolution of length 2 gives y0=K0x0+K1x1 instead, because the index 0−1 wraps around to 1. The future input x1 has leaked into the first output. The cure is to pad. Append zeros so both sequences have length 4. Now the wrapped index lands on a zero, and y0=K0x0+K1⋅0 is right.
In general, zero-pad both sequences to a length L≥2T−1. Every true lag lies in [−(T−1),T−1], every wrapped index lands on a zero, and the first T entries of the circular convolution equal the causal one. Three FFTs of length L cost O(TlogT).
The next cell builds a diagonal SSM with 8 conjugate pairs (N=16) and runs it as a recurrence over 4,096 tokens. It compares the result with the padded FFT convolution and with an unpadded one.
Python · cell 3
The recurrence and the padded FFT agree to rounding. The kernel's imaginary part is at rounding level, because the modes come in conjugate pairs. Without padding, the first outputs are off by 0.1 to 0.3, as large as the outputs themselves, because they have absorbed the tail of the sequence. This bug never crashes. In a causal model it leaks the future into the past, which is why it deserves its own test.
In practice, an SSM layer trains in convolution mode and decodes in recurrent mode, with the same parameters. An interviewer will ask you to show that the two give the same outputs, and what goes wrong without padding.
Interview
The SSM kernel and an FFT convolution
Implement the two halves of an SSM layer in convolution mode.
ssm_kernel(lam, B, C, delta, T) returns the ZOH kernel of a diagonal SSM, Kˉj=∑nCnBˉnejΔλn for j=0,…,T−1, as a real array of shape (..., T). The inputs lam, B, C are complex arrays of shape (..., N), closed under conjugation (every mode appears together with its conjugate). delta is a float, or an array of shape (...) with one step size per channel.
causal_fft_conv(x, K) returns yt=∑j=0tKjxt−j along the last axis, for real x and K of shape (..., T), computed with the FFT and without wraparound.
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.
What should the state remember
We now have a layer that is both a recurrence and a convolution, and stable at any step size. What it is good for depends entirely on the state matrix A, and a random one is nearly useless. The linear state-space layer, LSSL for short (Gu et al. 2021), was the direct predecessor of S4. The S4 paper reports what happened when the LSSL read handwritten digits one pixel at a time (Gu, Goel & Ré 2021). With a random state matrix it was 60% accurate. With the matrix derived below it was 98% accurate. The LSSL paper's own figure for random matrices is 62%, on a version of the task with the pixels shuffled. Where does the good matrix come from?
Gu, Dao, Ermon, Rudra & Ré 2020 asked a cleaner question. Suppose that at every moment t we want N numbers from which we could redraw the entire history x(s), 0≤s≤t, as well as possible. And we want to update those numbers online, as time moves forward, without storing the history. Which N numbers should we keep, and how should they change?
A natural answer is the best polynomial fit. Fit a polynomial of degree less than N to the history on [0,t] by least squares, and keep its coefficients. As t grows, the interval stretches, the fit is redone, and the coefficients change. The surprise is that they change by a linear differential equation. So the coefficients are the state of an SSM, and the equation tells us its matrix. The framework is called HiPPO, for high-order polynomial projection operators.
To make the fit easy, write the polynomial in a basis of polynomials that are orthonormal on the interval. Each has unit size, and any two are perpendicular in the sense of an inner product, an average of their product. Then each coefficient of the fit can be computed on its own.
The projection theorem of §0.10 says that the least-squares fit by a combination of orthonormal vectors has coefficients equal to the inner products with those vectors. That section works in Rn with the dot product. Its proof uses only the inner product (the residual is orthogonal to the subspace, then Pythagoras), so it holds word for word for functions on [0,t] with ⟨f,g⟩t. With the basis g0,…,gN−1, requiring the residual x−∑kckgk to be orthogonal to each gn gives cn=⟨x,gn⟩t. So the state we want is
That is a one-dimensional SSM whose rate 1/t slows down as the history grows, so that every past moment keeps an equal share of the mean. The general case is the same calculation with more bookkeeping.
In plain words, the coefficients of the best polynomial fit to everything seen so far obey a linear equation whose matrix is lower triangular. Coefficient n is driven by the new input with weight 2n+1 and pulled by the lower-order coefficients. The whole system runs on a clock that slows like 1/t, so the newest moment and the oldest count equally in the fit. The name LegS is short for Legendre polynomials on a scaled window, the window [0,t] that stretches with time.
∂ Proof of the HiPPO-LegS equation
We need two facts about Legendre polynomials besides orthogonality. First, Pn(1)=1. Second, for n≥1,
(u+1)Pn′(u)=nPn(u)+k=0∑n−1(2k+1)Pk(u).
To prove it, note that the left side is a polynomial of degree n, so it is a combination ∑k≤nαkPk. Its leading coefficient is n times that of Pn (from u⋅Pn′), so αn=n. For k<n, orthogonality gives αk=22k+1∫−11(u+1)Pn′Pkdu. Integrate by parts, ∫−11(u+1)Pn′Pkdu=[(u+1)PnPk]−11−∫−11Pn((u+1)Pk)′du=2⋅1⋅1−0, because ((u+1)Pk)′ has degree k<n and is orthogonal to Pn. So αk=2k+1.
Now differentiate hn(t)=t1∫0tx(s)gn(s,t)ds. The variable t appears in three places, the prefactor, the upper limit, and inside gn. So we need the product rule and Leibniz's rule for an integral whose upper limit and integrand both depend on t,
dtd∫0tF(s,t)ds=F(t,t)+∫0t∂t∂F(s,t)ds,
valid when F is continuous at (t,t) and ∂F/∂t is continuous. With F(s,t)=x(s)gn(s,t), and dtdt1=−t21 for the prefactor,
where the identity was used and each 2n+1(2k+1)Pk was rewritten as 2n+12k+1gk. Integrating against x turns each gk into thk, so the last term is −t1[nhn+∑k<n2n+12k+1hk]. Adding the three terms,
This is the matrix that took the LSSL from 60% to 98%. A random matrix makes the state a few arbitrary fading sums. This one makes it a summary from which the past can be redrawn. In practice, HiPPO is the reason S4 is initialized the way it is, and an interviewer will ask you to say in one sentence what the state of a HiPPO system holds.
The HiPPO matrix up close
Write A=−ALegS for the matrix with the minus sign, the form S4 uses as its state matrix. It is lower triangular, so its eigenvalues are its diagonal entries, −1,−2,…,−N. They are distinct and in the left half-plane, so A is stable, and it is diagonalizable, A=VΛV−1. In the eigenvector basis the system would be N independent leaky buckets, and the kernel would be the cheap Vandermonde product of the convolution theorem. Why not diagonalize it once and be done?
Because V is catastrophically ill-conditioned. Gu, Goel & Ré 2021, in their Lemma 3.2, show that its entries grow like 24N/3. Recall from §0.10 that the condition number κ of a matrix is the ratio of its largest stretch to its smallest, and that working with the matrix costs about log10κ decimal digits of accuracy. In the diagonal basis, the parameters V−1B and CV would be enormous numbers that cancel, and float32 has only about 7 digits to cancel with.
Python · cell 4
The condition number passes 1010 at N=16, and by N=24 it passes 1016, beyond what float64 can resolve (so that last value is itself only a rough estimate). S4 used N=64. But the cell also shows the way out. Add a rank-one matrix to A, and the result is a multiple of the identity plus a skew-symmetric matrix, one with S⊤=−S. That matrix can be diagonalized by a unitaryV, a complex matrix whose columns are orthonormal, with condition number exactly 1 even at N=64.
In plain words, the HiPPO matrix is a well-behaved system of rotations and decays, every mode decaying at rate 21 and rotating at its own frequency, plus a rank-one correction that ruins the conditioning. Keep the two pieces apart and nothing is ill-conditioned. That structure is what the word structured in S4's name refers to.
∂ Proof that the HiPPO matrix is normal plus rank one
Compare entries. On the diagonal, −(n+1)+21(2n+1)=−21. Below it (n>k), −2n+12k+1+212n+12k+1=−212n+12k+1. Above it (n<k), 0+212n+12k+1. So S has zero diagonal and Skn=−Snk.
For a real skew-symmetric S, the matrix iS is Hermitian (equal to its conjugate transpose), because (iS)H=−iS⊤=iS. The spectral theorem for Hermitian matrices, the complex version of the symmetric case in §0.10, gives a unitary V and real μn with iS=Vdiag(μn)VH. So S=Vdiag(−iμn)VH and Λn=−21−iμn. ■
Core
HiPPO-LegS, built and checked
Build the HiPPO matrix and check the two facts above, that its eigenvectors are hopelessly ill-conditioned and that it is normal plus rank one. In S4 the state matrix is A=−ALegS and the input vector is B=p. Write three functions.
hippo_legs(N) returns (A, B), the state matrix with the minus sign, of shape (N, N), and B of shape (N,).
normal_plus_low_rank(N) returns (Lam, V), with V unitary and Lam complex of shape (N,), such that A=Vdiag(Λ)VH−21pp⊤. It must be accurate at N=64, so do not call np.linalg.eig on A.
eigvec_condition(N) returns the condition number of the matrix of unit-length eigenvectors of A, as np.linalg.eig returns them.
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.
Running HiPPO online, exactly
This subsection and the next are the deepest part of the section. The first proves that a recurrence over samples really does keep the best polynomial fit of a history it never stores. The second shows what S4's time-invariant version of the system remembers instead. On a first read you can skip ahead to the diagonal state spaces.
The factor 1/t looks like trouble, because it makes the system change with time. But t1dt=d(lnt). On the clock τ=lnt the same equation reads dτdh=−ALegSh+px, a time-invariant SSM with state matrix −ALegS. So the discretizations above apply, with a step that is no longer constant. Count samples from k=0, and hold sample xk on [k,k+1). The step from t=k to t=k+1 then has length lnkk+1 on the new clock, and ZOH gives the exact update.
One more fact makes the update tidy. Column 0 of ALegS is (1,3,5,…)=p, so ALegSe0=p, where e0=(1,0,…,0). So the ZOH input term ALegS−1(I−Aˉk)p collapses to (I−Aˉk)e0.
In plain words, each step pulls the state toward "the history is the constant xk" by the factor Aˉk, which gets closer to the identity as k grows. The newest sample moves the state less and less, because it is a smaller and smaller share of the whole history.
∂ Proof of the exact online update
On [0,1) the history is the constant x0. Its projection has h0=x0 and every other coefficient zero by orthogonality, so h1=x0e0. For k≥1, apply the ZOH formula on the clock τ=lnt over a step of length lnkk+1, with state matrix −ALegS and input vector p. The input term is
The matrices commute because both are functions of ALegS. Rearranging gives the update. ■
The HiPPO paper prints a forward-Euler version, hk+1=(I−k1ALegS)hk+k1pxk, for illustration. Its experiments use the bilinear rule, which its ablation finds accurate enough. Both are approximations. Euler's matrix also has diagonal entries 1−(n+1)/k of size above 1 while k is less than half of N.
The next two cells check the Legendre identity used in the proof of the HiPPO equation. Then they run the exact update on a signal of 500 samples and compare its state with the projection computed offline from the whole history.
Python · cell 5
Python · cell 6
The online state equals the offline projection to rounding at every N. So the recurrence really does keep the best polynomial fit of a history it no longer has. The signal contains a cosine with seven cycles, and no fit with fewer than about 25 polynomials can follow it. That is why the error stays large up to N=16 and then falls to 2×10−4 at N=32.
The Euler update stalls near 0.16 however large N is. Its error per step grows with the degree, like ((n+1)/k)2 for coefficient n. So the high-degree coefficients that carry the fine detail are exactly the ones it computes worst.
The widget runs the same memory on several signals. The faint curve is the whole signal, the dark curve the part seen up to time t, and the colored curve what the N numbers of the state can redraw of it. The bars underneath are the state itself. Pick a signal and a state size N, then run or scrub t.
How well can N numbers summarize everything a signal did so far?
The scaled memory keeps the best polynomial fit of the whole history from time 0 to now.
Loading interactive figure…
Research
Remembering a signal with HiPPO
Implement the exact online HiPPO-LegS memory and its read-out. The setup provides legs_matrix(N), which returns ALegS (with a positive diagonal) and p.
legs_online(x, N) takes samples x of shape (T,), with sample x[k] held on [k,k+1). It returns H of shape (T, N), where H[k] is the state at time t=k+1, computed with the exact update of the theorem (scipy.linalg.expm is available).
reconstruct(h, t, s) evaluates ∑nhn2n+1Pn(2s/t−1) at an array of past times s in [0,t]. Use numpy.polynomial.legendre.legval.
The tests redraw a smooth signal of 200 samples from its final state. With the exact update and N=32, the root-mean-square error at the sample midpoints is below 10−3, and the tests require less than 5×10−3.
import numpy as np
from scipy.linalg import expm
from numpy.polynomial import legendre as leg
def legs_online(x, N):
raise NotImplementedError
def reconstruct(h, t, s):
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.
The time-invariant version, and what it remembers
There is a catch. S4 does not use the time-varying LegS system. It puts the same matrix in a time-invariant SSM, h˙=Ah+Bx with a learned Δ, so that the convolution view applies. Dropping the 1/t throws away the optimality argument above. For a while the HiPPO matrix in S4 had no explanation, only results. Gu, Johnson, Timalsina, Rudra & Ré 2022 supplied one, and the log clock lets us prove it.
∂ Proof through the log clock
By the solution formula, h(t)=∫0∞e−ALegSrpx(t−r)dr, so we need the impulse response e−ALegSrp. Feed the scaled LegS system a single impulse at time s0>0, x(s)=s0δ(s−s0), where δ is the Dirac delta, a unit spike at one instant. Its Legendre coefficients at any t>s0 follow from the definition, hn(t)=ts02n+1Pn(2s0/t−1).
On the clock τ=lnt the same input is a unit impulse at τ0=lns0, since δ(s−s0)=δ(τ−τ0)/s0. On that clock the system is the time-invariant one above, so its state is e−ALegS(τ−τ0)p. Equate the two expressions with r=τ−τ0=ln(t/s0), so that s0/t=e−r. ■
In plain words, the time-invariant system compresses the past onto Legendre polynomials in the variable e−r, with a weight e−r that fades exponentially. Most of the interval (0,1] of w is spent on the last couple of time units, and everything older is squeezed toward w=0. So the memory is sharp for the recent past and blurry for the distant past. With a step Δ, one time unit is 1/Δ tokens, so Δ again sets how far back "recent" reaches. The scaled LegS memory of the earlier widget is the other choice, equally blurry everywhere but never forgetting. The cell checks the formula for the impulse response at three lags.
Python · cell 7
The widget below draws this memory over the scaled one, both with 16 numbers about the same signal. Before you drag Δ, predict which part of the history each one redraws well.
What does the time-invariant memory remember, next to the scaled one?
Both memories keep 16 numbers about the same 400 samples. Each error reads time-invariant, then scaled.
Loading interactive figure…
The HiPPO paper also proves that in the scaled memory, the gradient of the state at time t with respect to an input at any earlier time is of order 1/t (Gu et al. 2020, Proposition 5). It falls polynomially with time, not exponentially as in a classical RNN. That is the theoretical counterpart of the 16,000-step memory we wanted at the start.
To run this time-invariant layer as a convolution, S4 needs the kernel of a matrix that is not diagonal. It gets it from the normal-plus-low-rank split proved above, with an algorithm that the next box describes. You do not need its details for the rest of the chapter, because the diagonal models that followed made it unnecessary.
Diagonal state spaces
S4's machinery is heavy. A few months after it, Gupta, Gu & Berant 2022 asked whether the low-rank term was needed at all. Their diagonal state space model (DSS) kept only a diagonal state matrix and matched S4 on Long Range Arena and on speech. Gu, Gupta, Goel & Ré 2022 then explained why, and made the model simple. Their diagonal S4, called S4D, rests on a proof about the limit N→∞. Recall that S4's matrix is a normal part, which a unitary change of basis turns into the diagonal matrix Λ, minus a rank-one term. Keeping only Λ and halving the input vector gives the same kernel as the full HiPPO matrix in that limit. Their experiments showed that what matters is how the eigenvalues are initialized, not the low-rank structure.
So a state-space layer can be a set of independent complex leaky buckets, each a decaying rotation, whose kernel is the Vandermonde product of the convolution theorem. The definition below fixes the two standard ways to set one up.
The S4D-Lin initialization repays a second look, because it says how big N and Δ must be for a given job.
🔥 In PyTorchfor a machine with PyTorch
The loss falls from 0.84 at the start, about the 0.80 of always predicting zero, to 0.013 after 300 steps. The trained layer gives the same outputs whether it runs as an FFT convolution (the training mode) or one token at a time (the decoding mode), up to float32 rounding.
Two more simplifications, S5 and the LRU
The simplified state space layer, S5 (Smith, Warrington & Linderman 2022), changed a different choice. An S4 layer is d independent single-input single-output SSMs, a block-diagonal system with dN state numbers. S5 uses one multi-input multi-output (MIMO) SSM instead, with B∈CN×d and C∈Cd×N. Its state matrix is diagonal, initialized from the diagonalized normal part of HiPPO. S5 computes the recurrence with a parallel scan, an algorithm that combines the steps of a linear recurrence in a tree of depth O(logT), instead of a convolution. A scan does not need a fixed kernel, so S5 can change Aˉ at every step, for example for data sampled at irregular times. That is a first step toward the input-dependent dynamics of §21.4, and §21.5 develops the scan.
The LRU of Orvieto et al. 2023 went furthest. Starting from a plain RNN, the authors removed the nonlinearity from the recurrence and diagonalized it over the complex numbers. They found that three choices closed the remaining gap to S4, and all three are in the definition box.
The exponential parameterization keeps ∣λ∣=e−eν<1 for any real ν, so training can never leave the stable region.
The ring initialization samples λ uniformly by area over the ring rmin≤∣λ∣≤rmax, close to the unit circle for long memory. Their best result on sequential CIFAR-10 (images classified from their pixels read one at a time) used rmin=0.9 and rmax=0.999.
The normalization γ stops the state from blowing up as ∣λ∣→1, for the reason in the next box.
Proof. Unrolling, ht=γ∑j=0t−1λjxt−j. Then E∣ht∣2=E[htht∗]=γ2∑i,jλi(λ∗)jE[xt−ixt−j]=γ2∑j=0t−1∣λ∣2j, since only the terms with i=j survive. The geometric sum gives the formula. ■
In plain words, a mode with ∣λ∣=0.999 sums about a thousand past inputs. Without normalization its state is about 22 times larger than that of a mode with ∣λ∣=0, since 1/(1−0.9992)≈22.4. The factor γ puts every mode on the same scale, whatever its memory, so the readout C does not have to undo a scale that varies by orders of magnitude.
Core
A stable diagonal recurrence with normalization (LRU)
Implement the three pieces of a bank of LRU channels, with a scalar input for simplicity.
lru_params(nu, theta) returns (lam, gamma) with λ=exp(−eν+iθ) and γ=1−∣λ∣2, elementwise.
ring_init(N, r_min, r_max, rng, max_phase=2 * np.pi) returns (nu, theta), each of shape (N,). The eigenvalues λ they define must be spread uniformly by area over the ring rmin≤∣λ∣≤rmax, so that equal areas of the ring get equal numbers of eigenvalues, and θ must be uniform on [0,max_phase]. Draw the radius part first and then the phase, each with rng.uniform.
lru_scan(lam, gamma, B, x) returns the complex states H of shape (T, N) of ht=λ⊙ht−1+γ⊙Bxt with h0=0, for a real input x of shape (T,), complex lam and B, and real gamma, all three of shape (N,).
The S4 row gives the average of the original paper and the one in its third arXiv version. The LRU paper prints no average, so its entry is our mean of the six scores in its Table 3. DSS is from its Table 1, and the other rows are from each paper's abstract.
On language the story was different. A language model is scored by its perplexity, the exponential of its average next-token loss, so lower is better. On WikiText-103, a standard benchmark of Wikipedia articles, S4 came within 0.8 perplexity of a Transformer, and within 0.4 after retraining in the paper's final version. That was a strong result for a model with no attention, but the gap would not close. Fu, Dao, Saab, Thomas, Rudra & Ré 2022 found the reason with synthetic tasks. SSMs struggled to recall a token seen earlier and to compare tokens across the sequence.
Their layer, called H3 (Hungry Hungry Hippos), added two things. A shift SSM has a kernel that only delays its input by a few steps. Multiplicative interactions, products of SSM outputs with projections of the input, make what passes through depend on content. H3 came within 0.4 perplexity of a Transformer on OpenWebText, a corpus of web pages. A 125M-parameter hybrid that kept two attention layers beat the Transformer by 1.0. Hyena (Poli et al. 2023) replaced the SSM by long convolutions whose filters are generated by a small network, interleaved with gates computed from the data. It matched Transformer quality at a context of 2,048 tokens with 20% less training compute.
Both fixes point at the same root cause, which the convolution theorem makes plain. The weight with which token s reaches output t is Kˉt−s, a number fixed by the parameters and the lag. It cannot depend on what token s is. Take associative recall. The input is a list of key-value pairs followed by a query key, and the answer is the value stored next to that key. The position of the answer depends on the input, but a time-invariant layer weighs each position by its distance alone. And since the layer is linear in x, it cannot multiply "this key matches the query" by "this is the value".
Nonlinear layers stacked between SSMs can approximate this product only with much depth or width. H3 and Hyena patched the gap with gates around a time-invariant core. Mamba goes to the root and lets Δ, B, and C depend on the current token. That costs the convolution view, and it is the subject of §21.4. An interviewer will ask exactly this, why a time-invariant SSM cannot do associative recall and what it takes to fix it.
Rush & Karamcheti. The Annotated S4. S4 implemented line by line, including the DPLR kernel this section only states.
Watch next
🎬 Efficiently Modeling Long Sequences with Structured State Spaces - Albert Gu | Stanford MLSys #46 · Stanford MLSys SeminarsThe author of S4 on continuous-time SSMs, HiPPO, the recurrent and convolutional views, and why the structured matrix was needed.🎬 Do we need Attention? - Linear RNNs and State Space Models (SSMs) for NLP · Sasha RushA tour from linear RNNs to S4, S4D, and the LRU by the co-author of The Annotated S4, ending with why time-invariant models struggle on language.🎬 Mamba and S4 Explained: Architecture, Parallel Scan, Kernel Fusion, Recurrent, Convolution, Math · Umar JamilDerives zero-order-hold discretization and the convolution kernel step by step, then bridges to Mamba.