KV 还能再压吗?
本课一问:KV 还能再压吗?
这一课先不碰:把序列本身压短、只读一部分缓存的稀疏注意力,以及不存 KV 的线性注意力(C11);把缓存量化到更低精度(C9、C13)。本课讲到 MLA 为止。
从已有出发
缓存 = 层数 × 每层每 token 存的条目 × 字节 × 上下文长度。上一课动的是条目里的「KV 头数」:从每个 Q 头一份,减到几个 Q 头共用一份。这一课沿着 DeepSeek 的真实演进走一遍,看看还能从哪里下刀。下面所有数字都由组件从 frontier-models.json 里的官方 config 现算。
先自己设计:继续减 KV 头
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
KV 头数是一个整数,从 8 减到 4、2、1,每次最多砍一半;到了 1 就没法再减了,而质量在这条路的末端已经开始吃亏。要继续压,得换一个维度。
每层每 token 真正需要存的,也许不是「g 个头 × 每头 dh 维」的 K 和 V,而是能生成它们的一个更小的东西。MLA(DeepSeek-V2 提出)只存一个 dc 维的潜向量 c,所有头的 K、V 都由它展开;RoPE 另外存一小段。DeepSeek-V2/V3 与 GLM-5.3 都是 dc = 512、RoPE 部分 64 维:每层每 token 576 个数。
试三件事:① 沿着阶梯从左往右看每一张卡的「砍掉了哪个维度」:先是头数,再是「每层每 token 存什么」;② 注意 V2 与 V3 是两张卡:同样是 576 个数,层数不同(60 与 61)所以字节不同——旧稿把它们合成一行是错的;③ 打开「−93.3% 是相对谁」面板,看同一个 MLA 为什么能被说成降了 82.2%、93.3% 或 98.2%。
改进:MLA 怎么存、怎么用
MLA 的核心是四个矩阵乘法。点每一项看它是什么、为什么在那(旧版课程的解说在这里补上了漏掉的 RoPE 一项,并纠正了 −93.3% 的基线):
真实形状(GLM-5.3 的 config;它和 DeepSeek-V3 一样是 latent 512 + RoPE 64,只是 K 的非位置部分每头 192 维、V 每头 256 维):
| 维度 | 含义 | 真实规模 | 小规模 |
|---|---|---|---|
| T | 序列长度(token 数) | 5 | 5 |
| d | 隐藏维度(hidden_size) | 6144 | 8 |
| dc | KV 潜向量维度(kv_lora_rank),每层每 token 缓存它 | 512 | 2 |
| dR | 解耦 RoPE 维度(qk_rope_head_dim),所有头共用,也缓存 | 64 | 1 |
| nh·dnope | 头数 × 每头不带 RoPE 的 K 维 = 64 × 192(num_attention_heads × qk_nope_head_dim) | 12,288 | 6 |
| nh·dv | 头数 × 每头 V 维 = 64 × 256(num_attention_heads × v_head_dim) | 16,384 | 6 |
「−93.3%」到底是相对谁。DeepSeek-V2 报告说它「相对 DeepSeek 67B 把 KV 缓存降了 93.3%」 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条官方自报;基线是 DeepSeek 67B(GQA-8),并且部署时把 KV 量化到平均约 6 bit 出处DeepSeek-V2 摘要与 §3.2.3 ↗ 。这句话有两个容易被略掉的前提:基线是 67B(本身已经是 GQA-8),不是同配置的 MHA;并且部署时还把 KV 量化到了平均约 6 bit。只看 BF16,V2 相对 67B 降的是 82.2%;按 V2 论文表 1 的口径和「同配置 MHA」比,降的是 98.2%——上面组件的面板把三个数都现算了一遍。
⚠ 常见误解
「MLA 只是换了个名字的 GQA。」不是。GQA 仍然存 K、V,只是少存几份(条目按 g × dh 记账);MLA 不存 K、V,存的是能生成全部头的 K、V 的一个潜向量,加一小段位置。DeepSeek-V2 报告里的换算是:MLA 的缓存量相当于只有 2.25 组的 GQA 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条(d_c + d_R) ÷ (2·d_h) = 576 ÷ 256 = 2.25 出处DeepSeek-V2 表 1 ↗ ,但每个头仍有自己独立展开的 K、V。报告还自称它的效果强于 MHA——这是官方自报的实验结论 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条官方自报,在他们自己的模型与评测上 出处DeepSeek-V2 表 1 与附录 ↗ 。
「DeepSeek-V2 的 KV −93.3%,说明 MLA 相对同配置 MHA 降了 93.3%。」见上:基线是 DeepSeek 67B(GQA-8),还含了约 6 bit 的 KV 量化。三种口径(82.2% / 93.3% / 98.2%)各有前提,引用时要带上。
注意力的演进,你在这里
① 训练时:一个 token 怎么从别的 token 那里取信息
- 解决了能读任意长的序列,参数与长度无关;手里只带一个固定大小的状态 h。卡在ht 要等 ht−1 算完:T = 4,096 时每层 4,096 步不能并行,第 1 个 token 的信息要经过 4,095 次更新才到最后;翻译时整句还得挤进一个定长向量——Bahdanau 2014 说的瓶颈。展开岔路 B1
- 解决了解码器每一步回头给源句的每个位置打分、加权取回,不再只靠那一个定长向量:score = vT·tanh(W1q + W2k)。卡在每一对都要单独过 tanh,交不给矩阵乘单元;朴素实现要先落一个 T×T×da 的中间张量:T = 4,096、da = 256 时单头单层 8.0 GiB(BF16),点积只留 T×T 个分数,32 MiB。它还长在 RNN 翻译模型里,串行没解决。展开第 2 课
- 解决了直接比 embedding 时「给了」最像的是它自己(余弦 1.000),小明、苹果、小王排在最后;投影成 Q、K 再点积,它自己排到最后、前三名是苹果、小王、小明,全部配对一次矩阵乘 QKT;取回用单独的 V——两个人名的 K 余弦 0.995,取回 K 分不清是谁。卡在不缩放:锚点每头 256 维,点积标准差 √256 = 16,同一行分差十几二十是常态(e20 ≈ 4.9 亿),softmax 几乎独占,梯度传不回 WQ、WK。
- 解决了softmax 把分数变成正的、和为 1 的权重,只看分差(按占比分,对话样本的 10、3、−4 会给「汽车」−44.4%);除以 √dk = 16,点积方差回到 1。卡在训练时整句一次喂进去:不加掩码,「给了」一行有 32.5% 的注意力直接落在它要预测的答案「小王」上,loss 低得虚假。展开第 3 课
- 解决了旋转后 q·k 只依赖距离 j − i;V 不参与打分,不转(转了会把绝对位置混进取回的内容)。锚点每头 256 维里只转 64 维。卡在旋转矩阵随位置变,夹在展开矩阵和缓存之间就没法事先并进 Q:MLA 因此把 RoPE 单独拆出 64 维另存(DeepSeek-V2 config,第 10 课)。
- 解决了标准 softmax 逼每行分完 100%:「苹果」一行 3 个分数只差零点几(0.04、0.36、0.00),照样分成 30% / 41% / 29%;分母加可学习的 sink logit,或在输出上乘门,这个头就能「这次不取」——锚点开着输出门(attn_output_gate = true)。卡在出口的效果只有实验证据:Gated Attention 原文说门控「缓解」sink,不是消除;DeepSeek-V4 没给 sink logit 的单独消融。展开岔路 B4
② 生成时:K、V 怎么越存越少
- 解决了缓存直接除以头数:锚点形状下每 token 16 KiB,262,144 上下文 4 GiB。卡在质量会掉、训练可能不稳:GQA 论文表 1(T5-XXL,七个任务平均)MHA 47.2 → MQA 46.6。展开第 9 课
- 解决了24 个 Q 头分 4 组、每组 6 个共用 1 个 KV 头(组数 = KV 头数):每 token 64 KiB、262,144 上下文 16 GiB;T5-XXL 上 GQA-8 平均 47.1、推理 0.28 s,质量接近 MHA(47.2、1.51 s),速度接近 MQA(0.24 s)。卡在再减 KV 头就回到 MQA:DeepSeek LLM 67B 已是 GQA-8,每 token 仍要 389,120 B(每头一份的 MHA 是 3,112,960 B),1M token 单条请求 380 GiB。展开第 9 课
- 解决了不存 K、V,每层每 token 只存 512 维潜向量 + 64 维 RoPE = 576 个数,用时再展开:DeepSeek-V2(60 层)每 token 69,120 B、V3(61 层)70,272 B;相对 67B 按 BF16 降 82.2%,官方「−93.3%」还含约 6 bit 的 KV 量化。卡在仍随长度线性增长,打分仍是 T²:GLM-5.3 同是 576 个数、78 层,每 token 89,856 B,原生 1,048,576 上下文时单条请求只算潜向量就要 87.75 GiB。展开第 10 课
- 解决了换掉「按长度存」本身:锚点 64 层里 48 层不存 KV,只存固定大小的状态(FP32 共 144 MiB),与上下文长度无关(这些层是什么、怎么更新,C11 第 4 课揭晓);DeepSeek-V4.1 叠加压缩、稀疏与低精度,官方报告全局 KV 890 B/token。卡在固定状态装不下任意多对键值,锚点仍留 16 层全注意力;稀疏注意力每步只读挑出来的一部分,挑本身也有代价——各自省了什么、没省什么,C11 讲。
证据三问
- 它解决什么问题?在不减少头数、不牺牲每个头独立 K/V 的前提下,把每层每 token 的缓存从「2 × 头数 × 每头维数」降到「潜向量 + 一小段 RoPE」。
- 数学上为什么可能有效?缓存字节是计数 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条组件由 config 现算,并与 frontier-models.json 的核对值比对 出处待补 ;WUK 可以并进 Q 是矩阵乘法结合律 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条qᵀ(W^UK c) = (W^UKᵀ q)ᵀ c 出处待补 ;RoPE 会让吸收失效是因为旋转矩阵随位置变化、矩阵乘法不可交换 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条原文:matrix multiplication does not obey a commutative law 出处DeepSeek-V2 §2.1.3 ↗ 。「K、V 的有效信息是低秩的」是理论动机 证据等级 · 有理论动机 有数学或机制上的理由说明它可能有效,但没有证明它一定更好。最后是否采用,仍然要看实验。 本条低秩假设,没有证明 出处待补 。
- 实验上为什么选了它?DeepSeek-V2 报告它比 MHA 更强、KV 小得多 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条官方自报 出处DeepSeek-V2 ↗ ;之后 DeepSeek-V3、GLM-5 系列、Kimi 的全局注意力层都采用了 MLA(见 frontier-models.json 与调研档案 R2、R3)。
一句话
MLA 不存 K、V,只存一个低秩潜向量;RoPE 会破坏「把展开矩阵并进 Q」,所以位置那部分单独存一小段。
下一个缺口
到这里,「注意力」这一环讲完了:每个 token 按自己的 Q,从前面的 token 那里按权重取回 V,多个头各取各的,WO 把它们混在一起。可这些都只是在搬运和混合已有的信息(第 5 课:加权平均造不出新方向)。取回来之后,每个 token 自己还要做什么加工?→ C4:一个 Transformer 块——FFN、残差、归一化、门控。
在进入 C4 之前,推荐先去 实验课 看一眼我们自己训练的模型,它的注意力到底长什么样。
你可能会问
这一课还没有收到真实提问;你的提问会记到 05-问答记录/C3-L9.md。
🔍 KV 压到 576 个数之后,还能再压吗?
还能,但要换维度。前面两步砍的是「每层每 token 存什么」;接下来可以砍「序列有多长」(把几个 token 的 KV 压成一条、只读挑出来的一部分)、「多少层存」(几层共用一份)、以及「每个数几个比特」(FP8、FP4)。DeepSeek-V4.1 把这些叠在一起,官方报告全局 KV 只有 890 字节/token——上面组件的阶梯末尾有一张虚线卡。那是 C11 的故事:稀疏注意力省了什么、没省什么,以及锚点 Qwen3.8 那 48 层不存 KV 的注意力是什么。
🔍 MLA 省下了显存,是不是也省了计算?
没有,反而多了一点。训练和 prefill 时,每个头的 K、V 仍要从 c 展开出来参与打分;decode 时可以把 WUK 并进 Q、在 512 维的 c 上直接打分,省掉展开,但每个头的查询维度也跟着变成了 512 + 64。DeepSeek 的选择是用这点计算换大幅减少的显存与读取量——decode 的瓶颈在读数据,这笔交易划算。