看懂一张 softmax(QK^T/sqrt(d))V 注意力图,并解释其中每个格子、每一行、每一列的含义。
没有注意力,Transformer 就没有任何途径让一个 token 的表示依赖于它前面的内容。
术语表 · 7 个术语
- query (Q)
- 由 token 经学习投影得到的向量,表达“我在找什么?”——每个 token、每个头各有一个 query 向量。
- key (K)
- 表达“如果你看向我,我能提供什么?”的学习投影——通过点积与 Q 比较。
- value (V)
- 携带实际内容的学习投影:当一个 token 被注意到时,它真正贡献出去的就是这个向量。
- attention pattern
- softmax(QK^T/sqrt(d)) 的一行:单个 query token 在更早位置上的概率分布。
- scaled dot product
- QK^T 除以 sqrt(d_head)。这个缩放让 softmax 保持在梯度不会随 d_head 增大而消失的区间。
- causal mask
- 把分数矩阵的上三角设为 -inf,使位置 i 只能注意位置 0..i。
- self-attention vs cross-attention
- 自注意力(本章)的 Q、K、V 都来自同一条运行中的序列——token 互相看彼此。cross-attention 是它来自机器翻译的同胞机制:query 来自一条序列,key/value 来自另一条。我们这条纯文本 decoder 路径只会做自注意力。
自注意力:每个 token 如何看到其他所有 token
到本章为止,我们已经把文本变成了 token(第 2 章),又把 token 变成了向量(第 3 章)。现在有趣的问题是:一个 token 是怎么知道句子其余部分的?现代 LLM 的答案是自注意力(self-attention)。
直觉
直观地说,每个 token 会扫过它前面的所有 token,把自己需要的信息拉进来——就像重读一遍句子,把注意力集中在对接下来的内容最要紧的那几个词上。
想象你是模型,有人递给你提示词 "The cat sat on the ___",让你猜下一个词。要猜得好,你必须回看前面的 token:cat 告诉你主语是一只动物,sat 告诉你它正停在某个东西上,on the 告诉你接下来要出现一个名词。自注意力就是这种“回看”的可学习、可被训练调节的版本:对序列中的每一个 token,模型决定它应该对其他每个 token 投入多少注意力。右侧面板运行的正是这条提示词,并展示它的注意力模式。
同一个词,同一个起点向量
要看清这种“回看”为什么必不可少,最干净的例子是多义词。第 3 章的 embedding 查表是不看上下文的:它只是一张按 token id 索引的表,所以无论句子讲的是金钱、飞机转弯还是河边野餐,单词 bank 取到的都是 embed_tokens.weight 的同一行。在最后一个 token 进入层堆栈的那一刻,模型完全无法区分这些含义——三个起点向量逐字节相同。
把这些含义拉开,正是上面各层的全部工作;而你可以在流水线的另一端看到结果:给这个模型三条都以同一个 token 结尾的 prompt,它对下一个 token 的押注会完全分岔。有一点要说在前面——在这个模型里,消歧义的工作由 6 个全注意力层和18 个 GatedDeltaNet 线性注意力层(第 12 章)共同分担,所以“是注意力做到的”只是“是混合上下文的层堆栈做到的”的简写。
三条 prompt 都以字节完全相同的 token ·bank 结尾——embedding 查表每次返回 embed_tokens.weight 的同一行,所以层堆栈在这个位置拿到的起点向量一模一样。
- ·account0.351
- .0.275
- ,0.241
- ·and0.036
- ·in0.022
模型把 ·account 排到第 1 名——它读懂了“房租从哪里扣”的上下文,而不是只看这个词本身。
- ·angle0.277
- ,0.188
- ·at0.160
- .0.126
- ·and0.111
·angle 跃居第 1(“bank angle” 是航空术语“坡度角”)——这个续写在另外两种上下文里完全不会浮现。
- .0.468
- ,0.224
- ·and0.051
- ·in0.049
- ·where0.040
在这里模型基本认为句子已经说完(“.” 占 47%),位置词如 ·where 排在更后面。
真实的录制输出,并非人工编写:每张列表都来自把这些 prompt 原样(裸补全,不套 chat 模板)送进本站第 10 章的 LM-head 检查面板——与课程其余部分完全相同的 Qwen3.5-0.8B 检查点——它会捕获最后一个位置上 top-12 的下一 token logits;图中概率是对这些已捕获 logits 做 softmax 的结果,与检查面板的显示方式一致。“同一个起点向量”这一点是由构造保证的:embedding 查表只按 token id 取行,而三条 prompt 的最后一个 token id 都是 5883。
Q、K、V——同一个向量的三个投影
每个 token 以一个向量 x 的形式进来(它的嵌入,经过前面各层加工后的结果)。模型用三种不同的方式对 x 做线性投影(这里的“投影”只是指用一个学习到的矩阵乘以该向量,得到一个新向量):
Q = x · Wq——query(查询):这个 token 在找什么?K = x · Wk——key(键):如果你看向这个 token,它能提供什么?V = x · Wv——value(值):当这个 token 被注意到时,它应该贡献什么?
三者都是长度为 d_head 的向量(即每个头的维度——原始论文 “Attention Is All You Need” 称之为 d_k,模型配置里叫 head_dim)。关键在于,这并不是三个不同的 token——而是同一个 token 的三种不同视角,分别独立学习,让注意力能做出比“直接比较嵌入”更有意思的事。在这个模型里:x 是 1,024 个数 → 8 个长度 256 的 query 向量,外加 2 组长度 256 的 key/value(8 对 2 的安排是 GQA,下一章讲)。
初学者还必须搞清楚一点:同样的三个矩阵 Wq、Wk、Wv 应用于每个位置上的每个 token——它们只学习一次、整条序列共享,所以投影本身不携带 token 位于哪里的任何信息;位置信息是单独注入的(由 RoPE 完成,第 6 章)。
趁热说一句命名:因为 Q、K、V 都来自同一条运行中的序列,整章讲的都是自注意力。它的同胞机制是 cross-attention——query 来自一条序列,key/value 来自另一条;最初的用途是机器翻译,由 decoder 的 query 去读 encoder 的 key。不过 cross-attention 在这整个模型家族里都不会出现,哪怕是在完整的多模态 checkpoint 里也不会:视觉信息是通过早期融合(early fusion)接入的——投影后的图像 patch token 会被直接拼接进和文本 token 相同的那条自注意力序列里,而它在本演示里是关闭的——这里 6 个全注意力层没有一个在做 cross-attention。
公式
对单个头来说,核心运算就是一行数学:
我们从左往右读。Q · Kᵀ(ᵀ 表示行列互换——含义是“拿每个 query 与每个 key 做点积”)是一个 [seq_len, seq_len] 矩阵:格子 (i, j) 是 token i 的 query 与 token j 的 key 的点积——一个衡量“i 的提问与 j 的供给有多匹配”的原始分数。
除以 √d_head 是出于数值上的原因:随着 d_head 增大,点积平均也会变大;不做缩放,softmax 就会饱和(一个格子是 1.0,其余接近 0.0),梯度也会消失(梯度是调节权重的训练信号——训练一章会讲;现在觉得模糊没关系)。除以随机点积的标准差(≈ √d_head)能让分数保持在 softmax 仍有信息量的区间。
下面的小部件把这句话变得可感。拖动头维度,观察同一组 key 的对齐情况:不缩放时,分布随着 d_head 增大坍缩到单个 key 上;加上 ÷√d_head 缩放后,它在任何宽度下都保持完全一致。
softmax(U · √d)——随 d 增大峰值越来越高;到 256 时接近 one-hot。
softmax(U · √d / √d) = softmax(U)——在每个 d 下都完全相同。
随着 d_k 增大,随机点积的离散程度按 √d_k 增长,于是未缩放的分数会爆掉,softmax 坍缩到单个 key 上——梯度消失。除以 √d_k 恰好抵消这一增长,所以缩放后的分布不随头宽变化。Qwen3.5-0.8B 的 head_dim = 256,所以它把每个分数都除以 √256 = 16。
仅作示意——六个 key 的对齐分数是固定的合成数字,按 √d 放大,并非模型的实时输出。
softmax 把分数矩阵的每一行变成一个概率分布:每行之和为 1。这就是每个 token 的注意力模式(attention pattern)。再用这个 [seq_len, seq_len] 分布乘以 V,就是按该模式加权地把各个 value 混合起来——这一混合结果,就是注意力层输出的 token i 的新表示。
有一点很容易想错:这个混合后的 value 向量并不是对 token 的整体替换。它是一个小小的修正,会被加回到 token 带进来的那个向量上——注意力是往运行中的表示上写一个增量(delta),而不是把它覆盖掉。这个运行中的表示有个名字,叫做残差流(residual stream);“只加不替换”这条规则,是这个模型里每一个 block 都围绕着接线的那道接缝,完整 Transformer block 一章会展示这套接线。
这个 [seq_len, seq_len] 矩阵也正是注意力变贵的地方。三篇可选的深入阅读再往下挖一层——讲它的内存开销、怎么给这份开销一个具体的数字,以及绕开开销的技巧。第一遍阅读都可以跳过。
因果掩码——以及它为何是 decoder LLM 的承重墙
到目前为止定义的自注意力是对称的:每个 token 都能看到其他所有 token。对编码器(想想 BERT——完形填空类任务)来说没问题。但对生成式 LLM 是一场灾难。训练的全部意义在于根据前文预测下一个 token。如果模型在训练时被允许看后面的内容,它直接照抄就能绕过整个问题。
所以在 softmax 之前,我们把 QKᵀ 的上三角设为 -∞(等价地,加上一个对角线及以下为 0、对角线以上为 -∞ 的掩码矩阵,再逐行 softmax)。softmax 之后这些格子恰好变成 0——token i 只能注意 key 0 … i。右侧热力图呈下三角正是这个原因。最下面一行——提示词的最后一个 token——是唯一能看到整条提示词的一行,所以下一个 token 的预测由那一行产生。
下面的开关小部件把这一点变得具体:把掩码关掉,看模型对第一个 token 的注意力伸向序列的未来。
下面的矩阵是 softmax(QKᵀ / √d),使用与上方小部件相同的五个固定分数。把掩码关掉,盯住高亮的那一行——"The" 的 query——看它开始关注还没发生的 token。
注意力权重——已应用因果掩码 | |||||
| The | ·cat | ·sat | ·on | ·the | |
|---|---|---|---|---|---|
| The | 1.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| ·cat | 0.23 | 0.77 | 0.00 | 0.00 | 0.00 |
| ·sat | 0.07 | 0.29 | 0.64 | 0.00 | 0.00 |
| ·on | 0.22 | 0.12 | 0.18 | 0.48 | 0.00 |
| ·the | 0.20 | 0.12 | 0.06 | 0.17 | 0.45 |
"The" 的 query)只有一个位置可看——它自己。它的预测只能依靠 "The" 本身告诉模型的信息。这正是模型训练时所处的状态:每一次下一 token 预测都在答案可见之前做出。这一个三角形掩码,是让训练保持诚实的唯一机制。它在推理时毫无代价(decode 步骤甚至从不计算上三角),在教学上却举足轻重——它正是 encoder 与 decoder Transformer 之间那个结构性区别。
一个微妙但重要的推论:正是这个掩码让并行训练成为可能。掩码就位后,你可以把整条序列一次性塞进模型,在一次前向传播里同时计算每个位置的损失——因为谁也看不到前方,每个位置的预测彼此独立。没有掩码,你只能一个一个 token 地喂。这种三角形的稀疏模式,是让 decoder-only 训练在大规模下可行的那个唯一的架构决策。
热力图里每个格子的含义
右侧热力图正是某一层、某一个头的 softmax(QKᵀ/√d) 矩阵:
- 行是 query(发起注意的 token)
- 列是 key(被注意的 token)
(i, j)处的亮格子表示“在计算 token i 的新表示时,模型大量提取了 token j 的 value”- 每行之和为 1;由于因果掩码,上三角为 0
看看热力图下方扇出图画出的那些长边:一次全注意力就能把很靠后的 token 的内容直接送到当前位置,无论中间隔着多少 token。在这里距离是免费的——只有相关性才有代价,因为分数是点积,并不是两个 token 间距的函数。(这种“免费触达”说的是 6 个全注意力层;18 个 GatedDeltaNet 层则改为通过一份滚动状态往回触达,KV 缓存一章会讲为什么模型两者都需要。)
为什么要多个头、多个层
不同的头最终会各有分工——一个可能跟踪“上一个 token”,一个跟踪“句子的开头”,另一个跟踪“句法上相关的名词”。堆叠注意力层让后面的层把这些模式组合起来:第 5 章看头,第 9 章看完整的堆叠。
关于这个模型的一个细节:它是混合架构。只有每第 4 层——24 层中的 6 层——使用你一直在读的完整 softmax 自注意力。其余 18 层使用更便宜的线性注意力变体(GatedDeltaNet),它维护固定大小的滚动状态,而不是对所有过去的 token 做注意力(KV 缓存一章会讲)。右侧的实时热力图反映了这一点:把层滑块拨到 6 个全量注意力层之一就能看到真实分数;落在线性层上时,面板只会提示该层不输出 softmax 注意力矩阵。
关于头滑块的说明:这个模型有 8 个 query 头,但这些头共享的 key/value 组只有 2 个——这就是分组查询注意力(GQA),下一章展开。现在只需把每个头当作各自独立的注意力模式来读。
手算一个微型例子
矩阵代数配上具体数字更容易找到感觉。下面的小部件带着两个 token——“The” 和 “cat”——走完注意力公式的每一步。所有数字都由固定的 Q、K、V 算出;逐步点击,看分数矩阵、缩放、掩码、softmax 和最终输出依次出现。
两个 token,每个是一个小小的 2 维向量。
x[The] = [1.00, 0.20]
x[cat] = [0.30, 0.90]W_Q 和 W_K 是单位矩阵;W_V 不是,这样最终的混合才看得见。
Q[The] = [1.00, 0.20] Q[cat] = [0.30, 0.90]
K[The] = [1.00, 0.20] K[cat] = [0.30, 0.90]
V[The] = [0.54, -0.16] V[cat] = [0.33, 0.54]每个格子 (i, j) 是 Q[i] 与 K[j] 的点积——对应位置相乘再相加。展开其中一格:Q[cat]·K[The] = 0.3×1.0 + 0.9×0.2 = 0.48。
| K[The] | K[cat] | |
|---|---|---|
| Q[The] | 1.04 | 0.48 |
| Q[cat] | 0.48 | 0.90 |
d_k = 2,sqrt(d_k) = 1.414。把每个格子都除一遍。
| K[The] | K[cat] | |
|---|---|---|
| Q[The] | 0.74 | 0.34 |
| Q[cat] | 0.34 | 0.64 |
token “The” 不能看到 “cat”(它出现在后面)——把右上角的格子设为 -inf,softmax 之后它就消失了。
| K[The] | K[cat] | |
|---|---|---|
| Q[The] | 0.74 | -∞ |
| Q[cat] | 0.34 | 0.64 |
每一行都变成一个概率分布。“The” 这一行只有一个有效的 key,所以 100% 关注自己。“cat” 这一行把注意力按 0.43 / 0.57 分给两个 token。
| K[The] | K[cat] | |
|---|---|---|
| Q[The] | 1.00 | 0.00 |
| Q[cat] | 0.43 | 0.57 |
用注意力权重混合各个 value 向量。“The” 的输出恰好就是 V[The];“cat” 是 V[The] 与 V[cat] 的加权混合。
out[The] = [0.54, -0.16]
out[cat] = [0.42, 0.24]因果掩码,逐格看
同一个掩码的第二种视角:一个 5×5 网格,点击任意格子,看“i 能看到 0..i”对这条提示词具体意味着什么。
K 0 The | K 1 ·cat | K 2 ·sat | K 3 ·on | K 4 ·the | |
|---|---|---|---|---|---|
Q 0 The | |||||
Q 1 ·cat | |||||
Q 2 ·sat | |||||
Q 3 ·on | |||||
Q 4 ·the |
训练时,掩码让 teacher forcing 变得安全:我们把整句并行喂入并预测每个下一 token,但任何位置都看不到自己的未来答案。没有掩码,下一 token 预测就退化成简单的抄写。
推理时,同样的掩码仍然在用,哪怕 token N + 1 物理上还不存在——这让训练与推理的数学完全一致,而这正是 KV 缓存(第 12 章)成立的前提。
在右侧点击 Run。模型预测的下一个 token 会带着一道彩虹流光出现在提示词末尾,热力图展示的是产生该预测的那次前向传播中真实的 softmax 后注意力分数。编辑提示词,或者换一个层/头,观察模式(和预测)如何变化。
- 热力图的每一行是一个 query token 的注意力分布;softmax 之后每行的和恒为 1。
- 下三角形状不是装饰——它是因果掩码,是让训练保持诚实的唯一机制,也是所有位置能够并行训练的原因。
- 除以 sqrt(d_head) 的缩放,让注意力在头变宽时不会令 softmax 坍缩到单个格子上。
在默认提示词 “The cat sat on the” 上点击 Run,然后在热力图里看最下面一行(预测之前的最后一个 token)。哪个更早的 token 拿到的注意力最多?这说明你正在看的这个头在做什么?