跳转至

Sparse 路线:softmax + 跳过部分 (q,k) 对

更新日期:2026-04-26

核心原理:仍用 \(\text{softmax}(QK^\top) V\),但不让所有 query 看所有 K/V。用 mask(静态)或 indexer(动态)决定哪些 (q, k) 对参与 softmax。

Full 路线 K/V 压缩相比:

  • Full表示(K/V dim 变小)→ 信息密度高但仍全 attend
  • Sparse计算(部分 (q, k) 对置零)→ 计算量降但 attention 矩阵稀疏

主要变体一览:

变体 稀疏 pattern 选择方式 复杂度 代表
Sliding Window 只看前 W 个 token 静态 mask \(O(N W)\) Mistral 7B (W=4096)
Block Sparse 块对角 + 全局 静态 mask \(O(N \sqrt{N})\) BigBird, Longformer
Strided / Dilated 跳格子 静态 mask \(O(N \log N)\) Sparse Transformer
DSA (V3.2) 每 query 选 top-k 动态(lightning indexer) \(O(N \cdot d_\text{idx} + k d_h)\) DeepSeek-V3.2
CSA (V4) 序列 4× 压缩 + DSA 动态 + 压缩 \(O((N/m) d_\text{idx} + k d_h)\) DeepSeek-V4
Native Sparse Attn 学习的 sparse pattern 动态 varies DeepSeek NSA / Kimi K2

FlashAttention 严格说不是 sparse——它是 dense \(O(N^2)\) 计算的 IO-aware tiling 优化(fuse softmax + matmul,避免 attention matrix 写到 HBM)。混进 sparse 表只会让人误解,详见本文 §五 IO 优化章节。



一、为什么需要 Sparse Attention

1.1 Full Attention 的 O(n²) 代价

标准 Self-Attention 的核心运算:

\(\text{Attn}(Q,K,V) = \text{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right) V\)

其中 \(Q, K, V \in \mathbb{R}^{n \times d}\)\(n\) 为序列长度,\(d\) 为头维度。

计算复杂度\(QK^\top\) 的矩阵乘法为 \(O(n^2 d)\),softmax 为 \(O(n^2)\),乘 \(V\) 再一次 \(O(n^2 d)\)。总计 \(O(n^2 d)\) FLOPs per head per layer。

存储复杂度:注意力矩阵 \(S = QK^\top / \sqrt{d_k}\) 需要存储 \(n^2\) 个元素。

1.2 具体数字:128K context + 70B model

以 LLaMA-3 70B 为参考(\(H=64\), \(d_h=128\), \(L=80\) layers, GQA \(n_{kv}=8\)): 关键观察:

  • 在 4K 时 FFN 计算远大于 Attention(compute-bound 在 FFN)。

  • 在 128K 时 Attention 的 \(O(n^2)\) 开销反超 FFN,成为瓶颈。交叉点大约在 \(n \approx 8d_{model} \approx 65K\)

  • 128K 的注意力矩阵需要 2 TB 显存——任何硬件都无法存储;即使用 FlashAttention 不存储完整矩阵,计算量也是 4K 的 1024 倍。

1.3 瓶颈分解


二、Sparse Attention 全景

重要区分:FlashAttention 和 SageAttention 不改变注意力的数学语义(仍是全 \(O(n^2)\) attention),但通过硬件层面优化大幅加速。它们与 Sparse Attention 正交且可组合——例如 FlashAttention 已原生支持 Sliding Window mask。


三、Sliding Window Attention

SWA mask pattern: 每个 q 只看前 W 个 k

3.1 核心思想

每个 token \(i\) 只关注位置 \([i-w, i]\) 内的 token(causal 情况下只看左侧),\(w\) 为窗口大小。

\(\text{Attn}_i = \text{softmax}\!\left(\frac{q_i \cdot K_{[i-w:i]}^\top}{\sqrt{d_k}}\right) V_{[i-w:i]}\)

3.2 Mistral 的设计选择

Mistral 7B (Jiang et al., 2023) 采用 \(w=4096\) 的 sliding window,配合 32 层 Transformer。

有效感受野:第 \(l\) 层的 token 可通过逐层传递访问 \(l \times w\) 距离的信息。32 层 × 4096 窗口 = 理论上 131,072 token 的有效范围。

3.3 与 RoPE 的交互

RoPE 为位置 \(m\) 的向量施加旋转 \(R_{\Theta,m}\),使内积 \(\langle R_{\Theta,m} q, R_{\Theta,n} k \rangle\) 仅依赖相对位置 \(m-n\)

在 Sliding Window 中:

  • 窗口内 token 的相对位置 \(|m-n| \leq w\),RoPE 编码值在正常范围内。

  • 窗口外 token 不参与计算,不存在 RoPE 外推问题。

  • 多层传递的信息并非通过注意力分数的位置编码传递,而是通过隐状态向量——因此不受 RoPE 频率衰减影响。

3.4 KV Cache 节省

3.5 实现伪代码

class SlidingWindowAttention:
    def __init__(self, d_model, n_heads, n_kv_heads, window_size):
        self.window_size = window_size
        self.n_heads = n_heads
        self.n_kv_heads = n_kv_heads
        self.d_head = d_model // n_heads
        self.group_size = n_heads // n_kv_heads

        self.wq = Linear(d_model, n_heads * self.d_head)
        self.wk = Linear(d_model, n_kv_heads * self.d_head)
        self.wv = Linear(d_model, n_kv_heads * self.d_head)
        self.wo = Linear(n_heads * self.d_head, d_model)

    def forward(self, x, freqs_cis, kv_cache=None):
        B, S, _ = x.shape
        Q = self.wq(x).view(B, S, self.n_heads, self.d_head)
        K = self.wk(x).view(B, S, self.n_kv_heads, self.d_head)
        V = self.wv(x).view(B, S, self.n_kv_heads, self.d_head)

        Q, K = apply_rotary_emb(Q, K, freqs_cis)

        if kv_cache is not None:
            K, V = kv_cache.update(K, V)
            K, V = kv_cache.get_last(self.window_size)  # 只取最近 w 个

        K = K.repeat_interleave(self.group_size, dim=2)
        V = V.repeat_interleave(self.group_size, dim=2)

        scores = torch.einsum('bshd,bthd->bhst', Q, K) / math.sqrt(self.d_head)

        mask = build_sliding_window_mask(S, K.shape[1], self.window_size)
        scores = scores.masked_fill(~mask, float('-inf'))

        attn = F.softmax(scores, dim=-1)
        out = torch.einsum('bhst,bthd->bshd', attn, V)
        return self.wo(out.reshape(B, S, -1))


class RollingKVCache:
    def __init__(self, max_size, n_kv_heads, d_head):
        self.max_size = max_size
        self.buffer_k = torch.zeros(1, max_size, n_kv_heads, d_head)
        self.buffer_v = torch.zeros(1, max_size, n_kv_heads, d_head)
        self.pos = 0

    def update(self, k_new, v_new):
        seq_len = k_new.shape[1]
        indices = (torch.arange(seq_len) + self.pos) % self.max_size
        self.buffer_k[:, indices] = k_new
        self.buffer_v[:, indices] = v_new
        self.pos = (self.pos + seq_len) % self.max_size
        return self.buffer_k, self.buffer_v

    def get_last(self, window_size):
        w = min(window_size, self.max_size)
        indices = [(self.pos - 1 - i) % self.max_size for i in range(w)]
        indices.reverse()
        return self.buffer_k[:, indices], self.buffer_v[:, indices]

四、DSA / CSA — 动态 top-k 选择(DeepSeek V3.2 / V4)

SWA / BigBird 都是静态 mask(compile-time 决定哪些 (q, k) 对算)。DSA / CSA 是动态选择——每个 query 由学习的 indexer 在 runtime 选 top-k 个最相关的 KV。这俩跟 MLA 共生用于 frontier,是 §三 静态 sparse 的进化版。

4.1 DSA (DeepSeek Sparse Attention)

DSA mask pattern: indexer 给每个 q 动态选 top-k 个 k

参考 DeepSeek-V3.2-Exp HF(2025-12)。

每个 query 不 attend 全部 KV,经一个 lightning indexer 选 top-k 个 KV 块。\(O(N^2) \to O(N \cdot k)\)(attend 部分),但 indexer 仍 \(O(N)\) per query——所以是 sub-quadratic 不是 linear。

class DSA(nn.Module):
    """简化版 DeepSeek Sparse Attention. 基于 MLA, 加 lightning indexer 选 top-k."""
    def __init__(self, d_model, n_heads, d_h, d_c, top_k=64):
        super().__init__()
        self.mla = MLA(d_model, n_heads, d_h, d_c)
        self.top_k = top_k
        # lightning indexer: 轻量打分网络(低秩 → 速度快)
        self.indexer_q = nn.Linear(d_model, d_c)
        self.indexer_k = nn.Linear(d_c, d_c)            # 作用在 c_kv 上

    def forward(self, x, c_kv_cache):
        B, S, _ = x.shape
        # 1. lightning indexer 给所有 (q, k) 打分
        q_idx = self.indexer_q(x)                       # [B, S, d_c]
        k_idx = self.indexer_k(c_kv_cache)              # [B, T, d_c]
        scores = q_idx @ k_idx.transpose(-2, -1)        # [B, S, T]   ← 这一步仍 O(N²)
        topk_idx = scores.topk(self.top_k, dim=-1).indices  # [B, S, top_k]

        # 2. 收集 top-k 对应的 c_kv(gather)
        c_kv_topk = torch.gather(
            c_kv_cache.unsqueeze(1).expand(-1, S, -1, -1),
            2,
            topk_idx.unsqueeze(-1).expand(-1, -1, -1, c_kv_cache.size(-1)),
        )  # [B, S, top_k, d_c]

        # 3. 在 top-k 子集上跑 MLA attention
        return self.mla.attend_subset(x, c_kv_topk)

为什么是 Sparse 而不是 Full:第 3 步只 attend 到 top-k 个 KV,不是全部 —— 这是 sparse selection 的定义。lightning indexer 用低秩投影让步骤 1 比 full softmax attention 便宜很多(\(d_\text{idx} \ll H d_h\)),但 selection 本身仍是 sparse 操作。

4.2 CSA (Compressed Sparse Attention)

CSA mask pattern: 序列 m=2 压缩 + 块上 top-k 选择

参考 DeepSeek-V4 tech report

DSA 把 indexer 跑在原始序列上仍 \(O(N)\) per query。CSA 先沿序列维 4× 压缩,indexer 跑在压缩后的 \(N/m\) 个块上 → 进一步 sub-linear。

class CSA(nn.Module):
    """每 m=4 个 KV 学习压成 1 个块,再 DSA 选 top-k 块。"""
    def __init__(self, d_model, d_c, m=4, top_k=64):
        super().__init__()
        self.m = m
        # softmax-gated pooling: 块内 m 个 token 加权合成 1 个
        self.gate = nn.Linear(d_c, m)
        self.pos_bias = nn.Parameter(torch.zeros(m, d_c))
        self.dsa = DSA(d_model, n_heads=128, d_h=128, d_c=d_c, top_k=top_k)

    def compress(self, c_kv):
        B, T, d = c_kv.shape
        T_blocks = T // self.m
        c_kv_blocks = c_kv[:, :T_blocks * self.m].reshape(B, T_blocks, self.m, d)
        gate_logits = self.gate(c_kv_blocks).softmax(dim=-2)
        weighted = c_kv_blocks + self.pos_bias
        compressed = (gate_logits.unsqueeze(-1) * weighted.unsqueeze(-2)).sum(-2).mean(-2)
        return compressed                                       # [B, T/m, d]

    def forward(self, x, c_kv_cache):
        compressed = self.compress(c_kv_cache)                  # 4× 压缩 → N/4 块
        return self.dsa(x, compressed)                          # 在压缩块上 DSA top-k

4.3 V4 = HCA (Full) + CSA (Sparse) 双路径

DeepSeek-V4 真实架构 = CSA 看局部细 + HCA 看全局粗,两路并行输出叠加。1M ctx 实测:

  • KV cache: BF16 GQA8 baseline 的 2%(即 50× 压缩)
  • 单 token inference FLOPs: V3.2 的 27%
  • 整体相比 V3.2:−73% FLOPs + −90% KV memory

V4 同时跨了 Full(HCA)和 Sparse(CSA)两条路线——同 layer 内 hybrid,是 frontier 罕见的"细粒度+粗粒度"双引擎设计。HCA 在 full.md 有详细介绍。


五、SageAttention

5.1 核心观察

Attention score 矩阵 \(S = QK^\top / \sqrt{d_k}\) 在 softmax 之前是平滑的——相邻元素差异小,整体分布近似正态。这意味着 对 Q 和 K 做 INT8 量化产生的误差在 softmax 后被大幅压缩

直觉:如果 \(S_{ij}\)\(\hat{S}_{ij}\)(量化后的近似值)差距 \(\epsilon\),softmax 的输出差距约为 \(O(\epsilon \cdot \sigma^2)\),其中 \(\sigma^2\) 是 attention score 的方差。对于平滑分布 \(\sigma^2\) 小,误差被抑制。

5.2 SageAttention v1

论文:SageAttention: Accurate 8-Bit Attention for Plug-and-play Inference Acceleration (Zhang et al., 2024)

核心方法:

  1. Per-warp 量化 Q, K → INT8:对每个 warp 负责的 tile 独立计算 scale,最大化量化精度。

  2. INT8 Tensor Core 计算 \(QK^\top\):利用 INT8 GEMM 的 2x 吞吐量(相比 FP16)。

  3. 反量化回 FP16 → softmax → FP16 matmul with V:V 保持 FP16 精度。

def sage_attention_v1(Q, K, V, block_m=128, block_n=64):
    N, d = Q.shape
    O = torch.zeros_like(V)

    for i in range(0, N, block_m):
        q_block = Q[i:i+block_m]
        q_scale = q_block.abs().amax(dim=-1, keepdim=True) / 127.0
        q_int8 = (q_block / q_scale).round().to(torch.int8)

        row_max = torch.full((block_m,), float('-inf'))
        row_sum = torch.zeros(block_m)
        acc = torch.zeros(block_m, d)

        for j in range(0, N, block_n):
            k_block = K[j:j+block_n]
            k_scale = k_block.abs().amax(dim=-1, keepdim=True) / 127.0
            k_int8 = (k_block / k_scale).round().to(torch.int8)

            s_int32 = q_int8 @ k_int8.T  # INT8 Tensor Core
            s_fp16 = s_int32.float()  (q_scale  k_scale.T) / math.sqrt(d)

            new_max = torch.maximum(row_max, s_fp16.max(dim=-1).values)
            exp_old = torch.exp(row_max - new_max)
            p = torch.exp(s_fp16 - new_max.unsqueeze(-1))

            row_sum = row_sum * exp_old + p.sum(dim=-1)
            acc = acc * exp_old.unsqueeze(-1) + p @ V[j:j+block_n].float()
            row_max = new_max

        O[i:i+block_m] = (acc / row_sum.unsqueeze(-1)).half()

    return O

5.3 SageAttention v2

论文:SageAttention2: Efficient Attention with Thorough Outlier Smoothing and Per-thread INT4 Quantization (Zhang et al., 2024)

5.4 性能数据

精度影响(LLaMA-2 7B, WikiText-2 perplexity): 实践意义:SageAttention 是目前推理加速最 "plug-and-play" 的方案——不改模型结构、不改训练流程,只替换 attention kernel 即可获得 2x+ 加速。与 FlashAttention 正交,实际上 SageAttention 内部使用了 FlashAttention 相同的 online softmax tiling 策略。


六、Sparse 与其他路线的关系

Linear / RNN 路线本身是另一个独立家族,详见 linear.md。Sparse + Linear hybrid 的具体配比方案(Jamba / Nemotron / Kimi Linear / MiniMax M1)在 attention/index.md §五 Hybrid 设计 一处统一讲,避免重复。

本节只点出互补关系

  • Sparse(此处)= 保留 softmax,跳过部分 (q,k) 对,牺牲信息可达性换计算量
  • Linearlinear.md)= 砍 softmax + 固定 state 矩阵,牺牲精确召回换 KV 常数

两者在 frontier 经常同时使用(如 MiniMax-M1 = Lightning Linear + softmax MHA hybrid,softmax 层本身可以再加 Sliding Window mask 进一步省)。

选型决策、跨路线 trade-off、各家 hybrid 比例 → attention/index.md §七 选型决策


七、追问延伸

问题 分析 深度方向
Sliding Window 会导致远距离信息完全丢失吗? 不会。信息通过多层隐状态传递,但会逐层衰减——类似 SSM 的指数遗忘。经验上 LLaMA-3 128K 在 NIAH 任务表现良好,说明 full attention 的多层传递优于 sliding window 量化分析多层传递的信息保留率;与 SSM 的遗忘曲线对比
SageAttention 能用于训练吗? 目前主要用于推理。训练时 backward 需要对量化操作做 STE(Straight-Through Estimator),误差会累积。但 FP8 训练(如 DeepSeek-V3)已经验证了低精度 attention 训练的可行性
FlashAttention 支持哪些稀疏模式? FA⅔ 原生支持 causal mask 和 sliding window mask。自定义稀疏模式需要 FlexAttention (PyTorch 2.5+) 或 Triton 手写 FlexAttention 的 block mask API 允许用 Python 定义任意稀疏模式并 JIT 编译为高效 kernel
为什么 Reformer (LSH) 实际很少被用? LSH 的常数因子大,实际速度在 \(n < 64K\) 时不比 FlashAttention 快;多轮 hash 的准确率-速度 tradeoff 不如固定模式 sliding window 可控;GPU 不擅长动态稀疏 固定模式 vs 数据依赖模式的工程 tradeoff
Native Sparse Attention (NSA, DeepSeek) 是什么? DeepSeek 提出的 learned sparse attention:模型学习每个头/层的最优稀疏模式,而非手工设定。训练时 full attention + Gumbel-softmax 选择;推理时固化为静态模式 NSA (DeepSeek, 2025):结合 compression token + sliding window + selection token 三种模式
Ring Attention 与 Sparse 的关系? Ring Attention 是序列并行策略,不改变注意力模式。可与 Sparse 组合:每个设备只需发送/接收窗口内的 KV block,通信量从 \(O(n)\) 降为 \(O(w)\)

参考文献

  1. Child, R., Gray, S., Radford, A., & Sutskever, I. (2019). Generating Long Sequences with Sparse Transformers. arXiv:1904.10509

  2. Kitaev, N., Kaiser, Ł., & Levine, A. (2020). Reformer: The Efficient Transformer. arXiv:2001.04451

  3. Beltagy, I., Peters, M. E., & Cohan, A. (2020). Longformer: The Long-Document Transformer. arXiv:2004.05150

  4. Zaheer, M., et al. (2020). Big Bird: Transformers for Longer Sequences. arXiv:2007.14062

  5. Dao, T., Fu, D. Y., Ermon, S., Rudra, A., & Ré, C. (2022). FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness. arXiv:2205.14135

  6. Dao, T. (2023). FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning. arXiv:2307.08691

  7. Shah, J., Bikshandi, G., Zhang, Y., Thakkar, V., Ramani, P., & Dao, T. (2024). FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precision. arXiv:2407.08608

  8. Jiang, A. Q., et al. (2023). Mistral 7B. arXiv:2310.06825

  9. Zhang, J., et al. (2024). SageAttention: Accurate 8-Bit Attention for Plug-and-play Inference Acceleration. arXiv:2410.02367

  10. Zhang, J., et al. (2024). SageAttention2: Efficient Attention with Thorough Outlier Smoothing and Per-thread INT4 Quantization. arXiv:2411.10958

  11. Lieber, O., et al. (2024). Jamba: A Hybrid Transformer-Mamba Language Model. arXiv:2403.19887

  12. DeepSeek-AI. (2025). Native Sparse Attention: Hardware-Aligned and Natively Trainable Sparse Attention. arXiv:2502.11089

  13. Milakov, M. & Gimelshein, N. (2018). Online normalizer calculation for softmax. arXiv:1805.02867


上级 · A2 注意力机制全景