06. 多头注意力与 KV 头:MHA、MQA、GQA、MLA
上一篇用 4 个 token、每头 2 维的小向量手算了一次注意力:“申请”拿自己的 Q 去和前面每个 token 的 K 比分数,softmax 之后按权重把 V 加起来。真实模型不是只算一次,Llama-3.1-8B 每层同时算 32 份,这就是多头注意力。这一篇回答三个问题:为什么要拆成多个头;为什么 K、V 的头可以比 Q 少,少了省什么、丢什么;DeepSeek 的 MLA 怎样把 K、V 压成一个小向量再存。读完你应该能从任意模型的 config.json 判断它用的是哪种注意力,并算出每个 token 的 KV Cache 字节数。
一个头只能给出一种权重分布
回到资料助手的问题“年假怎么申请?”。模型处理到“申请”时,至少有两件事要从上下文里取:申请的对象是“年假”,句子的形式是“怎么……”提问。一次注意力只产生一组权重,权重和为 1。如果它把 0.6 给“年假”、0.3 给“怎么”,取回来的就是两者 V 向量按 6:3 混合的结果,两种信息搅在了一起,后面的层很难再把它们分开。
多头注意力(Multi-Head Attention,MHA)的做法是把同一个 4096 维向量切成 32 段,每段 128 维,各自独立算一次注意力。每一段叫一个头(head):有自己的 Q、K、V,有自己的 softmax,也就有自己的一组权重。一个头可以几乎全看“年假”,另一个头可以几乎全看“怎么”,最后 32 个头的结果拼回 4096 维,再由一个输出矩阵混合起来。
text
Q = x · W_q [n, 4096] → [n, 4096] → 看成 [n, 32 个头, 128]
K = x · W_k [n, 4096] → [n, 1024] → 看成 [n, 8 个头, 128]
V = x · W_v [n, 4096] → [n, 1024] → 看成 [n, 8 个头, 128]
第 j 个头:head_j = softmax(Q_j · K_g(j)ᵀ / √128 + 因果掩码) · V_g(j) [n, 128]
输出: o = concat(head_0, …, head_31) · W_o [n, 4096]n 是 token 数,W_q、W_k、W_v、W_o 是这一层的四个投影矩阵(Transformers 里叫 q_proj、k_proj、v_proj、o_proj)。√128 ≈ 11.3,用来把点积缩回合适的范围,理由见上一篇。g(j) 表示第 j 个 Q 头用哪一组 K、V:在 Llama-3.1-8B 里 K、V 只有 8 个头,g(j) = ⌊j / 4⌋,每 4 个 Q 头共用一组。这一点后面细讲。
拆成多头并不增加计算。一个 4096 维的大头做点积要 4096 次乘加,32 个 128 维的小头合计也是 4096 次;q_proj 不管拆不拆都是 4096×4096 的矩阵。多头只是把“一次 4096 维的比较”换成“32 次 128 维的比较”,换来 32 组相互独立的权重。
图里颜色相同的 4 个 Q 头读同一组 K、V,但它们的 Q 不同,算出来的权重分布也不同。所以“共用 KV”不等于“这 4 个头看的东西一样”,它们是在同一份被查询的内容上提不同的问题。
每个头学到了什么
头的分工不是人设计的,训练只规定“把 32 个头的结果拼起来后要有助于预测下一个 token”,每个头学成什么样由数据决定。可解释性研究看到了一些规律,但都有边界:
- 在机器翻译模型里,有些头稳定地看前一个或后一个位置,有些头对应句法关系,有些头专门看罕见词;剪掉其余大部分头,质量下降不多(Voita 等,2019)。
- 在 BERT 和翻译模型上,测试时去掉相当一部分头,指标几乎不变,有的层甚至只留一个头也可以(Michel 等,2019)。
- 在语言模型里能找到 induction head:看到“A B … A”时,去找上一次 A 后面跟着什么,并把 B 抄过来。这类头和模型的上下文学习能力出现的时间点吻合(Olsson 等,2022)。
这些结论多来自中小模型和特定任务,说明头之间有分工、也有大量冗余,不能推出“第 5 层第 3 个头负责主语”这种可以直接查表的结论。同一个头在不同输入上可能做不同的事,一种功能也可能分散在多个头里。
冗余带来了一个直接的工程问题:既然很多头可以去掉,K、V 是不是不需要每个头一份?
KV Cache 只和 KV 头有关
推理时每生成一个 token,新 token 只需要自己的 Q,但要读所有历史 token 的 K 和 V,所以历史的 K、V 被存成 KV Cache(原理见推理系列第 03 篇)。每个 token 要存多少,取决于层数、KV 头数和每头维度,与 Q 头数无关:
text
每 token KV 字节数 = 2(K 和 V)× 层数 × KV 头数 × 每头维度 × 每个数的字节数
Llama-3.1-8B 实际(8 个 KV 头):2 × 32 × 8 × 128 × 2 = 131,072 字节 = 128 KiB
如果每个 Q 头一组 KV(32 个): 2 × 32 × 32 × 128 × 2 = 524,288 字节 = 512 KiB32,768 个 token 的上下文,按 MHA 存要 16 GiB,比 8B 模型 15 GiB 的 BF16 权重还多;按实际的 8 个 KV 头存是 4 GiB。整本员工手册放进上下文、几十个员工同时提问时,这个差距决定一张卡能服务几个人(显存的完整账见推理系列第 04 篇)。
除了占地方,KV 还要每步读一遍。Decode 阶段每生成一个 token,32 层的全部历史 K、V 都要从显存读到计算单元,读取时间往往比计算时间长(推理系列第 06 篇)。少存一个 KV 头,就是每步少搬一份数据。
MQA 与 GQA:让多个 Q 头共用 K、V
多查询注意力(Multi-Query Attention,MQA)走到了极端:Q 仍有很多头,K、V 只保留一个头,所有 Q 头共用(Shazeer,2019)。KV Cache 直接降到 MHA 的 1/头数。Falcon-7B 就是这样:71 个 Q 头、1 个 KV 头、每头 64 维,32 层每 token 只要 8 KiB。
代价是质量。所有头只能在同一份 K、V 上提问,被查询的内容只剩一种“写法”,模型容量下降;把训练好的 MHA 模型改成 MQA 还容易训练不稳定(Ainslie 等,2023)。分组查询注意力(Grouped-Query Attention,GQA)取中间:把 Q 头分成若干组,每组共用一个 KV 头。8 组时 KV 是 MHA 的 1/4、MQA 的 8 倍。GQA 论文报告,用原预训练约 5% 的算力就能把一个 MHA 检查点改训成 GQA,质量接近 MHA、解码速度接近 MQA。Llama 2 论文在 30B 规模上对比过三种结构:GQA 在多数任务上与 MHA 相当,平均好于 MQA,于是 34B 和 70B 用了 GQA;到 Llama 3,8B 也改用 8 个 KV 头。
GQA 在代码里很简单。Transformers 的 LlamaAttention 先按 8 个 KV 头算出 K、V 并写进缓存,计算注意力前用 repeat_kv 把每个 KV 头复制 4 份,对齐 32 个 Q 头:
python
# 简化自 transformers/models/llama/modeling_llama.py 的 LlamaAttention
q = q_proj(x).view(n, 32, 128) # 32 个 Q 头
k = k_proj(x).view(n, 8, 128) # 8 个 KV 头
v = v_proj(x).view(n, 8, 128)
q, k = apply_rope(q, k, pos) # 位置编码,见第 09 篇
k, v = cache.update(k, v) # 缓存里存的是 8 个头
k = k.repeat_interleave(4, dim=1) # repeat_kv:复制成 32 个头参与计算
v = v.repeat_interleave(4, dim=1)
out = softmax(q @ k.T / 128**0.5 + causal_mask) @ v # 逐头计算
return o_proj(out.reshape(n, 4096))复制只发生在计算时,缓存里始终只有 8 份。高效的注意力算子会直接按组读 KV,连复制都省掉。
GQA 也有不适用的地方。KV 头太少会和张量并行冲突:把一层注意力切到 16 张卡上,每张卡要分到至少一个 KV 头,8 个 KV 头不够分,推理引擎只好在多张卡上复制同一个 KV 头,每张卡的 KV 显存不再按卡数下降(多卡切分见推理系列第 16 篇)。另外,KV 头的数目是预训练时定下的,对现成的 MHA 模型直接合并 KV 头而不再训练,质量会明显下降;要改,就得像 GQA 论文那样再训一段。
每 token KV 的对比
把几种结构放在一起,按字节等比例画出来:
最下面一行值得多看一眼:DeepSeek-V3 是 6710 亿参数的模型,每层 128 个注意力头,每 token 的 KV 却只有 68.6 KiB,比 80 亿参数、8 个 KV 头的 Llama-3.1-8B 还少一半。它没有减少头数,用的是另一种办法。
MLA:缓存一个压缩向量,用时再展开
GQA 省 KV 的方式是让头共用,代价是头之间的 K、V 不再独立。DeepSeek-V2 提出的多头潜在注意力(Multi-head Latent Attention,MLA)换了个思路:每个头仍有自己的 K、V,但它们都由同一个低维向量算出来,缓存里只存这个低维向量。
低秩压缩是核心。以 DeepSeek-V3 的配置为例(kv_lora_rank 512,128 个头,每头 K 的“内容部分” 128 维、V 128 维):
text
压缩(下投影):c_kv = RMSNorm(h · W_DKV) [n, 7168] → [n, 512] ← 存进缓存
展开(上投影):k_nope = c_kv · W_UK [n, 512] → [n, 128 头, 128]
v = c_kv · W_UV [n, 512] → [n, 128 头, 128]如果直接按 MHA 存 128 个头的 K、V,每层每 token 要 128 × (128 + 128) = 32,768 个数;MLA 只存 512 个。“低秩”指 128 个头的 K、V 拼起来虽然有 32,768 维,但都是同一个 512 维向量的线性变换,自由度只有 512。这相当于假设各个头需要的 K、V 信息高度重叠,可以用一个共享的 512 维摘要表示。Q 也做了类似的压缩(7168 → 1536 → 每头 192 维),那是为了省训练时的激活显存,与 KV Cache 无关。
解耦 RoPE 解决位置编码的问题。RoPE 按 token 的位置旋转 Q 和 K(第 09 篇讲),旋转角度和位置有关。如果对展开后的 k_nope 做 RoPE,就必须先展开再旋转,缓存压缩向量的意义打了折扣,下一节的矩阵吸收也做不了。MLA 的做法是把位置信息单独拿出来:每个头的 Q 额外有 64 维 q_rope,K 额外有 64 维 k_rope,只有这两部分做 RoPE;k_rope 由输入直接算出、所有头共用一份,也存进缓存。所以 DeepSeek-V3 每层每 token 存 512 + 64 = 576 个数:
text
每头分数 = q_nope · k_nope + q_rope · k_rope (128 维 + 64 维,再除以 √192)
每 token KV = 61 层 × 576 × 2 字节 = 70,272 字节 ≈ 68.6 KiB
若按 128 头 MHA 存:61 × 128 × (192 + 128) × 2 = 4,997,120 字节 ≈ 4.77 MiB,是 MLA 的 71 倍576 个数相当于 2.25 个 128 维 KV 头的 K 和 V,缓存量接近一个只有 2—3 组的 GQA,但 128 个头的 K、V 各不相同。
矩阵吸收让推理时连展开都省掉。看一个头内容部分的分数:
text
q_nope · k_nope = q_nope · (c_kv · W_UK) = (q_nope · W_UKᵀ) · c_kv右边先把 Q 乘上 W_UK 的转置,得到一个 512 维的向量,再直接和缓存里的 c_kv 点积。V 一侧同理:先在 512 维上按注意力权重加权求和,最后再乘 W_UV(也可以并进 o_proj)。这样 Decode 时 128 个头都在同一份 576 维缓存上计算,访存模式像 MQA,每个头的 K、V 却仍是各自的。DeepSeek 官方推理代码 inference/model.py 的默认实现 attn_impl = "absorb" 就是这样算的。代价是每个头的点积从 192 维变成 576 维,计算更多;Decode 本来受显存带宽限制,多出的计算基本被空闲算力吸收。有的推理引擎在 Prefill 阶段仍走展开路径,在 Decode 阶段走吸收路径。
读 Transformers 源码时要注意:modeling_deepseek_v3.py 的 DeepseekV3Attention 已经把压缩后的 c_kv 和 k_rope 写进缓存,但计算前调用 expand_kv 展开成 128 个头,没有做矩阵吸收。它的缓存大小是对的,计算方式是“参考实现”。
MLA 的问题也很具体:结构比 GQA 复杂,位置编码要单独拆一路,上下文扩展方法要跟着改;算子需要专门实现,早期很多推理引擎不支持或很慢;它解决的是 KV 大小,不改变注意力随上下文平方增长的计算量。质量方面,DeepSeek-V2 报告里的对比实验显示 MLA 不差于甚至好于 MHA,这是作者自己的结果,其他模型改用 MLA 时仍要用自己的评测确认。
主流模型选了哪一种
| 模型 | 注意力 | Q 头 / KV 头 | 每头维度(K / V) | 层数 | 每 token KV(BF16) |
|---|---|---|---|---|---|
| Llama-2-7B | MHA | 32 / 32 | 128 | 32 | 512 KiB |
| Falcon-7B | MQA | 71 / 1 | 64 | 32 | 8 KiB |
| Llama-3.1-8B | GQA | 32 / 8 | 128 | 32 | 128 KiB |
| Llama-3.3-70B | GQA | 64 / 8 | 128 | 80 | 320 KiB |
| Qwen2.5-0.5B | GQA | 14 / 2 | 64 | 24 | 12 KiB |
| Qwen3-8B | GQA | 32 / 8 | 128 | 36 | 144 KiB |
| gpt-oss-20b | GQA | 64 / 8 | 64 | 24 | 48 KiB(未计滑动窗口) |
| DeepSeek-V3 | MLA | 128 头,缓存 512 + 64 | 192 / 128 | 61 | 68.6 KiB |
| Kimi K2 | MLA | 64 头,缓存 512 + 64 | 192 / 128 | 61 | 68.6 KiB |
| GLM-5 | MLA | 64 头,缓存 512 + 64 | 256 / 256 | 78 | 87.75 KiB |
表中数据来自各模型 2026 年 10 月时 Hugging Face 上的 config.json。看配置时:num_key_value_heads 等于 num_attention_heads 是 MHA,等于 1 是 MQA(老模型可能写成 multi_query: true),介于两者之间是 GQA;出现 kv_lora_rank 和 qk_rope_head_dim 就是 MLA,每层缓存 kv_lora_rank + qk_rope_head_dim 个数,此时 num_key_value_heads 不再表示缓存的头数。gpt-oss 一半的层只看最近 128 个 token,这些层的 KV 不随上下文增长,滑动窗口在第 12 篇讲;DeepSeek 系列的完整结构见第 17 篇。
趋势很清楚:中小规模的稠密模型基本停在 GQA,KV 头数 2—8;参数很大、要服务长上下文的 MoE 模型越来越多地采用 MLA。纯 MQA 在新的主流模型里已经少见。
多头注意力用 32 组独立的权重分布让一个位置同时取几种信息,计算量与单头相同;KV Cache 只取决于 KV 头的数目和维度,GQA 用“多个 Q 头共用一组 KV”换来 4 倍的节省且质量损失很小,MQA 省得更多但质量下降明显,MLA 则缓存一个低秩压缩向量、单独处理 RoPE,并在推理时把展开矩阵吸收进 Q 和输出。注意力把上下文的信息搬到了每个位置,下一篇看搬来之后每个位置自己做什么:07. FFN:前馈网络做了什么,也就是占了 8B 模型七成参数的那一部分。
