Code Architecture — Mamba Selective State-Space Model Notebook
Overview
The notebook implements a simplified selective state-space model (S6) from scratch in PyTorch, trains it on toy sequence tasks, and benchmarks its speed/memory against a vanilla Transformer at increasing sequence lengths.
Section-by-Section Breakdown
1. Imports and Setup
torch,torch.nn,torch.nn.functional,math,time,random,warnings- Device selection (cuda if available, else cpu)
- Reproducibility:
torch.manual_seed(42),random.seed(42)
2. State Space Model (S4) — Baseline LTI
Class: S4Layer
- Parameters:
A_log(logarithm of diagonal A, shape(D, N)),B(D, N),C(D, N),D(D,),dt(delta, shape (D,)) _init_Auses HiPPO-inspired diagonal initialization:A[n] = -(n+1)for real case (S4D-Real)forward(x): x has shape(B, L, D)- Discretize via ZOH:
A_bar = exp(dt * A),B_bar = (dt * A)^{-1} (exp(dt*A) - I) * dt * B - Since A is diagonal, this simplifies to element-wise operations
- Compute via recurrence:
h_t = A_bar * h_{t-1} + B_bar * x_t,y_t = C * h_t - LTI: B, C, dt are fixed (not input-dependent)
- Discretize via ZOH:
- Output shape:
(B, L, D) - Note:
S4Layeris defined for reference but is not trained or benchmarked in the notebook.
3. Selective State Space Model (S6) — Mamba’s Core
Class: SelectiveSSM
- Parameters:
A_log(D, N) — fixed, learnable, HiPPO-initialized - Input-dependent projections:
x_proj(D → 2*N + D) producing B_t, C_t, and delta_t for each timestep forward(x): x has shape(B, L, D)- For each timestep t:
B_t = x_proj_B(x[:, t])→ shape (B, N)C_t = x_proj_C(x[:, t])→ shape (B, N)delta_t = softplus(x_proj_delta(x[:, t]))→ shape (B, D)- Discretize:
A_bar = exp(delta_t * A),B_bar = delta_t * B_t(simplified ZOH for diagonal A) h_t = A_bar * h_{t-1} + B_bar * x[:, t]y_t = C_t * h_t→ shape (B, D)
- Returns y of shape (B, L, D)
- For each timestep t:
- Key difference from S4: B, C, delta change at every timestep based on input → selection
Class: MambaBlock
- Input projection:
in_proj(D → 2*D) for gated branch and main branch - Conv1d (kernel_size=4) on the main branch
- SiLU activation on the gate branch
SelectiveSSMon the main branch- Multiplicative gating:
output = gate * ssm_output - Output projection:
out_proj(D → D) - Residual connection:
output + residual
4. Full Mamba Model
Class: MambaLM
- Token embedding:
nn.Embedding(vocab_size, D) - Stack of
n_layersMambaBlocks - Output head:
nn.Linear(D, vocab_size) forward(x): x is token indices (B, L) → logits (B, L, vocab_size)- Loss: cross-entropy
5. Baseline Transformer
Class: TransformerBlock
- Multi-head self-attention (4 heads)
- Feed-forward (D → 4D → D with GELU)
- LayerNorm pre-norm
TransformerLM: embedding + n_layers blocks + output head
6. Toy Task: Selective Copy
- Generate sequences of length L with random tokens from vocab
- Place a few “marker” tokens at random positions
- Target: copy the markers to the end (or reconstruct the marked positions)
- This tests content-based selection — the model must identify which tokens are special
7. Toy Task: Induction Heads
- Generate sequences: [random tokens] + [anchor token] + [random tokens] + [anchor token] + [?]
- Target: the model must output the token that followed the first anchor
- This tests in-context retrieval — requires selective recall
8. Training Loop
- Adam optimizer, lr=1e-3
- Train both Mamba and Transformer on each toy task
- 50 epochs of 10 batches each, batch size 32, sequence length 64
- Track and plot training loss
9. Speed and Memory Benchmark
- For sequence lengths [256, 512, 1024, 2048, 4096]:
- Measure forward+backward time (2 warm-up runs, then the average of 3 runs), batch size 4
- Measure peak GPU memory with
torch.cuda.max_memory_allocated() - Compare Mamba vs Transformer
- Plot: time vs sequence length, memory vs sequence length
- Expected: Mamba scales linearly, Transformer scales quadratically
10. Visualization and Conclusions
- Plot training loss curves (Mamba vs Transformer on each task)
- Plot speed benchmark (log-log scale)
- Plot memory benchmark
- Summary table of results
Key Functions/Classes
| Component | Purpose |
|---|---|
S4Layer | Baseline LTI state-space model (fixed parameters) |
SelectiveSSM | Core S6: input-dependent B, C, Δ with recurrence |
MambaBlock | Full Mamba block: proj → conv → SiLU → SSM → gate → proj |
MambaLM | Language model: embedding + Mamba blocks + output head |
TransformerBlock | Baseline: multi-head attention + FFN |
TransformerLM | Baseline language model |
generate_selective_copy | Generate toy data for selective copy |
generate_induction_heads | Generate toy data for induction heads |
train_model | Adam training loop that returns the per-epoch losses |
benchmark_model | Measure time and memory at varying seq lengths |
Data Flow and Shapes
Input tokens: (B, L) — integer indices
→ Embedding: (B, L, D)
→ MambaBlock:
→ in_proj: (B, L, 2D) → split into gate (B,L,D) and main (B,L,D)
→ Conv1d on main: (B, L, D)
→ SiLU on gate: (B, L, D)
→ SelectiveSSM on main: (B, L, D) [internal state: (B, L, N)]
→ gate * ssm_out: (B, L, D)
→ out_proj: (B, L, D)
→ + residual: (B, L, D)
→ [repeat for n_layers]
→ Output head: (B, L, vocab_size)
Deliberate Simplifications vs Full Paper
- Naive recurrence loop: We implement the SSM recurrence with a Python for-loop over timesteps. The real Mamba uses a hardware-aware fused CUDA parallel scan. This is O(BLDN) and slower on GPU but demonstrates the algorithm clearly.
- Diagonal A only: We use a diagonal A matrix (S4D-style) rather than the full HiPPO structured matrix. This is simpler and still effective.
- Toy-scale model: D=64, N=16, 2 layers — vs the paper’s D up to 4096, N=16-256, dozens of layers.
- No RMSNorm/weight decay tricks: The paper uses RMSNorm in later versions; we use simple LayerNorm or no norm.
- Toy tasks only: We train on synthetic selective-copy and induction-head tasks, not real language modeling (The Pile, etc.).
- Simplified ZOH: For diagonal A, the ZOH B_bar simplifies; we use the simplified form rather than the full matrix inverse.
- No CUDA kernel fusion: All operations are in pure PyTorch with standard ops — no custom CUDA kernels.