注意力:每个位置自己查一次表
第 6 章那台小网络只能看固定的 8 个字符,把它们拼成一长条硬塞进去。GPT-2 要看前面 1023 个 token,而哪一个重要,每一句话都不一样。解决办法叫注意力:让每个位置自己决定回头去读谁、读多少。这一章打开 GPT-2 的 144 个注意力头,看几个真的:一个只盯着上一个 token 的,一个替你找出「谁该拿到那杯饮料」的——以及一个看得最认真、却在唱反调的。
GPT-2 有 12 层,每层 12 个注意力头,一共 144 个。每个头在每个位置,都会把「注意力」分配给它前面的所有 token,加起来是 100%。
第 4 层的第 11 个头,在一篇它从没见过的文章(第 4 章那篇)的 1024 个 token 上,平均把多少注意力分给了「紧挨着的上一个 token」?
答案在本章末尾。
为什么需要「回头看」
看这句话:When Mary and John went to the store, John gave a drink to。下一个词该是谁?人一眼就知道是 Mary——John 是给东西的人,两人里另一个才是接收者。可要做出这个判断,读到最后那个 to 的时候,得回头找到 12 个 token 之前的 ␣Mary,还得知道 ␣John 出现过两次。
第 6 章那种「把前面几个 token 拼起来」的做法在这里行不通:拼 1023 个向量,网络会大到没法训练,而且第 3 个位置上的 Mary 和第 10 个位置上的 Mary 会被当成两回事。需要的是一种不管距离多远、不管在第几个位置,都能按内容找到要的东西的机制。
一次「模糊」的查表
注意力的做法,像是在每个位置上做一次数据库查询,只不过是模糊的:
- 每个位置把自己的向量变换出三样东西:一个查询(query,「我在找什么」)、一个键(key,「我这儿有什么」)、一个值(value,「如果你要我,我给你这个」)。三个变换都是学出来的矩阵。
- 当前位置拿自己的查询,和前面每个位置的键做点积——第 5 章说过,点积量的是方向有多像。越像,分越高。
- 把这些分数过一次 softmax,变成一组加起来是 1 的权重。
- 按权重把前面各位置的值加权混合,取回来。
和真正的数据库查询比:数据库是「键完全匹配就返回那一行」,注意力是「每一行都按像不像打个分,返回所有行的加权平均」。像的多拿,不像的少拿。整个过程每一步都可以求导,所以「该查什么、该亮出什么」全都能用第 6 章的反向传播训出来。
GPT-2 每层有 12 个这样的查询器,叫 12 个头(head),各自有自己的三套矩阵、各查各的——一个可能负责「找上一个词」,另一个负责「找主语」。每个头只用 64 维(768 ÷ 12),12 个头的结果拼回 768 维,再写回去。12 层 × 12 个头 = 144 个头。下面挑几个看。
一个最简单的头:只看上一个
把第 4 章那篇文章的前 1024 个 token 送进去,量每个头平均把多少注意力分给了「紧挨着的上一个 token」:
| 头(层.编号,从 0 数) | 分给上一个 token 的注意力 |
|---|---|
| 4.11 | 99.4% |
| 2.2 | 55.5% |
| 3.7 | 38.3% |
| 2.9 | 33.5% |
| 3.2 | 33.3% |
第 4 层第 11 个头,在一千多个位置上平均把 99.4% 的注意力放在上一个 token 身上。它是一个近乎固定的「搬运工」:把上一个 token 的信息搬到当前位置来。这种事为什么需要专门一个头?因为每个位置的向量一开始只知道自己是谁,不知道前面是谁;有了这个头,第 t 个位置上就写着「我前面那个是 X」。第 12 章会看到,这一点点信息,是 GPT-2 「现学现卖」能力的一半零件。
一个聪明的头:谁该拿到那杯饮料
回到那句话。GPT-2 small 的候选表:
Mary 44.60%,排第一;John 只有 6.00%。它做对了。是谁做的?在最后那个 ␣to 的位置上,看各个头把注意力分给了谁:
| 头 | 看 Mary | 看两个 John |
|---|---|---|
| 10.7 | 58.2% | 23.3% |
| 11.10 | 52.9% | 31.3% |
| 9.6 | 49.2% | 17.3% |
好几个后面几层的头,都从 ␣to 这个位置把目光投向了 Mary,而且明显多于两个 John 加起来。2022 年 Redwood Research 的一篇论文(《Interpretability in the Wild》)专门拆过 GPT-2 small 里解这类句子的整套电路,把这些头叫做「搬名字的头」:它们找出「出现过但还没当过主语的那个名字」,把它搬到当前位置,直接推高它的概率。
但这张表里藏着一个陷阱。下一章会把最终的那个判断——「Mary 比 John 高多少」——拆回到每个头身上,逐个算账。拆出来的结果是:9.6 号头确实在帮 Mary(+1.16),而看 Mary 看得最多的 10.7 号头,在给 Mary 减分(−1.03);11.10 号也一样(−0.57)。那篇论文给它们起了个名字:反向搬名字的头——它们去看 Mary,是为了把 Mary 往下压一压,像是在给其他头的过度自信踩刹车。一个头看向哪里,和它往哪个方向使劲,是两回事。
它天生不知道顺序
注意力有一个结构上的盲点:它只按内容查找,不看位置。把一组输入的顺序打乱,送进一层注意力(不加遮罩、不加位置信息),输出会跟着同样打乱,而每个向量本身一点都不变——本机实测,打乱前后对应位置的最大差值是 0。对它来说,「dog bites man」和「man bites dog」是同一袋词。
所以 GPT-2 在第一步查完词嵌入之后,还要给每个位置加上一个位置向量:第 0 个位置一个、第 1 个位置一个……一共 1024 个,也是学出来的(这张表占了 786432 个参数)。拿掉它会怎样?同一篇文章:
正常 4.53 比特/token 拿掉位置向量 ⟦10.79 比特/token⟧
意外翻了一倍多。模型的每一层都默认「位置信息已经混在向量里了」,突然抽掉,它连哪个词在前都分不清。第 19 章会看到,今天的模型换了一种注入位置的方式(叫 RoPE,直接把位置「转」进查询和键里),但「注意力本身不知道顺序、必须另外告诉它」这一点没变。
注意力里的一个怪现象:倒在第一个 token 上
还有一件事是在量这些头的时候顺便发现的。看每一层的头平均把多少注意力分给了整段文字的第一个 token(从第 50 个位置往后算,免得被开头几个位置干扰):
层 0 1 2 3 4 5 6 7 8 9 10 11 第一个 0% 1% 11% 31% 35% 53% 48% ⟦58%⟧ 48% 49% 43% 34%
中间几层,平均一半左右的注意力落在了第一个 token 上——而那个 token 只是文章的第一个词「Chat」,和第 800 个位置在想什么毫无关系。这是什么?
答案在 softmax 里:权重必须加起来是 1,一个头不能选择「这一次什么都不看」。当一个头在某个位置上没什么可找的,它需要一个地方把注意力「倒掉」。训练学会了用第一个位置当垃圾桶:它的值向量被学成了几乎不影响结果的东西,倒在那里就等于没看。这个现象后来被叫做注意力汇(attention sink),2023 年的一篇论文(StreamingLLM)还利用它做了一个工程技巧:长对话里永远保留最开头那几个 token,模型就不会崩。
它很贵
最后一笔账。1024 个 token,每个头要给每一对(位置,前文位置)打一次分:1024 × 1024 = 1048576 个分数(因果遮罩挡掉其中一半),144 个头都要算。上下文长度翻一倍,这一项就翻四倍。注意力的代价随上下文长度平方增长——这是「长上下文」一直贵的原因,第 15 章算这笔账。
让 transformers 把每一层的注意力矩阵交出来(要用 eager 实现,默认的快速实现不保留这些矩阵):
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
tok = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelForCausalLM.from_pretrained("gpt2", attn_implementation="eager").eval()
x = torch.tensor([tok.encode(open("chatgpt-2026-10-21.txt").read())[:1024]])
with torch.no_grad():
att = model(x, output_attentions=True).attentions # 12 层,每层 (1, 12 个头, 1024, 1024)
T = x.shape[1]
prev = torch.stack([a[0, :, torch.arange(1, T), torch.arange(T - 1)].mean(-1) for a in att])
for i in prev.flatten().topk(3).indices.tolist():
L, h = divmod(i, 12)
print(f"第 {L} 层第 {h} 个头:平均 {prev[L, h]:.1%} 的注意力给了「上一个 token」")
first = [a[0, :, 50:, 0].mean().item() for a in att] # 从第 50 个位置往后,平均有多少落在第一个 token 上
print("各层落在第一个 token 上的注意力:", " ".join(f"{v:.0%}" for v in first))
本机输出:
第 4 层第 11 个头:平均 99.4% 的注意力给了「上一个 token」 第 2 层第 2 个头:平均 55.5% 的注意力给了「上一个 token」 第 3 层第 7 个头:平均 38.3% 的注意力给了「上一个 token」 各层落在第一个 token 上的注意力: 0% 1% 11% 31% 35% 53% 48% 58% 48% 49% 43% 34%
想看那句 Mary 和 John 的注意力,把输入换成那句话,看 att[10][0, 7, -1]——第 10 层第 7 个头在最后一个位置上的那一行。
pip install torch transformers && python 09.py
可视化工具:BertViz 能在 Jupyter 里把每个头的注意力画成连线图;Colab 里装好就能用。
- 「注意力热力图」常被当成模型的解释——「你看,它关注了这几个词,所以它是这么想的」。这一章的两个现象(倒在第一个 token 上的那一半、看得最多却在唱反调的头)都说明:注意力权重只告诉你信息从哪里被搬过来,不告诉你它被拿去干了什么。
- 长上下文里「中间的内容容易被忽略」,在实际使用中被反复观察到。把最关键的指令放在开头或结尾,是一条朴素但有效的经验。
- FlashAttention 这类优化不改变注意力算的是什么,只是重新安排计算顺序,避免把那个 1024 × 1024(或更大)的矩阵整个写进显存——平方级的东西,省内存比省计算更要紧。
「注意力权重高的地方,就是模型认为重要的地方,也是它推高的那个答案。」
两件事都不成立。第一,中间几层平均有一半左右的注意力倒在第一个 token 上(第 7 层是 58%),那个 token 什么内容都不提供——高权重可能只是「没地方去」。
第二,在 Mary 那句话里,看 Mary 看得最多的 10.7 号头(58.2%),对「Mary 胜过 John」的贡献是 −1.03:它在压低 Mary。注意力只决定从哪里取信息;取回来之后往哪个方向写,由这个头的另一组矩阵决定。要知道一个部件在帮谁,得像下一章那样拆账。
正确答案是 D:4.11 号头平均把 99.4% 的注意力给了上一个 token。
排第二的 2.2 号头是 55.5%,再往后就只剩三成多了。
《张成》第 22 章「Attention 就是三次矩阵乘」从线性代数的角度把这一章的四个步骤写成了矩阵:查询、键、值各一次乘法,加一次 softmax。《当真》里讲的查询计划器是精确查找;注意力是它的「模糊」版本——不选一行,按相似度混合所有行。
这一章的一句话
注意力是一次模糊的查表:每个位置用自己的查询去比前面所有位置的键,按相似度把它们的值混合着取回来。每个头学一种查法;它们只按内容找、天生不知道顺序;而一个头看向哪里,不等于它往哪个方向使劲。
注意力头把信息取回来之后,写到哪里去?下一章看 GPT-2 的「总线」:每一层都只是往同一条线上加一笔。所以你可以整层删掉中间某一层——删掉第 1 层(层号从 0 数),每个 token 的意外只从 4.53 比特变成 4.56 比特,多了 0.6%。