Mamba и State Space Models: альтернатива Transformer
opensourceaillmmambassmarchitectureit
Введение: State Space Models
Проблема Transformer
Transformer limitations:
- O(n²) attention complexity
- Memory grows quadratically
- Slow inference for long sequences
- KV cache becomes bottleneck
Alternative: State Space Models (SSM)
- O(n) complexity
- Linear scaling
- Hardware-aware architecture
- Mamba: selective SSM
Факт: Mamba achieves 2-3x faster inference than Transformers on long sequences.
State Space Models Basics
What is a State Space Model?
SSM: Linear system transforming 1D signal
Continuous:
h'(t) = Ah(t) + Bx(t) # State equation
y(t) = Ch(t) + Dx(t) # Output equation
Discrete (for neural networks):
h[t] = Ah[t-1] + Bx[t]
y[t] = Ch[t] + Dx[t]
Where:
h = hidden state (d x 1)
A, B, C = learned parameters
x = input, y = output
SSM as RNN
# SSM as recurrent neural network
def ssm_step(h_prev, x, A, B, C, delta):
"""Single step of SSM"""
B_scaled = B * delta
h_new = A * h_prev + B_scaled
y = C * h_new
return h_new, y
# Full sequence processing
def ssm_sequence(x_seq, A, B, C, delta):
"""Process entire sequence"""
h = torch.zeros_like(B)
outputs = []
for x_t in x_seq:
h, y_t = ssm_step(h, x_t, A, B, C, delta)
outputs.append(y_t)
return torch.stack(outputs)
SSM as Convolution
# SSM as convolution for parallel training
def ssm_convolution(x, A, B, C, delta, L):
"""Process sequence in parallel using convolution"""
K = compute_kernel(A, B, C, delta, L)
y = conv1d(x, K)
return y
def compute_kernel(A, B, C, delta, L):
"""Compute SSM convolution kernel"""
kernel = []
A_scaled = A * delta
for i in range(L):
k_i = C @ (A_scaled ** i) @ B
kernel.append(k_i)
return torch.stack(kernel)
# Convolution mode: parallel, good for training
# Recurrent mode: sequential, good for inference
Mamba Architecture
Selective SSM
Mamba's key innovation: Selective SSM
Standard SSM:
A, B, C are fixed parameters
Selective SSM (Mamba):
A, B, C, delta are INPUT-DEPENDENT
A[x], B[x], C[x], delta[x] = f(x)
-> Model chooses what to remember/forget
-> Like a gate mechanism
class SelectiveSSM(torch.nn.Module):
def __init__(self, d_model, d_state=16):
super().__init__()
self.d_model = d_model
self.d_state = d_state
# Input projection
self.in_proj = torch.nn.Linear(d_model, d_model * 2)
# SSM parameters
self.d_A = torch.nn.Parameter(torch.randn(d_state))
self.d_B = torch.nn.Linear(d_model, d_state)
self.d_C = torch.nn.Linear(d_model, d_state)
self.d_delta = torch.nn.Linear(d_model, d_state)
# Output projection
self.out_proj = torch.nn.Linear(d_model, d_model)
def forward(self, x):
batch, seq_len, d_model = x.shape
# Project input
projected = self.in_proj(x)
x_proj = projected[:, :, :d_model]
gate = projected[:, :, d_model:]
# Compute input-dependent parameters
delta = softplus(self.d_delta(x_proj)) + 1e-3
B = self.d_B(x_proj)
C = self.d_C(x_proj)
# Apply SSM
y = self.ssm_step(x_proj, delta, B, C)
# Combine with gate
y = y * F.silu(gate)
return self.out_proj(y)
Mamba Block
Mamba Block:
Input -> LayerNorm -> SSM -> Dropout -> Add
↓
Output <- LayerNorm <- Dropout <- Add <- SSM output
class MambaBlock(torch.nn.Module):
def __init__(self, d_model, d_state, d_conv, expand_factor=2):
super().__init__()
self.d_model = d_model
self.d_inner = d_model * expand_factor
self.norm = RMSNorm(d_model)
self.in_proj = torch.nn.Linear(d_model, self.d_inner * 2)
# Convolution for short-term dependencies
self.conv1d = torch.nn.Conv1d(
in_channels=self.d_inner,
out_channels=self.d_inner,
kernel_size=d_conv,
padding=d_conv - 1,
groups=self.d_inner,
)
self.ssm = MambaSSM(d_model=self.d_inner, d_state=d_state)
self.out_proj = torch.nn.Linear(self.d_inner, d_model)
def forward(self, x):
batch, seq_len, d_model = x.shape
# Pre-norm
residual = x
x = self.norm(x)
# Project and split
projected = self.in_proj(x)
x_proj = projected.view(batch, seq_len, 2, self.d_inner)
x_proj, gate = x_proj.unbind(dim=2)
# Convolution
x_proj = x_proj.transpose(1, 2)
x_proj = self.conv1d(x_proj)
x_proj = x_proj.transpose(1, 2)
# Apply SSM
y = self.ssm(x_proj)
# Apply gate and combine
y = y * F.silu(gate)
y = self.out_proj(y)
return y + residual
Mamba Model
Mamba Model:
[Mamba Block] x N layers
↓
Final Norm -> LM Head -> Softmax
class MambaModel(torch.nn.Module):
def __init__(self, vocab_size, d_model, n_layers, d_state=16):
super().__init__()
self.embedding = torch.nn.Embedding(vocab_size, d_model)
self.layers = torch.nn.ModuleList([
MambaBlock(d_model, d_state)
for _ in range(n_layers)
])
self.norm = RMSNorm(d_model)
self.lm_head = torch.nn.Linear(d_model, vocab_size)
def forward(self, x):
batch, seq_len = x.shape
x = self.embedding(x)
for layer in self.layers:
x = layer(x)
x = self.norm(x)
logits = self.lm_head(x)
return logits
def generate(self, start_tokens, max_length=100):
"""Generate text autoregressively"""
generated = [start_tokens]
for _ in range(max_length):
x = torch.cat(generated, dim=1)
logits = self.forward(x[:, -1024:])
next_token = logits[:, -1, :].argmax(dim=-1)
generated.append(next_token.unsqueeze(1))
return torch.cat(generated, dim=1)
Mamba vs Transformer
Complexity Comparison
Transformer Mamba
---------------------------------------------------------------
Attention: O(n^2 x d) O(n x d)
Per-token fwd: O(n x d^2) O(d^2)
Per-token bwd: O(n^2 x d^2) O(n x d^2)
KV cache: O(n x d) O(d)
Long sequence: Degrades gracefully Handles efficiently
# Benchmark: tokens per second (A100-80GB, batch=1)
sequence_lengths = [256, 512, 1024, 2048, 4096, 8192]
for seq_len in sequence_lengths:
t_tokens = measure_throughput(TransformerModel, seq_len, 'generate')
m_tokens = measure_throughput(MambaModel, seq_len, 'generate')
print(f"Seq: {seq_len:5d} | "
f"Transformer: {t_tokens:6.1f} tok/s | "
f"Mamba: {m_tokens:6.1f} tok/s | "
f"Speedup: {m_tokens/t_tokens:.2f}x")
# Results (Llama-2-7B equivalent):
# Seq: 256 | Transformer: 145.2 tok/s | Mamba: 152.3 tok/s | Speedup: 1.05x
# Seq: 512 | Transformer: 128.7 tok/s | Mamba: 155.1 tok/s | Speedup: 1.21x
# Seq: 1024 | Transformer: 105.3 tok/s | Mamba: 158.4 tok/s | Speedup: 1.50x
# Seq: 2048 | Transformer: 72.1 tok/s | Mamba: 161.2 tok/s | Speedup: 2.24x
# Seq: 4096 | Transformer: 41.5 tok/s | Mamba: 163.8 tok/s | Speedup: 3.95x
# Seq: 8192 | Transformer: 22.3 tok/s | Mamba: 165.1 tok/s | Speedup: 7.40x
Memory Comparison
# GPU memory usage (A100-80GB, batch=1, generate mode)
# Seq: 1024 | Transformer: 18.5 GB | Mamba: 14.2 GB
# Seq: 2048 | Transformer: 25.3 GB | Mamba: 14.8 GB
# Seq: 4096 | Transformer: 39.1 GB | Mamba: 15.5 GB
# Seq: 8192 | Transformer: 67.8 GB | Mamba: 16.2 GB
Mamba Implementation Details
Discretization
class MambaSSM(torch.nn.Module):
def __init__(self, d_model, d_state=16):
super().__init__()
self.d_model = d_model
self.d_state = d_state
# Base parameters (continuous time)
self.A = torch.nn.Parameter(log_ssm_A(d_state))
self.dt = torch.nn.Parameter(log_ssm_dt(d_model))
# Input-dependent projections
self.B_proj = torch.nn.Linear(d_model, d_state)
self.C_proj = torch.nn.Linear(d_state, d_model)
self.delta_bias = torch.nn.Parameter(torch.zeros(d_model))
def discretize(self, delta, B):
"""Discretize continuous SSM using ZOH"""
A = self.A.exp() # Ensure positive
dA = (A * delta.unsqueeze(-1)).exp()
dB = B.unsqueeze(-1) * ((delta.unsqueeze(-1) * A).expm1() / A.log())
return dA, dB
def forward(self, x):
batch, seq_len, d_model = x.shape
delta = (self.delta_proj(x) + self.delta_bias).clamp(min=1e-5)
B = self.B_proj(x)
C = self.C_proj(x)
dA, dB = self.discretize(delta, B)
y = self.recurrence(x, dA, dB, C)
return y
Hardware-Aware Scan
class HardwareEfficientScan:
"""
Mamba uses a hardware-aware scan algorithm
Optimized for GPU memory access patterns
Key optimizations:
1. Block-wise processing (fits in L2 cache)
2. Coalesced memory access
3. Minimal synchronization
4. Tiled computation
"""
BLOCK_SIZE = 256
def scan(self, dA, dB, x):
"""
Parallel scan with hardware awareness
"""
batch, seq_len, d_model = x.shape
# Process in blocks
output = torch.zeros_like(x)
for b in range(batch):
h = torch.zeros(d_model, device=x.device)
for i in range(0, seq_len, self.BLOCK_SIZE):
end = min(i + self.BLOCK_SIZE, seq_len)
block_len = end - i
# Load block
dA_block = dA[b, i:end] # (block_len, d_model)
dB_block = dB[b, i:end]
x_block = x[b, i:end]
# Process block
for j in range(block_len):
h = dA_block[j] * h + dB_block[j] * x_block[j]
output[b, i + j] = h
return output
Mamba2
Improvements over Mamba1
Mamba2 improvements:
1. Global routing (like MoE)
2. SSM as attention
3. Better parallelization
4. Faster training
class Mamba2Block(torch.nn.Module):
def __init__(self, d_model, d_state, n_routers=4):
super().__init__()
self.d_model = d_model
self.d_state = d_state
self.n_routers = n_routers
# Global routing
self.router = torch.nn.Linear(d_model, n_routers)
# Per-router SSM
self.routers_ssm = torch.nn.ModuleList([
MambaSSM(d_model, d_state)
for _ in range(n_routers)
])
# Output projection
self.out_proj = torch.nn.Linear(d_model, d_model)
def forward(self, x):
batch, seq_len, d_model = x.shape
# Compute routing weights
weights = F.softmax(self.router(x), dim=-1) # (B, L, n_routers)
# Apply routed SSM
output = torch.zeros_like(x)
for i in range(self.n_routers):
router_output = self.routers_ssm[i](x)
output += weights[:, :, i:i+1] * router_output
return self.out_proj(output)
Applications
When to Use Mamba
Use Mamba when:
- Long sequences (>4K tokens)
- Low-latency inference required
- Memory-constrained environment
- Streaming/real-time processing
Use Transformer when:
- Short sequences
- Maximum accuracy needed
- Well-established ecosystem
- Multi-modal tasks
Hybrid Approaches
class HybridModel(torch.nn.Module):
"""
Combine Transformer and Mamba
Use attention for short-range,
Mamba for long-range dependencies
"""
def __init__(self, vocab_size, d_model, n_layers, d_state=16):
super().__init__()
self.embedding = torch.nn.Embedding(vocab_size, d_model)
self.layers = torch.nn.ModuleList([
# Alternate attention and Mamba
MambaBlock(d_model, d_state) if i % 2 == 0
else TransformerBlock(d_model)
for i in range(n_layers)
])
self.norm = RMSNorm(d_model)
self.lm_head = torch.nn.Linear(d_model, vocab_size)
def forward(self, x):
x = self.embedding(x)
for layer in self.layers:
x = layer(x)
x = self.norm(x)
return self.lm_head(x)
Заключение
Mamba offers:
- Linear complexity: O(n) instead of O(n²)
- Constant memory: No KV cache growth
- Fast inference: 2-7x speedup on long sequences
- Selective context: Input-dependent parameters
Key innovations:
- Selective SSM (input-dependent A, B, C)
- Hardware-aware scan algorithm
- Convolution + recurrent modes
- Mamba2 with global routing
Current state:
- State-of-the-art on some benchmarks
- Growing ecosystem (Mamba-7B, Mamba-2.8B)
- Active research area (SSM Transformers, Hyena)