arXiv'25 | DSA 让 DeepSeek-V3.2 的长上下文 attention 少读一些 KV

arXiv’25 | DSA 让 DeepSeek-V3.2 的长上下文 attention 少读一些 KV

原文:DeepSeek-V3.2: Pushing the Frontier of Open Large Language Models


1. 128K context,为什么还是会慢

模型明明已经支持 128K context,但上下文越长,生成速度还是越慢。尤其是在 decode 阶段,也就是模型逐个生成新 token 的阶段,每多生成一个 token,都要回头读取越来越长的历史。

很多人第一反应是:是不是模型参数太大,矩阵乘法算不动了?但对 decode 来说,常见的瓶颈其实是另一件事:模型要从 GPU 的 HBM 里把历史 KV cache 读出来

KV cache 可以理解成 Transformer 对历史 token 算好的“记忆”。当前 token 生成 query 后,需要拿它和历史的 key 做匹配,再用匹配权重把 value 加权求和。历史越长,理论上就要读越多 key/value。MLA(Multi-head Latent Attention)已经把每个历史位置的 KV 压缩成更小的 latent 表示,减少了单个 KV 的体积;但它没有改变访问数量:如果仍然要看完整历史,128K 个位置还是要一个个扫描。

所以问题自然变成了:当前 query 真正需要看的历史可能只有一小部分,能不能先把这部分找出来?

DeepSeek-V3.2 的 DSA(DeepSeek Sparse Attention)就是围绕这个问题设计的。它不是训练完 dense 模型后再硬剪掉一些连接,而是在继续预训练和 post-training 中都使用稀疏 attention,让模型真正适应“历史不再全部可见”的计算路径。

2. 先用一个小例子看懂 sparse attention

先把模型缩小到一个 toy example。假设当前 query 是第 9 个 token,前面有 8 个历史 token:

历史位置:  1   2   3   4   5   6   7   8
相关性分数: 0.1 0.2 0.8 0.1 0.1 0.7 0.1 0.1

标准 attention 会把 8 个位置全部读进来,经过 softmax 后对 8 个 value 做加权平均。这里位置 3 和 6 的分数最高,直觉上它们可能是当前问题真正需要的证据。

如果我们规定每个 query 只看 2 个历史位置,理想答案就是选出位置 3 和 6。但有两个不能绕过的问题:

  1. 不能先完整计算 8 个位置的 attention,再选 3 和 6。那样已经把最贵的读取和匹配做完了。
  2. 不能永远选最近的两个位置。当前 token 可能正在回答很早之前的定义,重要信息不一定在窗口里。

DSA 的做法是加一个便宜的检索器,先对 8 个位置打分;它预测位置 3 和 6 后,核心 attention 只读取这两个位置的 KV,再做一次精确的 attention。也就是说,检索器负责找,attention 负责算。检索器的分数不是最终模型输出,只是一个“先读谁”的排序。

真实模型里,历史位置可能是 128K 个,保留的不是 2 个,而是 2048 个。问题规模变大了,逻辑没有变。

3. DSA 的两步计算

设当前 query token 的 hidden state 为 $h_t$,历史位置 $s$ 的 hidden state 为 $h_s$。DSA 的 lightning indexer 为每个历史位置计算一个标量分数:

\[I_{t,s}=\sum_{j=1}^{H_I} w^I_{t,j}\,\mathrm{ReLU}\left(q^I_{t,j}\cdot k^I_s\right)\]

把公式翻译成人话:indexer 有少量的 head。第 $j$ 个 head 从当前 query 生成一个向量 $q^I_{t,j}$,从历史 token 生成一个向量 $k^I_s$,两者做点积后经过 ReLU,再由 query 产生的权重 $w^I_{t,j}$ 加权。所有 head 的结果相加,就得到位置 $s$ 的总分数 $I_{t,s}$。

论文选择 ReLU,是因为它适合高吞吐实现;indexer 的 head 数较少,也可以使用 FP8 低精度计算。它的任务很单一:给历史位置排序,不负责 value 聚合。

有了分数以后,选择 top-k:

\[\mathcal I_t=\mathrm{TopK}(I_{t,1},\ldots,I_{t,t-1})\]

然后,核心 attention 只读取集合 $\mathcal I_t$ 对应的 latent KV:

\[u_t=\mathrm{Attn}\left(h_t,\{c_s\mid s\in\mathcal I_t\}\right)\]

这里 $c_s$ 是 MLA 为历史位置保存的 latent key-value。对 DeepSeek-V3.2 的主要设置,$k=2048$。于是 128K context 的一个 query,会先从 128K 个历史位置中做排序,再只把其中 2K 个交给核心 attention。

DSA 注意力架构

对着上图从下往上看:输入 hidden state $h_t$ 分成几条路径。一条路径生成 MLA 的正常 query/key/value 表示;另一条绿色路径生成 indexer 的 query、key 和权重。Indexer 对历史 latent 表示打分,Top-k Selector 选出位置,最后把这些位置的 KV 交给上方的 Multi-Query Attention,得到输出 hidden $u_t$。

4. 为什么要和 MLA 的 MQA 结合

这里容易把几个缩写混在一起。MHA(Multi-Head Attention)是每个 query head 都有自己的 key/value;MQA(Multi-Query Attention)则让多个 query head 共享一份 key/value。MLA 可以在这两种模式之间切换,DeepSeek-V3.1-Terminus 的训练和 prefill 主要使用 MHA,decode 时使用 MQA。

为什么 DSA 选择建立在 MQA 上?还是回到“读数据”的问题。假设有 32 个 query head:

  • 如果每个 head 都单独做检索,就要重复计算 32 份分数、维护 32 个 top-k 集合,还可能重复读取同一位置的 KV;
  • 如果所有 head 共享一份 latent KV,那么 indexer 只需为每个历史位置排序一次,选出的 top-k 可以被所有 head 复用。

因此,MLA 和 DSA 是两个不同层次的优化:MLA 减少每个 KV 的表示大小,DSA 减少需要访问的 KV 数量。 DSA 并不是替代 MLA,而是利用 MLA 的共享 latent 表示,让动态检索变得更划算。

MLA 的 MHA 与 MQA

上图左侧是 MHA,多个 head 各自携带 key/value;右侧是 MQA,多个 query head 共享 latent key/value。DSA 采用右侧这种 decode 形态,核心原因不是名字更简洁,而是同一份 top-k 结果能被多头复用。

5. 训练为什么要分两阶段

到这里还剩一个很大的坑:Top-k 是离散操作。Indexer 一开始还不会排序,如果第一步就让模型只看它选出的 2048 个位置,选错了就意味着重要上下文直接消失,主模型也很难得到稳定的训练信号。

DeepSeek-V3.2 的继续预训练因此分两阶段。这个安排不是训练技巧的装饰,而是由“离散选择会切断信息”这个问题推出来的。

5.1 阶段一:让 indexer 先学会模仿 dense attention

第一阶段保持完整的 dense attention,冻结主模型,只训练 indexer。因为此时主模型仍然看得到所有历史,所以可以把它的 attention 分布当成教师信号。

具体做法是:第 $t$ 个 query 有很多 attention head,每个 head 都会产生一组历史位置分数。论文先把这些 head 的分数在 head 维度上相加,再沿序列维度做 L1 normalization,得到一个位置分布 $p_{t,:}$。这个分布表达的是:在 dense attention 看来,哪些历史位置更重要。

Indexer 的分数经过 softmax 后得到另一组分布,训练目标是让两者的 KL divergence 尽量小:

\[\mathcal L_I=\sum_tD_{KL}\left(p_{t,:}\,\middle\|\,\mathrm{Softmax}(I_{t,:})\right)\]

这里不需要死记 KL 公式。它做的事情就是把 dense attention 的“重要位置排序”交给 indexer 学习。Indexer 不必精确复现每个 attention 权重,只要把真正重要的位置排到前面,Top-k 才有机会覆盖它们。

论文用 1000 steps、每步 16 条 128K 序列完成 warm-up,总计约 2.1B token,learning rate 为 $10^{-3}$。主模型在这个阶段冻结,训练信号只用于校准检索器。

5.2 阶段二:让模型适应只看到 top-k

Indexer 有了基本排序能力后,训练切换到真正的 DSA:每个 query 只选择 2048 个 key-value token,主模型和 indexer 一起继续训练。

这一阶段仍然会对 indexer 做分布对齐,但只在已经选中的集合 $S_t$ 上计算:

\[\mathcal L_I=\sum_tD_{KL}\left(p_{t,S_t}\,\middle\|\,\mathrm{Softmax}(I_{t,S_t})\right)\]

同时有两个容易忽略的实现细节:

  1. Indexer 的输入从主模型计算图中 detach,避免语言模型 loss 反向改变 indexer 的输入路径;
  2. Indexer 只由 $\mathcal L_I$ 更新,主模型只由 language modeling loss 更新。

这样做相当于把“找哪些位置”和“在这些位置上如何建模”拆成两个优化问题,避免两个梯度互相干扰。

阶段二使用 15,000 steps、每步 480 条 128K 序列,总计约 943.7B token,learning rate 为 $7.3\times10^{-6}$。主模型在这段继续预训练中逐渐学会:当只有 top-k 历史可见时,怎样组织表示、完成预测和生成。

这就是 DSA 与“训练完再剪枝”的根本区别。后者是模型从未见过稀疏输入,部署时突然改变计算图;DSA 则先用 dense attention 给检索器提供稳定监督,再让整个模型在 sparse path 上收敛。

6. 复杂度到底降在哪里

这里必须把三个量分开,否则很容易把 $O(L^2)$ 读错。

对完整序列的主 attention。 长度为 $L$ 的序列,dense attention 的累计计算量近似是 $O(L^2)$;如果每个 query 只保留 k 个历史位置,主 attention 变成 $O(Lk)$。当 $k=2048$ 且 $L$ 很大时,后者增长慢得多。

对单个 decode token。 原来的 query 需要扫描 $L$ 个历史位置,主 attention 的工作量近似是 $O(L)$;DSA 的核心 attention 只处理 k 个位置,变成近似 $O(k)$。

Indexer 本身。 它仍然需要为当前 query 和历史位置计算分数,所以论文明确指出 indexer 仍有 $O(L^2)$ 的复杂度。DSA 能够加速,不是因为 indexer 神奇地不用看历史,而是因为 indexer 的 head 数更少、使用低精度,并且计算量明显小于被它替代的 MLA 主 attention;真正昂贵的 KV gather 和多头 attention 则只在 top-k 上执行。

因此,DSA 的实际收益还依赖底层 kernel。Top-k 搜索、索引重排和 gather(按索引收集不连续 KV)都需要专门优化;如果选中的 KV 位置太分散,内存访问效率下降,理论上少算的部分可能被搬运开销抵消。

论文在实际部署的 H800 集群上统计每百万 token 的成本。如下图,V3.1-Terminus 的 prefill 和 decode 成本随 token position 增长更快,V3.2 的曲线在长上下文下更平。图里反映的是论文实现、H800 价格和服务配置下的结果,不是一个可以直接套到所有 GPU 上的固定倍数。

长上下文推理成本

另外,论文对短序列 prefill 特别实现了 masked MHA 来模拟 DSA,因为短上下文时完整矩阵计算可能更适合 GPU,动态检索未必占优。这也提醒我们:稀疏 attention 的收益和序列长度有关,不是打开开关就处处更快。

7. 实验结果说明了什么

DeepSeek-V3.2 的实验大致回答了三类问题。

7.1 稀疏后有没有明显掉点

论文先发布 V3.2-Exp,与 DeepSeek-V3.1-Terminus 做 parity evaluation。两者使用相同的 post-training 策略,ChatbotArena 的 Elo 接近;在若干短上下文和长上下文任务上,也没有观察到明显退化。独立长上下文评测中,V3.2-Exp 在 AA-LCR reasoning 模式下比 V3.1-Terminus 高约 4 分,在 Fiction.liveBench 的多个指标上也更高。

这些结果支持一个比较克制的结论:在论文的训练配方和评测范围内,DSA 没有造成明显的能力崩溃。 但它们不能证明每个任务都完全等价,也不能把所有提升都归因于 attention。V3.2 同时改变了继续预训练、RL 和 reasoning 设置,严格归因仍需要更多单变量消融。

7.2 长上下文成本有没有变化

Figure 3 直接比较了 V3.1-Terminus 和 V3.2 在 H800 上的每百万 token 成本,prefill 和 decode 都画了随 token position 变化的曲线。对于短序列,两者差距有限;随着位置接近 128K,DSA 曲线增长更慢,decode 的差距尤其明显。

这里的 insight 不是“所有场景固定快多少”,而是:当历史长度成为主要成本时,固定的 top-k 给了系统一个不随 context 无限增长的主 attention 工作量。

7.3 能力榜单能不能证明 DSA 有效

论文还报告了 V3.2-Thinking 和 V3.2-Speciale 的数学、代码、工具使用结果。比如 AIME、HMMT 使用 Pass@1,表示单次回答通过的比例,越高越好;Codeforces 使用 rating;工具任务使用 success rate 或 resolved rate。

V3.2 与其他模型的能力对比

V3.2-Speciale 在论文列出的若干竞赛和 benchmark 上取得很高分,甚至达到 IMO、CMO、IOI、ICPC 的金牌级别。但这些结果主要说明最终训练系统具备很强能力,并不能单独证明 DSA 让模型变强。reasoning token 数量、RL 训练、测试时计算预算和评测框架都会影响分数。

8. 把 V3.2 放回发展历程

现在 DeepSeek-V4 已经发布,再看 V3.2,它更像是长上下文效率路线上的一个公开节点。

第一步是 MLA:把历史 KV 压缩成更小的 latent 表示,解决“每个位置太大”的问题。第二步是 DSA:学习当前 query 需要哪些历史位置,解决“位置太多”的问题。第三步是训练流程跟着计算图变化:先用 dense attention 校准 indexer,再用 sparse attention 继续训练主模型。第四步是把同一套 sparse path 带进 post-training,避免 base model 和最终模型使用两套 attention 行为。

这个顺序不是论文作者随意堆了几个模块,而是前一个设计留下了下一个问题:MLA 已经压缩了 KV,但仍要扫描全部历史;动态 top-k 可以少读,但 top-k 不会自动学会;indexer 学会排序后,主模型又必须适应信息被筛选过的输入。

9. 我的 take

如果只记住几个结论,我会记下面这些。

第一,DSA 的核心不是“把 attention 变稀疏”,而是把稀疏选择变成一个可训练的检索问题。 先找再算,才有机会省下读取和聚合成本。

第二,indexer 的训练信号比公式本身更关键。 Dense attention 提供了“哪些位置重要”的教师分布;sparse training 再让模型适应只看 top-k。没有这两个阶段,top-k 只是一个可能丢信息的硬门槛。

第三,复杂度分析必须和硬件实现一起看。 主 attention 的 $O(L^2)\rightarrow O(Lk)$ 是理想化描述,indexer 仍然要扫描历史,top-k 和不连续读取也有成本。最终收益取决于 kernel、HBM 带宽、序列长度和 batch 调度。

第四,模型能力与系统效率要分开归因。 V3.2 的 benchmark 结果说明整套训练方案可行,但 DSA、继续预训练、RL 和 test-time compute 的贡献不能混成一个数字。

所以,回头看 V3.2,比较准确的说法是:它把“压缩 KV、学习检索、原生训练稀疏路径”串成了一套完整方案,并在长上下文成本曲线上展示了收益;至于这种动态稀疏在 V4 以及后续模型中会采用什么具体形态,还要继续看真实硬件、任务分布和质量要求。


扯一句题外话:DSA 直接对应的是推理系统问题,书里不会替你讲完 indexer kernel 或 KV cache 调度;但《动手学 AutoML:从 NAS 到大语言模型优化实战》第 8、11 章整理了 LLM 的剪枝、量化和模型压缩方法。它们和 DSA 的切入点不同,目标却都是在模型质量和计算/存储成本之间找可验证的折中。

动手学AutoML书籍封面

Flag Counter