Mamba is a recurrent layer that decides, token by token, what to keep in its memory and what to forget. It was the first layer without attention to match a Transformer of the same size on language, and its descendants now sit inside the hybrid models that labs ship. This section builds it from scratch, proves why it works, and counts what it costs on a GPU.
Start from what came before. A state-space model (SSM), the layer of §21.3, keeps a small vector of numbers called its state. At every token it shrinks the state by a fixed factor and adds a fixed multiple of the token. The rule is the same at every step, so a token's influence on later outputs depends only on how long ago it arrived, never on what it is. Mamba lets the token decide. Each token sets its own step size, the number that decides how much of the old state is kept and how much of the new token is written. It also sets which parts of the state it writes to and which parts the output reads.
Here is a task that shows why that change is needed. A sequence of 4,096 tokens is mostly one filler token, and 16 data tokens sit among the filler at random positions. At the end, the model must output the 16 data tokens in the order they appeared. Gu & Dao (2023) trained two-layer models of width 64 on this selective copying task. With S4, the time-invariant SSM of §21.3, the accuracy was 18.3%. With the selective layer of this section it was 97.0%, and with the full Mamba block 99.8%.
That gap should surprise you. S4 was the first model to solve Path-X, a benchmark that requires carrying a signal across 16,384 steps. Remembering 16 tokens is a much smaller job. What S4 cannot do is choose which 16 to remember.
Here is the plan. We first prove that no layer that is linear in its input can solve even a three-token version of the task. Then we let each token choose its step size, and prove that this turns an SSM into a recurrent network with an input-dependent gate. The price is the fast convolution of §21.3. So we implement the scan that replaces it, count the memory traffic that forces the scan into one fused GPU kernel, build and count the full Mamba block, and decode with a cache whose size never changes. The section ends with what Mamba still cannot do, which is where §21.5 and §21.6 pick up.
Why a time-invariant layer cannot select
A time-invariant layer applies the same rule at every step. §21.3 showed that a time-invariant SSM is a long convolution. Take one channel with input xt. Write Aˉ for the matrix that shrinks the state at each step, Bˉ for the vector that writes the input into the state, and C for the vector that reads the output from it. Then the output is
yt=j≥0∑Kˉjxt−j,Kˉj=CAˉjBˉ.
In plain words, the output is a weighted sum of past inputs, and the weight on the input j steps back is a fixed number Kˉj. The weight with which token s reaches output t is Kˉt−s. It depends on the distance t−s and on nothing else.
That is enough for the plain copying task, where the tokens to copy sit at fixed positions. A kernel with a spike at the right distance copies them, and convolutions as long as the sequence are known to solve it (Romero et al. 2021). Selective copying randomizes the spacing. The right distance changes from sequence to sequence, and only the content of the tokens says where the data are.
Shrink the task to its smallest form, recall the last data token. Each token is either filler or data, and each data token carries a value. The target at step t is the value of the most recent data token at or before t. For the values (3,filler,7,filler) the targets are (3,3,7,7). A layer that solves it must give the newest data token weight 1 and every older token weight 0, and which token is newest depends on the input. The obstacle already shows with three tokens.
In plain words, in a linear layer the contribution of token u is the same whether or not a newer data token follows it. Recall needs that contribution to vanish when w arrives and to survive when filler arrives. Only a layer whose mixing weights depend on the tokens themselves can do both.
Notice what the proof did not use. It never assumed time invariance, so a linear layer with different weights at every position fails too. The defect is linearity in the input.
Two kinds of layer escape the theorem, and neither escapes it well. The first adds nonlinear MLPs after the linear layers. §21.1 quoted a theorem that a linear recurrence followed by an MLP can approximate any well-behaved causal sequence map. The theorem says nothing about how large the state must be. Here the linear state would have to keep enough of the whole history for the MLP to find the newest data token in it, and that grows with the sequence length. The second is a gated layer, which multiplies the output of a time-invariant SSM or long convolution by a projection of the input. Two such layers met in §21.3 are H3 (Fu et al. 2022) and Hyena (Poli et al. 2023). The product is nonlinear, but it acts at one position at a time. It never changes how far a token's information travels along the sequence. In the same selective copying experiment, H3 around an S4 layer reached 57.0% and Hyena 30.1%.
If an interviewer asks why S4 struggled on language, this is the answer they want. A time-invariant layer weighs tokens by position, and language needs weights that depend on content.
Letting each token choose its step size
An SSM starts life as a system in continuous time, and §21.3 turned it into a recurrence over tokens. Zero-order hold (ZOH) treats each token as an input held constant for a stretch of time of length Δ, the step size, and solves the system exactly over that stretch. Each state number (a mode) has a rate λ<0, and over one step it shrinks by the factor aˉ=eΔλ. So Δ sets a timescale. A small Δ keeps the state, since aˉ≈1. A large Δ forgets it, since aˉ≈0. In S4 the same Δ is used at every step.
Mamba's idea fits in one sentence. Let each token choose its ownΔ. A token the model wants to ignore picks Δ≈0, which leaves the state untouched. A token it wants to store picks a large Δ, which flushes the old state and writes the token.
Start with a case you can do by hand, the leaky bucket of §21.3. Its level h drains in proportion to how full it is and fills from the input, h˙=−h+x, where h˙ is the rate of change. So there is one state (N=1), with A=−1 and B=C=1. ZOH with a step Δt that may differ at every token gives
In plain words, each token keeps the share aˉt of the old state and writes itself with the weight bˉt, and the two shares add up to one.
Python · cell 1
Now scale the same comparison up. The next cell draws 400 sequences of 48 tokens. Each token is data with probability 0.2 and then carries a value from 1 to 8. Filler is embedded as 0, which already removes it from every weighted sum, so the kernel only has to decide how much weight each older data token gets. The cell fits the best possible causal kernel by least squares, which gives the time-invariant layer every advantage, and compares it with the selective rule.
Python · cell 2
This is selection in its simplest form, and the first thing to say if someone asks what Mamba changed relative to S4. The step size stops being a fixed timescale and becomes a decision that each token makes.
The selective state-space model
A real layer runs many channels side by side and keeps more than one state number per channel. Here is the general model in words first. Each channel keeps N state numbers, and each of them decays at its own fixed rate. At every token, three things are computed from the token itself. A step size per channel decides how much to keep and how much to write. A write vector Bt decides which state numbers receive the token. A read vector Ct decides which state numbers form the output.
This is the diagonal SSM of §21.3 (S4D, in which each channel's state matrix is diagonal) with three of its parameters computed from the input at every step. Gu and Dao call it S6, for S4 with selection, computed with a scan.
Look at one channel c and one state index n. The update is the scalar linear recurrence of §21.1, ht=atht−1+utxt, with decay at=aˉt,cn and input weight ut=Δt,cBt,n. What is new is that these coefficients are now functions of xt. They depend on the input and not on the state, so the recurrence stays linear in its state. Every tool of §21.1 still applies, the loop, the mixing matrix, and the scan.
where Δtλ means the matrix with entries Δt,cλcn, exp acts entrywise, and ⊙ is the entrywise product.
In plain words, each step shrinks every entry of a din×N state at its own rate, adds the outer product of the step-scaled input with Bt, and reads the state against Ct. Linear attention (§21.2), attention with the softmax removed so that it runs as a recurrence, has the same shape. It keeps a matrix state, adds the outer product of a key and a value at every token, and reads the state with a query. Here Bt plays the key, Ct the query, and Δt⊙xt the value, with a decay on every entry. (In §21.2's layout the state would be Ht⊤, with rows indexing keys.) §21.6 turns that resemblance into a theorem.
The discretization the code actually uses
The definition writes the input weight as Δt,cBt,n, not as the ZOH formula. That is deliberate, because it is what the released code computes. For one mode with eigenvalue λ<0, write κ=Δ∣λ∣, the step measured in units of that mode's timescale. ZOH on both parameters gives
aˉ=e−κ,bˉZOH=λeΔλ−1B=κ1−e−κΔB.
The Mamba paper states this ZOH rule. The released kernels compute something slightly different. The reference function selective_scan_ref in the authors' repository, and the CUDA kernel tested against it, keep the exact decay aˉ=eΔλ but use the input weight ΔB. That is the first term of the expansion (1−e−κ)/κ=1−κ/2+κ2/6−⋯. The Mamba-3 paper later derived this rule and named it exponential-Euler (Lahoti et al. 2026).
Neither rule is wrong, since a network learns its weights under whichever rule it is trained with. But they define different models. A checkpoint trained with one gives different outputs if run with the other, mostly in the modes with Δ∣λ∣ near 1 or above. That is the practical reason to know which one your code uses.
Selection is a gate
The step size behaves exactly like a gate in the recurrent networks of §3.3. A gated recurrent unit (GRU) updates its state as a blend, ht=(1−gt)ht−1+gth~t. The update gategt∈(0,1) is a sigmoid of the input and the previous state, and h~t is a candidate new state. A gate near 0 keeps the old state, and a gate near 1 replaces it. (§3.3 writes the GRU with the roles of gt and 1−gt swapped, so check the convention of any formula you meet.)
Now put the leaky bucket through ZOH with Δt=softplus(zt), where zt is a number computed from the token, and try a number. Take zt=0. Then Δt=log2=0.693, the decay is aˉt=e−0.693=0.5, and the write is bˉt=1−0.5=0.5. The logistic sigmoid of 0 is also 0.5. Take zt=2. Then Δt=log(1+e2)=2.127 and aˉt=e−2.127=0.119, while 1−σ(2)=1−0.881=0.119. The coincidence is exact, and it is the reason Mamba passes Δ through a softplus.
In plain words, the softplus and the exponential cancel into a sigmoid, so the discretized leaky bucket is a GRU's update gate. Unlike a GRU's, this gate is computed from the current input only, and the candidate is the input itself. The bias b plays the role of bΔ in the definition, the learned constant part of Δ.
With the kernels' exponential-Euler rule, the decay is the same 1−σ(zt), but the write becomes bˉt=Δt=softplus(zt). For very negative zt both σ(zt) and softplus(zt) are about ezt, so the two rules agree there. For large zt the softplus grows like zt. The code's layer is a gate whose write is not capped at 1.
The theorem is the standard answer when an interviewer asks how Mamba relates to an LSTM or a GRU. It also explains a design choice. Mamba computes Δ through a projection of low rank rΔ. It squeezes xt to rΔ numbers with W↓ and expands them back to one step size per channel with W↑. The squeeze makes the channels share their decision, because if a token should be ignored, every channel should ignore it. The paper motivates this with a projection of xt to a single number broadcast to all channels. The code generalizes it to rank rΔ=⌈d/16⌉ for model width d, which can still express one shared decision and costs few parameters. Perplexity, the exponential of the average next-token loss (lower is better), measures the effect. In the paper's ablations on models of about 350M parameters and width 1,024, it was 9.12 with no input-dependent Δ, 8.97 with rank 1, and 8.71 with rank 64, the default ⌈d/16⌉ at that width (Gu & Dao 2023). Rank 1 already gives over a third of the gain, and the rest comes gradually as the rank grows, for under 4% more parameters.
Warm-up
Selection is a gate, numerically
Implement two functions.
zoh_scalar(delta, A, B) returns the pair (a_bar, b_bar) of the ZOH discretization of the scalar system h˙=Ah+Bx with A<0. Its inputs may be NumPy arrays of the same shape.
selective_gate_scan(z, x) runs the setting of the theorem. Given arrays z and x of length T, use Δt=softplus(zt), A=−1, B=1, and ZOH for both parameters. Return the states h1,…,hT as an array of shape (T,), starting from h0=0.
Extreme inputs such as zt=±1000 must give finite states. The tests check that your scan equals the gated recurrence of the theorem.
import numpy as np
def zoh_scalar(delta, A, B):
raise NotImplementedError
def selective_gate_scan(z, x):
raise NotImplementedError
Tests, verified solutions, and AI help, open once you add your API key.
What each parameter does
With the theorem in hand, each selective parameter has a clear job.
The step size Δ sets the balance between keeping the state and taking in the current token. As Δt,c→0 the decay tends to 1 and the write to 0, so the token is ignored and the state passes through untouched. As Δt,c→∞ the decay tends to 0, so the old state is flushed and the token written. Values in between give a partial update.
A large Δ also lets the layer reset. Training packs several documents into one sequence, and the state should forget everything at each document break (§4.4 built masks for the same job in attention). One token with a large Δ does that. Its decay near 0 zeroes every entry of the mixing matrix that links the two documents, as in the reset example of §21.1.
The eigenvaluesλcn enter only through the products Δt,cλcn. Making them input-dependent too would add little that Δ does not already provide, so the paper keeps them fixed. What they provide is a spread of timescales for each step size. With the initialization λcn=−n (called S4D-Real), one channel's N modes forget at rates 1,2,…,N times Δ. A stored value in the mode with λ=−n halves after ln2/(nΔ) steps, its half-life (§21.3).
The vectors Bt and Ct decide what enters the state and what is read out. A selective Bt can write a token into some state entries and not others, and a selective Ct can read the entry that matters now. They are the content side of selection, and they become important once N>1, as the ablations later in this section show.
The widget lets you play the recall game yourself on a strip of 24 tokens. In time-invariant mode the layer uses one step size for every token and up to four modes. Its readout C is fitted by least squares to the current strip, the best those fixed modes can do. In selective mode, filler and data tokens get their own step sizes. Filler tokens enter the layer as 0, and the row of green bars shows how much of the state each token replaces, 1−aˉ=1−e−Δ. Click any token to switch it between data and filler, and press Play to watch the layer read the strip one token at a time.
What does an input-dependent step size let a recurrent state do that a fixed kernel cannot?
Output the latest data token's value at every step. Click any token to switch it between data and filler.
Loading interactive figure…
Selection breaks the convolution
Selection has a price, and the mixing matrix of §21.1 shows exactly what it is. Recall what that section proved. A scalar linear recurrence ht=atht−1+utxt with readout yt=ctht can be unrolled, so each output is a weighted sum of all past inputs. The weight on input s is its input weight us, times the product of every decay applied after it, at:s=atat−1⋯as+1 (the segment product), times the readout weight ct. These weights fill a lower-triangular mixing matrixM with y=Mx.
Apply that to one channel c and one mode n of the selective SSM, with decay aˉt,cn and input weight Δt,cBt,n, and sum over modes with the readout weights Ct,n.
In plain words, the weight from token s to output t now depends on three things. It depends on what token s wrote (ΔsBs), on how much every token in between let the state decay (the sum of their step sizes), and on what output t reads (Ct). That is what selection needs, and it is exactly what kills the convolution.
A convolution is a Toeplitz matrix, one that is constant along each diagonal. The FFT algorithm of §21.3 applies it in O(TlogT) time by transforming its single kernel. The selective layer's matrix depends on (t,s), not only on t−s, so there is no single kernel to transform.
Two ways of computing the layer survive. The recurrence runs in O(TNdin) time and keeps only the current state. The parallel scan of §11.4 gives the same result in O(logT) parallel steps. A scan computes every prefix of an associative operation, and here each step is the affine map h↦ah+b. Applying (a1,b1) and then (a2,b2) is again an affine map, (a1,b1)∙(a2,b2)=(a1a2,a2b1+b2), and this rule is associative. The scan runs on dinN independent pairs at once, one per channel and state entry.
The matrix M(c) also helps interpretation. It is a data-dependent, lower-triangular mixing matrix, like attention's but with no softmax. It is also highly structured, since every block that lies within its lower triangle has rank at most N, a property §21.6 calls semiseparable. Ali, Zimerman & Wolf (2024) plot it as Mamba's "hidden attention" and apply attention-style interpretability tools to it. The next cell builds the matrix for a small example, checks it against the scan, and tests whether it is a convolution.
Python · cell 3
The selective scan from scratch
The function selective_scan_ref above is the whole layer, and it maps line by line to the definition. The decay factor is exp(delta[t][:, None] * A), one number per channel and state entry. The write is the outer product of Δt⊙xt with Bt, and the read is HtCt plus the skip term. It costs O(TdinN) arithmetic and O(dinN) memory, with a Python loop over T that the GPU kernel replaces. Writing it yourself, with a batch dimension and the kernels' exact discretization, is a standard interview problem.
Interview
The selective scan, from scratch
Implement selective_scan(x, delta, A, B, C, D), the selective SSM of the definition with the discretization of the released kernels. The shapes are as follows.
x and delta have shape (T, d_in), or (batch, T, d_in). The step sizes are already positive, because the softplus has been applied.
A has shape (d_in, N) and holds the negative eigenvalues λcn.
B and C have shape (T, N), or (batch, T, N), and are shared by all channels.
D has shape (d_in,).
Start from H0=0. Compute Ht=exp(Δtλ)⊙Ht−1+(Δt⊙xt)Bt⊤ and yt=HtCt+D⊙xt. The input weight is exactly ΔB, not the ZOH formula. Return y with the shape of x. A loop over T is fine, and everything else should be vectorized.
import numpy as np
def selective_scan(x, delta, A, B, C, D):
raise NotImplementedError
Tests, verified solutions, and AI help, open once you add your API key.
The theorem at the start of this section proved that a linear layer cannot recall the last data token. The next exercise turns that proof into a measurement and asks you to build the selective layer that solves the task exactly.
Research
Why a fixed kernel cannot select
The setup provides make_recall(n, T, p, seed). It returns X (the values, 0 for filler), is_data (booleans), and the target Y, each of shape (n, T). Y[i, t] is the value of the most recent data token at or before t in sequence i, and 0 before the first one. Implement two functions.
best_lti_kernel(X, Y) fits the best causal kernel K∈RT, with y^t=∑j=0tKjxt−j, by least squares over every sequence and step. It returns (K, rel_err), where rel_err is ∑(y^−y)2/∑y2.
selective_solution(X, is_data) returns arrays (a_bar, b_bar) of shape (n, T) such that the recurrence ht=aˉtht−1+bˉtxt, h0=0, outputs ht=yt exactly. The coefficients may depend on whether a token is data, never on its position.
In a comment, explain with the four-sequence argument of this section why no kernel length or data set size can drive rel_err to zero.
Tests, verified solutions, and AI help, open once you add your API key.
Why the scan must be fused
A GPU has two kinds of memory that matter here. HBM (high-bandwidth memory) is the large main memory, 80 GB on an H100, read at about 3.35 TB/s. On-chip memory (registers and shared memory, often called SRAM) holds only a few hundred kilobytes per streaming multiprocessor, but it is far faster (§11.1). The roofline model of §11.2 says an operation takes at least the longer of two times, its bytes moved divided by the bandwidth, and its arithmetic divided by the peak rate. An elementwise operation does a few operations per byte, so it is limited by the bytes.
On paper the recurrence is cheap, O(BTdinN) arithmetic for a batch of B sequences. (Italic B is the batch size here and below, unrelated to the bold vector Bt.) The trouble is memory traffic. Over a whole sequence the states form a tensor of shape (B,T,din,N), which is N times the size of the input (B,T,din).
Count the bytes of the straightforward implementation, the one you would write with PyTorch operations. It computes the decays Aˉ and the writes ΔBx as two tensors of shape (B,T,din,N) and stores them in HBM. It runs a scan that reads both and writes all the states Ht back to HBM. Then it reads the states again to multiply by Ct. That is six passes over a state-sized tensor. A fused kernel does everything in one pass instead. It reads Δ, x, B, C, and λ from HBM into on-chip memory, discretizes and runs the recurrence there, and writes only y.
In plain words, storing the states costs bytes in proportion to N, and fusing removes all of them. What remains is arithmetic on data that never leave the chip, and it comes in two kinds. Per state element, the kernel does about 6 floating-point operations on the ordinary fp32 units (the product Δλ, the write Δx⋅B, a multiply-add for the decay, and a multiply-add for the readout). It also computes one exponential, eΔλ.
The exponential does not run on the ordinary units. As §11.1 showed, exp runs on the special-function units, which on an H100 deliver only about 3.9×1012 results per second in total. That is 17 times fewer than the 67 TFLOP/s of the fp32 units. So one scan takes at least
This is the roofline of §11.2 with a third limit for the special functions. Mamba's scan needs one exponential for every sequence, token, channel, and state entry, BTdinN in all, because every entry of the diagonal state matrix has its own decay.
Python · cell 4
The table shows why Mamba could afford a larger state than earlier recurrent models. Unfused, every doubling of N doubles the time. Fused, the time stays at the 0.15 ms of traffic up to N≈7. Above that the exponentials set it. They also grow with N, but at the rate of the special-function units rather than the memory. At N=128 the exponentials alone take 2.8 ms per layer for this batch (the fp32 arithmetic 1.0 ms). That is the regime where Mamba-2 takes over (§21.6), with one decay per head instead of one per state entry and its products on tensor cores.
The widget below draws the same bound for this layer at every state size N from 1 to 256.
Why must the selective scan be fused, and how much does that save?
Fastest possible time of one scan in a Mamba-2.8B layer, batch 8 at 2,048 tokens, on an H100.
Loading interactive figure…
The backward pass has the same problem in a sharper form. Autograd needs every intermediate state to compute gradients, and storing them costs S bytes per layer, 5.37 GB in the example above, for each of 64 layers. Mamba's kernel stores none of them. It keeps only the inputs x, Δ, B, and C. In the backward pass it reloads them into on-chip memory and recomputes the states. FlashAttention, the fused attention kernel of §12.3, uses the same trick for its attention weights. Recomputation adds arithmetic but removes the O(BTdinN) reads of stored states, so it is faster as well as smaller.
The paper applies recomputation to the rest of the block as well, to the convolution and activation outputs, which are cheap to recompute. Two Mamba layers then need about as much activation memory as one FlashAttention layer plus one MLP (§11.7 covers fusion and recomputation in general). The backward pass of a linear recurrence is itself a linear recurrence run in reverse, which §21.5 derives.
In an interview about Mamba's speed, these are the numbers to give. Fusing saves a factor of about 4N in memory traffic, and after fusing, the exponentials set the floor.
The Mamba block
The selective SSM mixes information along the sequence. A network also needs to mix information across channels, which an MLP does in a Transformer. Mamba wraps the SSM in one block that does both, and it stacks that one block everywhere with no separate MLP. Write ut∈Rd for the block's input at step t. It is the normalized residual stream, the running vector that every layer reads and adds its output to (§5.1).
In words, the block expands the input to twice the width and splits it into two halves. One half goes through a short convolution over the last four tokens and then the SSM. The other half becomes a gate that multiplies the result. A final projection maps back to width d.
Read the block as two branches. The main branch expands to din, mixes each channel with its few predecessors through the convolution, and then mixes along the whole sequence through the SSM. The gate branch SiLU(zt) multiplies the result entrywise. A SwiGLU MLP (§5.3) has the same shape. It computes two projections of its input and multiplies one by an activated copy of the other. The Mamba block is that gated MLP with an SSM inserted into one branch. RMSNorm is the normalization of §5.2, which rescales a vector by its root mean square.
Gu and Dao built this block by merging two earlier designs into one, the H3 block (an SSM layer between gates) and the gated MLP. With a selective layer inside both, the merged block is simpler than H3 and slightly better (perplexity 8.69 against 8.95 in the paper's architecture ablation).
The short convolution deserves a word, because it is easy to dismiss. It is depthwise, meaning each channel is convolved with its own four weights and channels do not mix. It gives every channel direct access to the last three tokens before the SSM sees them, and Δt, Bt, and Ct are computed after it, so they can use that local context. An induction head (§6.8) needs exactly such information. It completes a pattern [A][B]…[A]→[B] and so must know which token came just before each position. A Transformer gets that from a previous-token head, an attention head that looks one position back. H3 got it from a shift SSM, whose kernel only delays its input by a few steps. In Mamba the four-tap convolution does the same job for almost no parameters.
The projections dominate. Win and Wout hold 3ddin=6d2 parameters. Everything specific to the SSM (Δ, B, C, ℓ, D) is din(2rΔ+3N+2)≈d2/4+6Nd. For Mamba-2.8B that is 1.9 million parameters, under 5% of the layer.
A Transformer layer has 4d2 parameters in attention and 8d2 in an MLP four times as wide, 12d2 in all (§6.2). So two Mamba layers stand in for one Transformer layer. That is why Mamba-2.8B has 64 layers at the width where the Transformer Pythia-2.8B has 32.
Core
Count Mamba's parameters and its decode state
Implement mamba_param_count(d, n_layer, N, vocab, d_conv=4, expand=2, dt_rank=None, tied=True). It returns the exact parameter count of a Mamba-1 language model laid out as in the table above. dt_rank=None means rΔ=⌈d/16⌉. With tied=False, the output head is a separate d×vocab matrix without bias.
Then implement mamba_state_numbers(d, n_layer, N, d_conv=4, expand=2). It returns the number of values one sequence must keep between decoding steps, which is the SSM state plus the last d_conv - 1 convolution inputs of every channel in every layer. Both functions return Python integers.
def mamba_param_count(d, n_layer, N, vocab, d_conv=4, expand=2, dt_rank=None, tied=True):
raise NotImplementedError
def mamba_state_numbers(d, n_layer, N, d_conv=4, expand=2):
raise NotImplementedError
Tests, verified solutions, and AI help, open once you add your API key.
Decoding with a constant-size cache
Generation is where the fixed-size state pays off. To produce each new token, a Transformer reads the keys and values of every earlier token, its KV cache (§6.5), and that cache grows with the context. A Mamba layer needs only two small things from the past, and both have a fixed size.
The convolution cache, the last k−1=3 inputs to the convolution, x~t−3,x~t−2,x~t−1, which is 3din numbers.
The SSM stateHt−1, which is dinN numbers.
One decoding step runs the block's definition once. Project ut to x~t and zt. Append x~t to the convolution window, and apply the convolution and SiLU. Compute Δt, Bt, and Ct, update the state entrywise, read it, gate it, and project out. Then drop the oldest entry of the window. Apart from the projections, the step costs O(dinN) arithmetic, whatever the context length. Prefill, the processing of the prompt, runs the scan over the whole prompt and keeps only the final state and the last three convolution inputs.
Python · cell 5
This is where Mamba's measured speed comes from. Gu and Dao generated 128 tokens after a 2,048-token prompt on one A100 80GB, at batch sizes from 1 to 128. Mamba's throughput was 4 to 5 times that of a Transformer of similar size, because without a KV cache it can run much larger batches. By their measurement, even an untrained Mamba-6.9B would generate faster than a Transformer five times smaller, at 1.3B parameters.
Interview
One decoding step with conv and state caches
The setup provides make_params(d, N, d_conv, expand, seed), the helpers silu and softplus, and mamba_block_forward(params, U). The last one runs the block, without norm or residual, over a whole sequence U of shape (T, d). Read it first, because it is this section's definition in code. Then implement two functions.
init_cache(params) returns the cache before the first token. It is a dict with "conv" of shape (d_conv - 1, d_in), the previous inputs to the convolution with the oldest first, and "h" of shape (d_in, N).
mamba_block_step(params, cache, u) takes one input u of shape (d,) and returns (out, new_cache), where out has shape (d,).
Running mamba_block_step over the tokens of U one at a time must reproduce mamba_block_forward(params, U) at every step. Each step must take O(1) time in the sequence length, with no recomputation over the past.
Tests, verified solutions, and AI help, open once you add your API key.
Mamba at real scale
Gu and Dao trained Mamba language models from 130M to 2.8B parameters on 300B tokens of the Pile, an 825 GiB collection of diverse English text (Gao et al. 2020). They used the same tokenizer, data, and number of tokens as the Pythia suite of Transformers, which makes the comparison unusually clean.
The paper's ablations, on models of about 350M parameters, show where the gains come from. They are worth knowing in detail, because interviewers ask which part of selection matters. Each row makes a different set of parameters depend on the input.
selective parameters
perplexity
none (a time-invariant layer)
10.93
B only
10.15
C only
9.98
Δ only
9.81
Δ, B, and C
8.71
The step size alone captures half the gain, as the gate theorem suggests, and the three together do much better than any one. The state size tells the same story from the other side. With B and C fixed, growing N from 1 to 16 moved perplexity only from 9.88 to 9.81. With B and C selective, the same growth moved it from 9.73 to 8.71, more than a full point, for about 1% more parameters. A larger state helps only if the layer can choose what goes into it.
Mamba layers soon appeared elsewhere. Jamba (Lieber et al. 2024) interleaves Mamba-1 layers with attention and mixture-of-experts layers. Its authors found that pure Mamba models struggled to follow the output format of few-shot prompts, and that a hybrid with one attention layer in eight fixed it. MambaByte (Wang et al. 2024) models raw bytes with no tokenizer (§2.7). Vision Mamba (Zhu et al. 2024) scans image patches in both directions, a bidirectional use of the layer that competes with the vision Transformer on long sequences of patches and has been applied to video models too.
recurrence, constant memory, plus a 3-token conv cache
strengths
continuous signals, very long fixed-range dependencies, resampling
discrete data (text, DNA), content-based filtering, resets
weaknesses
cannot select by content
no tensor cores, real eigenvalues, recall limited by the state
Where Mamba breaks
Mamba has four limits, and each one drives a later part of the chapter.
The scan does not use tensor cores. Every operation inside it is entrywise, so its arithmetic runs at the 67 TFLOP/s of ordinary fp32 units, not the 989 TFLOP/s of the tensor cores, the GPU's bf16 matrix-multiply units (§21.1). The arithmetic grows with N. Each state entry also needs its own exponential, BTdinN of them, on special-function units 17 times slower still, and at N=16 these already set the fused kernel's time. Together these costs are why Mamba kept N=16.
Mamba-2 fixes this by giving each head, a group of P channels, a single scalar decay. That turns most of the work into matrix products and allows N=64 to 256 (§21.6). §21.5 develops the chunked algorithm that makes it possible.
Positive real decays cannot count. Every decay aˉt,cn=eΔλ lies in (0,1], so the state can shrink but never flip sign or rotate. Tracking the parity of a bit string needs exactly a flip. §21.6 states the theorem that finite-precision linear recurrences with only positive eigenvalues cannot compute parity, and shows the negative and complex eigenvalues that fix it.
A fixed state cannot recall everything. Mamba-2.8B's state is 6.2 million numbers, which is large, but the update rule decides what to keep before it knows what will be asked. Waleffe et al. (2024) compared 8B-parameter models trained on the same 3.5T tokens. Pure Mamba and Mamba-2 models matched or beat a Transformer on many tasks. They lagged on tasks that need strong copying or in-context learning, such as 5-shot MMLU and phonebook lookup (given a long list of names and numbers, return one person's number). A hybrid with 7% attention layers beat the Transformer on all 12 standard tasks. §21.6 states the recall and copying theorems and designs such hybrids.
A state tuned to the training length. A recurrent layer can read sequences of any length, but it has been trained on one length, and its decays are tuned to it. Mamba and Mamba-2 language models trained at 2K to 8K tokens degrade beyond that length, sometimes sharply. Two mechanisms have been identified, and they are opposite failures. Some layers forget too fast, so the start of a long input fades out of reach. Other layers forget too little, so their state keeps growing past anything seen in training. In both cases passkey retrieval, finding a number hidden in a long filler text, fails past the training length.
The fixes follow from the mechanisms. Train, or continue training, at longer lengths. With continued pre-training on longer sequences, Chen et al. brought a Mamba-2 370M model to near-perfect passkey retrieval at 256K tokens. At inference, force more forgetting or cap the state's norm. Or, as Ben-Kish et al.'s DeciMamba does, keep only the tokens with the largest Δt in the long-range layers during prefill, so that the total step size stays near the trained range. Better still, use layers whose update rules control forgetting more directly, the gated and delta-rule recurrences of §21.6. In practice, measure any recurrent model past its training length before trusting it there.
In PyTorch
The block below is the whole Mamba mixer in PyTorch, with a plain loop for the scan, which is also how the reference repository's selective_scan_ref computes it. It checks three things any implementation should pass. The output is causal, the parameter count equals the formula, and step-by-step decoding with the two caches reproduces the full forward pass. In production you would call the fused kernels instead, selective_scan_fn from the mamba_ssm package and causal_conv1d_fn from the causal-conv1d package, or the Hugging Face MambaForCausalLM, which uses them when they are installed.
state-spaces/mamba. Reference implementation. selective_scan_ref is the readable definition of the kernel, including its ΔB discretization, and mamba_simple.py holds the block, its initialization, and the decoding step with both caches.
Rush. Mamba: The Hard Way. Builds the fused selective scan step by step in Triton, the best way to see what lives in on-chip memory.
Ali, Zimerman & Wolf (2024). The Hidden Attention of Mamba Models. Mamba's mixing matrix read as data-dependent attention, with explainability methods built on it.
Chen et al. (2024). Stuffed Mamba: Oversized States Lead to the Inability to Forget. State collapse in Mamba-2 past its training length, and the training length needed to learn forgetting as a function of state size. The first version, titled "State Collapse and State Capacity of RNN-Based Long-Context Modeling", also tests training-free fixes such as state normalization.
Lieber et al. (2024). Jamba: A Hybrid Transformer-Mamba Language Model. The first large hybrid built on Mamba-1 layers, with the in-context learning ablation and the RMSNorm needed inside Mamba layers at scale.
Watch next
🎬 Mamba and S4 Explained: Architecture, Parallel Scan, Kernel Fusion, Recurrent, Convolution, Math · Umar JamilA long, careful walk from S4's two views to Mamba's selection, parallel scan, and kernel fusion, with the math written out.🎬 Mamba: Linear-Time Sequence Modeling with Selective State Spaces (Paper Explained) · Yannic KilcherA critical reading of the Mamba paper, its selection mechanism, the hardware-aware scan, and how to read its experiments.🎬 MAMBA from Scratch: Neural Nets Better and Faster than Transformers · Algorithmic SimplicityBuilds Mamba up from linear RNNs and the parallel scan with clear animations, a good companion to this section's derivations.