4. 层维度(下):CSA2 与每 token 890 字节
4.1 三件事要一起做
回到第 1 章那个乘积:条目多大、多少条、几层各存一份。GQA/MLA 动第一项,窗口和稀疏动第二项,CED 动第三项。CSA2(压缩稀疏注意力第二版)把三项合成到一个注意力块里,并且额外加了一条:让"选择哪些条目"这件事本身也跨层复用。
先记住它解决的问题。如果 40 层里每层都自己选 512 条,那每层都要跑一遍 Indexer 打分,成本 \(O(N)\) × 层数,第 2.3 节算过这是 \(4.1\times10^9\) 次乘加/查询/层。1M 上下文下,40 层就是每生成一个 token 要扫 \(1.6\times10^{11}\) 次——比全历史主注意力还贵。所以"选择"必须被复用。
读这一章之前,先把四个问题分开,后面就不会混:
- KV 的信息来自哪里——本层隐状态,还是第 20 层(CED);
- 哪一层负责生产——40 层里只有 4 个来源层(4.3 节);
- 哪些层重新选地址——8 个索引来源层;
- 哪些层只重算读出权重——其余做全局分支的层。
第 3 章回答的是第一个问题,这一章回答后三个。四个问题分开之后,"共享"到底共享了什么就清楚了——共享的是缓存的内容与份数,不是注意力计算本身。
4.2 压缩器:把相邻 2 个位置合成 1 条
序列维度的压缩由一个叫压缩器(compressor)的模块做。压缩比 \(m\) 的含义是"每 \(m\) 个原始位置合成一个缓存条目"。V4.1 Flash 的取值是分段函数:
\[ m_l=\begin{cases}2,& l\le 20\ (\text{Encoder})\\[2pt] 1,& l>20\ (\text{Decoder})\end{cases} \]Encoder 用 \(m=2\) 是有道理的:它的职责是"把长历史压成一份表示",压一半能同时减少条目数和 Indexer 候选数。Decoder 用 \(m=1\)(即不压缩),按报告的取舍是为了保留逐位置的细粒度全局记忆;按块寻址的自回归解码在原理上也能工作,所以这更像是质量上的选择,而不是算法上的必要条件。
注意"压一半"不等于"两两相加"。报告里的压缩器是一个逐通道学习权重的池化:对通道 \(c\),
\[ c^{(c)}_{j}=\frac{w^{(c)}_{0}x^{(c)}_{2j-1}+w^{(c)}_{1}x^{(c)}_{2j}}{w^{(c)}_{0}+w^{(c)}_{1}}, \]这里 \(w_0^{(c)},w_1^{(c)}\) 是随通道变化、由训练学出来的参数。两点要注意:要让它真的是"池化",权重通常得非负(由 softmax 或 softplus 给出)且分母不为零,否则组合可能落到两个输入之外;如果实现里的权重其实是由输入投影得到的门控,这个式子就只是静态示意。读报告时按实际实现核对。CSA2 相对上一代 CSA 删掉了两样东西:相邻压缩条目之间的重叠(原来每个条目由 \(2m\) 个原始条目产生,与邻居共享一半),以及压缩过程中的绝对位置嵌入。删得掉是因为 Encoder 最前面已有两层 SWA(感受野 255,见 2.2 节),局部顺序信息已经进到表示里了,压缩器不必再自己编码位置。
4.3 三种模式:两个指示量说清一切
每一层的注意力需要四样东西:主查询 \(q\)(本层自己算)、本地窗口 KV(本层自己算)、主 KV 内容 \(C\)、Top-K 选择集 \(S\)。三者的差别只在后两样由谁产出。用两个 0/1 指示量就写完了:
\[ \mathbb 1^{\text{kv}}_l=\big[\kappa(l)=l\big], \qquad \mathbb 1^{\text{idx}}_l=\big[\iota(l)=l\big], \]其中 \(\kappa(l)\) 是第 \(l\) 层的主 KV 来源层,\(\iota(l)\) 是它的索引来源层。"\(=l\)" 表示本层自己算,"\(\ne l\)" 表示复用更早的某一层。三种模式恰好是三种取值组合:
模式 主 KV K Top-K Full 写 写 算 Reindex 读 读 重算 Reuse 读 不读 读
用体系结构的语言说,这三种就是缓存的三种命中状态:Full 是缺失后写回填充,Reindex 是数据命中、地址重算,Reuse 是全命中、只做一次读出。第 5 章会证明为什么"只读"不算退化。
把它们排到 40 层上,结构就出来了。Encoder(第 1–20 层)= 2 层纯 SWA + 3 个 6 层块,每块是 1 层 Full 加 5 层 Reuse;Decoder(第 21–40 层)= 1 个 4 层块(1 Full + 3 Reuse)+ 4 个 4 层块(各 1 Reindex + 3 Reuse)。
Encoder SWA SWA
3 x (F R R R R R)
Decoder F R R R
4 x (X R R R)
F=Full R=Reuse X=Reindex
数一下:Encoder \(2+3\times6=20\),Decoder \(4+4\times4=20\),合计 40。全局 KV 只有 4 个来源层在产出。报告与代码用 0-based 下标,记作 layer_idx = 2, 8, 14, 20;换算成第几层(1-based),就是第 3、9、15、21 层——正好是"两层 SWA 之后,每个块的第一层"。这就是"全网只保留 3 份 Encoder 缓存和 1 份 Decoder 缓存"。
层号约定:本篇正文一律用 1-based,\(l\in\{1,\dots,40\}\);引用配置时给 0-based 下标。看到"第 20 层"时,它指的是 Encoder 的最后一层,对应 layer_idx=19。
4.4 890 字节的账
现在可以算报告里那个数字了。每层的全局 KV 由两部分组成:主 KV(\(d_c=512\) 个通道)和 Indexer K(\(d_{\text{idx}}=128\) 个通道)。两者都是 4 bit 载荷,但不是同一种 FP4 格式——这是报告里最容易读漏的一处:
- 主 KV:E2M1 + 每 16 通道一个 E4M3 缩放因子,即"跟随 NVFP4 但省掉第二级全局缩放"。
- Indexer K:V4 起就沿用的 OCP 标准 MXFP4,即 E2M1 + 每 32 元素一个 E8M0 缩放因子。
报告的取舍写在 2.4.4:MXFP4 是为了兼容尽量多的硬件平台,尽管实验里别的格式精度更高;主 KV 需要更细的分组,才改用 NVFP4 系的 E4M3 分组缩放。于是一个 \(D\) 通道、分组 \(G\) 的条目占
\[ b(D,G)=\underbrace{\frac{D}{2}}_{\text{4 bit/通道}}+\underbrace{\frac{D}{G}}_{\text{缩放因子}}\ \text{字节}. \]逐项代入(注意主 KV 的 \(G=16\)、Indexer K 的 \(G=32\)):
\[ b_{\text{kv}}=b(512,16)=\frac{512}{2}+\frac{512}{16}=256+32, \] \[ b_{\text{idx}}=b(128,32)=\frac{128}{2}+\frac{128}{32}=64+4, \] \[ \Longrightarrow\quad b_{\text{kv}}=288\ \text{B},\qquad b_{\text{idx}}=68\ \text{B}. \]接下来乘"多少条"和"几层"。压缩比为 \(m\) 的层每 \(m\) 个 token 才存一条,所以每 token 摊到的是 \(b/m\);来源层只有 4 个,其中三个 \(m=2\)、一个 \(m=1\):
\[ \sum_{l\in\mathcal F}\frac{1}{m_l} =\underbrace{\frac12+\frac12+\frac12}_{\text{第 }3,9,15\text{ 层}}+\underbrace{\frac{1}{1}}_{\text{第 }21\text{ 层}} =2.5 . \] \[ S_{\text{main}}=288\times 2.5=720\ \text{B}, \] \[ S_{\text{idx}}=68\times 2.5=170\ \text{B}, \] \[ S_{\text{global}}=720+170=\boxed{890\ \text{B/token}} . \]合并成一张账:
来源层 m 主 KV Index K 小计 3,9,15 2 432 102 534 21 1 288 68 356 合计 720 170 890
1M 上下文就是把 890 B 乘一百万:0.89 GB——这是常驻容量,不是每步读取量(2.5 节)。对比第 2.1 节全注意力 MLA 的 40.96 GB,以及完全不分层的 3514 B/token(4.6 节)。
4.5 FP4 会不会掉精度:动态范围和量化误差是两件事
量化最怕的第一件事是动态范围不够——值一大就被截断。算一下这个格式的上界:E2M1 能表示的最大幅度是 6,缩放因子是 E4M3(最大 448),所以单通道最大可表示
\[ 6\times 448=2688 . \]而实际需要表示的幅度有多大?报告给了一条完整的不等式链:KV 潜变量在 RoPE 之前过了一次 RMSNorm,训练后最大的 RMSNorm 权重幅度约为 1,所以 512 维向量的 L2 范数上界约为 \(\sqrt{512}\);RoPE 是旋转,保长,旋转后单通道的绝对值仍被 \(\sqrt{512}\approx22.6\) 界住。训练中实际观测到的最大值约 10。
\[ \text{上界 }2688\ \gg\ \sqrt{512}\approx22.6 \ \gtrsim\ \text{实测 }10 . \]所以不额外加一级全局缩放因子不会因为溢出而出问题。报告的原话是:省略这一级"没有可测量的精度下降,同时简化了缓存布局"。
但要强调:2688 这个上界只说明"不饱和",不说明"4 bit 本身就够"。4 bit 只有 16 个可表示值,小值会下溢,分组内的尺度差异会损害精度,舍入误差还会逐层累积——这些都不是动态范围能回答的。真正让 FP4 可用的是第 8.3 节的 QAT:前向模拟量化、继续训练,把信息挪到量化后仍能分辨的位置上。报告把这两件事分开写,读的时候也要分开。两个工程细节:
- 在 RoPE 之后量化。报告说 RoPE 之前量化"只有边际的精度提升",但会给解码带来额外开销;它没有说一定多一次显存读写。是否真的多一次读写,取决于实现能不能把"反量化 + 旋转 + 注意力"融成一个 kernel。DeepSeek-V4 的 FlashMLA 就是这类融合(报告 3.2 节)。
- 滑动窗口的 KV 保留 FP8。报告的措辞是"SWA KV 对量化敏感",且总量是常数(128 条),不值得冒险。
把 FP4 当成"用 FP4 做矩阵乘"。报告明确说 FP4 的作用是减少存储,注意力计算前会反量化成更精确的格式。所以它能在没有原生 FP4 矩阵乘单元的硬件上跑。
4.6 从 3514 到 890:四步账
报告只给出一个结论数:每 token 全局 KV 是 V4-Flash 的约 1/4(890 B,见 Figure 1b),对 DeepSeek-V1 则是 437 倍。下面这张分解表是本篇按报告配置反推的,不是报告原文;它的用处是看清"哪一步在省、哪一步在花钱",而不是替代那个 1/4。V4-Flash 按 43 层、CSA 与 HCA 混合、每层独立持有全局缓存,条目格式 448 通道 FP8 + 64 通道 BF16 RoPE + 8 字节缩放:
\[ 448\times1+64\times2+8=584\ \text{B}. \]21 层 CSA(\(m=4\),带 Indexer)与 20 层 HCA(\(m=128\),无 Indexer):
\[ B_{4\text{-Flash}} =21\times\frac{584+68}{4}+20\times\frac{584}{128} \] \[ =21\times163+20\times4.5625 =3514.25\ \text{B}. \]然后按四步拆:
- 加跨层复用:41 个来源坍缩成 4 个,仍是 \(m=4\)、仍是 V4 格式。\(4\times163=652\) B,因子 \(\div5.39\)。
- 放松序列压缩:\(m\) 从 4 松到 2(Encoder)/1(Decoder),\(\sum1/m\) 从 1 变成 2.5。\(652\times2.5=1630\) B,因子 \(\times2.5\)。这是一次"花钱"的改动:为了 Decoder 能逐位置寻址,条目数放回来了。
- 主 KV 换 FP4:584 B \(\to\) 288 B / 条目(Indexer K 仍是 68 B)。\(1630\times(288+68)/(584+68)=890\) B,因子 \(\div1.83\)。
- 净效果:\(3514.25/890=3.95\),即"KV 再压 4 倍"。
把三步写成一条链:\(3514.25\div5.39\times2.5\div1.83\approx890\),对应的净节省倍数是 \(5.39\div2.5\times1.83\approx3.95\)。注意第 2 步是乘不是除——它不是节省,而是主动花钱。任何"省了 N 倍"的账都要这样拆开看,否则会把"假装每一步都在省"的 \(7.4\) 当成结论,然后去找一个并不存在的差额。
4.7 HSI:把后续层的搜索域变成常数
Reindex 层仍要自己重选 512 条。若允许它在全历史里选,成本还是 \(O(N)\)。HSI(分层稀疏 Indexer)的做法是:让一个 Full 层顺便产出一个候选池,后续 Reindex 层只在池子里选。注意它常数化的是后续层的搜索域,不是候选池的生成成本——产出池子的那个 Full 层仍要扫全部可见历史。
把因果可见的历史按宽度 \(b=8\) 切块,第 \(r\) 块记为 \(B_r\)。Full 层本来就要给每个位置打分,顺手在同一份分数上做块内取最大:
\[ g_r(t)=\max_{j\in B_r}s(t,j), \] \[ \mathcal B_t=\operatorname{TopM}_{r}g_r(t), \qquad M=2048, \] \[ P_t=\bigcup_{r\in\mathcal B_t}B_r, \qquad |P_t|\le 2048\times8=16384 . \]于是后续 Reindex 层的搜索空间从"全部 \(N\) 个位置"降到"至多 16,384 个位置",而且这个上界与上下文长度无关:
\[ \underbrace{N}_{\text{Full:扫全部历史}} \;\longrightarrow\; \underbrace{\min(N,16384)}_{\text{Reindex:搜索域上界固定}} . \]两端都是搜索域的大小,不是最终输出条数——两种层最终都只选至多 512 条。取块内最大而不是均值,是为了避免"峰值被平均稀释":一个块里只要有一个位置极相关,这个块就该入选。另有一个边界策略:包含最新可见位置的块,其分数被临时设成 \(+\infty\),强制占用一个名额,保证"最近的内容一定在池子里"。
HSI 是训练感知的:它在后训练阶段引入,训练与推理施加完全相同的候选限制,使深层 Indexer 在推理真正使用的搜索域下被优化。这也是为什么它不能"事后直接开"——训练时能搜全历史、上线时只给一份名单,训推分布就不一致了。
报告说得很清楚:产池子的是Decoder 里第一个被指定为 Full 的层,config 的 candidate_source_layer_id = 20 就是它(1-based 第 21 层)。所以 Encoder 那三份缓存不参与索引池;池子的地址域与 Decoder 的 Reindex 层一致。同一节还有一句最容易被忽略的话:"第一个 Full 层仍然要扫全部因果可见范围"——HSI 省的是后续层,不是它自己。
4.8 容易算错的地方
- 用 4 字节/通道算主 KV。512×4=2048 B;按最终配置的 2.5 份算就是 5120 B,把 Indexer 也算上则是 6400 B——和 890 B 差一个数量级。
- 忘了缩放因子。288 里 32 B 是缩放,占 11%。按最终配置(主 KV \(32\times2.5\) + Indexer \(4\times2.5\))算,漏掉它每 token 少 90 B;只有按"40 层各持一份、未压缩"的假想模型算,才是 1.3 KB。
- 把 2.5 当成"压缩比"。它是 \(\sum 1/m\),是"层数 × 序列压缩"合并后的等效份数。
- 把 16384 当成最终选中数。16384 是候选池,最终每层只读 512 条。
- 认为 Reindex 会重建 KV。它复用主 KV 与 Indexer K,只重跑"地址生成"(打分 + Top-K)。