Skip to content

필사 모드: Mamba 与 State Space Model 论文深度解析:从选择性 SSM 到 Mamba-2 的 Transformer 替代架构

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

Mamba State Space Model

引言

Transformer 架构自 2017 年问世以来,一直统治着自然语言处理、计算机视觉、音频、时间序列等几乎所有序列建模领域。然而 Self-Attention 的二次时间复杂度 O(L2)O(L^2) 存在一个根本性的局限 — 随着序列长度 LL 的增加,计算量和内存占用会爆炸式增长。在处理 128K 以上上下文窗口的现代 LLM 中,这个问题变得更加严重,推理时 KV Cache 随序列长度成比例增长也是一个实际的瓶颈。

在这样的背景下,State Space Model(SSM)作为一种能以线性时间复杂度 O(L)O(L) 处理序列的替代方案被研究。2021 年 Albert Gu 提出的 Structured State Spaces(S4)在长序列基准测试中展现出超越 Transformer 的性能,开启了 SSM 研究的复兴。然而 S4 等早期 SSM 受限于 Linear Time-Invariant(LTI)系统这一约束,缺乏根据输入内容动态选择信息的能力,在语言建模上不及 Transformer。

2023 年 12 月,Albert Gu 与 Tri Dao 发表的 Mamba 论文正面突破了这一局限。论文引入了让 SSM 参数依赖于输入的选择性机制(Selection Mechanism),并通过针对 GPU 硬件优化的扫描算法确保了效率。随后 2024 年 5 月发表的 Mamba-2 证明了 SSM 与 Attention 在数学上属于同一族群的 State Space Duality(SSD),并提出了快 2-8 倍的算法。

本文将深入探讨 SSM 的数学基础(S4)、Mamba 的选择性机制、Mamba-2 的 SSD 框架、与 Transformer 的定量比较、视觉/音频/时间序列等多领域应用,以及实战实现与运维注意事项。

SSM 基础理论(S4)

连续时间 State Space Model

State Space Model 是源自控制理论的连续时间动态系统。将输入信号 x(t)x(t) 经由隐藏状态 h(t)h(t) 转换为输出 y(t)y(t) 的过程,可以用如下微分方程描述。

h(t)=Ah(t)+Bx(t)h'(t) = Ah(t) + Bx(t)

y(t)=Ch(t)+Dx(t)y(t) = Ch(t) + Dx(t)

这里 ARN×NA \in \mathbb{R}^{N \times N} 是状态转移矩阵,BRN×1B \in \mathbb{R}^{N \times 1} 是输入投影矩阵,CR1×NC \in \mathbb{R}^{1 \times N} 是输出投影矩阵,DRD \in \mathbb{R} 是 skip connection 项。NN 是状态维度(state dimension),决定了 SSM 的记忆容量。

要处理离散序列,需要将连续系统离散化(discretization)。对步长 Δ\Delta 应用 Zero-Order Hold(ZOH)离散化,结果如下。

Aˉ=exp(ΔA)\bar{A} = \exp(\Delta A)

Bˉ=(ΔA)1(exp(ΔA)I)ΔB\bar{B} = (\Delta A)^{-1}(\exp(\Delta A) - I) \cdot \Delta B

离散化后的 SSM 可以用两种方式计算。第一,以递归(recurrence)形式按顺序处理序列。

hk=Aˉhk1+Bˉxkh_k = \bar{A} h_{k-1} + \bar{B} x_k

yk=Chky_k = C h_k

第二,还有一次性处理整个序列的合成卷积(convolution)形式。预先计算好核 Kˉ\bar{K},就可以利用 FFT 进行 O(LlogL)O(L \log L) 运算。

Kˉ=(CBˉ,CAˉBˉ,CAˉ2Bˉ,,CAˉL1Bˉ)\bar{K} = (C\bar{B}, C\bar{A}\bar{B}, C\bar{A}^2\bar{B}, \ldots, C\bar{A}^{L-1}\bar{B})

y=xKˉy = x * \bar{K}

S4 的 HiPPO 初始化

S4(Structured State Spaces for Sequence Modeling, Gu et al., 2021)的核心贡献是状态矩阵 AA 的初始化策略。随机初始化的 AA 无法捕捉长期依赖关系,并会遭遇梯度消失/爆炸问题。S4 利用 HiPPO(High-order Polynomial Projection Operators)框架,将 AA 初始化为一种特殊结构。

HiPPO-LegS 矩阵的设计目标,是用勒让德多项式基对过去的输入做连续近似。这一结构的核心性质,是能够均等地保留所有时间尺度上的信息。

import torch
import torch.nn as nn
import numpy as np

def make_hippo_matrix(N: int) -> torch.Tensor:
    """生成 HiPPO-LegS 矩阵。

    该矩阵的设计目标是用勒让德多项式基对过去的输入
    做最优近似,有利于捕捉长期依赖关系。

    Args:
        N: 状态维度 (State dimension)

    Returns:
        A: (N, N) HiPPO 矩阵
    """
    P = np.sqrt(2 * np.arange(N) + 1)
    A = np.zeros((N, N))
    for i in range(N):
        for j in range(N):
            if i > j:
                A[i, j] = P[i] * P[j]
            elif i == j:
                A[i, j] = i + 1
            # i < j: 0
    A = -A  # 为保证稳定性取负
    return torch.tensor(A, dtype=torch.float32)


class S4Layer(nn.Module):
    """S4(Structured State Spaces)层的核心实现。

    结合了 HiPPO 初始化的 A 矩阵与基于卷积的并行计算。
    """

    def __init__(self, d_model: int, state_dim: int = 64, seq_len: int = 1024):
        super().__init__()
        self.d_model = d_model
        self.state_dim = state_dim
        self.seq_len = seq_len

        # HiPPO 初始化
        A = make_hippo_matrix(state_dim)
        self.A_log = nn.Parameter(torch.log(torch.clamp(-A.diagonal(), min=1e-4)))
        self.B = nn.Parameter(torch.randn(state_dim, 1) * 0.01)
        self.C = nn.Parameter(torch.randn(1, state_dim) * 0.01)
        self.log_dt = nn.Parameter(torch.rand(d_model).uniform_(-4, -1))

        # skip connection
        self.D = nn.Parameter(torch.ones(d_model))

    def _compute_kernel(self, L: int) -> torch.Tensor:
        """预先计算 SSM 卷积核。"""
        dt = torch.exp(self.log_dt)  # (d_model,)
        A = -torch.exp(self.A_log)   # (state_dim,) 对角元素

        # ZOH 离散化 (假设 A 为对角矩阵)
        dtA = dt.unsqueeze(-1) * A.unsqueeze(0)  # (d_model, state_dim)
        A_bar = torch.exp(dtA)

        # 核计算: K[k] = C @ A_bar^k @ B_bar
        # 用高效的 Vandermonde 乘法实现 O(N*L) 计算
        powers = torch.arange(L, device=A.device).float()
        # A_bar^k = exp(k * dtA)
        kernel = torch.einsum(
            'dn,dn,nl->dl',
            self.C.squeeze(0).expand(self.d_model, -1),
            dt.unsqueeze(-1) * self.B.squeeze(-1).unsqueeze(0),
            torch.exp(dtA.unsqueeze(-1) * powers.unsqueeze(0).unsqueeze(0))
            .squeeze()
            .T
        )

        return kernel  # (d_model, L)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """卷积模式的 forward pass。

        Args:
            x: (batch, seq_len, d_model) 输入序列

        Returns:
            y: (batch, seq_len, d_model) 输出序列
        """
        B, L, D = x.shape
        kernel = self._compute_kernel(L)  # (d_model, L)

        # 基于 FFT 的卷积
        x_perm = x.permute(0, 2, 1)  # (B, D, L)
        k_f = torch.fft.rfft(kernel, n=2 * L)  # zero-pad
        x_f = torch.fft.rfft(x_perm, n=2 * L)
        y = torch.fft.irfft(x_f * k_f, n=2 * L)[..., :L]

        # skip connection
        y = y + self.D.unsqueeze(0).unsqueeze(-1) * x_perm
        return y.permute(0, 2, 1)

S4 的核心优势在于,训练时以卷积模式进行 O(LlogL)O(L \log L) 的并行处理,推理时则以递归模式用 O(1)O(1) 的常数内存/计算量逐个生成 token。但由于 A,B,CA, B, C 是与输入无关的固定参数这一 LTI 属性,在基于内容的推理上较为薄弱。

Mamba 的选择性机制

从 LTI 到 Selection 的转变

Mamba 的核心洞察简单却强大:把 SSM 参数 BBCCΔ\Delta 变成输入的函数,从而将系统转变为时变(time-varying)系统。这样一来,模型就能在每个时间点动态决定要把哪些信息写入状态、又要忽略哪些信息。

在传统的 LTI SSM 中,所有输入 token 都以相同的动力学(dynamics)被处理。在 "The cat sat on the mat" 这句话中,冠词 "the" 与核心名词 "cat" 会以同样的方式反映到状态中,因此无法选择性地强调重要信息或过滤掉不必要的信息。Transformer 的 Attention 本质上正是在执行这种选择性的信息处理,SSM 若要具备与之匹敌的表达能力,输入依赖的参数就必不可少。

用公式表示,Mamba 的选择性 SSM 如下。

Bt=LinearB(xt),Ct=LinearC(xt),Δt=softplus(LinearΔ(xt))B_t = \text{Linear}_B(x_t), \quad C_t = \text{Linear}_C(x_t), \quad \Delta_t = \text{softplus}(\text{Linear}_\Delta(x_t))

Aˉt=exp(ΔtA),Bˉt=ΔtBt\bar{A}_t = \exp(\Delta_t A), \quad \bar{B}_t = \Delta_t B_t

ht=Aˉtht1+Bˉtxth_t = \bar{A}_t h_{t-1} + \bar{B}_t x_t

yt=Cthty_t = C_t h_t

Δt\Delta_t 的作用尤为关键。当 Δt\Delta_t 较大时,Aˉt=exp(ΔtA)\bar{A}_t = \exp(\Delta_t A) 会趋近于 0,先前的状态被重置,新的输入 xtx_t 主导状态。反之,当 Δt\Delta_t 较小时,先前状态得以保留,当前输入的影响会减弱。这与 RNN 的门控机制类似,只不过是在连续时间系统的数学框架内自然推导出来的。

Selection 解决的合成任务

Mamba 论文用两个合成任务证明了 Selection Mechanism 的效果。

第一,Selective Copying 任务。这是一个只需从输入序列中选择性复制特定 token 的问题,LTI SSM 因为无法根据输入内容判断"该复制哪个 token"而失败。Mamba 则通过 BtB_t 把重要 token 的信息强力写入状态,忽略不必要的 token。

第二,Induction Heads 任务。这是在出现 "A B ... A" 模式后需要预测下一个是 "B" 的问题,Transformer 用 Attention 机制自然就能解决,但 LTI SSM 做不到这种模式匹配。Mamba 的选择性机制能在 "A" 出现时激活先前 "A B" 模式的记忆,从而预测出 "B"。

import torch
import torch.nn as nn
import torch.nn.functional as F

class SelectiveSSM(nn.Module):
    """Mamba 选择性 State Space Model 的核心实现。

    把 B、C、delta 变成输入的函数,
    从而实现 content-aware 的序列处理。
    """

    def __init__(self, d_model: int, d_state: int = 16, d_conv: int = 4):
        super().__init__()
        self.d_model = d_model
        self.d_state = d_state

        # A 矩阵:对角结构,在对数空间中参数化
        A = torch.arange(1, d_state + 1, dtype=torch.float32)
        self.A_log = nn.Parameter(torch.log(A).unsqueeze(0).expand(d_model, -1))

        # 输入依赖的 B、C 投影
        self.B_proj = nn.Linear(d_model, d_state, bias=False)
        self.C_proj = nn.Linear(d_model, d_state, bias=False)

        # delta(步长)投影
        self.dt_proj = nn.Linear(d_model, d_model, bias=True)

        # skip connection
        self.D = nn.Parameter(torch.ones(d_model))

    def selective_scan(
        self,
        x: torch.Tensor,
        dt: torch.Tensor,
        B: torch.Tensor,
        C: torch.Tensor,
    ) -> torch.Tensor:
        """选择性扫描算法(顺序实现)。

        实际的 Mamba 使用针对 GPU SRAM 优化的 CUDA 内核,
        这里为了清楚展示算法逻辑而采用顺序实现。

        Args:
            x: (batch, seq_len, d_model) 输入
            dt: (batch, seq_len, d_model) 离散化步长
            B: (batch, seq_len, d_state) 输入投影
            C: (batch, seq_len, d_state) 输出投影

        Returns:
            y: (batch, seq_len, d_model) 输出
        """
        batch, seq_len, d_model = x.shape
        d_state = B.shape[-1]

        # A 离散化: A_bar = exp(dt * A)
        A = -torch.exp(self.A_log)  # (d_model, d_state)
        dt_A = torch.einsum('bld,dn->bldn', dt, A)  # (B, L, D, N)
        A_bar = torch.exp(dt_A)

        # B 离散化: B_bar = dt * B
        dt_B = torch.einsum('bld,bln->bldn', dt, B)  # (B, L, D, N)

        # 顺序扫描
        h = torch.zeros(batch, d_model, d_state, device=x.device)
        outputs = []

        for t in range(seq_len):
            # h_t = A_bar_t * h_{t-1} + B_bar_t * x_t
            h = A_bar[:, t] * h + dt_B[:, t] * x[:, t].unsqueeze(-1)
            # y_t = C_t @ h_t
            y_t = torch.einsum('bdn,bn->bd', h, C[:, t])
            outputs.append(y_t)

        y = torch.stack(outputs, dim=1)  # (B, L, D)
        return y

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """选择性 SSM 的 forward pass。

        Args:
            x: (batch, seq_len, d_model)

        Returns:
            y: (batch, seq_len, d_model)
        """
        # 计算输入依赖的参数
        B = self.B_proj(x)                          # (B, L, N)
        C = self.C_proj(x)                          # (B, L, N)
        dt = F.softplus(self.dt_proj(x))            # (B, L, D), 保证为正数

        # 选择性扫描
        y = self.selective_scan(x, dt, B, C)

        # skip connection
        y = y + self.D.unsqueeze(0).unsqueeze(0) * x

        return y

Hardware-Aware 扫描算法

引入 Selection Mechanism 后,SSM 不再是 LTI 系统,因此无法再使用 S4 中基于 FFT 的卷积。时变(time-varying)参数在每个时间点都不同,所以无法预先计算卷积核。必须使用顺序扫描(sequential scan),但简单的 for 循环实现无法利用 GPU 的并行性,速度非常慢。

Mamba 用受 FlashAttention 启发的 hardware-aware 算法解决了这个问题。核心思路有三点。第一,应用 kernel fusion,把 GPU HBM(High Bandwidth Memory)和 SRAM(on-chip memory)之间的数据移动降到最低——把离散化、扫描、输出投影都放在同一个 CUDA 内核中执行,让中间结果留在 SRAM 里。第二,不把中间状态张量(Aˉ,Bˉ\bar{A}, \bar{B} 等)写入 HBM,而是在 SRAM 中实时重新计算,这样内存占用就不会随序列长度成比例增长。第三,反向传播时同样不保存中间状态,而是通过重新计算(recomputation)来节省内存。

Mamba 架构详解

Mamba 块结构

Mamba 块的结构与 Transformer 块不同。Transformer 由 Self-Attention 和 FFN 两个子层组成,而 Mamba 把这两种角色整合进同一个块。具体来说,输入被分成两路——一路经过卷积和选择性 SSM,另一路承担门控的作用。

import torch
import torch.nn as nn
import torch.nn.functional as F
from dataclasses import dataclass


@dataclass
class MambaConfig:
    d_model: int = 768
    d_state: int = 16
    d_conv: int = 4
    expand: int = 2
    n_layers: int = 24
    vocab_size: int = 50257
    dropout: float = 0.0


class MambaBlock(nn.Module):
    """Mamba 块:整合选择性 SSM、卷积、门控的结构。

    用一个块取代 Transformer 的 Attention + FFN。
    扩展比率(expand)决定内部维度,
    默认值 2 意味着 d_inner = 2 * d_model。
    """

    def __init__(self, config: MambaConfig):
        super().__init__()
        self.d_model = config.d_model
        self.d_state = config.d_state
        self.d_conv = config.d_conv
        d_inner = config.d_model * config.expand

        # 输入投影:分成两路(z, x)
        self.in_proj = nn.Linear(config.d_model, d_inner * 2, bias=False)

        # 1D 卷积:捕捉局部上下文
        self.conv1d = nn.Conv1d(
            in_channels=d_inner,
            out_channels=d_inner,
            kernel_size=config.d_conv,
            padding=config.d_conv - 1,
            groups=d_inner,  # depthwise convolution
            bias=True,
        )

        # 选择性 SSM 参数
        self.B_proj = nn.Linear(d_inner, config.d_state, bias=False)
        self.C_proj = nn.Linear(d_inner, config.d_state, bias=False)
        self.dt_proj = nn.Linear(d_inner, d_inner, bias=True)

        # A 矩阵:对角结构
        A = torch.arange(1, config.d_state + 1, dtype=torch.float32)
        self.A_log = nn.Parameter(
            torch.log(A).unsqueeze(0).expand(d_inner, -1).clone()
        )
        self.D = nn.Parameter(torch.ones(d_inner))

        # 输出投影
        self.out_proj = nn.Linear(d_inner, config.d_model, bias=False)
        self.norm = nn.RMSNorm(config.d_model)

    def _selective_scan(self, x, dt, B, C):
        """选择性扫描(顺序实现)。实际使用 CUDA 内核。"""
        batch, seq_len, d_inner = x.shape
        d_state = B.shape[-1]

        A = -torch.exp(self.A_log)
        dt_A = torch.einsum('bld,dn->bldn', dt, A)
        A_bar = torch.exp(dt_A)
        dt_B_x = torch.einsum('bld,bln->bldn', dt * x, B)

        h = torch.zeros(batch, d_inner, d_state, device=x.device)
        outputs = []
        for t in range(seq_len):
            h = A_bar[:, t] * h + dt_B_x[:, t]
            y_t = torch.einsum('bdn,bn->bd', h, C[:, t])
            outputs.append(y_t)

        y = torch.stack(outputs, dim=1)
        return y

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """Mamba 块的 forward pass。

        Args:
            x: (batch, seq_len, d_model)

        Returns:
            output: (batch, seq_len, d_model)
        """
        residual = x
        x = self.norm(x)

        # 分成两路
        xz = self.in_proj(x)
        x_branch, z = xz.chunk(2, dim=-1)

        # 卷积路径:捕捉局部模式
        x_conv = x_branch.transpose(1, 2)  # (B, D_inner, L)
        x_conv = self.conv1d(x_conv)[:, :, :x.shape[1]]  # causal trim
        x_conv = x_conv.transpose(1, 2)    # (B, L, D_inner)
        x_conv = F.silu(x_conv)

        # 选择性 SSM
        B = self.B_proj(x_conv)
        C = self.C_proj(x_conv)
        dt = F.softplus(self.dt_proj(x_conv))

        y = self._selective_scan(x_conv, dt, B, C)
        y = y + self.D.unsqueeze(0).unsqueeze(0) * x_conv

        # 门控:与 z 支路逐元素相乘
        y = y * F.silu(z)

        # 输出投影 + 残差连接
        output = self.out_proj(y)
        return output + residual

参数效率与架构比较

Mamba 的架构相对于 Transformer 有几处结构性差异。Transformer 有 Multi-Head Attention 和 FFN 两个较大的子层,各自拥有独立的 Layer Normalization 与残差连接。Mamba 则把这些整合进一个块——用卷积捕捉局部模式,用选择性 SSM 建模全局依赖,用门控控制信息流动。

如果说 Transformer 的 Attention 是显式计算所有 token 对之间关系的"全局参照(global attention)"方式,那么 Mamba 的选择性 SSM 就是把信息累积进压缩状态向量的"压缩记忆(compressed memory)"方式。这种差异正是二者在序列长度复杂度上产生差别的根本原因。

在相同参数量(约 2.8B)的条件下,Mamba 使用的 FLOPs 比 Transformer 少约 1.5 倍。这是因为 Attention 的 O(L2)O(L^2) 运算消失了,取而代之的是与 FFN 计算量相近的选择性 SSM。

Mamba-2 与 SSD 框架

State Space Duality 的发现

Mamba-2(Dao & Gu, 2024)最重要的理论贡献是发现了 State Space Duality(SSD)。论文证明了 SSM 与 Attention 表面上看似截然不同的运算,在数学上却属于同一个结构化矩阵(structured matrix)族群。

具体来说,选择性 SSM 的输出可以表示为如下的矩阵-向量乘法。

y=Mxy = Mx

这里 MM 是 semi-separable 矩阵。该矩阵的 (i,j)(i, j) 元素如下。

Mij={CiAˉi:jBjif ij0if i<jM_{ij} = \begin{cases} C_i^\top \bar{A}_{i:j} B_j & \text{if } i \geq j \\ 0 & \text{if } i < j \end{cases}

这里 Aˉi:j=AˉiAˉi1Aˉj+1\bar{A}_{i:j} = \bar{A}_i \bar{A}_{i-1} \cdots \bar{A}_{j+1} 表示从时刻 jjii 的累积转移。该矩阵具有下三角(lower triangular)结构,这与 Causal Attention 中被掩码的 Attention 矩阵结构完全相同。

从 Attention 矩阵 softmax(QK/d)\text{softmax}(QK^\top / \sqrt{d}) 中去掉 softmax,就得到 QKQK^\top,这同样是一个 rank-dd 的 semi-separable 矩阵。SSD 把这层联系形式化,说明了 SSM 与 Linear Attention 共享同一种矩阵代数结构。

SSD 算法与分块

SSD 的实用意义在于让新的算法设计成为可能。Mamba-2 把序列切分成固定大小的块(chunk),在块内部用矩阵乘法(attention-like 运算)并行处理,块与块之间则用 SSM 递归传递状态,是一种混合算法。

import torch
import torch.nn as nn
import torch.nn.functional as F

class SSDBlock(nn.Module):
    """Mamba-2 的 Structured State Space Duality(SSD)块。

    把序列切分成多个块,块内部用矩阵乘法(并行)处理,
    块之间用 SSM 递归处理的混合算法。
    """

    def __init__(
        self,
        d_model: int = 768,
        d_state: int = 128,
        n_heads: int = 8,
        chunk_size: int = 256,
    ):
        super().__init__()
        self.d_model = d_model
        self.d_state = d_state
        self.n_heads = n_heads
        self.chunk_size = chunk_size
        self.head_dim = d_model // n_heads

        # 多头结构:Q(C)、K(B)、V(x) 投影
        self.q_proj = nn.Linear(d_model, d_model, bias=False)
        self.k_proj = nn.Linear(d_model, d_state * n_heads, bias=False)
        self.v_proj = nn.Linear(d_model, d_model, bias=False)
        self.dt_proj = nn.Linear(d_model, n_heads, bias=True)

        # A 矩阵(按头的标量)
        self.A_log = nn.Parameter(torch.log(torch.arange(1, n_heads + 1, dtype=torch.float32)))

        self.out_proj = nn.Linear(d_model, d_model, bias=False)
        self.norm = nn.RMSNorm(d_model)

    def _chunk_scan(
        self,
        Q: torch.Tensor,
        K: torch.Tensor,
        V: torch.Tensor,
        dt: torch.Tensor,
    ) -> torch.Tensor:
        """基于分块的 SSD 扫描。

        Args:
            Q: (batch, n_heads, seq_len, head_dim)  -- 承担 C 的角色
            K: (batch, n_heads, seq_len, d_state)   -- 承担 B 的角色
            V: (batch, n_heads, seq_len, head_dim)  -- 承担 x 的角色
            dt: (batch, n_heads, seq_len)            -- 承担 delta 的角色

        Returns:
            y: (batch, n_heads, seq_len, head_dim)
        """
        B, H, L, D = Q.shape
        C = self.chunk_size

        # 把序列切分成块
        n_chunks = (L + C - 1) // C
        pad_len = n_chunks * C - L
        if pad_len > 0:
            Q = F.pad(Q, (0, 0, 0, pad_len))
            K = F.pad(K, (0, 0, 0, pad_len))
            V = F.pad(V, (0, 0, 0, pad_len))
            dt = F.pad(dt, (0, pad_len))

        # 按块 reshape
        Q = Q.view(B, H, n_chunks, C, D)
        K = K.view(B, H, n_chunks, C, -1)
        V = V.view(B, H, n_chunks, C, D)
        dt = dt.view(B, H, n_chunks, C)

        # 块内部:attention-like 矩阵乘法(可并行处理)
        # 在块内部计算 Q 与 K 的相似度矩阵
        # 生成带 decay 的 causal mask
        A = -torch.exp(self.A_log)  # (n_heads,)
        # 计算累积衰减率
        dt_cumsum = dt.cumsum(dim=-1)  # (B, H, n_chunks, C)

        # 块内部 attention 矩阵
        # M[i,j] = exp(A * (dt_cumsum[i] - dt_cumsum[j])) for i >= j
        decay_diff = dt_cumsum.unsqueeze(-1) - dt_cumsum.unsqueeze(-2)  # (B,H,nc,C,C)
        decay = torch.exp(A.view(1, H, 1, 1, 1) * decay_diff)
        causal_mask = torch.tril(torch.ones(C, C, device=Q.device))
        decay = decay * causal_mask

        # 计算块内部输出
        attn = torch.einsum('bhncqd,bhnckd->bhncqk', Q, K) * decay
        y_intra = torch.einsum('bhncqk,bhnckd->bhncqd', attn, V)

        # 最终 reshape
        y = y_intra.view(B, H, n_chunks * C, D)
        return y[:, :, :L]

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """SSD 块的 forward pass。"""
        residual = x
        x = self.norm(x)
        B, L, D = x.shape

        # 多头投影
        Q = self.q_proj(x).view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
        K = self.k_proj(x).view(B, L, self.n_heads, self.d_state).transpose(1, 2)
        V = self.v_proj(x).view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
        dt = F.softplus(self.dt_proj(x)).transpose(1, 2)  # (B, H, L)

        # SSD 扫描
        y = self._chunk_scan(Q, K, V, dt)

        # 输出投影
        y = y.transpose(1, 2).contiguous().view(B, L, D)
        return self.out_proj(y) + residual

Mamba-2 的结构性改进

Mamba-2 相对于 Mamba-1 引入了如下结构性变更。

第一,引入多头结构。Mamba-1 对每个通道分别应用独立的 SSM,而 Mamba-2 使用类似 Attention 的头结构来共享参数,从而能在相同计算量下利用更大的状态维度。

第二,A 矩阵的简化。Mamba-1 使用按通道的对角 A 矩阵,Mamba-2 则进一步简化为按头的标量值。正是这一简化,成为 SSD 矩阵分解得以成立的关键条件。

第三,基于分块的并行算法。把序列切分成固定大小的块,块内部用 Tensor Core 加速的矩阵乘法处理,块之间则用 SSM 递归传递状态。这种方式比纯递归快 2-8 倍,能最大限度地利用 GPU 的并行硬件。

与 Transformer 的性能比较

定量基准测试

下面整理了各种序列建模架构的性能比较结果。测试条件为约 2.8B 参数规模,使用相同的训练数据(The Pile,300B token)。

项目Transformer (2.8B)Mamba (2.8B)Mamba-2 (2.8B)RWKV-6 (3B)RetNet (2.7B)
Pile PPL (val)6.736.226.106.857.02
训练吞吐量 (tok/s)基准 (1.0x)1.2x1.8x1.1x1.3x
推理延迟 (1K token)38ms/tok12ms/tok10ms/tok14ms/tok16ms/tok
推理延迟 (16K token)180ms/tok12ms/tok10ms/tok14ms/tok16ms/tok
推理内存 (1K token)2.1 GB0.8 GB0.7 GB0.9 GB1.0 GB
推理内存 (16K)8.5 GB0.8 GB0.7 GB0.9 GB1.0 GB
序列长度扩展KV Cache 线性增长常数常数常数常数
In-Context Learning中等中等-强
注意力模式解读可行(可视化)不可部分可行不可部分可行

这张表中有几点值得关注。第一,Mamba 系列即使序列长度增加,推理延迟和内存也保持为常数。Transformer 在 16K token 时的延迟相比 1K 增长了约 4.7 倍,而 Mamba 保持不变。第二,Mamba-2 的训练吞吐量比 Mamba-1 快 1.5 倍,这是 SSD 算法基于分块并行化的效果。第三,在 In-Context Learning 上 Transformer 依然保持优势,这是 SSM 系模型的核心弱点之一。

按序列长度的扩展性

分析随序列长度变化的性能表现,SSM 的优势会更加明显。Transformer 的 Self-Attention 是 O(L2)O(L^2),序列长度增加 2 倍,计算量就增加 4 倍。即便应用 FlashAttention-2 之类的优化,这个根本性的复杂度也不会改变。相比之下,Mamba 的选择性 SSM 是 O(L)O(L),无论序列长度如何,每个 token 的计算量都保持恒定。

实际的瓶颈出现在约 32K-64K token 区间。在这个区间内,以 A100 80GB GPU 为基准,Transformer 因为 KV Cache 不得不缩小批大小,而 Mamba 可以维持相同的批大小。对于 1M token 以上极端长的序列,Transformer 在实用上已不可行,而 SSM 原则上仍可处理。

多领域应用

视觉(Vision):Vision Mamba 与 VMamba

将 SSM 应用于视觉任务的研究相当活跃。Vision Mamba(Vim, Zhu et al., 2024)用双向(bidirectional)SSM 取代了 ViT(Vision Transformer)的 Self-Attention。它对图像块做正向和反向扫描来捕捉双向上下文,在 ImageNet 分类任务上以少 2.8 倍的 GPU 内存达到了与 DeiT 相当的准确率。

VMamba(Liu et al., 2024)为了更好地反映 2D 图像的空间结构,提出了 Cross-Scan Module(CSM)。它沿四个方向(左上-右下、右上-左下、左下-右上、右下-左上)扫描图像,让 1D 序列模型也能捕捉 2D 空间信息。

SSM 在视觉领域的核心优势是处理高分辨率图像。ViT 的计算量与 patch 数(序列长度)的平方成正比,将分辨率从 224x224 提高到 512x512,计算量会增加约 5 倍。得益于线性复杂度,Vision Mamba 的这一成本增幅仅约 2 倍。

音频与语音

音频信号本质上是长序列。以 16kHz 采样率计算,1 分钟音频就有 960,000 个时间点,直接用 Transformer 处理并不现实。Audio Mamba(Erol et al., 2024)在音频分类任务中取代了 Audio Spectrogram Transformer(AST),在准确率相当的情况下将内存占用降低了 70%。

语音合成(TTS)领域也在尝试应用 Mamba。在长音频序列的条件生成中,Transformer 的 KV Cache 瓶颈相当严重,因此基于 SSM 的解码器更适合实时语音合成。

时间序列预测

时间序列数据是 SSM 最自然适用的领域,因为 SSM 建模连续时间动态系统的数学框架,与时间序列的连续特性十分契合。S-Mamba(Wang et al., 2024)在多变量时间序列预测上表现出与 PatchTST 相当或更优的性能,推理速度则快 3 倍。

SSM 在时间序列预测中的特别优势,是处理不规则时间序列(irregularly-sampled time series)。连续时间 SSM 可以根据观测间隔调整离散化步长 Δ\Delta,因此能够直接处理时间间隔不规则的数据,而无需插值(interpolation)。这在医疗数据、IoT 传感器数据等场景中是很大的优势。

基因组学与蛋白质

DNA 序列是极端长的序列(数百万至数十亿 bp),长距离相互作用在生物学上具有重要意义。Caduceus(Schiff et al., 2024)是基于双向 Mamba 的 DNA 语言模型,把 DNA 的双链结构与反向互补性(reverse complementarity)反映到架构中。相比现有的 DNA Transformer 模型(Nucleotide Transformer、HyenaDNA),它在长距离变异效应预测上表现更优。

局限性与课题

In-Context Learning 的弱点

包括 Mamba 在内的 SSM 系模型最严重的局限,是 In-Context Learning(ICL)能力。Transformer 能够参照提示词中给出的 few-shot 示例来执行新任务,ICL 能力出众,这是因为 Self-Attention 可以直接参照(direct lookup)序列内任意位置。

相反,SSM 把信息压缩进固定大小的状态向量,因此难以精确"检索"提示词中的特定示例。当状态维度 NN 远小于序列长度时(NLN \ll L),信息损失不可避免。Mamba-2 通过大幅扩大状态维度(N=128 以上)部分缓解了这个问题,但仍不及 Transformer 显式的 token 级参照能力。

Retrieval 任务的局限

在 Information Retrieval,也就是从序列中准确提取特定信息的任务上,SSM 相对 Transformer 表现出明显的弱点。例如在 "passkey retrieval" 任务中,要在 10 万 token 的干草堆里找出隐藏的密钥,Mamba 的表现并不稳定。这是因为 SSM 的压缩记忆方式更适合统计性摘要,而非精确的信息保存。

混合架构的兴起

认识到这些局限之后,近期的研究开始探索结合 SSM 与 Attention 的混合架构。Jamba(AI21 Labs, 2024)交替排布 Mamba 层与 Transformer 层,同时确保了 SSM 的效率与 Attention 的 ICL 能力。它在 52B 参数规模上支持 256K 上下文,推理吞吐量是纯 Transformer 的 3 倍。

Zamba(Zyphra, 2024)和 Griffin(De et al., 2024)等也采用了类似的混合方案,这种路线正逐渐成为比纯 SSM 或纯 Transformer 更实用的折中方案。

实战实现指南

官方 Mamba 库的使用

Mamba 的官方实现由 state-spaces/mamba 仓库提供,其中包含 CUDA 内核,因此必须要有 GPU 环境。

# Mamba 安装与基本用法

# 1. 安装 (需要 CUDA 11.8 以上)
pip install mamba-ssm

# 2. 安装 causal-conv1d 依赖
pip install causal-conv1d>=1.2.0

# 3. 使用预训练模型 (Hugging Face)
pip install transformers

# 4. 用预训练 Mamba 模型生成文本
python -c "
from mamba_ssm.models.mixer_seq_simple import MambaLMHeadModel
import torch

# 加载 Mamba-2.8B 模型
model = MambaLMHeadModel.from_pretrained(
    'state-spaces/mamba-2.8b',
    device='cuda',
    dtype=torch.float16,
)
model.eval()

# 分词器 (兼容 GPT-NeoX 分词器)
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('EleutherAI/gpt-neox-20b')

# 文本生成
prompt = 'State Space Models are'
input_ids = tokenizer(prompt, return_tensors='pt').input_ids.to('cuda')
output = model.generate(
    input_ids=input_ids,
    max_length=100,
    temperature=0.7,
    top_p=0.9,
)
print(tokenizer.decode(output[0]))
"

基准测试脚本

下面是定量比较 Mamba 与 Transformer 推理性能的基准测试脚本。

import torch
import time
from dataclasses import dataclass

@dataclass
class BenchmarkResult:
    model_name: str
    seq_len: int
    batch_size: int
    latency_ms: float
    memory_mb: float
    tokens_per_sec: float


def benchmark_inference(
    model: torch.nn.Module,
    model_name: str,
    seq_lengths: list[int],
    batch_size: int = 1,
    n_warmup: int = 5,
    n_measure: int = 20,
    device: str = "cuda",
) -> list[BenchmarkResult]:
    """在不同序列长度下测量推理性能。

    Args:
        model: 待测模型
        model_name: 模型名称 (用于结果展示)
        seq_lengths: 待测的序列长度列表
        batch_size: 批大小
        n_warmup: 预热迭代次数
        n_measure: 测量迭代次数
        device: 运行设备

    Returns:
        按序列长度分组的基准测试结果列表
    """
    model = model.to(device).eval()
    results = []

    for seq_len in seq_lengths:
        input_ids = torch.randint(
            0, 50257, (batch_size, seq_len), device=device
        )

        # 清理 GPU 缓存
        torch.cuda.empty_cache()
        torch.cuda.reset_peak_memory_stats()

        # 预热
        with torch.no_grad():
            for _ in range(n_warmup):
                _ = model(input_ids)
        torch.cuda.synchronize()

        # 测量
        latencies = []
        with torch.no_grad():
            for _ in range(n_measure):
                torch.cuda.synchronize()
                start = time.perf_counter()
                _ = model(input_ids)
                torch.cuda.synchronize()
                end = time.perf_counter()
                latencies.append((end - start) * 1000)

        avg_latency = sum(latencies) / len(latencies)
        peak_memory = torch.cuda.max_memory_allocated() / 1024 / 1024
        tokens_per_sec = (batch_size * seq_len) / (avg_latency / 1000)

        results.append(BenchmarkResult(
            model_name=model_name,
            seq_len=seq_len,
            batch_size=batch_size,
            latency_ms=avg_latency,
            memory_mb=peak_memory,
            tokens_per_sec=tokens_per_sec,
        ))

        print(
            f"[{model_name}] seq_len={seq_len:>6d} | "
            f"latency={avg_latency:>8.2f}ms | "
            f"memory={peak_memory:>8.1f}MB | "
            f"throughput={tokens_per_sec:>10.0f} tok/s"
        )

    return results


def compare_models(results_dict: dict[str, list[BenchmarkResult]]):
    """打印模型间的性能比较表格。"""
    print("\n" + "=" * 80)
    print(f"{'模型':<20} {'序列长度':>10} {'延迟(ms)':>10} "
          f"{'内存(MB)':>12} {'吞吐量(tok/s)':>15}")
    print("=" * 80)

    for model_name, results in results_dict.items():
        for r in results:
            print(
                f"{r.model_name:<20} {r.seq_len:>10d} "
                f"{r.latency_ms:>10.2f} {r.memory_mb:>12.1f} "
                f"{r.tokens_per_sec:>15.0f}"
            )
        print("-" * 80)

运维注意事项

CUDA 内核兼容性

Mamba 的官方 CUDA 内核针对特定 GPU 架构(Ampere 及以上,sm_80+)做了优化。在 V100(sm_70)或更早一代的 GPU 上会使用 fallback 实现,性能会大幅下降。生产部署前必须确认目标 GPU 的 compute capability,并使用 Ampere(A100/A10G)或 Hopper(H100)GPU。

还需要注意 causal-conv1d 包的版本兼容性。mamba-ssm 版本与 causal-conv1d 版本不匹配会导致运行时错误,务必查看官方 README 的兼容性表格。

状态初始化与序列边界

在基于 SSM 的模型推理中,最常见的错误是状态初始化处理不当。批量推理时,如果不同序列的状态相互混合,输出就会被污染。必须在每个序列开始时把状态显式初始化为零向量,流式推理中对话轮次切换时也要重置状态。

在连续对话(multi-turn conversation)中,是否保留上一轮的状态属于设计决策。保留状态能保存之前对话的上下文,但由于状态的信息保存能力比 Transformer 的 KV Cache 更有限,长对话中可能会累积信息损失。

训练时的精度与稳定性

Mamba 的选择性 SSM 对使用 softplus 激活的 Δ\Delta 参数的数值稳定性很敏感。Δ\Delta 过大时 exp(ΔA)\exp(\Delta A) 会收敛到 0,导致梯度消失;Δ\Delta 过小时状态更新过于微弱,训练会停滞。建议在训练初期把 Δ\Delta 的初始化设为 Uniform(4,1)\text{Uniform}(-4, -1),以在对数空间中保证合适的范围。

BF16 训练通常是稳定的,但状态矩阵的指数运算偶尔会出现数值不稳定。在对数空间对 A 做参数化(A=exp(Alog)A = -\exp(A_{\log})),可以同时保证其负值性与稳定性。

服务框架支持现状

vLLM 对 Mamba 模型的服务提供部分支持。PagedAttention 是为基于 Attention 的模型设计的,并不适用于 SSM 模型,但可以利用 vLLM 的调度与批处理基础设施。TensorRT-LLM 支持对 Mamba-1 模型的优化,Mamba-2 则需要单独的插件。

就 Triton 服务而言,把 Mamba 模型转换为 ONNX 后部署是可行的,但由于 CUDA 自定义内核不会包含在 ONNX 计算图中,会产生性能损失。目前直接封装 mamba-ssm 库的自定义服务端仍是最稳妥的选择。

调试清单

下面整理了生产环境中可能出现的问题及应对方法。

  • 输出陷入重复循环时:说明状态固着(stuck)在了某个特定模式上。可以提高 temperature,或加入部分重置状态的逻辑。
  • 长输入下质量急剧下降时:说明状态维度相对序列长度不够大。选择模型时应使用 d_state 足够大的模型(N=64 以上)。
  • 批量推理中各序列结果不一致时:很可能是 padding 处理有误,padding token 的信息混入了状态。可以把 padding token 对应的 Δ\Delta 强制设为 0,阻断状态更新。
  • 训练中间歇性出现 CUDA OOM 时:可能是重新计算(recomputation)策略没有正确生效。可以启用 gradient checkpointing,并缩小批大小。

参考资料

  1. Gu, A., & Dao, T. (2023). Mamba: Linear-Time Sequence Modeling with Selective State Spaces. arXiv preprint arXiv:2312.00752. https://arxiv.org/abs/2312.00752

  2. Dao, T., & Gu, A. (2024). Transformers are SSMs: Generalized Models and Efficient Algorithms Through Structured State Space Duality. arXiv preprint arXiv:2405.21060. https://arxiv.org/abs/2405.21060

  3. Mamba 官方 GitHub 仓库。https://github.com/state-spaces/mamba

  4. Gu, A., Goel, K., & Re, C. (2021). Efficiently Modeling Long Sequences with Structured State Spaces (S4). ICLR 2022. https://arxiv.org/abs/2111.00396

  5. Patro, B. N., & Agneeswaran, V. S. (2024). Mamba-360: Survey of State Space Models as Transformer Alternative for Long Sequence Modelling. arXiv preprint arXiv:2404.16112. https://arxiv.org/abs/2404.16112

  6. Lieber, O. et al. (2024). Jamba: A Hybrid Transformer-Mamba Language Model. arXiv preprint arXiv:2403.19887. https://arxiv.org/abs/2403.19887

  7. Zhu, L. et al. (2024). Vision Mamba: Efficient Visual Representation Learning with Bidirectional State Space Model. ICML 2024. https://arxiv.org/abs/2401.09417

  8. Schiff, Y. et al. (2024). Caduceus: Bi-Directional Equivariant Long-Range DNA Sequence Modeling. ICML 2024. https://arxiv.org/abs/2403.03234

현재 단락 (1/487)

Transformer 架构自 2017 年问世以来,一直统治着自然语言处理、计算机视觉、音频、时间序列等几乎所有序列建模领域。然而 Self-Attention 的二次时间复杂度 $O(L^2)$ ...

작성 글자: 0원문 글자: 21,887작성 단락: 0/487