Part II · SSM Core Week 8 Published

LTI SSMs: S4, S4D, S5 as discretized continuous systems

The S4 architecture as a discretized HiPPO LTI system — the convolution–recurrence duality and its FFT kernel, the diagonal-plus-low-rank structure behind S4's fast kernel, S4D's diagonal restriction with by-construction stability, and S5's parallel associative scan.

On this page
  1. 8.1 State-space to discrete: the S4 setup
  2. 8.2 The structured state matrix, revisited
  3. 8.3 The convolution–recurrence duality
  4. 8.4 The Cauchy kernel: structure buys speed
  5. 8.5 S4D: the diagonal restriction
  6. 8.6 S5: the parallel associative scan
  7. 8.7 What’s next
  8. 8.8 Exercises
  9. Exercise 8.1 (computation)
  10. Exercise 8.2 (computation + code)
  11. Exercise 8.3 (code)
  12. Exercise 8.4 (theory) — solution in §8.9
  13. Exercise 8.5 (theory) — solution in §8.9
  14. Exercise 8.6 (theory) — solution in §8.9
  15. 8.9 Full solutions to theory exercises
  16. Solution to Exercise 8.4
  17. Solution to Exercise 8.5
  18. Solution to Exercise 8.6
  19. 8.10 Companion code

LTI SSMs: S4, S4D, S5 as discretized continuous systems

8.1 State-space to discrete: the S4 setup

Chapter 7 closed on a recurrence and called it the recurrent view of an S4-style layer. Make the layer explicit. S4 fixes a single-input, single-output (SISO) continuous system

ddth(t)=Ah(t)+Bu(t),y(t)=Ch(t)+Du(t),\ddt \statevec(t) = \statemat\, \statevec(t) + \inputmat\, u(t), \qquad y(t) = \outputmat\, \statevec(t) + D\, u(t),

with ARN×N\statemat \in \R^{N\times N} the HiPPO-LegS matrix of §7.3, BRN×1\inputmat \in \R^{N\times 1}, CR1×N\outputmat \in \R^{1\times N}, and DD a scalar feedthrough. To run it on a sampled sequence u0,u1,u_0, u_1, \dots we discretize at step Δ\stepsize. The S4 paper itself uses Chapter 4’s bilinear transform (§4.4), Aˉ=(IΔ2A)1(I+Δ2A)\discA = (I - \tfrac{\stepsize}{2}\statemat)^{-1}(I + \tfrac{\stepsize}{2}\statemat) Gu et al. (2022) ; this book standardizes on the zero-order hold (§4.3) — the rule S5 Smith et al. (2023) and the Mamba line Gu & Dao (2024) later adopted, and one whose difference from bilinear the S4D ablations measure as negligible Gu et al. (2022) :

Aˉ=eAΔ,Bˉ=A1 ⁣(eAΔI)B,hk+1=Aˉhk+Bˉuk,yk=Chk+1+Duk,\discA = e^{\statemat \stepsize}, \qquad \discB = \statemat^{-1}\!\left(e^{\statemat\stepsize} - I\right)\inputmat, \qquad \statevec_{k+1} = \discA\, \statevec_k + \discB\, u_k, \quad y_k = \outputmat\, \statevec_{k+1} + D\, u_k,

computed through the augmented matrix-exponential trick of §4.3 (the companion’s discretize_zoh stacks [AΔBΔ00]\big[\begin{smallmatrix}\statemat\stepsize & \inputmat\stepsize\\ 0 & 0\end{smallmatrix}\big] and exponentiates, sidestepping A1\statemat^{-1}).

What makes this an architecture rather than a fixed filter is which parts train. S4 learns C\outputmat, DD, and logΔ\log\stepsize (the timescale, kept positive by the log), while A,B\statemat, \inputmat are initialized from HiPPO and often frozen or updated slowly. Stability is inherited for free: by the §7.7 spectrum every eigenvalue of A\statemat has negative real part, so Chapter 4’s ZOH proposition gives λ(Aˉ)=e(λ(A))Δ<1\abs{\lambda(\discA)} = e^{\Re(\lambda(\statemat))\stepsize} < 1 for every Δ>0\stepsize > 0. The slowest mode (eigenvalue 1-1) decays per step by eΔe^{-\stepsize} — for the companion’s default Δ=0.1\stepsize = 0.1 that is 0.9050.905, a long but strictly contracting memory.

This is one way to compute the layer. The rest of the chapter is, in large part, other ways to compute the same thing — and the surprising fact that they are equal.

8.2 The structured state matrix, revisited

Before the second view, recall why the HiPPO A\statemat is special enough to deserve fast algorithms. It is dense and lower-triangular (§7.3), so a black-box eAΔe^{\statemat\stepsize} and a black-box kernel would cost more than the structure warrants. The structure S4 exploits is that the HiPPO-LegS matrix is normal-plus-low-rank (NPLR): up to an orthogonal change of basis it is a normal matrix plus a rank-one correction, and after diagonalizing the normal part it becomes diagonal-plus-low-rank (DPLR),

A=ΛPQ,Λ=diag(λ1,,λN)CN×N,P,QCN×1,\statemat = \Lambda - P\,Q^{*}, \qquad \Lambda = \diag(\lambda_1, \dots, \lambda_N)\in\C^{N\times N}, \quad P, Q \in \C^{N\times 1},

with Λ\Lambda the (complex) diagonal of eigenvalues and PQP Q^* a rank-one term Gu et al. (2022) . That the HiPPO-LegS matrix admits this diagonal-plus-rank-one form is not evident from the §7.3 closed form — it is a construction of the S4 paper, which we take as given rather than re-derive here. This is the same non-normality that §7.5 flagged as a conditioning hazard, now read constructively: the eigenvector basis is oblique (hence ill-conditioned), but the correction that makes A\statemat non-normal is only rank one, and a rank-one correction is exactly what the Woodbury identity handles cheaply.

The DPLR form is the hinge for the rest of the chapter. Section 8.4 uses the full ΛPQ\Lambda - PQ^* to compute S4’s kernel quickly; §8.5 throws away the PQPQ^* term entirely and keeps only Λ\Lambda, which is S4D.

8.3 The convolution–recurrence duality

The recurrence of §8.1 is sequential: hk+1\statevec_{k+1} needs hk\statevec_k. Unrolling it from zero initial state exposes a second, fully parallel description. With h0=0\statevec_0 = 0,

hk+1=j=0kAˉkjBˉujyk=j=0k(CAˉkjBˉ)uj+Duk.\statevec_{k+1} = \sum_{j=0}^{k} \discA^{\,k-j}\, \discB\, u_j \quad\Longrightarrow\quad y_k = \sum_{j=0}^{k} \big(\outputmat\, \discA^{\,k-j}\, \discB\big)\, u_j + D\, u_k.

Define the SSM convolution kernel KRLK \in \R^{L} by Ki=CAˉiBˉK_i = \outputmat\, \discA^{\,i}\, \discB. Then the output is a causal convolution plus the feedthrough:

y=Ku+Du.y = K * u + D\, u.
Proposition 8.1.

(Convolution–recurrence duality.) For zero initial state, the SISO recurrence hk+1=Aˉhk+Bˉuk\statevec_{k+1} = \discA\statevec_k + \discB u_k, yk=Chk+1+Duk\,y_k = \outputmat\statevec_{k+1} + D u_k produces exactly the convolution y=Ku+Duy = K * u + D u with kernel Ki=CAˉiBˉK_i = \outputmat\, \discA^{\,i}\, \discB, i=0,,L1\,i = 0, \dots, L-1. The recurrent form costs O(L)O(L) sequential steps; the convolution, computed by FFT, costs O(LlogL)O(L\log L) parallel work.

The proof (Exercise 8.4, §8.9) is the unrolling above made rigorous by induction. The two forms are not approximations of each other — they are the same numbers. The companion confirms it to machine precision: the recurrent lax.scan output and the FFT convolution agree to a measured residual 1015\sim 10^{-15} in float64, which test_conv_recurrence_duality pins below 101210^{-12}.

Left: the SSM convolution kernel as a smooth decaying sequence of taps. Right: the recurrent-scan output and the FFT-convolution output plotted on top of each other, indistinguishable, with the maximum residual annotated below 1e-15.
The convolution–recurrence duality for an $N=16$ HiPPO-S4 system. Left: the kernel $K_i = \outputmat\,\discA^{\,i}\,\discB$, a decaying tap sequence. Right: the recurrent-scan output (solid) and the FFT-convolution output (dashed) coincide to machine precision (measured residual $\sim 10^{-15}$, pinned below $10^{-12}$). Produced by companions/ch08/jax/s4_duality.py; pinned by test_conv_recurrence_duality in companions/ch08/jax/tests/test_s4.py.

The duality is why S4 is practical: train as a convolution, deploy as a recurrence. Training sees whole sequences at once, so the parallel O(LlogL)O(L\log L) FFT wins; autoregressive inference produces one token at a time, so the O(L)O(L) scan with its O(N)O(N) state wins. One set of parameters, two runtimes.

8.4 The Cauchy kernel: structure buys speed

The duality leaves one cost open: building the kernel KK. Naively, Ki=CAˉiBˉK_i = \outputmat\,\discA^{\,i}\,\discB iterates the state vector AˉiBˉ\discA^{\,i}\discB — one N×NN\times N mat-vec per tap, O(N2L)O(N^2 L) in total. For the teaching regime N64N \le 64 that is exactly what the companion does, and it is plenty fast. S4’s published contribution is to compute the same kernel in near-linear time, O~(N+L)\tilde O(N + L), by using the DPLR structure of §8.2 Gu et al. (2022) .

The route is a generating function. Collect the taps into a polynomial and evaluate it on the unit circle: the truncated generating function is

K^(z)=i=0L1CAˉiBˉ  zi    C(IAˉz)1Bˉ,\hat K(z) = \sum_{i=0}^{L-1} \outputmat\,\discA^{\,i}\,\discB\; z^{i} \;\approx\; \outputmat\,(I - \discA\, z)^{-1}\,\discB,

a geometric series in the matrix Aˉz\discA z. Recovering KK from K^\hat K evaluated at the LL-th roots of unity is one inverse FFT. The remaining cost is the resolvent (IAˉz)1(I - \discA z)^{-1}, and here DPLR pays off: with Aˉ\discA built from ΛPQ\Lambda - PQ^* the Woodbury identity turns the resolvent into a Cauchy matrix–vector product — entries 1/(ζkλn)1/(\zeta_k - \lambda_n) — plus a rank-one correction. The whole kernel is then a Cauchy/Woodbury solve in NN plus the inverse FFT in LL (the §8.4 figure isolates the NN-scaling at fixed LL); the dense Aˉi\discA^{\,i} never appears.

We present this kernel but do not implement it: the companions use the transparent naive kernel throughout (§8.10). The point of §8.4 is the shape of the cost, not the engineering — and the shape is what motivates S4D.

Log-log plot of kernel computation time versus state dimension N. The measured naive-kernel times track a slope-2 reference line; a much shallower N-log-squared reference line sits below it.
Why S4 needs structure. Measured wall-clock of the naive $O(N^2 L)$ kernel ($L=512$) tracks the $N^2$ reference; an $O(N\log^2 N)$ reference slope (analytic, not a timed implementation — the Cauchy kernel of §8.4 is not built here) sits asymptotically far below. For $N \le 64$ the naive kernel is more than adequate and far clearer. Produced by companions/ch08/jax/s4_kernel_cost.py.

8.5 S4D: the diagonal restriction

The Cauchy machinery is the price of keeping the rank-one term PQPQ^*. S4D asks what happens if we simply drop it Gu et al. (2022) — keep the diagonal Λ\Lambda, discard the low-rank correction. The state matrix is now genuinely diagonal, A=Λ=diag(λ1,,λN)\statemat = \Lambda = \diag(\lambda_1, \dots, \lambda_N), and everything decouples mode by mode. The kernel collapses from a Cauchy computation to a Vandermonde sum:

Kl=nCnBˉnΛˉnl,Λˉn=eλnΔ,Bˉn=Λˉn1λn,K_l = \sum_{n} \outputmat_n\, \bar B_n\, \bar\Lambda_n^{\,l}, \qquad \bar\Lambda_n = e^{\lambda_n \stepsize}, \quad \bar B_n = \frac{\bar\Lambda_n - 1}{\lambda_n},

with Bˉn\bar B_n the per-mode diagonal ZOH input (this book’s convention; the S4D paper itself treats the integration rule as interchangeable — its base version keeps S4’s bilinear — and measures no noticeable difference between the two). In practice the NN real states are carried as N/2N/2 complex conjugate pairs and the real kernel is reconstructed as Kl=2 ⁣(nCnBˉnΛˉnl)K_l = 2\,\Re\!\big(\sum_n \outputmat_n \bar B_n \bar\Lambda_n^{\,l}\big).

Proposition 8.2.

(S4D Vandermonde kernel and stability.) For diagonal A=diag(λn)\statemat = \diag(\lambda_n) the kernel is the Vandermonde sum Kl=nCnBˉnΛˉnlK_l = \sum_n \outputmat_n \bar B_n \bar\Lambda_n^{\,l} with Λˉn=eλnΔ\bar\Lambda_n = e^{\lambda_n\stepsize}. The discrete system is stable — every Λˉn<1\abs{\bar\Lambda_n} < 1 — if and only if (λn)<0\Re(\lambda_n) < 0 for all nn.

The derivation and the stability claim are Exercise 8.5 (§8.9). What makes S4D more than “S4 with worse expressivity” is how it secures the stability condition. The S4D-Lin initialization places the modes at λn=12+iπn\lambda_n = -\tfrac12 + i\pi n, and — crucially — stores them in the parameterization

λn=e(logAreal)n+i(Aimag)n,\lambda_n = -e^{(\,\log A_{\text{real}})_n} + i\,(A_{\text{imag}})_n,

so that (λn)=e(logAreal)n<0\Re(\lambda_n) = -e^{(\log A_{\text{real}})_n} < 0 for any real value the parameters take. Stability is not hoped for; it is structural.

This is the constructive answer to the §7.5 conditioning hazard: where S4 must diagonalize an ill-conditioned non-normal matrix and hope the basis behaves, S4D never forms that basis at all — it parameterizes the eigenvalues directly and pins their real parts negative.

Diagonal state space models are not a downgrade in practice: with the right initialization they match full S4 on long-range benchmarks while being markedly simpler to implement Gupta et al. (2022) . The companion plots the S4D-Lin spectrum against the full HiPPO spectrum to make the restriction visible.

Complex plane scatter. Gray points: the HiPPO-LegS eigenvalues spread along the negative real axis from minus 1 to minus 32. Navy points: the S4D-Lin modes at real part minus one-half, stepping up the imaginary axis at multiples of pi.
S4D-Lin diagonal initialization ($M=16$ complex modes at $-\tfrac12 + i\pi n$, navy) against the full HiPPO-LegS spectrum ($N=32$ real eigenvalues $-1,\dots,-N$, gray). S4D abandons HiPPO's real integer spectrum for a fixed-real-part comb up the imaginary axis; the common feature is $\Re(\lambda) < 0$, here enforced by construction. Produced by companions/ch08/jax/s4d_kernel.py.

8.6 S5: the parallel associative scan

S4D made the state diagonal but still computed it as a convolution. S5 keeps the diagonal state and abandons the convolution for a parallel scan Smith et al. (2023) . Two changes. First, S5 is MIMO: one shared diagonal state of size PP drives HH input/output channels through BCP×H\inputmat \in \C^{P\times H} and CCH×P\outputmat \in \C^{H\times P}, instead of HH independent SISO systems. Second, it computes the recurrence

hk=Aˉhk1+Bˉuk\statevec_k = \discA \odot \statevec_{k-1} + \discB\, u_k

(diagonal Aˉ\discA, so \odot is elementwise) with an associative scan. The trick is that a linear recurrence is an associative operation on (transition,input)(\text{transition}, \text{input}) pairs:

(a1,b1)(a2,b2)=(a2a1,  a2b1+b2),(a_1, b_1) \bullet (a_2, b_2) = (a_2 \odot a_1,\; a_2 \odot b_1 + b_2),

which composes “apply the first affine update, then the second.” Scanning \bullet over the pairs (Aˉ,Bˉuk)(\discA, \discB u_k) yields every state hk\statevec_k at once.

Proposition 8.3.

(Scan equivalence.) The operator \bullet above is associative, and the inclusive prefix scan of (Aˉ,Bˉu0),,(Aˉ,BˉuL1)(\discA, \discB u_0), \dots, (\discA, \discB u_{L-1}) under \bullet has as its second components exactly the sequential states h0,,hL1\statevec_0, \dots, \statevec_{L-1}. A balanced reduction computes all prefixes in parallel depth O(logL)O(\log L) Blelloch (1990) , versus the O(L)O(L) depth of the sequential recurrence.

The proof is Exercise 8.6 (§8.9). The companion pins the equivalence to machine precision — the associative scan and the sequential scan agree to about 3×10153\times10^{-15} (pinned below 101210^{-12}) — and contrasts the two on the algorithmic cost that actually differs: critical-path depth.

Two panels. Left: log-log plot of parallel steps versus sequence length; the sequential curve rises linearly as L while the parallel curve rises as log L. Right: semilog plot of the maximum state difference between the two methods versus L, flat near 1e-15.
S5's parallel scan vs the sequential recurrence. Left: critical-path depth — sequential $O(L)$ against parallel $\lceil\log_2 L\rceil$. Right: the two compute identical states, agreeing to $\sim 10^{-15}$ (pinned below $10^{-12}$) across sequence lengths. The depth advantage is realized on parallel hardware (GPU/TPU). Produced by companions/ch08/jax/s5_scan.py; pinned by test_s5_parallel_equals_sequential_states.

S5 deliberately stops at one diagonal SSM run in parallel. Its MIMO channel mixing and, above all, the move to input-dependent transitions are Chapter 9’s subject — and that move is what makes the associative scan indispensable rather than merely convenient.

8.7 What’s next

The thread of this chapter is that S4, S4D, and S5 are three computational treatments of one linear time-invariant system: a convolution and a recurrence that the duality proves equal (§8.3), a diagonal restriction whose stability is free (§8.5), and a parallel scan that reorders the same arithmetic into O(logL)O(\log L) depth (§8.6). Time-invariance is what every one of these relies on — a fixed Aˉ\discA is what lets a single kernel exist and a single transition seed the scan.

Chapter 9 breaks time-invariance on purpose: Mamba makes A,B,C\statemat, \inputmat, \outputmat functions of the input Gu & Dao (2024) (and the structured state-space duality Dao & Gu (2024) later reframes the result), which destroys the convolutional view and leaves the selective scan — the §8.6 primitive, now load-bearing — as the only way to compute the layer. Chapter 10 then replaces ZOH with the exponential-trapezoidal scheme of Chapter 4 when the selective dynamics turn stiff, in Mamba-3 Lahoti et al. (2026) ; that chapter is where the symplectic-integrator pilot finds its empirical anchor. Everything downstream inherits the discretized-LTI scaffold built here.

8.8 Exercises

Six problems mixing computation and theory. Short/numerical (8.1–8.3) have inline collapsible solutions; the theory exercises (8.4–8.6) have full worked solutions in §8.9.

Exercise 8.1 (computation)

Take a diagonal toy system A=diag(1,2)\statemat = \diag(-1, -2), B=(1,1)\inputmat = (1, 1)^\top, C=(1,1)\outputmat = (1, 1), D=0D = 0, and step Δ=0.1\stepsize = 0.1. Compute Aˉ\discA and Bˉ\discB by the diagonal ZOH, then the first four kernel taps K0,,K3K_0, \dots, K_3. Confirm the taps decay.

Solution

Diagonal ZOH acts mode-by-mode: Aˉn=eλnΔ\bar A_n = e^{\lambda_n \stepsize} and Bˉn=(Aˉn1)/λnBn\bar B_n = (\bar A_n - 1)/\lambda_n \cdot B_n. So Aˉ=(e0.1,e0.2)=(0.9048,0.8187)\bar A = (e^{-0.1}, e^{-0.2}) = (0.9048, 0.8187), and Bˉ1=(1e0.1)/1=0.0952\bar B_1 = (1 - e^{-0.1})/1 = 0.0952, Bˉ2=(1e0.2)/2=0.0906\bar B_2 = (1 - e^{-0.2})/2 = 0.0906. With C=(1,1)\outputmat = (1,1) the taps are Kk=nAˉnkBˉn=0.0952(0.9048)k+0.0906(0.8187)kK_k = \sum_n \bar A_n^{\,k}\, \bar B_n = 0.0952\,(0.9048)^k + 0.0906\,(0.8187)^k:

K0=0.1858,K1=0.1603,K2=0.1387,K3=0.1202.K_0 = 0.1858,\quad K_1 = 0.1603,\quad K_2 = 0.1387,\quad K_3 = 0.1202.

Each tap is a sum of two decaying geometric sequences (rates e0.1e^{-0.1} and e0.2e^{-0.2}), so the kernel decays monotonically — a discrete sum of exponentials, one per mode.

Exercise 8.2 (computation + code)

The S4D kernel sums one geometric sequence per mode. (a) Show that Λˉnl=e(λn)Δl\abs{\bar\Lambda_n^{\,l}} = e^{\Re(\lambda_n)\stepsize\, l}, so each mode decays at its own rate. (b) Show that a complex mode λ\lambda with weight w=CBˉw = \outputmat\bar B and its conjugate λˉ\bar\lambda with weight wˉ\bar w together contribute wΛˉl+wˉΛˉl=2(wΛˉl)w\,\bar\Lambda^{\,l} + \bar w\,\overline{\bar\Lambda}^{\,l} = 2\Re(w\,\bar\Lambda^{\,l}), explaining the real 22\Re form. (c) Run companions/ch08/jax/s4d_kernel.py and confirm the Vandermonde kernel matches the recurrence oracle.

Solution

(a) Λˉn=eλnΔ\bar\Lambda_n = e^{\lambda_n\stepsize}, so Λˉnl=eλnΔl\bar\Lambda_n^{\,l} = e^{\lambda_n\stepsize l} and Λˉnl=e(λn)Δl\abs{\bar\Lambda_n^{\,l}} = e^{\Re(\lambda_n)\stepsize l}; with (λn)<0\Re(\lambda_n) < 0 this decays geometrically at rate e(λn)Δe^{\Re(\lambda_n)\stepsize} per tap. (b) For any complex ζ\zeta, wζ+wˉζˉ=2(wζ)w\zeta + \bar w\bar\zeta = 2\Re(w\zeta) since a number plus its conjugate is twice its real part; applying it with ζ=Λˉl\zeta = \bar\Lambda^{\,l} (and noting Λˉl=Λˉl\overline{\bar\Lambda}^{\,l} = \overline{\bar\Lambda^{\,l}}) gives the claim. Carrying N/2N/2 conjugate pairs and taking 22\Re thus reproduces the NN-real-state kernel while storing only half as many complex numbers. (c) test_s4d_kernel_matches_recurrence_oracle pins the closed-form Vandermonde kernel against an independent complex-diagonal recurrence impulse response to 10910^{-9}.

Exercise 8.3 (code)

Run companions/ch08/jax/s4_duality.py and confirm the recurrent and convolutional outputs agree (the companion reports a residual below 101510^{-15}). Then explain why the agreement would break if the FFT used length LL instead of 2L2L.

Solution

The FFT computes a circular convolution of length equal to the transform size. With a length-LL transform, output position 00 receives a contribution from kernel tap L1L-1 wrapped around the end of the sequence — a spurious term that the causal linear convolution does not contain, corrupting the early outputs. Zero-padding both signals to 2L2L before the FFT makes the circular convolution agree with the causal linear one on the first LL positions (the wrapped terms land in the padded tail, which is discarded). The companion’s causal_conv_fft uses n = 2*L for exactly this reason; test_fft_pad_matches_direct_convolution pins the padded FFT against a direct convolution.

Exercise 8.4 (theory) — solution in §8.9

Prove the convolution–recurrence duality (Proposition, §8.3): for zero initial state, the SISO recurrence equals y=Ku+Duy = K * u + D u with Ki=CAˉiBˉK_i = \outputmat\,\discA^{\,i}\,\discB.

Exercise 8.5 (theory) — solution in §8.9

Derive the S4D Vandermonde kernel from the diagonal recurrence, prove the discrete system is stable iff (λn)<0\Re(\lambda_n) < 0 for all nn, and show that the S4D-Lin parameterization λn=e(logAreal)n+i(Aimag)n\lambda_n = -e^{(\log A_{\text{real}})_n} + i(A_{\text{imag}})_n enforces stability for every parameter value.

Exercise 8.6 (theory) — solution in §8.9

Prove that the scan operator (a1,b1)(a2,b2)=(a2a1,a2b1+b2)(a_1,b_1)\bullet(a_2,b_2) = (a_2 a_1,\, a_2 b_1 + b_2) is associative, that its inclusive prefix scan reproduces the sequential states hk\statevec_k, and that a balanced reduction achieves parallel depth O(logL)O(\log L).

8.9 Full solutions to theory exercises

Solution to Exercise 8.4

Start from h0=0\statevec_0 = 0 and hk+1=Aˉhk+Bˉuk\statevec_{k+1} = \discA\statevec_k + \discB u_k. Claim: hk+1=j=0kAˉkjBˉuj\statevec_{k+1} = \sum_{j=0}^{k} \discA^{\,k-j}\discB\, u_j. Induct on kk. Base (k=0k=0): h1=Aˉ0+Bˉu0=Aˉ0Bˉu0\statevec_1 = \discA\cdot 0 + \discB u_0 = \discA^{\,0}\discB\, u_0. Step: assuming hk=j=0k1Aˉk1jBˉuj\statevec_k = \sum_{j=0}^{k-1}\discA^{\,k-1-j}\discB u_j,

hk+1=Aˉhk+Bˉuk=j=0k1AˉkjBˉuj+Aˉ0Bˉuk=j=0kAˉkjBˉuj.\statevec_{k+1} = \discA\statevec_k + \discB u_k = \sum_{j=0}^{k-1}\discA^{\,k-j}\discB u_j + \discA^{\,0}\discB u_k = \sum_{j=0}^{k}\discA^{\,k-j}\discB u_j.

Now the output, using yk=Chk+1+Duky_k = \outputmat\statevec_{k+1} + D u_k:

yk=j=0kCAˉkjBˉuj+Duk=j=0kKkjuj+Duk,y_k = \sum_{j=0}^{k}\outputmat\discA^{\,k-j}\discB\, u_j + D u_k = \sum_{j=0}^{k} K_{k-j}\, u_j + D u_k,

with Ki=CAˉiBˉK_i = \outputmat\discA^{\,i}\discB. The sum j=0kKkjuj\sum_{j=0}^k K_{k-j} u_j is precisely the causal convolution (Ku)k(K * u)_k, so y=Ku+Duy = K * u + D u. The lower-triangular Toeplitz structure of the map uyDuu \mapsto y - Du is the convolution. \blacksquare

Solution to Exercise 8.5

For diagonal A=diag(λn)\statemat = \diag(\lambda_n) the matrix exponential is diagonal, Aˉ=diag(eλnΔ)\discA = \diag(e^{\lambda_n\stepsize}), and the ZOH input Bˉ=A1(eAΔI)B\discB = \statemat^{-1}(e^{\statemat\stepsize} - I)\inputmat acts mode-by-mode: Bˉn=(eλnΔ1)/λnBn\bar B_n = (e^{\lambda_n\stepsize} - 1)/\lambda_n \cdot B_n. The kernel inherits the diagonality,

Kl=CAˉlBˉ=nCneλnΔlBˉn=nCnBˉnΛˉnl,K_l = \outputmat\,\discA^{\,l}\,\discB = \sum_n \outputmat_n\, e^{\lambda_n\stepsize l}\, \bar B_n = \sum_n \outputmat_n\,\bar B_n\,\bar\Lambda_n^{\,l},

a sum of geometric sequences — the Vandermonde form (the real 22\Re version follows by pairing conjugates, Exercise 8.2). Stability: the kernel and the state stay bounded iff each geometric ratio satisfies Λˉn<1\abs{\bar\Lambda_n} < 1. Since Λˉn=eλnΔ=e(λn)Δ\abs{\bar\Lambda_n} = \abs{e^{\lambda_n\stepsize}} = e^{\Re(\lambda_n)\stepsize} and Δ>0\stepsize > 0, this holds iff (λn)<0\Re(\lambda_n) < 0 for every nn. Finally, under S4D-Lin the real part is (λn)=e(logAreal)n\Re(\lambda_n) = -e^{(\log A_{\text{real}})_n}, and a negative exponential is strictly negative for every real exponent — so (λn)<0\Re(\lambda_n) < 0 holds for all parameter values, with no constraint to enforce during training. Stability is built into the parameterization rather than maintained by it. \blacksquare

Solution to Exercise 8.6

Associativity. Write the action of a pair (a,b)(a, b) on a state hh as the affine map hah+bh \mapsto a h + b. Composition of affine maps is associative because function composition is. Concretely, with \bullet elementwise,

((a1,b1)(a2,b2))(a3,b3)=(a2a1,a2b1+b2)(a3,b3)=(a3a2a1,  a3a2b1+a3b2+b3),\big((a_1,b_1)\bullet(a_2,b_2)\big)\bullet(a_3,b_3) = (a_2 a_1,\, a_2 b_1 + b_2)\bullet(a_3,b_3) = (a_3 a_2 a_1,\; a_3 a_2 b_1 + a_3 b_2 + b_3), (a1,b1)((a2,b2)(a3,b3))=(a1,b1)(a3a2,a3b2+b3)=(a3a2a1,  a3a2b1+a3b2+b3),(a_1,b_1)\bullet\big((a_2,b_2)\bullet(a_3,b_3)\big) = (a_1,b_1)\bullet(a_3 a_2,\, a_3 b_2 + b_3) = (a_3 a_2 a_1,\; a_3 a_2 b_1 + a_3 b_2 + b_3),

and the two right-hand sides are identical. Prefix scan reproduces the states. Let ek=(Aˉ,Bˉuk)e_k = (\discA, \discB u_k) and sk=e0eks_k = e_0 \bullet \cdots \bullet e_k be the inclusive scan. By induction the second component of sks_k equals hk\statevec_k: for k=0k = 0 it is Bˉu0=h0\discB u_0 = \statevec_0 (with h1=0\statevec_{-1}=0); and sk=sk1eks_k = s_{k-1} \bullet e_k has second component Aˉ(2nd comp of sk1)+Bˉuk=Aˉhk1+Bˉuk=hk\discA\cdot(\text{2nd comp of } s_{k-1}) + \discB u_k = \discA\statevec_{k-1} + \discB u_k = \statevec_k. Depth. Because \bullet is associative, computing all prefixes is a prefix-sum problem, which a balanced binary reduction tree evaluates in O(logL)O(\log L) sequential rounds with O(L)O(L) total work Blelloch (1990) — versus the O(L)O(L) rounds of the naive recurrence. \blacksquare

8.10 Companion code

Two language tracks for Chapter 8 — JAX (the reference, with the parallel scan) and PyTorch (the nn.Module operators). Julia is omitted here: the S4D reference and the associative-scan primitive both live in the JAX/PyTorch world, and a stdlib-Julia rebuild would add a third spelling without a third lesson. The JAX and torch companions are pinned bit-for-bit against each other, the cross-framework lesson Chapter 7 introduced.

JAX (companions/ch08/jax/):

  • s4_core.py — the harness-free S4 numerical core ported from the Week 4 source: make_hippo_legs, discretize_zoh / discretize_bilinear, the naive O(N2L)O(N^2 L) kernel, and the recurrent and convolutional views. The §8.3 duality lives here.
  • s4_duality.py — emits kernel-duality.png (§8.3): kernel taps plus the two views overlaid.
  • s4_kernel_cost.py — times the naive kernel across NN; emits kernel-vs-n.png (§8.4).
  • s4d_kernel.py — the S4D-Lin diagonal init and Vandermonde kernel (§8.5), complex128; emits s4d-spectrum.png.
  • s5_scan.py — the S5 associative scan via jax.lax.associative_scan, with the sequential reference; emits s5-scan-depth.png (§8.6).
  • tests/test_s4.py pins the duality (recurrent \equiv convolutional), the kernel oracle, ZOH stability, and the FFT-pad guard; test_s4d_s5.py pins S4D by-construction stability, the Vandermonde-vs-recurrence cross-check, and S5 scan-equivalence.

PyTorch (companions/ch08/torch/):

  • s4d_kernel.py — the S4D kernel as an nn.Module (fixed-init buffers), the Vandermonde computation ported from the reference S4D, in complex128.
  • s5_sequential.py — the diagonal MIMO S5 as a sequential scan (PyTorch has no native parallel scan); the states match the JAX associative scan bit-for-bit.
  • tests/test_s4d_torch.py — cross-framework parity: the torch S4D kernel and S5 states equal their JAX counterparts to within 10810^{-8}.

To run from the repo root:

# JAX (uses the uv .venv with jax, numpy, matplotlib)
PYTHONPATH=. python companions/ch08/jax/s4_duality.py
PYTHONPATH=. python companions/ch08/jax/s4_kernel_cost.py
PYTHONPATH=. python companions/ch08/jax/s4d_kernel.py
PYTHONPATH=. python companions/ch08/jax/s5_scan.py

# PyTorch (needs the .venv [torch] extra)
PYTHONPATH=. python companions/ch08/torch/s4d_kernel.py
PYTHONPATH=. python companions/ch08/torch/s5_sequential.py

All figures emit to public/figures/ch08/.