06 课 · 自注意力
LESSON 06 · 卷六 注意力登场

让每个字环顾全班

零件齐了:词向量(第 3 课)、矩阵乘法(第 4 课)、位置指纹(第 5 课)。 现在把它们装成第一个真正的机制——自注意力。 要解决的问题只有一句话:一句话里的每个字,凭什么知道该多看谁一眼?这一课我们造出 Q、K、V 三个角色,用第 4 课那把尺子给全班打分, 把分数换成加起来 100% 的百分比,再按百分比把大家的内容收集回来。 全程 8 维、6 个字,每一个数都能手算核对。

STAGE 01
一把没方向的尺子
「谁像谁」不等于「该看谁」
STAGE 02
三个分身
Q 提问、K 挂牌、V 交货
STAGE 03
打分与换算
Q·K → softmax → 百分比
STAGE 04
取货
按百分比收集 V,整句跑一遍

先别急着看公式。下面这个动画把这一课要讲的东西整个演了一遍: 点「自动播放」,看这一排字是怎么互相「看一眼」的。 数字看不过来就点「数字:藏起来」,只看线的粗细 —— 线越粗,说明那个字被看得越多。

招牌动画环顾全班 —— 点任意一个字当主角,四步看完自注意力
名牌 K问题 Q这个字内容 V?我的问题 的内容 的内容 的内容 的内容 的内容? 的内容??」收集前后(8 个数)浅 = 收集前 深 = 收集后
· 待机下面 6 个字排排坐。点一个字让它当主角,再点「下一步」,看它怎么环顾全班 —— 整个过程只有四步:亮出分身 → 打分 → 换成百分比 → 按百分比取货。
谁当主角主角此刻最关注「」(25.7%)
整个过程只有四步:亮出三个分身 → 打分 → 换成百分比 → 按百分比取货。 后面的六站,就是把这四步拆开、一行一行算给你看。
动画里那一行数字,和下面每一站表里的数分毫不差 —— 都是同一份数据算出来的
注意力就是——
每个字都问一遍
再按关注度把大家的内容收回来。

三句话讲完自注意力

  • 每个字先准备三样东西:要问的问题(Q)、挂出来的名牌(K)、 真正要交出去的内容(V)。三串数都是同一个词向量乘三张矩阵长出来的。
  • 拿自己的问题,去和所有人的名牌对一遍(点积),得到「谁最对胃口」, 再把这一行分数换成加起来 100% 的百分比。
  • 按百分比,把每个人的内容各取一份回来,拼成自己的新内容。 谁被看得多,谁的内容就搬得多。
换个熟悉的地方想 · 在图书馆找资料

如果「注意力」这个名字让你发懵,就换成图书馆:你带着一个检索词走进去,从一排书里挑几本借走。三样东西一一对应:

Q(问题)= 你手里的检索词:我在找什么
K(名牌)= 每本书书脊上的标签:我是本什么书
V(内容)= 书里真正的内容:你能借走的东西

检索词和标签对得上(点积分数高),这本书就多借几页; 对不上就少借、甚至不借 —— 但借回来的,永远是 V,不是标签。

「自」注意力就是:这排书互相去借——每个字既是借书的人, 也是被别人借的书。下一站开始,我们把这三样东西造出来。

第 1 站

一把没有方向的尺子

第 4 课我们算过一张 20 × 20 的相似度表:第 (i, j) 格是「第 i 个字」 和「第 j 个字」的点积。那张表回答的是「谁像谁」

可注意力要回答的是另一个问题:「我该看谁一眼」。 这一课的场景很具体——一句话「三加五等于?」进来, 「?」那个座位上的字,得知道该从「三」「加」「五」里搬走什么信息,才能猜出答案。 它凭什么决定?

最偷懒的答案是「谁跟我像,我就看谁」。这个答案会立刻撞上三个麻烦。 先看第一个,也是最要命的那个:相似度表是对称的

核心实验两张表摆在一起 —— 同一对字,两个方向的分数
① 用字向量自己点自己第 4 课那张「谁像谁」的表
??
第 (0,1) 格 0.092100 / 第 (1,0) 格 0.092100 —— 差 0.000000
② 用 Q · K 打分这一课要用的那张表
??
第 (0,1) 格 -1.122200 / 第 (1,0) 格 0.920287 —— 差 2.042487
越蓝 = 分越高越黄 = 分越低点任意一格,看它和「镜像那一格」是不是同一个数
同一个格子、同一个位置:左表里「」看「」和「」看「永远是同一个数(全表最大差 0.000000); 右表里这两个数已经变成了 -1.120.92, 全表最大差 4.314716
「谁像谁」是一把没有方向的尺子 —— 它量不出「谁在问、谁在答」。 而注意力要的恰恰是这个:每个字都得喊出自己的问题,再去看谁的名牌对得上。
注意左表还有一个毛病:它的第 i 行只由第 i 个字自己决定, 和「这句话里有哪些字」没关系。换一句「十减四等于?」, 那一行还是这 6 个数。
👆 两张表都是 6 × 6,字一样、位置一样,唯一的区别是打分用的东西: 左边是字向量本身,右边是字向量乘过 Wq、Wk 之后的 Q 和 K。
左表用字向量打分,右表用 Q·K 打分。点任意一格,它和镜像那一格会被一起标出来: 左边永远是两个相等的数,右边不是。
图 6-1 · 左边那张表全表最大镜像差 0.000000(完全对称);右边是 4.314716

「对称」听起来是个中性词,放在这里却是致命的:它意味着「谁在问」和「谁在答」是同一个角色。可语言里这两件事从来不一样——「」需要从「」那里知道 「该做加法了」,而「」并不需要从「」那里知道这个。 左边那张表给不出这种区别:它算出来的两个数是同一个。

第二个麻烦:左边那张表的第 i 行,只由第 i 个字自己决定, 跟这句话里有哪些字毫无关系。「」在「三加五等于?」里的那一行, 和在「十减四等于?」里的那一行,是一模一样6 个数。 可它在这两句里该看的东西完全不同。

第三个麻烦:每个字只会找「跟我像的字」,不会找「我需要的信息」。「?」这个座位上的字,它自己长得跟谁都不像——它需要的不是「像」,而是「答案该从哪几个字里来」。

一把尺子量不出方向,
那就别用一把尺子。
打分这件事本身是对的(点积、第 4 课那把尺子)。 那最小的一步修补是什么,才能让分数带上「谁在问、谁在答」的方向?
第 2 站

一个向量,三个分身

现在把「准备三串数」这件事落到最实在的做法上。 每个字手上只有一份东西——第 3 课学到的词向量 x。要长出三串不同的数,只有一个办法:乘三张不同的矩阵

第 4 课你已经很熟了:一行 x 乘一张矩阵,一次算出一整行数。 这里就是把它做三遍,只不过三张矩阵各管一件事:

Q = x · Wq 我在找什么
每个字喊出的问题。它要和别人的名牌做点积,所以它决定了「我看重什么」。
K = x · Wk 我挂出的名牌
每个字对外挂出的招牌:「我是谁、我能提供什么」。它只负责被别人的问题匹配。
V = x · Wv 我要交出去的内容
真正会被搬走的东西。它不参与打分,只在最后按百分比被取走。
核心实验分身机 —— 挑一个字、挑一维,看那一个数怎么算出来
x(
0.67-0.29-0.31-1.050.240.530.12-0.09
这是「」在第 3 课学到的词向量:8 个数。它要被三个角色各用一次 —— 乘三张不同的矩阵,长出三串不一样的数。
看第几维怎么来的
Q = x · Wq我在找什么
0.090.72-0.33-0.120.97-0.390.080.79
Q 的第 0 个数 = x · (Wq 的第 0 列)
0.67 × -0.34+-0.29 × 0.19+-0.31 × -0.50+-1.05 × -0.45+0.24 × -0.05+0.53 × -0.43+0.12 × -0.40+-0.09 × -0.35
= -0.227800 + -0.055100 + 0.155000 + 0.472500 + -0.012000 + -0.227900 + -0.048000 + 0.031500 = 0.088200
K = x · Wk我挂出的名牌
-0.87-0.11-0.300.71-0.14-0.080.13-0.30
K 的第 0 个数 = x · (Wk 的第 0 列)
0.67 × -0.11+-0.29 × 0.37+-0.31 × 0.28+-1.05 × 0.36+0.24 × 0.22+0.53 × -0.39+0.12 × -0.48+-0.09 × 0.11
= -0.073700 + -0.107300 + -0.086800 + -0.378000 + 0.052800 + -0.206700 + -0.057600 + -0.009900 = -0.867200
V = x · Wv我真正要交出去的内容
0.220.500.160.220.76-0.150.090.39
V 的第 0 个数 = x · (Wv 的第 0 列)
0.67 × 0.00+-0.29 × -0.26+-0.31 × 0.06+-1.05 × -0.12+0.24 × -0.29+0.53 × 0.33+0.12 × -0.49+-0.09 × 0.12
= 0.000000 + 0.075400 + -0.018600 + 0.126000 + -0.069600 + 0.174900 + -0.058800 + -0.010800 = 0.218500
同一个「」,一份词向量,出来三串完全不同的数: Q = [0.09, 0.72, -0.33, -0.12, 0.97, -0.39, 0.08, 0.79]; K = [-0.87, -0.11, -0.30, 0.71, -0.14, -0.08, 0.13, -0.30]; V = [0.22, 0.50, 0.16, 0.22, 0.76, -0.15, 0.09, 0.39]。
Q、K、V 不是什么新东西,就是第 4 课那条矩阵乘法: 一行 x 乘一张矩阵,一次算完 8 个数。真正新的是「为什么要三张矩阵」—— 一张只管提问、一张只管挂牌、一张只管交货,三种活互不干扰。
👆 换一个字、换一维,三张卡里的数都会跟着变。 每一维都能照着一行 8 个乘积手算核对(印出来的数由印出来的数推)。 真实模型里 Wq、Wk、Wv 比这大得多:3584 × 3584,而且是训练出来的 —— 这里固定成一组演示矩阵,方便手算。
三张卡里都是同一个 x,出来三串完全不同的数。每一维都能照着 8 个乘积手算核对:印出来的数由印出来的数推。
图 6-2 · Q = x·Wq、K = x·Wk、V = x·Wv:一次算一整行,8 个数各由 8 个乘积加出来

举个可以按计算器核对的小例子。第 2 站里「」的词向量第一维是 0.67, 而它的 Q 的第一个数是 0.09—— 这个数就是 x 的 8 个数和 Wq 第 0 列的 8 个数逐项相乘再相加的结果。 三张矩阵共 3 张、每张 8 × 8 = 64 个数, 加起来 192 个可训练参数——只是演示规模。

真实模型里这三张矩阵有多大
Qwen2.5-7B:宽度 3,584(= 28 个头 × 128 维)
Wq、Wo(输出那张):3,584 × 3,584 = 12,845,056 个数,各一份
Wk、Wv:3,584 × 512 = 1,835,008 个数,各一份

四张加起来 = 29,360,128 个数 ≈ 0.29 亿, 和第 2 课账本里那一行分毫不差

注意 Wk、Wv 为什么比 Wq 窄:28 个头共用同样几组 K、V(这叫 GQA), 所以它们只要 512 宽就够。省下来的正是「长文本最贵的那部分」。

面试常问 · 为什么非要三个矩阵,一个不够吗
  • 方向性:只要「提问」和「挂牌」用不同的矩阵,打出来的分数表就不再对称, 「谁在问、谁在答」这才分得开(第 1 站那张右表)。
  • 角色分离:一个向量同时当「问题」和「内容」会互相拖累。 分开之后,模型可以学「我该看重什么」和「我该交出什么」两件独立的事。
  • 表达力:三张矩阵 = 三组可训练参数,模型能把 x 投影到三个不同的子空间里, 各干各的活。这也是「多头注意力」的基础(第 7 课)。
第 3 站

打分:我的问题,对上你的名牌

有了 Q 和 K,打分这件事就回到了第 4 课那条老规则上:点积。「」拿着自己的 Q,去和 6 个字的 K 各做一次点积, 得到 6 个分数——这就是它在决定「该看谁」。

形状也是第 4 课算过的:Q 是 [6, 8]、K 也是 [6, 8], 要让「行点列」,就得把 K 躺下来(转置): [6, 8] · [8, 6] → [6, 6]。 一次乘法,36 个分数全部到位。

核心实验分数表 —— 点一格,看那 8 个乘积怎么加出来
??
分数为正 = 名牌对得上分数为负 = 对不上行 = 「谁在问」(Q),列 = 「谁挂的名牌」(K),全表 6 × 6 = 36 个分数
」问「」:Q() · K() = 4.049102
1.11 × 0.77=0.848311+1.10 × -0.25=-0.273985+0.48 × -0.45=-0.216149+-1.74 × -0.87=1.512406+-0.97 × -1.62=1.582458+1.40 × 0.26=0.360867+-1.23 × 0.07=-0.083599+0.62 × 0.51=0.318793
8 个乘积加起来 = 4.049102(÷√8 = 2.828427 之后是 1.431574 —— 下一站送进 softmax 的就是它)
反过来问一次:「」问「」得到 2.082489。 两个数不一样 —— 因为 Q 和 K 是两张不同的矩阵长出来的, 「我在找什么」和「我挂什么名牌」本来就该是两件事。
」这一行 —— 它给 6 个字的分数 ↓(最高的是「」)
Q() · K()
-2.78
Q() · K()
4.05
Q() · K()
-1.84
Q() · K()
-6.12
Q() · K()
-0.32
?Q() · K(?)
1.80
👆 点表格里任意一格,看它的 8 个乘积怎么加起来。 条的长度按 |分数| 画、满格是全表最大的 6.12。 注意「」那一行:它给「」的分数 4.05 远高于其他 —— 分数本来就该有高有低,把差距拉成百分比是下一站的事。
行是「谁在问」(Q),列是「谁挂的名牌」(K)。 点任意一格,下面会把它的 8 个乘积一项项摊开;分数可以是负数, 负数就是「你这个名牌跟我的问题对不上」。这就是页首动画的第 ② 步。
图 6-3 · Q · Kᵀ 得到 [6, 6] 的分数表;不缩放时全表最大 |分数| = 6.12

拿两个具体的格子看看方向性是怎么出来的: 「」问「」得到 4.05,而「」问「」只有 2.08同一对字,两个方向的分数差了一截—— 因为一个用的是「 的问题 × 的名牌」, 另一个是反过来的组合。左边那张相似度表永远给不出这种差别。

分数可以是负数(「对不上」),也可以是很大的正数(「对得上」)。下一步该拿这些分数做什么,才能把它变成「分配方案」?
第 4 站

分数变百分比:softmax 与 ÷√d

softmax 做的事一句话讲完:把一行任意大小的分数, 变成一行加起来正好 100% 的百分比,而且不改变谁大谁小。 分数最高的那个字,拿到的百分比也一定最高。

数学小白也不怕 · 它是怎么算的

两步,没有别的:先把每个分数取指数ex,一个永远为正、且越大的数涨得越猛的函数), 再除以它们的总和

一行分数:0.08、-0.45、-0.01、0.53、0.23、-0.08(÷√8 之后)
变成百分比:16.5%、9.7%、15%、25.7%、19.1%、14%
加起来 = 100%

为什么先取指数?因为它把「负分」变成「很小的正数」,把「高分」变成「很大的正数」, 于是百分比永远在 0 和 1 之间、且分数越高份额越大。减掉最大值只是为了数值稳定(ex−max 不会溢出), 结果一模一样。

图解推导softmax 三步机 —— 分数怎么一步步变成加起来 100% 的百分比
0 / 3 步
0.08
分数 s
可正可负
−0.45
分数 s
可正可负
−0.01
分数 s
可正可负
0.53
分数 s
可正可负
0.23
分数 s
可正可负
?
−0.08
分数 s
可正可负
条形从中间的 0 刻度往两边画(±0.6 画满半格):向右是正分,向左是负分。
先看这一行「分数」:它有正有负 —— −0.45 是「对不上」,0.53 是「很对胃口」,所以条形只能从中间的 0 刻度往两边画。softmax 要做的事只有两件:取指数、相除。
这一行 6 个分数(正文刚印过):0.08、−0.45、−0.01、0.53、0.23、−0.08 ← ÷√d 之后,有正有负
softmax 只有两步:取指数 → 相除。点「下一步 ▸」,一步一步看数字变成了什么。
溢出风险:e^s 最大 = 1.6913(不加保护)/ 1.0000(加了保护)
e^s 最大(不加保护)
1.6913
最大的分数是「」的 0.53:e^0.53 = 1.6913
分数再大就危险了:s = 100 时 e^100 ≈ 2.7×10⁴³,s ≈ 710 就撑爆成 Infinity
e^s 最大(加了保护)
1.0000
每个分数先减掉最大值 0.53,最大的那个一定变成 0
e^0 = 1.0000 —— 分数再大,指数也不会爆;百分比一格都不变
👆 点「下一步 ▸」三次:① 取指数(数字原地翻成 e^s,负分变成很小的正数)→ ② 求和(出现分母 6.5779)→ ③ 相除(每格 e^s ÷ 6.5779,加起来 100.0%)。 再点开「减最大值」:6 个 e^s 全变小了,百分比一格都没动 —— 这就是原文那句「只是为了数值稳定,结果一模一样」。分数最高的「」始终拿到最大的一份 25.7% —— softmax 不改大小顺序。
先点「下一步 ▸」三次,数字和条形一起变:取指数 → 求和 → 相除。 再点开「减最大值」开关:6 个 e^s 全都变小了,百分比一格都没动。
图 6-4 · 第 ① 步 e^x 把负分变成很小的正数、高分变成很大的正数;第 ③ 步 6 份加起来正好 100.0%

÷√d 是干什么的?它是 softmax 前面的一步除法, 防的是「分数太大」。而分数为什么会太大——答案在维度上:点积是 8 个数相乘再相加,维度越高,加进来的项越多,分数就越大(随机向量的点积典型值正比于 √d)。下面这台机器把这件事直接摆出来:

核心实验softmax 与 ÷√d —— 拖维度滑块,看两组百分比怎么分道扬镳
假设每个字有 d 维8
维度一涨,点积就会跟着涨(随机向量点积的典型值正比于 √d): d = 8 时分数被放大 1.00 倍。 下面两组用的是同一批分数,唯一区别是「除不除 √d」。
」拿到的原始分数(已经按 √d 放大):-2.78、4.05、-1.84、-6.12、-0.32、1.80
① 不缩放 —— 直接 softmax最大一份 89.1%
-2.784001
0.1%
4.049102
89.1%
-1.837786
0.2%
-6.122322
0%
-0.316030
1.1%
?1.800176
9.4%
② ÷√d —— 先缩放再 softmax最大一份 52.4%
-0.984293
4.7%
1.431574
52.4%
-0.649755
6.5%
-2.164568
1.4%
-0.111733
11.2%
?0.636458
23.7%
6 个字平分的话每人 16.7%。 d = 8 时:不缩放那一组最大的一份是 89.1%; ÷√d 那一组永远是 52.4%
拖滑块:上面那组一路从 89.1% 走向 100%,下面那组纹丝不动。 这就是 ÷√d 的全部作用 —— 它不改「谁比谁高」,只把分数拉回一个 softmax 还能分辨轻重的区间。不缩放的话,梯度会小到几乎为零,模型学不动。
另外注意:softmax 只是把一行分数换成「加起来 100% 的分配方案」, 它不改大小顺序 —— 分数最高的那个字,拿到的百分比也一定最高。
👆 上排是「不缩放」,下排是「÷√d」。真实模型里一个头宽 128, √128 ≈ 11.31;我们演示的 8 维是 √8 ≈ 2.83。 不管多少维,除以 √d 之后这张百分比表都长一个样。
上下两组用的是同一批分数,唯一区别是除不除 √d。 拖滑块:上面那组最大的一份从 89.1% 一路走向 100%, 下面那组永远停在 52.4%这就是页首动画的第 ③ 步。
图 6-5 · 不缩放时,d = 128 的分数被放大 4 倍, softmax 直接饱和成一枝独秀

饱和为什么是灾难?因为 softmax 一旦输出「一个 100%、其余全 0%」, 它的梯度就接近 0——反向传播时,这几个字收不到「你分错了」的信号, 参数就不再更新。这不是「效果差一点」,是「学不动」。 除以 √d 的全部作用,就是把分数拉回一个 softmax 还能分辨轻重的区间。

面试常问 · 缩放这一步
  • 除的是什么√d,d 是每个头的宽度。 真实模型一个头 128 维 → √12811.313708; 演示里 8 维 → √82.828427
  • 放在哪一步softmax 之前。分数 ÷√d → 再 softmax。 放到 softmax 之后没有意义(百分比除一个数还是百分比,只是不再加起来等于 1)。
  • 为什么是 √d 而不是 d:因为点积的典型大小本来就正比于 √d (8 项随机数相乘相加,量级长这样)。除以 √d 之后,分数的量级就和维度无关了。
  • 一句话记住:缩放不改「谁比谁高」,只保证 softmax 不饱和 —— 它是个防爆阀,不是调音台。
第 5 站

按百分比取货

现在每个字手上都有一张分配方案了:6 个百分比,加起来 100%。 接下来这一步是整个机制里最朴素的一步——按百分比,把大家的 V 搬回来

做法就是加权平均:给「」分到 52.4%, 就把它的 V 乘 0.524 收进来;分到 1.4%,就乘 0.014。 全部 6 份加起来,就是这个座位的新向量。

核心实验取货机 —— 拖滑块把注意力压到一个字上,看输出往谁靠
把注意力全压到「」上(0%)0%
权重( 怎么分配它的 100%)平均分一份是 16.7%
0.046830
4.7%
0.524470
52.4%
0.065435
6.5%
0.014386
1.4%
0.112065
11.2%
?0.236814
23.7%
输出(收集回来的新向量)最像「」的 V
0.52-0.420.13-0.170.96-0.40-0.05-0.17
out[0] = 0.05×0.22 + 0.52×0.81 + 0.07×-0.00 + 0.01×-1.00 + 0.11×-1.24 + 0.24×1.02 = 0.521513
out[1] = 0.05×0.50 + 0.52×-0.81 + 0.07×0.44 + 0.01×0.76 + 0.11×-0.49 + 0.24×-0.00 = -0.417518
out[2] = 0.05×0.16 + 0.52×0.26 + 0.07×0.01 + 0.01×-0.99 + 0.11×0.54 + 0.24×-0.26 = 0.131252
out[3] = 0.05×0.22 + 0.52×-0.55 + 0.07×0.32 + 0.01×-0.53 + 0.11×1.36 + 0.24×-0.25 = -0.173402
out[4] = 0.05×0.76 + 0.52×1.45 + 0.07×0.55 + 0.01×-3.21 + 0.11×-0.68 + 0.24×1.05 = 0.957330
out[5] = 0.05×-0.15 + 0.52×-0.71 + 0.07×0.04 + 0.01×0.31 + 0.11×1.08 + 0.24×-0.64 = -0.401461
out[6] = 0.05×0.09 + 0.52×0.04 + 0.07×0.26 + 0.01×-0.33 + 0.11×0.17 + 0.24×-0.45 = -0.052074
out[7] = 0.05×0.39 + 0.52×-0.23 + 0.07×0.13 + 0.01×1.04 + 0.11×-1.31 + 0.24×0.26 = -0.165618
输出和 6 个字的 V 有多像(余弦相似度)↓
和 V() 的余弦被关注 4.7%
0.4511
和 V() 的余弦被关注 52.4%(最多)
0.9855
和 V() 的余弦被关注 6.5%
0.2230
和 V() 的余弦被关注 1.4%
-0.8794
和 V() 的余弦被关注 11.2%
-0.4342
和 V(?) 的余弦被关注 23.7%
0.8203
滑块在 0 的时候,输出是模型算出来的加权平均,最像「」的 V(余弦 0.9855); 把它拖到 100%,6 份权重变成「」独占 100%,输出就一格不差地等于 V()。
注意力机制里真正「搬东西」的就是这一步:权重只决定「看谁」,搬回来的内容来自 V。 6 份权重加起来正好 100%,所以输出永远是 6 个 V 的加权平均 —— 它像谁,取决于谁被看得多。
也注意一下:权重最大的那个字,输出最像的也往往是它的 V(这一行是 , 余弦 0.9855),但不会完全相等 —— 因为别的字也分到了一点百分比, 它们的内容同样被搬了进来。
👆 换一行(换一个字来收集),或者拖滑块把注意力全压到一个字上。 下面 8 行账是逐维算的:每一维都等于「6 个权重 × 6 个 V 分量」相加, 照着印出来的数按计算器能核对。
左边是权重(分配方案),右边是收集回来的输出向量。 下面 8 行账是逐维算的:每一维都等于 6 个「权重 × V 分量」相加这就是页首动画的第 ④ 步。
图 6-6 · 输出 = Σ 权重 × V;权重加起来 100%,所以输出永远是 6 个 V 的加权平均

具体到「」这个座位:它把 52.4% 的注意力给了「」, 于是收上来的输出最像「」的 V(余弦 0.9855), 输出是 [0.52, -0.42, 0.13, -0.17, 0.96, -0.40, -0.05, -0.17]但它不等于 V()—— 因为另外 5 个字也各分到了一点百分比,它们的内容同样被搬了进来。

权重决定「看谁」,
V 决定搬回来什么
如果某一行的 6 个百分比完全平均(每人 16.7%), 输出会是什么?这说明什么?
第 6 站

整句跑一遍

四步凑齐了,把它们连起来就是自注意力。 下面这台机器把「三加五等于?」整句跑一遍,四张表依次摆开:① 三个分身 → ② 打分 → ③ 变百分比 → ④ 取货

招牌实验一条流水线跑完 —— 点分数表任意一格,四张表一起标出那个位置
现在这张分数表是缩放之后的 —— 权重表也跟着变
① 三个分身:Q / K / V(每个字 8 个数)
Q 我在找什么
?
K 我挂出的名牌
?
V 我要交出去的内容
?
② 打分:Q · K,全表 6 × 6 个分数(点任意一格)
??
③ 变成百分比:每一行各自 softmax(加起来 100%),高亮的是第 5 行「?
?12.29.711.924.93011.319.79.117.829.81112.614.49.913.426.524.111.64.752.46.51.411.223.728.112.324.912.21111.6?16.59.71525.719.114
④ 按百分比取货:输出 = Σ 权重 × V(每个位置一行 8 个数)
-0.400.08-0.070.26-0.580.24-0.04-0.06-0.190.27-0.210.01-0.520.04-0.070.28-0.330.13-0.110.18-0.570.18-0.050.040.52-0.420.13-0.170.96-0.40-0.05-0.170.020.19-0.010.130.18-0.040.020.13?-0.240.17-0.130.12-0.460.11-0.060.12
选中的是「?」看「」这一格: Q(?) · K() = 1.486414 → ÷√8 = 0.525527;「?」那一行 softmax 之后,这一格分到 25.7%, 于是从「」的 V 里搬走 25.7% 的内容。
整条流水线就这四步:算三个分身 → 打分 → 变百分比 → 按百分比取货。 输入是 68 个数的词向量,输出还是 68 个数的向量 —— 形状没变,但每个字的内容已经被「按关注度重新搅拌」过一遍了。
每个位置最关注谁(÷√d这一张)↓
30%29.8%26.5%52.4%28.1%?25.7%
👆 点分数表里任意一格,上面四张表会一起把那个位置标出来。 这一课的演示矩阵是固定的一组(没训练过),所以注意力分得很散: 整张表最大的一份只有 52.4%,平均分一份是 16.7%。 真实模型里这些百分比是训练出来的分工 —— 有的头盯着运算符、有的盯着左操作数, 那是第 14 课的内容。这一课你要带走的是机制,不是这一组具体的数。
上面还有个开关:÷√d 缩放不缩放。 切一下就能看到同一批分数在两种情况下差多少(第 4 站那件事的完整版)。 页首动画只演了「一个主角」那一行,这里把 6 行全部摆出来。
图 6-7 · [6,8] 词向量 → [6,8] 的 Q/K/V → [6,6] 分数 → [6,6] 权重 → [6,8] 输出:形状绕了一圈,回到出发时的样子

值得停下来看一眼最后一行数字:输入是 68 个数,输出还是 68 个数。自注意力不改形状、不做非线性,它只做一件事—— 把每个字的内容,按「关注度」重新搅拌一遍。 这一条性质非常要紧:正因为输入输出形状一样, 它才能被一层层叠起来,也才能和「残差连接」那条高速公路对接(第 8 课)。

面试常问 · 自注意力这一块
  • 为什么叫「自」:Q、K、V 全都来自同一句话(自己跟自己算)。 如果 Q 来自一句话、K/V 来自另一句话,那叫交叉注意力(第 8 课会遇到)。
  • 「看全班」对比 RNN「传纸条」:RNN 把信息一个词一个词往后传, 第 8 个字要拿到第 1 个字的信息得走 7 步,远了就丢; 自注意力让任意两个字一步直接对上,代价是 6 个字要算 6² 个分数。
  • 代价就是那个平方:一句话 1024 个 token 时,一层要算 3,758,096,384 次乘法(≈ 37.58 亿), 而且打分、取货各算一遍。演示规模是 288 次(6×6×8), 取货再 288 次。
  • 它没有位置感:把「三加五等于?」的字换个顺序,输出的每一行只是跟着换位置, 内容一模一样 —— 这就是第 5 课为什么要给每个座位发指纹。
把这一课的四步写成代码(伪代码)
Q = x @ Wq K = x @ Wk V = x @ Wv   # 三个分身
scores = Q @ K.T / sqrt(d)       # 打分 + ÷√d
w = softmax(scores, dim=-1)       # 每行加起来 100%
out = w @ V               # 按百分比取货

就这五行。真实框架里的 nn.MultiheadAttentionF.scaled_dot_product_attention 干的就是这五行 (再加上第 7 课的掩码和多头)。这一课你亲手把这五行算了一遍,每一个数都对得上。

第 7 站 · 收官

把这一课钉在墙上

本课核心 · TAKEAWAY

第 4 课那张相似度表不够用:它对称、只认内容,量不出「谁在问、谁在答」。 补的办法是让每个字准备三个分身——Q(我在找什么)、K(我挂出的名牌)、 V(我要交出去的内容),都由同一个词向量乘三张各自要训练的矩阵得到。 打分就是第 4 课那条点积:Q 乘 K 的转置,得到一张 [T, T] 的分数表; 先除以 √d(因为点积的量级正比于 √d,不缩放 softmax 会饱和、梯度会消失), 再 softmax 成加起来 100% 的百分比; 最后按百分比把 V 加权平均回来。 输入 [6,8]、输出 [6,8],形状不变,但每个字的内容已经被 「按关注度」重新搅拌过一遍 —— 这就是自注意力

这一课你亲手做完了

  • 看见尺子缺了什么:用词向量打分的表全表最大镜像差 0.000000(完全对称), 换成 Q·K 之后是 4.314716;同一对字「」↔「」两个方向分别是 4.052.08
  • 造了三个分身:Q = x·Wq、K = x·Wk、V = x·Wv, 每个数都由 8 个乘积加出来(演示里三张矩阵各 8×8 = 64 个数)。
  • 打过一次分:Q · Kᵀ → [6, 6] 共 36 个分数, 不缩放时全表最大 |分数| = 6.12
  • 换算成百分比:softmax 之后一行加起来严格 100%(平均分一份是 16.7%);不缩放的最大一份会冲到 89.1% (d = 128 时是 100%),÷√d 之后稳定在 52.4%。
  • 取过货:「」把 52.4% 给了「」,输出最像它的 V(余弦 0.9855); 把注意力全压到一个字上,输出就完全等于那个字的 V。
  • 整句跑通:[6,8] → Q/K/V → [6,6] 分数 → [6,6] 权重 → [6,8] 输出。演示里打分、取货各 288 次乘法; 真实规模一层是 3,758,096,384 次(≈ 37.58 亿),而且两边各算一遍。

学习小测验

已完成 0 / 60.0%答对 0
还没提交过 —— 每题先选一个选项,再点「提交」,答完就能看到诊断。
Q1为什么不能让每个字直接用「自己的词向量」去和别的字打分(也就是第 4 课那张相似度表)?
Q2Q、K、V 三串数是从哪来的?
Q3一句话 6 个字,注意力分数表是什么形状?第 (i, j) 格是什么?
Q4为什么要除以 √d 再送进 softmax?
Q5输出 = Σ 权重 × V。如果把某一行的注意力 100% 压到一个字上,输出会是什么?
Q6一句话从 1024 个 token 变成 2048 个,注意力这一块的计算量大约涨多少?
NEXT · 第 7 课

多头与掩码注意力

现在一句话里的每个字都会「环顾全班」了,可是还有两个大问题没解决。 第一,我们只有一张关注表——而语言里同时存在好几种关系 (谁修饰谁、代词指代谁、数字对应哪个运算符),一张表装不下, 所以要把它拆成好几个头并行地看。 第二,我们的模型是「猜下一个字」的:训练时如果第 3 个字能直接看到第 7 个字, 它根本不用学,抄答案就行了——所以必须给未来加一块挡板。 下一课:多头因果掩码

从零手册 —— 下一课:多头与掩码注意力