基础篇

F11|Attention逐步计算:先算相关分数,再混合内容

基础篇约 5.2 千字1 组分步图解

从具体对象与小例子开始,理解计算过程与适用条件。

本章目录
动态图解 · F11

Q/K/V:先算从哪读,再算读进多少

Query、Key、Value各自是什么数,为什么分三条路径?

计算某个位置的新表示时,它是接收者;所有可见位置都是潜在信息提供者。Query与Key决定各提供者的读取份额,Value提供实际被混合的数。可以借“提问—匹配—取内容”记住分工,但Q不是自然语言问题,K不是数据库主键,V不是正确答案。

X本次进入注意力的数值表示;不等于Token编号。
Wq/Wk/Wv已给定的三张投影参数表。
Q/K/VX经三张表计算后的三份中间表示。
读取份额本轮通过softmax算出的权重,区别于参数W里的权重。

输入与适用条件:沿用F11正文的独立两位置算例:X=[[1,1],[1,−1]]。本图省略偏置、RoPE、归一化和多头。所有Q/K/V和输出都由配套程序计算。

1 / 5 · 同一输入,三条参数路径

第1步,共5步

窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。

Q/K/V:先算从哪读,再算读进多少:同一输入,三条参数路径同一个输入 X我11爱1-1参数 Wq0.50.5-0.50.5参数 Wk0.50.50.5-0.5参数 Wv5105-10生成 Q生成 K生成 V

先不打分。两行输入分别代表“我”和“爱”;Wq、Wk、Wv是三张乘法系数表。它们不是另外三份文字。

本步状态由图形与说明共同给出。

默认静止。用“下一步”看完解释再继续;播放到最后一步自动停止。键盘聚焦播放器后,可用左右键逐步查看、空格播放/暂停。图中颜色辅助区分,含义同时用文字注明。

为什么需要

让匹配特征与被传递内容分开学习,给模型不同的变换能力。

怎样实现

先分别投影每行,再跨位置打分、屏蔽、softmax、混合V。

可能的代价与误解

“注意力最大”不直接证明模型最终回答的因果原因;若只有一个可见Key,softmax永远为1,不能用来展示竞争。

如何判断是否做对

核对六行投影、两项分数、份额和输出;改变Wv不应在本层直接改变由Q/K算出的份额。

它想解决什么问题

一个Token先由Embedding表换成一排数,但这还不够。“苹果很好吃”和“苹果发布了电脑”需要结合不同上下文。注意力(Attention)是一段数值运算:为当前位置计算它应从哪些可读位置取得多少信息,再按这些份额混合信息。 它不是模型的眼睛,也不是另外三个会说话的智能体。

先抓住两件事:匹配时用什么来打分;打完分后实际拿什么来混合。 Query、Key、Value就是在这两件事里承担不同职责的数字。下面先给它们的来源,再算完整过程。来源:注意力原论文的机制说明。

进入注意力之前,X到底是什么

这里开始一个独立的两位置小模型,只处理“我/爱”两个教学Token。为了能逐格计算,把每个Token的表示宽度设为2,不沿用F05的四维Embedding表。从本节这张表直到输出,始终使用下面同一份输入与参数,不再切换数字。

序列位置(从0开始) 本例Token 当前输入向量 这排数从哪里来
0 我 x₀=[1,1] 本例给定的小模型Embedding表示
1 爱 x₁=[1,-1] 本例给定的小模型Embedding表示

把两行摆在一起,给整张表起名为大写 X:

X = [[1, 1],       第0行对应“我”
     [1,-1]]       第1行对应“爱”
形状:[2,2] = [Token位置数, 每个位置的特征数]

X不是整句中文,也不是Token编号表,而是本次注意力计算收到的数值表示。小写x₀只取X的第0行;x₁取第1行。方括号里的1和-1是人为指定的教学特征,没有“一定表示主语或动词”的含义,更不是二进制位。

本例省略归一化、位置旋转、偏置、多头与残差,只观察一个注意力头。真实网络中,注意力收到的X也可能是上一层输出或归一化后的表示;不能把每一层的X都当成最初的Embedding。那些额外步骤在F12—F16及系统篇逐步接入。

W是什么:先认识“乘法系数表”,再认识三个名字

F07里,[1,2] × [[3,4],[5,6]] = [13,16]:输出的第一个数是1×3+2×5,第二个是1×4+2×6。右边那张表里的3、4、5、6,决定了输入数字怎样被重新组合。它是一张权重矩阵,也就是乘法系数表,通常用字母W表示。

W是规则里的参数,X是这次送来的数据。 同一张W可以处理很多不同X;改X会改变本次结果,但正常推理不会因此改写W。神经网络里把“每行输入乘一张参数表,得到一排新特征”称为线性变换或线性投影。它不一定降低维度,也不是把三维东西投影到墙上。

Attention准备了三套这样的参数:

参数名字 中文含义 用它算什么 它自身是什么
Wq,也写W_Q 查询投影权重矩阵 从X算出Query表示Q 一张数值系数表
Wk,也写W_K 键投影权重矩阵 从X算出Key表示K 另一张数值系数表
Wv,也写W_V 值投影权重矩阵 从X算出Value表示V 第三张数值系数表

下标q、k、v只是说明“这张W负责哪一路”。Wq不是W乘q;W_Q中的下划线也不是一个计算动作。后面公式XWq才表示矩阵乘法,Python中通常写作X @ Wq。

这些系数从哪里来? 训练前,程序按初始化方法设置它们;训练中根据预测误差调整;使用训练好的模型时,从模型权重文件加载已经得到的数值。它们不是对当前提问临时随机生成,也不是程序员给每个词手写的语法规则。这里的三张表是为演示手算而设定,不声称经过语言训练。来源:线性层的参数与计算约定。

Query、Key、Value各做哪一件事

现在把名字与对象连起来:

名字 是什么对象 在当前计算中的职责 不是什么
Query,查询向量q 当前接收信息的位置生成的一排数 分别与各位置的Key比较,为“读多少”打分 不是用户输入的问句,也不是搜索字符串
Key,键向量k 每个可读位置生成的一排数 接受Query的比较,为这个位置提供匹配特征 不是密钥,也不是从文字里直接摘出的关键词
Value,值向量v 每个可读位置生成的另一排数 在份额确定后,提供真正参与加权求和的内容 不是标准答案,也不是最终输出Token

同一个位置可以同时当接收者和信息提供者。例如计算“爱”位置的新表示时,用“爱”的Query去比较“我”的Key和“爱”的Key,再混合“我”的Value与“爱”的Value。

小写q、k、v是一行;大写Q、K、V是把所有位置各自的行摆成的表。 在本节里每张表有2行,每行2个数。它们不是三份独立原文,而是同一份X经过不同参数变换得到的三份中间表示。

因此,三条计算是:

Q = X @ Wq     输入表示 × 查询参数表 → 所有位置的查询表示
K = X @ Wk     输入表示 × 键参数表   → 所有位置的匹配表示
V = X @ Wv     输入表示 × 值参数表   → 所有位置的内容表示

“分成三套参数”不意味着数学上不允许它们恰好相等。某些教学特例会设成相同矩阵;有些研究结构也会共享参数。但一般的分开设计允许三条路径学到不同变换。数值是否相同、是否共享,必须看该例子或模型的具体设定。

用同一份X,把三张参数表完整乘一次

本节始终使用下面三张2行2列的系数表,且不加偏置:

Wq = [[ 0.5, 0.5],    Wk = [[0.5, 0.5],    Wv = [[5, 10],
      [-0.5, 0.5]]          [0.5,-0.5]]          [5,-10]]

先取“我”的输入x₀=[1,1]。每个输出格子都是输入的一行与参数表的一列做点积:

q₀ = x₀Wq = [1×0.5+1×(-0.5), 1×0.5+1×0.5] = [0,1]
k₀ = x₀Wk = [1×0.5+1×0.5,    1×0.5+1×(-0.5)] = [1,0]
v₀ = x₀Wv = [1×5+1×5,        1×10+1×(-10)] = [10,0]

再取“爱”的输入x₁=[1,-1],仍然使用相同的三张参数表,没有为“爱”换另一组W:

q₁ = x₁Wq = [1×0.5+(-1)×(-0.5), 1×0.5+(-1)×0.5] = [1,0]
k₁ = x₁Wk = [1×0.5+(-1)×0.5,    1×0.5+(-1)×(-0.5)] = [0,1]
v₁ = x₁Wv = [1×5+(-1)×5,        1×10+(-1)×(-10)] = [0,20]

于是,整个X变成三张结果表:

位置 原输入X的一行 算出的Q的一行 算出的K的一行 算出的V的一行
我,0 [1,1] [0,1] [1,0] [10,0]
爱,1 [1,-1] [1,0] [0,1] [0,20]

到这里还没有混合不同Token。 三个投影都是分别加工每一行;真正跨位置的比较和混合发生在下一步。Q/K/V各行的数字是由上面的X和W算出的,不是凭空给出的新输入。

对“爱”这个位置,从打分一直算到新表示

本节使用因果读取规则:当前位置能读自己和前面的位置。现在计算序列第1号位置“爱”,所以它能读第0号“我”和自己。取出的对象是:

当前接收者:爱,q₁=[1,0]
可读位置0:我,k₀=[1,0],v₀=[10,0]
可读位置1:爱,k₁=[0,1],v₁=[0,20]

第一步:Query和每一个Key打分。

对“我”的原始分数:q₁·k₀ = 1×1+0×0 = 1
对“爱”的原始分数:q₁·k₁ = 1×0+0×1 = 0
原始分数:[1,0],顺序仍是[我,爱]

第二步:把分数除以Query/Key的宽度的平方根。 宽度记作d_k,这里是2;√2≈1.414214。这叫缩放,不是再修改W:

缩放分数:[1/√2, 0/√2] ≈ [0.7071067812,0]

第三步:沿可读位置做softmax,得到读取份额。 算法与F09相同,但这是当前Q/K得到的分数,不是F09词表候选的[2,1,0]。这里候选对象是两个输入位置,不是三个待生成Token。

减最大值: [0, -0.7071067812]
取指数:   [1, 0.4930686914]
指数之和: 1.4930686914
除以总和: [0.6697615493, 0.3302384507]
            来自“我”的份额   来自“爱”的份额

这些份额也是“权重”,但和Wq里的参数权重不是同一种东西。Wq等是已经加载的模型参数;这里的两个份额由本次输入计算而来,随输入改变。

第四步:用份额混合Value的对应格子。

输出第0格 = 0.6697615493×10 + 0.3302384507×0
          ≈ 6.6976154933
输出第1格 = 0.6697615493×0  + 0.3302384507×20
          ≈ 6.6047690135

“爱”位置的注意力输出 o₁ ≈ [6.6976154933,6.6047690135]

这排数是**“爱”位置的新中间表示**。它不是随机抽中“我”,不是选出一个Value,也不是生成了一句回答。Attention在这里做确定的加权求和,不执行F09演示的随机抽样。

把一行变成所有行,公式就出现了

现在仍然使用同一X和同一组三张参数表,同时看两个接收位置:

Q = [[0,1],        K = [[1,0],        V = [[10,0],
     [1,0]]             [0,1]]             [0,20]]

QKᵀ = [[0,1],     行:接收信息的位置(Query)
       [1,0]]     列:提供信息的位置(Key)

上标ᵀ表示转置:让K的原来每一行成为一列,以便一次计算所有“Query行与Key行”的点积。它不是序列长度T,也不是K的某次方。

第0号“我”不能读未来的“爱”。给禁止的分数位置加负无穷,记为−∞,它的指数按极限取0。给允许的位置加0。把这种加性掩码表命名为M:

M = [[0,−∞],      第0行只能读“我”
     [0, 0]]      第1行能读“我”和“爱”

S = QKᵀ/√2 + M = [[0,−∞],
                   [0.7071067812,0]]
A = 每行softmax(S) ≈ [[1,0],
                      [0.6697615493,0.3302384507]]
O = AV ≈ [[10,0],
          [6.6976154933,6.6047690135]]

S是分数表,A是读取份额表,O是混合后的输出表。它们都是本次计算结果,不是另外加载的模型参数。第0行只有一个合法位置,份额是1,输出自然就是自己的Value。

所有动作合起来,才写成这条简式:

Q=XWq,K=XWk,V=XWv
O=softmax(QKᵀ/√d_k + M)V
符号 对象身份 本例形状 本次推理中如何获得
X 本次输入表示 [2,2] 上一步提供
Wq/Wk/Wv 三张投影参数表 各[2,2] 本例预设;实际加载训练好的参数
Q/K/V 三套中间表示 各[2,2] X分别乘对应的W
M 可读位置约束 [2,2] 根据序列、位置和读取规则构造
S 每行对每列的分数 [2,2] Q与K点积、缩放、加M
A 每行的读取份额 [2,2] 对S沿Key列做softmax
O 每个接收位置的新表示 [2,2] A乘V

本例所有形状刚好相同,不能据此认定所有对象可互换。一般单头设有n个查询、m个Key,Q为[n,d_k]、K为[m,d_k]、V为[m,d_v],则分数与份额为[n,m]、输出为[n,d_v]。d_v是每个Value有几个数,不必等于d_k;Query和Key的长度才需要配对做点积。

self并不是“只看自己”

本例Q、K、V都从同一条序列的X产生,所以叫Self-Attention,自注意力。每个位置仍可以读取规则允许的其他位置。“self”说来源,不说只能看自己。

Cross-Attention,交叉注意力则可以让Query来自一份序列、Key和Value来自另一份。例如翻译系统用正在生成的目标侧表示,读取源句编码器提供的表示。这里的两份输入是另一种结构,不能偷偷代入上面的同X公式。

Decoder是负责逐步生成后续Token的一类网络结构;Encoder是把给定输入转换成表示的一类结构。这里只认识它们的职责,具体网络结构在系统篇展开。因果读取规则来自生成任务的设计,不是英文字母Q/K/V自带的功能。

为什么需要三套表示,而不是直接拿X比较和混合

若只用原X匹配、也直接混合X,相当于限制匹配特征与被读取内容的变换方式。三套投影让模型可以分别学习“怎样接收”“怎样被匹配”“提供什么内容”。这是一种表达能力与参数量之间的设计选择,不是声称任何任务数学上都必须三张互不相同的表。

可以沿本例做两次明确分开的对照,每次都从本节基准恢复:

对照 保持不变 只改变什么 可预测的影响
改被读取内容 X、Wq、Wk 把Wv整体乘2 当前Q/K与读取份额不变,当前O整体乘2
改匹配规则 X、Wk、Wv 修改Wq中的一个系数 Query可能变化,份额与混合结果也可能变化

“份额不变”仅指当前这一次注意力分支;改变输出后,后面的层仍可能跟着变化。不能把局部对照扩大成整模型其他部分永远不受影响。

训练时,误差会沿“O→A与V→Q与K→各张W”的计算依赖传回,调整系数。到F17的梯度与F18的优化器,就会看到怎样算更新量。现在只要先区分:W保存可以学到的规则;Q/K/V保存这些规则作用在本次输入后的结果。

再进入多头、缩放和源码之前

一个头就是一套“Query/Key匹配,再混合Value”的计算路径。多头使用多组这样的路径,并把结果组合;不是模型同时开了多个聊天窗口。各头结果并排后,还可乘另一张参数表Wo,把混合结果转换回主网络需要的宽度。Wo是注意力输出投影,不是最后给词表候选打分的语言模型输出头。

除以√d_k是在控制点积分数的尺度。推导要用方差,F14会先解释这一统计量;它不是softmax的必要定义,也不是改变候选数量。为了逐步学习,本节先把它当作本例固定的缩放规则,再在Q007追问它的理由。

有些库的高级Attention接口把投影前的输入也命名为query/key/value,内部才乘W;另一些底层接口直接接收已经投影后的Q/K/V。同名不代表处于同一计算阶段。 读源码前要看接口是否负责投影,避免自己先乘一次W、库里又乘一次。PyTorch的线性层还把参数存成[输出宽度,输入宽度]并在计算时转置;本书手算采用[输入宽度,输出宽度]右乘约定,两者转置后可以表达同一变换。接口与形状依据。

以上数值使用未舍入结果继续计算;页面小数只作显示。可用本节的参数实验观察中间值,也可运行随包的concepts/verify_concepts.py复算。它验证教学算式与前提,不证明真实模型已学会中文。

运行示例 · F11

从两段内容到一个混合向量,而不是选中一句话

输入与条件。本表核对上面同一条“我/爱”计算链,不另换输入。X=[[1,1],[1,-1]];Wq=[[0.5,0.5],[-0.5,0.5]];Wk=[[0.5,0.5],[0.5,-0.5]];Wv=[[5,10],[5,-10]]。由它们算出q₁=[1,0]、k₀=[1,0]、k₁=[0,1]、v₀=[10,0]、v₁=[0,20];计算“爱”位置时两个位置均可读。

步骤与对象内部动作中间结果 / 新状态
1匹配分别算q与两个Key的点积得到 [1,0]。
2缩放两个分数都除以√2约 [0.707107,0]。
3归一化对两个可见位置做softmax约 [0.669762,0.330238]。
4混合第1个特征用两份Value的第1格加权0.6697615493×10+0.3302384507×0≈6.697615。
5混合第2个特征用两份Value的第2格加权0.6697615493×0+0.3302384507×20≈6.604769。
6得到输出把两格放一起约 [6.697615,6.604769],不是任何一份原始Value。

把过程连起来。匹配分数决定读取份额,Value决定混合什么。只改Value时,读取份额可以完全不变,但输出会变。

边界与反例。本例为单头、两位置、无偏置、无位置旋转的教学注意力,不是训练好的语言模型。所有Q/K/V由同一X和三张参数表实际相乘得到。改变任一参数或输入,应从对应步骤重新计算。

依据与适用范围:[SDPA]。示例输入和场景由本书明确设定。

参数实验:改一格W,观察哪些中间值改变

正文固定使用基准参数。下面是可重置的独立交互对照:只改变Wq的第0行第0列或Wv的第0行第0列,其余X、Wk及参数保持不变。仍采用因果读取规则。

只改变当前Wv时,Q、K与当前份额不变;改变Wq时,Query及份额可能变化。后续网络层不在本实验范围内。显示小数经舍入,内部使用未舍入值。

查看本节完整中间结果与可运行验证程序

课后自检

自检 1

Q与K点积得到的是什么,V负责什么?

查看参考答案收起参考答案
点积得到对可读位置的分数;V提供按归一化权重聚合的内容向量。
自检 2

两个Key分数完全相同且均可读,权重是什么?

查看参考答案收起参考答案
各0.5。输出是两份Value的逐维平均,不是随机任选一个Value。
自检 3

Wq与Q到底哪个存的是模型参数?

查看参考答案收起参考答案
Wq存乘法系数。Q是把本次X乘Wq得到的中间表示。普通推理更换输入会改变Q,但不会因此执行Wq的训练更新。
自检 4

本例x₁=[1,-1]怎样产生v₁=[0,20]?

查看参考答案收起参考答案
使用固定Wv=[[5,10],[5,-10]]:第一格1×5+(-1)×5=0;第二格1×10+(-1)×(-10)=20。这一步只加工该位置,没有混合其他Token。
自检 5

Q、K的每行宽度必须等于V的每行宽度吗?

查看参考答案收起参考答案
不必。Q与K需要相同宽度以计算点积;K与V需要对应同样的可读位置。V可有另一宽度d_v,聚合结果也有d_v个特征。

机制依据:[TR]。本节数值与情景除另注外均为原创教学示例;来源不代替中文讲解。

记录保存在本机浏览器。

输入关键词即可检索。

↑ ↓ 选择 Enter 打开Esc 关闭 · 仅本地搜索

阅读设置

阅读设置仅保存在本机浏览器,不上传任何数据。