对应 Maps to:Stanford CS349D AI Inference Infrastructure(Spring 2026),#11 Mon May 4 Hybrid LLMs。必读:R. Pan, Z. Wang, Z. Jia, C. Karakus, L. Zancato, T. Dao, Y. Wang, R. Netravali, Marconi: Prefix Caching for the Era of Hybrid LLMs(MLSys 2025);DeepSeek-AI, DeepSeek-V3.2: Pushing the Frontier of Open Large Language Models(2025)。选读:A. Gu, T. Dao, Mamba: Linear-Time Sequence Modeling with Selective State Spaces(2023)。 依据说明:原课没有公开 slides。本讲依据 Marconi 全文、DeepSeek-V3.2 技术报告中与架构和推理相关的部分(§2 的 DSA、推理成本、附录 A,以及 §3.2 关于上下文管理的部分;强化学习和评测部分只简述),以及 Mamba 的 §1–§3。§1 的框架、§6 的对比和 §7”之后的发展”是我补充的。讲义里的纸上数字全部来自配套代码的实际运行。 配套代码 Code:
code/lecture10/(numpy;3 个脚本 + 1 个模块hybrid_lab.py:选择性 SSM 与迷你混合模型、7B 规模的账本、混合模型前缀缓存的模拟器、top-k 稀疏注意力与闪电索引器;代码 02 约 40 秒,其余几秒) 预计阅读时间:3–4 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲,你应该能够:
- 说清注意力在长上下文推理中的三笔账(KV 的显存随长度线性增长、decode 每步要读全部 KV、prefill 的计算随长度平方增长),以及绕开它的三条路:缩小 KV、不要 KV、只读一部分 KV。
- 解释状态空间模型(Mamba)的递推、“选择性”为什么重要、同一个递推为什么既能逐 token 计算(decode)又能分块并行计算(prefill),以及它的状态为什么大小固定。
- 解释混合模型(少量注意力层 + 大量 SSM 层)给推理系统带来的好处(KV 小、批大)和麻烦:SSM 状态原地更新、只能精确命中、单个状态很大,前缀缓存因此陷入两难。
- 说清 Marconi 的两个策略:按”复用场景的分类”有选择地准入(只在分叉点和每轮结尾存快照),按”每字节省多少 FLOPs”淘汰;并能判断它们各在什么条件下有用。
- 说清 DeepSeek-V3.2 的稀疏注意力(DSA):闪电索引器 + top-k 选择、用 KL 损失训练索引器、复杂度从 O(L²) 到 O(Lk);知道它省的是”读和算”而不是”存”,以及短上下文时为什么不划算。
- 比较三种架构选择对 KV 管理、前缀缓存、批大小、内核实现的影响。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面再展开 Previewed only |
|---|---|
| 第 1 讲:KV cache、decode 的 memory-bound | 线性注意力、DeltaNet 等其他线性递推的数学 |
| 第 3 讲:前缀缓存、RadixAttention 的基数树与 LRU | 模型训练的细节(只讲推理需要的部分) |
| 第 4 讲:FlashAttention 的分块与 IO、split-KV | GPU 上 scan 内核的实现(前置讲 P1 的存储层级) |
| 第 8 讲:MLA(多头潜注意力)、DeepSeek-V3 的结构 | 强化学习的训练系统(第 15 讲) |
| 第 9 讲:分层 KV 缓存、“每字节省多少计算”的账 |
0.3 本讲地图 Map
§1 为什么要绕开 KV:三笔账、三条路
§2 Mamba:选择性状态空间模型、递推与分块并行、硬件感知的扫描 ← 代码 01 演示 1
§3 混合架构:少量注意力 + 大量 SSM;对推理系统的好处 ← 代码 01 演示 3
§4 Marconi:混合模型的前缀缓存——准入与淘汰 ← 代码 01 演示 2、代码 02
§5 DeepSeek-V3.2:稀疏注意力(DSA)——闪电索引器与 top-k ← 代码 03
§6 三条路的对比
§7 之后的发展
§8–12 小结、常见误区、练习、延伸阅读、下一讲预告
0.4 记号 Notation
| 符号 | 含义 |
|---|---|
| 上下文长度(token 数) | |
| 、 | SSM 的通道数(隐藏维度)、每个通道的状态维度;一层 SSM 的状态有 个数 |
| 选择性 SSM 里随输入变化的步长和投影 | |
| 快照(checkpoint) | 某个位置处 SSM 状态的一份拷贝;混合模型只能从有快照的位置复用前缀 |
| FLOPs 效率 | Marconi:复用一个缓存项能省下的 FLOPs ÷ 它占用的字节 |
| Marconi 淘汰分数里 FLOPs 效率的权重; 即 LRU | |
| DSA 每个 query 选的 token 数(DeepSeek-V3.2:2,048) | |
| 闪电索引器给(query ,历史 token )的分数 |
0.5 关于数字 · About the numbers
本讲的数字有三类:(1) numpy 上的真实计算:选择性 SSM 的两种算法、迷你混合模型的快照复用、top-k 注意力与索引器的训练(代码 01、代码 03 演示 1–2);(2) 账本与纸上 GPU:7B 模型的显存与 FLOPs 按 Marconi 附录 A 的公式(FLOPs 的公式是我写的近似),DSA 的代价用 roofline;(3) 模拟:混合模型前缀缓存在合成的多轮 / agent 负载上的回放。合成负载和真实数据集(ShareGPT、LMSys、SWE-bench)只在长度特征上相似。论文里的数字写成”论文报告”。
1. 为什么要绕开 KV · Why go beyond attention’s KV cache
注意力让每个新 token 去”看”之前的每一个 token,这带来三笔随上下文长度 增长的账:
- 显存:每个 token 在每层留下一份 K、V,KV cache 随 线性增长(第 1、3、9 讲);
- decode 的带宽:生成每个 token 都要把全部 KV 读一遍,每步的代价随 线性增长(第 4 讲);
- prefill 的计算: 个 token 两两之间都要算注意力,计算随 增长。
长上下文、长推理、agent 让 动辄几万到几十万。前几讲已经看到第一条路——缩小 KV:GQA / MQA 让多个 query 头共用 KV 头,MLA 把 KV 压成一个潜向量(第 8 讲),量化把每个数变小(第 9 讲)。这一讲看另外两条:
- 不要 KV:用一个固定大小的状态把历史”压”起来,每个新 token 只更新这个状态——状态空间模型(SSM)、线性注意力等线性递推。实际部署的多是混合模型:大量 SSM 层 + 少量注意力层(§2–§4)。
- 只读一部分 KV:保留完整的 KV,但每个 query 只挑最相关的 个 token 做注意力——稀疏注意力,DeepSeek-V3.2 的 DSA 是代表(§5)。
两条路对推理系统的影响截然不同:前者改变了”状态”的形态(固定大小、原地更新),让前缀缓存、分页这些为 KV 设计的机制都要重想;后者不改变状态,但改变了读状态的方式。
2. Mamba:选择性状态空间模型 · Mamba
2.1 从状态空间模型说起
(结构化)状态空间模型把每个通道的输入 通过一个 维的隐藏状态 映射到输出 :
其中 是连续系统 用步长 离散化得到的(零阶保持:)。它可以看成一个 RNN(逐步递推),也可以展开成一个很长的卷积——参数不随时间变化(线性时不变,LTI)时,整段序列可以用卷积一次算完,训练很高效。
可 LTI 有一个根本的弱点:它对每个 token 一视同仁,无法根据内容决定”记住这个、忽略那个”。Mamba 论文用两个合成任务说明这一点——“选择性复制”(从一串噪声里挑出特定的 token)和”归纳头”(看到 A 就想起上次 A 后面跟着的 B),LTI 模型都做不好;而语言恰恰是这种离散、信息密集、需要按内容检索的数据。
2.2 选择性:让参数随输入变化
Mamba 的改动很简单:让 、、 都成为当前输入的函数(代码里的 init_ssm):
直觉: 大, 接近 0,模型”忘掉过去、专注当前 token”; 小,状态几乎不变,当前 token 被”跳过”。于是模型可以按内容选择性地记住或忽略。代价是:参数随时间变化以后,卷积的技巧不能用了。
2.3 硬件感知的扫描:同一个递推,两种算法
Mamba 的第二个贡献是让”随时间变化”的递推仍然算得快。直接按递推算,要把 大小的展开状态写进显存,太慢。Mamba 用三个经典技巧:算子融合(把 从 HBM 读进 SRAM,离散化和扫描都在 SRAM 里做,只把 的输出写回去)、并行扫描(递推是可结合的,可以用前缀和的方式并行)、重算(反向传播时重新算中间状态而不是存下来)。论文报告这个扫描比以前的方法快最多 3 倍;Mamba 推理时的吞吐是同规模 Transformer 的 5 倍,Mamba-3B 的效果超过同规模的 Transformer、追平两倍规模的 Transformer(论文报告)。
后来的 Mamba-2 把块内的计算写成矩阵乘法(“SSD”):把递推展开,块内第 个状态是块内各 token 贡献的加权和,权重是累积衰减 ( 是 的前缀和)——一个下三角的”衰减矩阵”。代码 01 演示 1 实现了这个分块并行算法(ssm_chunked),并核对它与逐 token 递推的结果相同(差在 量级):
- prefill 用分块并行:块内是矩阵运算,GPU 能并行;块与块之间只传一个状态;
- decode 用递推:每个新 token 只做 的计算、读写一个固定大小的状态。
这就是 SSM 的推理优势:状态大小与已处理的 token 数无关。迷你混合模型里(代码 01 演示 1),3 层 SSM 的状态始终是 6 KB,而注意力层的 KV 从 16 个 token 时的 4 KB 涨到 256 个 token 时的 66 KB。注意:token 少时 SSM 的状态反而比 KV 大——一份状态相当于很多个 token 的 KV。
3. 混合架构 · Hybrid models
纯 SSM 模型在许多任务上不输 Transformer,但在需要强回忆和上下文学习的任务上(例如从长上下文里精确找回某个信息)仍然落后——把全部历史压进一个固定大小的状态,信息必然有损。于是实践中更常见的是混合模型:大量 SSM(或其他线性递推)层,配少量全注意力层,典型比例是每 6–10 个 SSM 层配 1 个注意力层(Marconi §2.1)。同等规模、同样数据训练时,混合模型在很多任务上比 Transformer 更好,同时保留了 SSM 的大部分效率(最多快 8 倍,论文引用的结果)。生产中的例子包括 Jamba(最大的 Jamba 1.5 有 398B 参数)、Zamba、NVIDIA 的 Mamba-2 混合模型等。
对推理系统来说,混合模型最直接的好处是 KV 小。代码 01 演示 3(7B 规模,FP16;混合模型按 Marconi 用的 4 个注意力层 + 24 个 SSM 层 + 28 个 MLP 层):
| 模型 | KV / token | SSM 状态 | 32K 上下文时每序列的状态 | 80 GB GPU(权重 14 GB)能放几个 32K 序列 |
|---|---|---|---|---|
| Transformer 7B(多头注意力) | 524 KB | — | 16.8 GB | 3 |
| 混合 7B(4 注意力 / 24 SSM) | 66 KB | 25 MB | 2.1 GB | 27 |
| Mamba 7B | — | 50 MB | 50 MB | 1,152 |
decode 的批大小由显存决定(第 1 讲),混合模型在长上下文时能把批做大近 10 倍;每个 token 要读的”历史”也少得多。
可 25 MB 的 SSM 状态相当于 384 个 token 的 KV。只要系统想像对待 KV 那样频繁地保存它,账就会变得很难看——这就是 Marconi 要解决的问题。
4. Marconi:混合模型的前缀缓存 · Marconi
4.1 问题:只能精确命中、而且很大的状态
Transformer 的前缀缓存(第 3 讲)之所以灵活,是因为 KV 有”序列”这一维:缓存了 token 1… 的 KV,想复用前缀 1…(),切一刀就行。Marconi 总结了 SSM 状态的三条性质,每一条都和这种灵活性冲突:
- 大小固定,与它代表多少个 token 无关;
- 原地更新,一个序列结束时的状态无法回滚成它某个前缀的状态;
- 比单个 token 的 KV 大几个数量级(一层 SSM 的状态是一个 block 里一层注意力 KV 的 倍,、block = 16 时是 4 倍;而 SSM 层又比注意力层多得多)。
结果是全有或全无的复用:要复用前缀 1…,注意力层需要 1… 的全部 KV,SSM 层需要恰好在第 个 token 处的状态——任何一层缺了,整个前缀都用不上。
代码 01 演示 2 在迷你混合模型上演示了这一点:旧请求处理了 60 个 token,新请求只共享前 40 个。
| 怎样得到前 40 个 token 的状态 | logits 与从头算的最大差 |
|---|---|
| 拿第 60 个 token 的缓存,把 KV 截到 40(SSM 状态没法截) | 9.31(下一个 token 都变了) |
| 在第 40 个 token 处存过快照 | 0 |
| 对照:纯 Transformer,把 KV 截到 40 | 0 |
怎样得到某个位置的快照?Marconi 给了两种办法:利用分块并行算法的块边界(免费,但只能落在块边界上,例如块长 16 时想要第 40 个 token 的状态,只能退到第 32 个);或者分两遍 prefill(先算到那个位置存一份,再算剩下的)。
于是系统陷入两难:想尽量复用任意前缀,就得每隔几个 token 存一份快照;可每份都很大,而且绝大多数永远用不上。论文的度量(论文报告):用 vLLM 的块式缓存扩展到混合模型、块大小 32 时,25.0% 的块的 KV 被后来的请求复用了,SSM 状态只有 0.4% 被复用(差 65 倍);一个 1 万 token 的序列按每 16 个 token 存一份,要 17.4 GB,是同规模 Transformer 的 3.3 倍。代码 01 演示 3 的估算是 16.4 GB(忽略了约 6% 的卷积状态);代码 02 演示 2 在合成负载上量到 KV 的复用率 82%–97%、SSM 状态只有 1.4%–6.6%(我们的多轮负载复用得更多,但两者相差 12–60 倍)。原因很简单:复用一段前缀要用到其中每一块的 KV,却只用到最后一块的状态。
4.2 准入:只存可能被复用的状态
Marconi 的出发点:虽然不知道将来的请求是什么,但前缀复用只有两类场景(作者分析了多个真实数据集和 trace):
- 纯输入的共享前缀:系统提示、指令、few-shot 示例、同一份长文档——通常被很多请求共享;
- 输入 + 输出的共享前缀:多轮对话的历史、agent 之前的交互轨迹——下一轮几乎总是从上一轮最后一个 decode 出来的 token 接着来,很少从中间分叉。
据此,Marconi 用一棵基数树记录请求的历史,只在两种位置存 SSM 状态:
- 每个请求结束时,存最后一个 decode 出来的 token 处的状态(第 2 类场景);
- 新请求 prefill 之前做一次推测插入:如果它的输入会在树中间创建一个新的分叉节点(与已有的路径在某处分开),说明这个位置是多个请求共享的纯输入前缀,就在这次 prefill 时把这里的状态存下来(第 1 类场景)。
每个序列因此最多存两份状态。代价是:纯输入的前缀要到第三次出现才能命中(第二次出现时才发现它、存下快照)。由于这类前缀通常被大量请求共享,少命中一次影响很小。与 SGLang 的区别:SGLang 也用基数树,但它把每个 token 的状态都准入;Marconi 用历史请求和推测插入来决定存哪些。
4.3 淘汰:每字节省多少计算
Transformer 的 KV 大小与它能省的计算大致成正比(都随长度线性增长),所以只看”最近用过没有”(LRU)就够了。混合模型不同:SSM 状态大小固定,前缀越长,同一份状态代表的计算越多。Marconi 定义了 FLOPs 效率:
代码 02 演示 1(单位:千 FLOP / 字节):
| 模型 | 100 token | 1,000 | 2,000 | 8,000 | 32,000 |
|---|---|---|---|---|---|
| Transformer 7B | 25 | 25 | 26 | 29 | 41 |
| 混合 7B | 49 | 170 | 198 | 228 | 248 |
| Mamba 7B | 20 | 198 | 396 | 1,584 | 6,336 |
Transformer 几乎是常数;SSM 层越多,效率随长度涨得越陡——长序列的缓存项”每字节更值钱”。Marconi 的淘汰分数:
两项都在当前所有节点上归一化到 (0, 1),每次淘汰分数最低的节点;子节点的”省下的 FLOPs”按相对父节点计算。 怎么定?Marconi 在第一次淘汰之后进入一个”自举期”(之前请求数的 5–15 倍),期间仍用 LRU、同时记录请求;然后在 CPU 上并行地把这段请求按不同的 回放一遍,选命中率最高的(几秒钟)。 就退回 LRU。
两个实现细节:子节点数 ≤ 1 的节点都可以被淘汰,不只是叶子(有多个孩子的节点是共享前缀,不该淘汰;只有一个孩子的中间节点不太可能再被复用,却占着一份状态)——淘汰中间节点时只释放它的状态,KV 并入孩子;命中时只更新被访问节点的时间戳。论文还对比了经典的按大小加权的淘汰(如 GDSF):KV 的大小可以代表省下的计算,SSM 状态的大小不行,所以要用 FLOPs 效率代替大小。
4.4 评测与我们的复现
论文在 7B 混合模型(4 注意力 / 24 SSM / 28 MLP)上,用 LMSys、ShareGPT 两个多轮对话数据集和 SWE-agent 在 SWE-bench 上的轨迹评测(论文报告):
- 相对 vLLM+(每 32 个 token 存一份),token 命中率平均高 4.5、7.3、34.4 倍;
- 相对 SGLang+(同样的准入,但用 LRU 淘汰),P95 的命中率收益分别为 45.6%、19.0%、219.7%——SWE-bench 的序列长度分布最宽,FLOPs 效率最重要;
- 一条 SWE-bench trace 上:SGLang+ 命中率 16.4%,Marconi 32.7%;Marconi 牺牲了 7K token 以下的短序列(命中率最多低 3.0%),换来 7K 以上长序列最多高 25.5%,省下的 FLOPs 多 90.3%;P5 TTFT 差 6.3%(只有 2.1 ms),P50 / P95 TTFT 低 13.4% / 22.0%;
- 相对不做前缀缓存,P95 TTFT 最多降低 36.9%、73.2%、46.8%(最多 617 ms);
- 缓存竞争适中时收益最大(容量 60 → 140 GB 时,相对 SGLang+ 的收益是 24.3%、51.5%、68.3%、30.0%、10.0%);SSM 层越多、状态维度越大,收益越大(状态维度 16 → 128 时,相对 vLLM+ 从 5.7 倍到 35.4 倍);纯 Transformer 时三者相同。
代码 02 演示 3 在合成的三种负载上复现(token 命中率):
| 负载 | 缓存 | vLLM+ | SGLang+ | Marconi(调出的 α) |
|---|---|---|---|---|
| 短对话 | 5 GB | 0.191 | 0.718 | 0.748(1.0) |
| 长输出对话 | 20 GB | 0.054 | 0.569 | 0.566(1.0) |
| agent | 20 GB | 0.061 | 0.762 | 0.800(1.0) |
| agent | 40 GB | 0.077 | 0.952 | 0.952(0) |
- 有选择地准入是大头:命中率是 vLLM+ 的 1.4–16 倍。vLLM+ 的每一项都背着 25 MB 的状态,缓存里能放的项太少,而且大多没用。
- 按 FLOPs 效率淘汰相对 LRU 的收益小得多:只在竞争激烈时有用(+4%–10%),容量够大时三者相同;用开头 20% 的请求调出的 在长输出对话上反而比 LRU 略差(−0.5%)。我在原型里试过 ,命中率掉了四成以上:FLOPs 效率高的往往是早已结束的长会话,按它淘汰会挤掉正在进行的短会话——这也说明了为什么 Marconi 必须用回放来选 、并允许退回 LRU。
- 模型结构的影响(代码 02 演示 4,短对话 5 GB):注意力 / SSM 为 9 / 19、4 / 24、2 / 26 时,Marconi 相对 vLLM+ 分别是 1.8、3.9、4.6 倍,与论文的趋势一致;状态维度 16、64、128 时是 2.8、4.2、3.9 倍,没有论文那么单调。纯注意力模型时三者几乎相同。
| 项目 | 内容 |
|---|---|
| 论文 | Marconi: Prefix Caching for the Era of Hybrid LLMs,Pan 等,MLSys 2025(Princeton、AWS) |
| 一句话 | 混合模型的 SSM 状态只能精确命中、又很大,所以只在”分叉点”和”每轮结尾”存快照,并按”每字节省多少 FLOPs”和最近使用时间共同决定淘汰谁 |
| 问题 | 为 KV 设计的前缀缓存在混合模型上要么错过复用机会,要么被大量用不上的大状态塞满(缓存颠簸) |
| 关键洞察 | 前缀复用只有”纯输入共享”和”输入 + 输出续接”两类场景,可以据此预判哪些位置值得存;SSM 状态的价值随它代表的前缀长度增长 |
| 机制 | 基数树 + 推测插入;分块状态传递或两遍 prefill 取快照;FLOPs 效率 + 最近性的效用分数;α 的回放调参;子节点 ≤ 1 的节点可淘汰 |
| 评测 | 命中率比 vLLM+ 平均高 4.5–34.4×;P95 TTFT 最多低 71.1%、或最多低 617 ms(不同数据集;论文报告) |
| 局限与假设 | 纯输入前缀要第三次出现才命中;每序列最多两份状态,覆盖不了任意前缀;α 依赖历史能代表未来;评测是模拟回放;我们的复现里 FLOPs 感知淘汰的收益只有几个百分点 |
| 与本课的关系 | 第 3 讲 RadixAttention 在新架构下的重新设计;第 9 讲”每字节省多少计算”的思想用于淘汰 |
5. DeepSeek-V3.2:稀疏注意力 · DeepSeek Sparse Attention
5.1 闪电索引器 + 细粒度的 token 选择
DeepSeek-V3.2 与上一版(V3.1-Terminus)相比,唯一的架构改动是加入 DeepSeek 稀疏注意力(DSA)。它有两个部分:
闪电索引器(lightning indexer)为当前的 query token 和每个历史 token 打分:
是索引头数, 和 来自 query token, 来自历史 token(所有索引头共用一份)。索引头少、维度低,可以用 FP8;激活函数选 ReLU 是为了吞吐。
细粒度的 token 选择:只取索引分数最高的 个历史 token 的 KV,在它们上面做注意力:
为了从 V3.1 继续训练,DSA 建立在 MLA 之上,而且用的是 MLA 的 MQA 模式:每个潜向量(MLA 的 KV 条目)被这个 query token 的所有头共用——内核层面,一个被选中的 KV 条目必须被多个 query 头共享才算得有效率。(V3.1 在训练和 prefill 时用 MLA 的 MHA 模式、decode 时用 MQA 模式,见论文附录 A。)
5.2 训练:让索引器学会”主注意力看哪里”
从 V3.1-Terminus(上下文已扩到 128K)继续预训练,分两个阶段(论文报告):
- 稠密预热:保持稠密注意力,冻结其余参数,只训练索引器。目标分布是主注意力在各头上的分数之和、按序列维 L1 归一化后的 ,损失是 。只训练 1,000 步(每步 16 条 128K 的序列,共 2.1B token)。
- 稀疏训练:打开 top-k 选择(每个 query 选 2,048 个 token),训练全部参数;索引器只在被选中的集合上继续对齐主注意力,而且它的输入从计算图上分离——索引器只从 KL 损失学,主模型只从语言建模损失学。15,000 步,共 943.7B token。
之后的后训练也全程使用稀疏注意力。论文报告 V3.2-Exp 在标准评测、Chatbot Arena 的 Elo 和独立的长上下文评测上都没有比 V3.1-Terminus 退步。
5.3 推理代价
DSA 把主注意力的复杂度从 降到 。索引器仍然是 ,但它的计算量比 MLA 小得多。论文图 3 用部署在 H800 上的真实服务估计了每百万 token 的成本随 token 位置的变化(按每 GPU 小时 2 美元):prefill 和 decode 都是位置越靠后,V3.2 比 V3.1 便宜得越多。对短序列的 prefill,他们专门实现了一个”带掩码的 MHA 模式”来模拟 DSA,因为短上下文时它更高效。
代码 03 演示 3 按 roofline 估算了 DeepSeek-V3.2 结构(61 层,索引器 64 头 × 128 维 FP8, = 2,048)的注意力部分:
| 上下文 | decode 每 token:稠密 MLA | DSA | 加速 | prefill FLOPs 的加速 |
|---|---|---|---|---|
| 1,024 | 26.9 µs | 29.8 µs | 0.90× | — |
| 4,096 | 107 µs | 65.6 µs | 1.64× | 1.18× |
| 32,768 | 859 µs | 149 µs | 5.76× | 4.53× |
| 131,072 | 3.44 ms | 436 µs | 7.89× | 7.64× |
decode 时,稠密注意力每层读全部 个潜向量(1,152 字节),DSA 读全部 个索引 key(128 字节)加上 2,048 个潜向量。上下文不超过约 2,300 个 token 时 DSA 反而更慢(练习 6);越长越划算。prefill 时索引器的 在 128K 时占了 DSA 计算量的大头,但它便宜,总体仍快 7.6 倍。
5.4 我们的实验:注意力要足够集中,索引器要训练
注意力必须集中(代码 03 演示 1)。随机权重的 tiny_llm 的注意力几乎是均匀的(熵 6.5–7.1,上限 7.3),只看 top-32 或 top-128 时输出的相对误差在 0.8–2.0 之间——稀疏注意力完全不可用。在一个注意力集中的合成数据上(低秩的 Q、K),集中度越高,top-k 越准:最集中时前 32 个 token 就占了 92% 的权重,top-128 的误差只有 2%。训练过的模型里,大多数头的注意力确实集中在少数 token 上(开头的汇聚点、最近的 token、语义相关的几处),这是稀疏注意力成立的前提。
索引器必须训练(代码 03 演示 2:2,048 个 key,4 个索引头 × 16 维;在 64 个 query 上训练,在另外 64 个上测试):
| 怎样选 top-32 | 召回率 | 选中的注意力质量 | 输出相对误差 |
|---|---|---|---|
| 理想(用真实分数) | 1.00 | 0.50 | 1.31 |
| 未训练的索引器 | 0.05 | 0.05 | 3.80 |
| 用 KL 训练过的索引器 | 0.92 | 0.50 | 1.31 |
未训练的索引器基本是乱猜;用 DeepSeek 的 KL 损失训练 300 步后,它在没见过的 query 上找回了九成以上的真正 top-k,效果和理想的 top-k 几乎一样。( = 512 时三行分别是:召回 1.00 / 0.28 / 0.96,误差 0.12 / 1.33 / 0.13。)训练时我踩了两个坑:随机初始化的分数太大,初始 KL 几十,普通梯度下降发散——改用小初始化 + Adam + 梯度裁剪;头权重 没有偏置时,零均值的合成 query 会让 变号、把排序整个颠倒,新 query 上的召回率只有 0.27——给 加偏置(模拟真实隐藏状态里的常数分量)后才到 0.92。
5.5 对推理系统意味着什么
- 省的是读和算,不是存:每个 token 的 KV 一个都不能丢——下一个 query 可能选中任何一个——还要多存一份索引 key(每 token 每层 128 字节,FP8),比不用 DSA 多约 11%(代码 03 演示 3)。第 9 讲的显存压力、前缀缓存、分层存储一样都不能少;Helix(第 8 讲)也指出稀疏注意力减少了 KV 的读取、但没有减少容量。
- 访问变得不规则:每个 query 选中的 2,048 个 token 各不相同,内核要按下标去取散落的 KV 条目(分页时还要再查一次块表);用 MQA 模式让一个条目被所有头共享,是为了让这种”收集”仍然划算。
- 短上下文不划算: 小于 附近时 DSA 只多了索引器的开销,所以短序列要退回稠密(或带掩码的)实现。
- 上下文管理也是推理系统的事:V3.2 在工具调用场景里改了”思考内容”的保留规则:只有新的用户消息到来时才丢弃历史的思考内容,只追加工具结果时保留(DeepSeek-R1 的做法是每轮都丢,导致模型反复重新推理)。从前缀缓存的角度看,保留思考内容让下一轮的前缀和上一轮完全一致、可以直接命中;丢弃则会在中间”挖掉”一段,后面的 KV 全部失效。论文还讨论了上下文超过 128K 时的几种处理(总结后重启、丢掉前 75% 的工具调用历史、全部丢掉)。
| 项目 | 内容 |
|---|---|
| 论文 | DeepSeek-V3.2: Pushing the Frontier of Open Large Language Models,DeepSeek-AI,2025 |
| 一句话(本讲关心的部分) | 用一个便宜的、用 KL 损失对齐主注意力的闪电索引器为每个 query 挑 2,048 个 token,主注意力只在它们上面算,长上下文的注意力代价从 O(L²) 降到 O(Lk) |
| 问题 | 稠密注意力让长上下文的部署和后训练(长推理、agent)都很贵 |
| 关键洞察 | 训练好的注意力是集中的;“该看哪里”可以由一个很小的打分器学会 |
| 机制 | 多头 ReLU 索引器(FP8);top-k 选择;建立在 MLA 的 MQA 模式上;稠密预热 + 稀疏训练,索引器单独以 KL 对齐;短序列用带掩码的 MHA 模式 |
| 评测 | 长上下文下推理成本明显下降,效果与 V3.1-Terminus 持平(论文报告) |
| 局限与假设 | 索引器仍是 O(L²);KV 不减反增;短上下文不划算;依赖注意力集中,选错的 token 无法恢复;不规则访问对内核要求高 |
| 与本课的关系 | 与混合模型相反的路线:保留完整的 KV,改变读取方式;KV 管理(第 3、9 讲)不变,内核(第 4 讲)要改 |
6. 三条路的对比 · Comparing the three routes
| 缩小 KV(GQA / MLA / 量化) | 不要 KV(混合 SSM) | 只读一部分 KV(DSA 等稀疏注意力) | |
|---|---|---|---|
| 每序列的状态 | 随 线性增长,但常数小 | 注意力层随 增长 + SSM 层固定大小 | 随 线性增长(还多了索引 key) |
| decode 每 token 读多少 | 全部 KV | 少量注意力层的 KV + 固定的状态 | 全部索引 key + 个 KV |
| prefill 的计算 | 注意力层 ,SSM 层 | 主注意力 + 索引器 (便宜) | |
| 前缀缓存 | 任意前缀可复用(截断 KV) | 只能在快照处精确命中;要决定存哪些快照(Marconi) | 与稠密相同 |
| 分页 / 分层存储 | 照常 | KV 照常;SSM 状态是整块的,不能分页 | 照常,但访问不规则 |
| 质量风险 | 小(训练时决定) | 固定状态有损,回忆类任务弱;靠少量注意力层弥补 | 选错的 token 无法补救;依赖注意力集中 |
这三条路并不互斥:DeepSeek-V3.2 同时用了 MLA(缩小 KV)和 DSA(只读一部分);混合模型里的注意力层也可以用 GQA。对推理系统来说,关键是状态的形态:它是否随长度增长、能否按 token 截断、能否分页、读取是否规则——每一种新的模型结构都会在这几个维度上逼着系统重新设计。
7. 之后的发展 · Since then
(这一节是我的补充,不是原课内容。)
- 更多的混合与线性注意力模型:2025 年出现了多个大规模的混合模型,把门控 DeltaNet、“闪电注意力”等线性注意力层与少量全注意力层按固定比例混合(例如 Qwen3-Next、Kimi Linear、MiniMax 的模型、NVIDIA 的 Nemotron-H),线性层的比例越来越高——正是 Marconi 演示 4 里”优势越大”的方向。
- 推理框架对混合模型的支持:vLLM、SGLang 等加入了混合模型的状态管理(KV 与 SSM 状态分开分配、为 SSM 状态做前缀缓存),Marconi 式的”有选择地存快照”成为常见的做法。
- 稀疏注意力的路线分化:NSA(原生稀疏注意力,按块选择、可训练)、DSA(按 token 选择、索引器单独对齐)等让”只读一部分 KV”进入了预训练;配套的内核(按下标收集 KV、与分页结合)成为新的工程重点。
- 上下文管理进入模型训练:长推理和 agent 让”哪些内容留在上下文里”变成训练和服务共同决定的事情(V3.2 的思考保留规则就是一例),与第 9 讲的 KV 缓存、第 14 讲的记忆交织在一起。
8. 小结 · Summary
- 注意力的三笔账(KV 显存、decode 带宽、prefill 计算)都随上下文增长;绕开它有三条路:缩小 KV、不要 KV、只读一部分 KV。
- Mamba:选择性 SSM 让 随输入变化,从而能按内容记住或忽略;同一个递推既能逐 token 算(decode,固定大小的状态),也能分块并行算(prefill),两者结果相同(代码 01)。
- 混合模型用少量注意力层弥补 SSM 的回忆能力,KV 小一个数量级、长上下文的批大近 10 倍;但一份 SSM 状态相当于几百个 token 的 KV,而且原地更新、只能精确命中——KV 可以截断,状态不能(代码 01 演示 2)。
- Marconi:只在分叉点(推测插入发现)和每轮结尾存快照,按”最近性 + α × FLOPs 效率”淘汰,α 用回放调参。我们的复现里,有选择地准入让命中率比细粒度快照高 1.4–16 倍;FLOPs 感知淘汰相对 LRU 只多 0%–10%,α 选错还会更差(代码 02)。
- DSA:闪电索引器(多头 ReLU、FP8)为每个 query 挑 2,048 个 token;索引器用 KL 损失对齐主注意力。它依赖注意力集中(随机模型上不可用)、索引器必须训练(召回率 0.05 → 0.92);长上下文时注意力代价降 7–8 倍,短上下文反而更慢;KV 不减反增约 11%(代码 03)。
- 新架构改变的是”状态的形态”——是否增长、能否截断、能否分页、读取是否规则——推理系统要随之重新设计缓存、调度和内核。
9. 常见误区 · Common pitfalls
- “SSM 的状态总是比 KV 小。” 一份状态大小固定,但相当于很多个 token 的 KV(混合 7B 里是 384 个);短序列时它反而更大,频繁存快照时总量远超 Transformer(代码 01 演示 1、3)。
- “混合模型的前缀缓存和 Transformer 一样,复用最长的公共前缀就行。” SSM 状态无法截断,只能在存过快照的位置精确命中(代码 01 演示 2)。
- “快照存得越密,命中越多,越好。” 每份快照都很大,绝大多数用不上(复用率比 KV 低一两个数量级),会把有用的缓存项挤出去(代码 02 演示 2–3)。
- “Marconi 的收益主要来自 FLOPs 感知的淘汰。” 在我们的复现里,大头来自有选择的准入;FLOPs 感知的淘汰只在竞争激烈时多几个百分点,α 太大还有害(代码 02 演示 3)。
- “稀疏注意力省显存。” DSA 要保留全部 KV,还多一份索引 key;它省的是读取和计算(代码 03 演示 3)。
- “top-k 稀疏注意力在任何模型上都能直接用。” 它依赖训练出来的注意力集中;在注意力接近均匀的模型上误差很大(代码 03 演示 1)。
- “有了索引器就不用训练了,随便投影一下就能挑出重要的 token。” 未训练的索引器召回率和随机差不多;DSA 用 KL 损失专门训练它(代码 03 演示 2)。
- “上下文越长越该用稀疏注意力,越短越无所谓。” 短上下文时 DSA 比稠密还慢( 小于约 2,300 时 decode 读的字节更多),要退回稠密实现(代码 03 演示 3)。
10. 练习 · Exercises
练习 1、4、5、6、7 的参考答案里的数字是用
hybrid_lab.py的函数实际算出来的。
练习 1(状态 vs KV 的分界)。混合 7B:4 个注意力层(多头注意力,每层每 token 的 KV 是 2 × 32 × 128 × 2 字节),24 个 SSM 层(每层状态 4096 × 128 × 2 字节)。(a) 一份完整的 SSM 状态(所有 SSM 层)相当于多少个 token 的 KV(所有注意力层)?(b) 一个序列多长时,它的 KV 才和 SSM 状态一样大?
参考答案
(a) 每 token 的 KV:4 × 16,384 = 65,536 字节;SSM 状态:24 × 1,048,576 = 25,165,824 字节;相当于 25,165,824 / 65,536 = 384 个 token。(b) 384 个 token。序列短于 384 时,SSM 状态是”大头”;更长时 KV 才是。所以给一个只有几百 token 的会话存一份快照,代价相当于把它的 KV 存一倍以上。
练习 2(全有或全无)。一个混合模型有 1 个注意力层和 3 个 SSM 层。缓存里有某个旧请求在第 512 个 token 处的完整状态(KV + SSM 快照),以及它第 1–1024 个 token 的 KV。新请求与旧请求共享前 800 个 token。(a) 最多能复用多少个 token?(b) 如果是纯 Transformer 呢?(c) 如果旧请求是用块长 256 的分块并行算法 prefill 的、并且保存了每个块边界的状态呢?
参考答案
(a) 512 个:SSM 层只有第 512 个 token 处的状态,第 800 个 token 处没有快照,虽然 KV 一直到 1024 都有,也只能用到 512。(b) 800 个:截断 KV 即可。(c) 块边界在 256、512、768、1024,最长不超过 800 的是 768,可以复用 768 个(损失 32 个),前提是这些块边界的状态都被存下来了——而这正是”存多少快照”的两难。
练习 3(Marconi 的准入)。所有请求共用一个系统提示 S。依次到来:请求 1 = S + 文档 D + 问题 q1;请求 2 = S + D + q2;请求 3 = S + D + q3;请求 4 = 请求 1 的输入 + 回答 a1 + 追问 f1。按 Marconi 的准入规则,每个请求到来时能命中多少?会新存哪些状态?
参考答案(要点)
请求 1:树是空的,不命中;结束时存 S + D + q1 + a1 末尾的状态。请求 2:推测插入发现它在 D 之后与请求 1 分叉,D 的末尾成为新的分叉点,在这次 prefill 时存下 S + D 末尾的状态;但这次本身还用不上(快照是这次才算出来的),只能重算 S + D(如果 S 末尾之前也成了分叉点并存过,就能命中 S)。结束时存 S + D + q2 + a2 末尾的状态。请求 3:命中 S + D(第三次出现才命中,Marconi §4.1 的”代价”)。请求 4:它的前缀正是请求 1 的全部输入 + 输出,命中到 a1 末尾(输入 + 输出续接的场景),只需算 f1。
练习 4(FLOPs 效率)。混合 7B 上,缓存里有两个最近使用时间相同的叶子:A 代表一段 500 token 的前缀,B 代表 8,000 token。(a) 复用各能省多少 FLOPs?各占多少字节?(b) 只能留一个,留哪个?LRU 会怎么选?
参考答案
(a) A:约 7.7 TFLOP,占 500 × 65,536 + 25,165,824 ≈ 57.9 MB,效率约 133K FLOP/字节;B:约 125.2 TFLOP,占约 549.5 MB,效率约 228K FLOP/字节。(b) 按 FLOPs 效率应留 B:每字节省的计算多 70%。LRU 在时间相同时无从区分(或按别的规则任选)。但如果 A 属于一个正在进行的会话、B 属于一个已经结束的会话,留 B 就错了——这就是 α 要和最近性一起权衡、并用历史回放来选的原因。
练习 5(细粒度快照的代价)。混合 7B,一个 1 万 token 的序列。(a) 每 32 个 token 存一份快照,一共要多少字节?(b) Marconi 每序列最多存两份呢?
参考答案
(a) 312 份状态 × 25.2 MB ≈ 7.85 GB,加上 KV 0.66 GB,约 8.5 GB。(b) 0.66 GB 的 KV + 2 × 25.2 MB ≈ 0.71 GB,小了约 12 倍。省下的空间可以缓存十几个别的会话。
练习 6(DSA 的分界点)。DeepSeek-V3.2 decode 时,每层每个历史 token 的 MLA 潜向量是 1,152 字节,索引 key 是 128 字节, = 2,048。只看读取的字节,上下文多长时 DSA 开始比稠密注意力省?
参考答案
稠密读 ,DSA 读 ()。令 ,得 。代码 03 演示 3 的表也显示 2,048 时 DSA 慢 10%、4,096 时快 1.64 倍。
练习 7(DSA 的 prefill)。用代码 03 的模型,64K 的 prefill:稠密注意力约 10.7 PFLOP,DSA 主注意力约 0.66 PFLOP、索引器约 2.15 PFLOP(FP8,按算力翻倍折算成 1.07)。(a) 加速多少?(b) 序列再长下去,加速会无限增大吗?
参考答案
(a) 10.7 / (0.66 + 1.07) ≈ 6.2 倍。(b) 不会。主注意力是 ,相对稠密的 越来越小;但索引器也是 ,稠密与索引器的比值趋于常数:每个 (query, key) 对,稠密 MLA(MHA 模式)约 81,920 次运算,索引器 16,384 次、FP8 折半后约 8,192 次,上限约 10 倍。要突破这个上限,索引器本身也得稀疏化或分块。
练习 8(思考:思考内容与前缀缓存)。一个 agent 用 DeepSeek-V3.2 做多步工具调用:每步模型先思考(几千 token)再调用工具,工具返回结果(几百 token)。比较”每步丢弃之前的思考内容”(R1 的做法)和”只在新的用户消息到来时才丢弃”(V3.2 的做法)对前缀缓存命中率和 prefill 计算量的影响。
参考答案(要点)
每步丢弃时,第 步的上下文 = 用户问题 + 之前的工具调用和结果(不含思考)+ 新的思考;而上一步缓存的是”含思考”的序列——从第一段被删掉的思考开始,后面的 token 位置全变了,前缀只能命中到那里,之后的工具调用历史都要重新 prefill,每步都重算一大段。只在新用户消息时丢弃,则同一个用户回合内每一步的上下文都是上一步的严格延续,前缀缓存几乎全部命中,每步只需 prefill 新的工具结果;代价是上下文更长(KV 更多,需要稀疏注意力或分层缓存来承担)。V3.2 的出发点是避免模型反复重新推理(省 decode),同时也顺带让前缀缓存更有效(省 prefill)。
练习 9(思考:怎样分页和卸载 SSM 状态)。第 3 讲把 KV 切成块来分页,第 9 讲把 KV 卸载到 CPU 内存和 SSD。SSM 状态能用同样的办法吗?你会怎样管理混合模型的”状态内存”?
参考答案(要点)
SSM 状态是一个整体( 的矩阵),没有序列维,不能按 token 切块;但它大小固定,可以按”整份”分配(例如固定大小的槽位池),不会有 KV 那样的增长和碎片问题。卸载:一份状态几十 MB、和一个请求绑定,适合整份换出到 CPU 内存;快照则可以放在更慢的层级,只在命中时取回(取回 25 MB 比重算几千 token 便宜得多)。注意力层的 KV 仍按页管理。所以一个合理的设计是”两种分配器”:KV 用分页池,SSM 状态用整份的槽位池,再由 Marconi 式的策略决定哪些快照留在显存、哪些下放、哪些丢弃。
11. 延伸阅读 · Further reading
本讲的必读材料
- R. Pan, Z. Wang, Z. Jia, C. Karakus, L. Zancato, T. Dao, Y. Wang, R. Netravali. Marconi: Prefix Caching for the Era of Hybrid LLMs. MLSys 2025(arXiv:2411.19379)。
- DeepSeek-AI. DeepSeek-V3.2: Pushing the Frontier of Open Large Language Models. arXiv:2512.02556,2025。
选读
- A. Gu, T. Dao. Mamba: Linear-Time Sequence Modeling with Selective State Spaces. arXiv:2312.00752,2023。
相关工作
- T. Dao, A. Gu. Transformers are SSMs: Generalized Models and Efficient Algorithms Through Structured State Space Duality(Mamba-2). ICML 2024(本讲代码里分块并行算法的思路)。
- R. Waleffe et al. An Empirical Study of Mamba-based Language Models. 2024(NVIDIA 的 Mamba-2 混合模型,Marconi 的主实验模型)。
- O. Lieber et al. Jamba: A Hybrid Transformer-Mamba Language Model. 2024。
- J. Yuan et al. Native Sparse Attention: Hardware-Aligned and Natively Trainable Sparse Attention. 2025。
- DeepSeek-AI. DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model. 2024(MLA 的来源)。
背景
- 第 3 讲:RadixAttention(Marconi 的基数树)。
- 第 4 讲:FlashAttention 的分块与 IO(Mamba 的扫描内核用同样的思路)。
- 第 8 讲:MLA 与 Helix;第 9 讲:分层缓存与”每字节省多少计算”。
12. 下一讲预告 · Next lecture
到这里为止,我们几乎一直在看”一个推理实例内部”的事情:批怎么组、显存怎么管、内核怎么写、状态怎么存。可生产中的服务是由很多副本组成的集群:请求来了先要决定发给哪个副本。第 9 讲的 Mooncake 已经让我们看到,“按负载均衡”和”按缓存命中”会互相冲突。第 11 讲:负载均衡与路由读两篇论文:Preble 研究前缀缓存感知的集群调度——一个请求该去缓存了它前缀的机器(省计算),还是去空闲的机器(省排队)?它用全局的基数树和”利用 vs 探索”的权衡(E2)来回答;LLMSelector 把路由推广到”复合 AI 系统里每个模块该用哪个模型”——同一个应用的不同步骤,可能适合不同的模型。我们会做一个带前缀缓存的集群模拟器,比较轮转、最少负载、前缀亲和与 E2 式调度,并用一个玩具版的 LLMSelector 做模块级的模型分配。