一套 Q/K/V 只能看一种关系,够吗?
本课一问:一套 Q/K/V 只能看一种关系,够吗?
这一课先不碰:K、V 能不能少存几份(第 9 课);头数到底选多少最好(实验问题,本课只给证据)。
从已有出发
一套 Q/K/V 的注意力已经完整。可一句话里的关系不止一种。对话样本里有个例子:「小明告诉小王,他昨天买的电脑坏了。」「他」要判断指代(小明还是小王),「电脑」和「坏了」之间是物体与状态,「昨天」和「买」之间是时间,「小明」和「告诉」之间是主谓。就在我们的贯穿例句里,「给了」也同时需要「谁给的」和「给了什么」。
问题是:一次 softmax 只有一份 100%。同一行权重既要照顾「小明」又要照顾「苹果」,只能劈开分;取回来的是两个 V 按比例搅成的一个向量。
先自己设计:就用一个头
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
单头用全部 4 维一起算 q·k,「给了」这一行(因果掩码)得到一组权重。「小明」和「苹果」都重要,可权重之和只能是 100%:谁多拿一点,另一个就少拿一点。输出是 Σ w·v,两个人的信息被搅在同一个向量的同一些维度里。
把 WQ、WK、WV 按列切成两半:第 1–2 列归头 A,第 3–4 列归头 B。每个头在自己的 2 维子空间里打分、做自己的 softmax、取回自己的 2 维输出,最后拼接成 4 维。参数一个没多,乘法次数也一样,变化只在于 softmax 从「一次」变成「每头一次」。
试三件事:① 看三张图里「给了」那一行:单头是两个中等亮的格子,两个头各有一个很亮的格子,而且不在同一列;② 核对说明里那条恒等式:单头的 q·k 恰好等于两个头的 q·k 之和——同一组参数,区别只在 softmax 做一次还是做两次;③ 把提问者换成「苹果」,两个头都没有明显盯住谁——多头只是给了分开看的机会,用不用、怎么用,由训练决定。
改进:多头注意力
多头注意力就是上面做的事,放到真实尺寸:把模型宽度 d 切成 h 个头,每头 dh 维;第 h 个头用自己那一段 Q、K、V 做一遍第 2–6 课的全部步骤;h 个头的输出拼接,再乘一个输出矩阵 WO。
- 计算量不变:打分的乘法次数是 h × T² × dh = T² × (h·dh),和一个 h·dh 维的大头一样 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条原文:due to the reduced dimension of each head, the total computational cost is similar to that of single-head attention with full dimensionality 出处Vaswani et al. 2017 §3.2.2 ↗ 。
- WO 为什么必须有:拼接只是把各头的结果首尾相接,头与头之间还没「商量」过;WO 是一个可学的矩阵,把它们混合,并把宽度变回模型宽度,才能和残差相加(C4)。
- 每个头的分工不是指定的:没有人规定头 A 管人名、头 B 管宾语;训练只给了它们分开关注的机会。
锚点 Qwen3.8 一个全注意力层里,头是这样切出来、再拼回去的(先看 Q 这一路;K、V 只有 4 个头,是第 9 课的事):
| 维度 | 含义 | 真实规模 | 小规模 |
|---|---|---|---|
| T | 序列长度(贯穿例句) | 5 | 5 |
| dmodel | 模型宽度 hidden_size | 5120 | 4 |
| H | Q 头数 num_attention_heads | 24 | 2 |
| dh | 每头维数 head_dim | 256 | 2 |
| H·dh | 所有头拼起来的宽度 | 6144 | 4 |
真模型:我们自己的模型,每个头在看什么
上面的两个头是手工构造的。下面是我们从 0 训练的小模型在例句上某一层每个头的真实注意力(项目导出数据;格式见 C3 设计卡)。每个小图下面是现算的三种模式分数:平均有多少注意力落在「前一个 token」「自己」「第一个 token」上。
⚠ 常见误解
「每个头都有干净的分工,可以起名字:这个管语法,那个管指代。」对话样本在这里也提醒过:头的功能不是人工指定的,通常也不能这样干净地命名。确实有少数头的模式很纯——比如几乎只看前一个 token,或者专门在重复片段里「找上次出现之后的那个 token」(归纳头) 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条对具体模型的分析找到了 previous-token head 与 induction head 这类可识别的头;并不说明每个头都能这样命名 出处Olsson et al. 2022(Anthropic) ↗需联网 ;但一个模型里有多少头能被一个词概括,只能对具体模型逐个看。上面的真模型数据和实验课的模式分数表可以直接核对:组件会现数出我们的小模型里有几个头在某一种模式上超过 50%、几个是混合。
「头数 × 每头维数必须等于模型宽度。」这是原版 Transformer 和 GPT 系的约定:GPT-3 是 96 × 128 = 12,288 = dmodel。但它不是必须的:锚点 Qwen3.8 是 24 × 256 = 6,144,比 5,120 大;WQ 与 WO 的形状跟着改就行。我们项目的代码按旧约定写成 head_dim = n_embd // n_head,所以在项目里头数必须整除模型宽度。
「多头 = 算得更多。」参数和乘法次数都和一个同宽的大头一样(上面的组件里可以数)。多出来的只是 softmax 的次数和一次拼接。
注意力的演进,你在这里
① 训练时:一个 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 讲。
证据三问
- 它解决什么问题?让一个 token 在同一层里同时从不同的 token 取不同的信息,而不是被迫把一份注意力劈开、取回一个混合。
- 数学上为什么可能有效?单头的分数 = 各头分数之和,但 softmax 是非线性的:「先相加再 softmax」只能给出一组权重,「各自 softmax」能给出 h 组 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条组件里逐格核对了分数恒等式;权重组数的差别是 softmax 非线性的直接结果 出处待补 。这让多头「可能」更有表达力,不保证训练会用上 证据等级 · 有理论动机 有数学或机制上的理由说明它可能有效,但没有证明它一定更好。最后是否采用,仍然要看实验。 本条表达力论证,不是性能证明 出处待补 。
- 实验上为什么选了它?原文在计算量不变的前提下改头数:单头比最好的设置低 0.9 BLEU,头太多质量也下降 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条只在当时的英德翻译任务、base 模型上比较过 出处Vaswani et al. 2017 表 3(A) ↗ 。头数没有公式可推,是按规模实验选的:GPT-3 96 个、Qwen3.8 24 个。
一句话
多头 = 同样的计算量切成几份,每份各做一次 softmax,各看各的关系。
下一个缺口
注意力这一层现在完整了。接下来换一个场景:生成。模型一次只吐一个 token,吐完把它接到末尾再算下一个。第 1 个 token 的 K、V 在第 2 步、第 3 步……每一步都要用。每一步都把前面所有 token 的 K、V 重算一遍吗?→ 第 8 课:生成时,每一步都要重算前面所有 K、V 吗?
你可能会问
这一课还没有收到真实提问;你的提问会记到 05-问答记录/C3-L6.md。学到这里常见的两个疑问,各开了一节岔路课:
- 注意力能并行算完整句,可在它之前,大家用的是 RNN。为什么不用 RNN?→ 岔路:为什么不用 RNN?
- 每一行都必须分完 100%。如果一个头这次根本没东西要找呢?→ 岔路:注意力一定要「看点什么」吗?(attention sink)
🔍 为什么不能用一个大头?它的维数更多,表达力不是更强吗?
一个 h·dh 维的大头,单次打分确实能用上全部维度;可它只有一组权重,必须用同一种「相关」解释这一行里所有的关系。拆成多头相当于强制分工:每个头在自己的低维子空间里各学一种注意力模式,可视化里能看到不同的头呈现出完全不同的图案(上面的真模型数据、实验课)。代价是每个头的维数变小,q·k 的分辨率下降——所以头也不是越多越好(表 3(A) 里头太多同样变差)。