AAttention · 白盒教程 模型初始化中

一个能逐张矩阵手算到底的 Transformer

注意力究竟
做了什么

我们在浏览器里从随机数开始训练一个真正的 Transformer,让它学会 0~9 × 0~9 的全部 100 道乘法。模型特意缩到只有 3,136 个可训练参数、2 层、2 个头、每个 token 8 维:小到每张矩阵都能摊开,每一步都能亲手推出。

全文只证明一件事:每个接收位置根据当前输入现场算出一行读取比例,再按这些比例混合各来源位置提供的内容。Q、K、V 的作用都从这两次矩阵乘法中推出。

本教程3,136参数 · 2 层 · 2 头
GPT-31,750 亿参数 · 96 层 · 96 头
GPT-4未公开参数、层数、头数均未披露

它与公开的 GPT-3 使用同一套 Decoder-only Transformer 核心机制:token 变向量,Masked Self-Attention 让位置间交换信息,残差与 FFN 继续加工,再用真实的下一个 token 反向训练全部参数。GPT-4 技术报告没有披露参数量、层数和具体架构,所以这里只能说基础范式相承,不能声称每个实现细节都相同。

公开数据:GPT-3 论文 · GPT-4 技术报告

从必须解决的问题亲手推出注意力 ↓

先操作,再沿数字追到底

8 维、2 层、2 个头,参数随机初始化。0~9 × 0~9 的 100 道题在当前浏览器里训练;没有 Python 后端,也没有预训练权重。

正在准备 7×8=…

训练步数0 Loss 答对0 / 100

模型初始化中…

当前前缀的下一个 token 等待推理

每输入一个 token,模型当时给下一个 token 的前三名

查看当前前缀之后完整 14 token 概率
正文主线 · 暂时忘掉 Query、Key、Value 三个英文名

如果让你设计跨 token 通信,最后会被迫发明出同一套注意力

不从公式开始。先规定唯一任务:输入 7×0= 后,最后位置“=”必须根据左边三个位置改变自己的 8 个数,最终让 token“0”得到最高概率。下面每一步只解决上一步留下的一个缺口。

01
第一个缺口:四行彼此隔离

“=”自己的 8 个数里没有题目;逐行计算永远拿不到左边的 7、×、0

TokenEmb 与 PosEmb 相加后,每个位置只得到自己的一行。无论单独把“=”这一行算多少次,它都没有读取第 0~2 行的运算。因此我们首先需要一个能把“别的行”写进“=”行的矩阵运算。

02
只用已经会的矩阵乘法解决

先发明一张 4×4 的读取表 A:它的一行规定一个位置分别读取四个来源多少

A [4×4]×来源内容 V [4×4]=收到的内容 H [4×4]

A 的第 3 行属于接收位置“=”;这一行的第 0~3 列分别对应来源 7、×、0、=。于是 H(“=”)=A[3,0]V(7)+A[3,1]V(×)+A[3,2]V(0)+A[3,3]V(=)。到这里,“跨行搬运”已经由普通矩阵乘法完成。

A 只回答一个问题:每个接收行应当从每个来源行取得多少比例。它还没有回答这些比例从哪里来。
03
第二个缺口:A 不能是一张固定表

7×0= 中 0 在第 2 列;0×7= 中 0 在第 0 列——关系必须随本次输入现场重算

如果 A 是训练后固定保存的 4×4 参数表,“=”每次都会读取同一列;内容交换位置后,它不会跟着移动。所以参数不能直接保存最终连接,而要保存一种“看到当前两行时,怎样计算它们连接强弱”的方法。

04
现在才推出 Q 与 K 的计算位置

A 的每个格子都要比较“谁正在接收”和“哪个来源可被读取”

LayerNorm 先逐行整理 8 个数,但不跨位置通信;把它的输出记作 X_norm。设接收位置是 X_norm 的第 i 行 xᵢ、来源位置是第 j 行 xⱼ。最直接的可训练比较,就是让两行先经过两张不同参数矩阵,再做点积:

接收行 xᵢ [1×8] × WQ_head [8×4] = qᵢ [1×4] 来源行 xⱼ [1×8] × WK_head [8×4] = kⱼ [1×4] 比较 S[i,j] = qᵢ × kⱼᵀ ÷ 2

对所有 i、j 同时计算,就是 Q×Kᵀ。Q 在左边,所以它的行号留在 S 的行上;K 转置后在右边,所以它原来的行号变成 S 的列。Softmax 再把 S 的每一行变成总和为 1 的比例 A。

作用是运算位置推出的,不是名字赋予的:Q 的行决定“谁正在接收”;K 的行变成“谁可被选择”的列。算完以后,人们才把它们简称为 Query 与 Key。
05
第三个缺口:连接强弱不等于要搬运的内容

Q、K 已经把 A 算完;必须再准备一份与来源行对齐的内容 V

如果把负责匹配的 K 同时当作被搬运内容,就把“怎样判断关系”和“关系成立后交付什么”绑死在同一组数上。因此再让来源行乘 WV 产生 V。V 不参与生成 A;只有 A 完成后,A 的列才与 V 的行对齐并执行 A×V

QK 链:决定连接A(X)=softmax(QKᵀ÷2+Mask)

输出是一张随当前输入改变的 4×4 读取比例表。

OV 链:决定写入Write(X)=A(X)×X_norm×WV_head×WO_head

来源内容沿 A 搬到接收行,再写回 8 维残差流。

06
把前五步合成一个头

注意力不是“关注重点”,而是由输入生成连接图,再沿图搬运并写回信息

Head(X) = softmax((X_norm WQ_head)(X_norm WK_head)ᵀ÷2 + Mask) ×X_norm WV_head×WO_head

前半段的结果 A 每次随输入重算;后半段把来源行内容写进接收行。写入后的“=”状态继续经过残差、FFN、第 2 层与 Final LayerNorm,最后乘 LM 的 14 列,才得到下一个 token 的 14 个概率。

训练怎样进入这里:正确下一个 token 的误差会同时修改 QK 链和 OV 链。大量样本反复更新后,参数保存的是可复用的“什么关系应当连接”与“连接后应当写入什么”;新输入到来时,连接图仍由新输入现场生成。
同一道题 · 连续两个生成时刻

7×8= 的“=”怎样预测 5;7×8=5 的“5”又怎样预测 6

这是观察注意力最有解释力的一组对照:参数没有换,题目没有换;只是在模型生成 5 后,把 5 接回输入,于是出现一个新的接收位置和一张新的连接图。

时刻 A7 × 8 = → 下一个 token“5”

接收位置是“=”。它只能读取 7、×、8、=;未来的 5 不存在,也不能被注意力看到。

生成 5,并接回输入 →
时刻 B7 × 8 = 5 → 下一个 token“6”

接收位置变成“5”。它现在可以读取题目以及已经生成的 5;未来的 6 仍然不可见。

这里还能看懂“层”为什么有用:

第 1 层中所有位置同时读取本层入口;“5”不能在同一层读取“=”刚刚算完的结果。到了第 2 层,“=”已经携带第 1 层汇入的题目信息,“5”便可以读取这个已经上下文化的中间位置。于是多层允许信息分多跳组合。

不要把最大注意力比例直接叫作原因:A 只说明这一头怎样混合各行 V。预测 5 或 6 还取决于 V 中装了什么、WO 怎样写回、残差、FFN、另一层以及 LM。所以下面同时给出 A×V 的头输出和最终概率。
把“训练会学会关注”拆成可以验算的因果链

训练为什么会把连接推向有用来源?

训练从未收到“请关注 7 和 8”的文字命令。它只收到正确下一个 token;下面从这一个要求逐步推出 S、Q、K 为什么会被修改,以及 100 道题为何能把可复用规律累积进同一组参数。

01 · 唯一目标正确 token 的概率太低Loss = −ln P(正确 token)

例如 7×8= 的正确目标是 5。P(5) 越小,Loss 越大;没有任何一项直接要求某个注意力比例变成最大。

反向传播 ↓
02 · 先抵达搬运结果哪种 H 变化能让 Loss 下降G_H = ∂Loss / ∂H

G_H 是后面所有层、LM 和 Softmax 把误差传回 H 后得到的 1×4 数字,不是新参数。

穿过 H=A×V ↓
03 · 给每个来源记功或记过来源 j 的 V 是否提供了有用方向u_j = G_H · V_j

这只是把“若多取一点第 j 行 V,Loss 怎样变化”写成一个数。不同来源的 V 不同,所以得到的 u_j 也不同。

穿过行 Softmax ↓
04 · 来源之间必须竞争得到每个连接分数的真实梯度∂Loss/∂S_j = A_j (u_j − Σₖ A_k u_k)

括号内比较“来源 j”与“当前整行加权平均”。梯度为负时,梯度下降倾向提高 S_j;为正时倾向降低 S_j。

这个等式不是空降的

H = Σⱼ A_j V_j,所以 A_j 多一点,H 就多一点 V_j,得到 ∂Loss/∂A_j = G_H·V_j = u_j。而 A=Softmax(S) 会让一项比例上升时挤压其他项;把 Softmax 的行内导数乘进去,就得到上面的 A_j(u_j−加权平均)

单题只给一个局部方向;100 道题才决定参数往哪里走

“更合理”不是模型先验,而是共享参数上的误差信号能否反复同向叠加

10 次同向前缀 7 后面,10 道题的目标全是 ×

7×0…7×9 都推动 P(×) 上升;使用同一组参数的梯度反复同向累加,因此这个稳定位置规律很容易学强。

10 个方向冲突前缀 后面,目标 0~9 各出现一次

输入完全相同,却没有唯一正确的下一位乘数。梯度不会合理地把某一位推到 100%,而会在十个数字附近形成约 10% 的分布。

跨题复用第三个 token 后面,100 道题的目标全是 =

数字虽不同,但位置结构重复;能同时降低许多题 Loss 的参数变化会得到一致支持。这才是“规律比偶然记忆阻力小”的精确含义。

答案需要上下文a×b= 后的目标随 a、b 改变

固定输出无法同时降低 100 道题的 Loss。模型必须让当前上下文改变“=”的状态;注意力提供跨位置取数通道,FFN 与后续矩阵再把状态变成答案概率。

继续榨干这 100 道题的信息

九九表不只给 100 个答案,而是给 558 次“下一个 token”监督

每条完整序列都向左错开一格作为输入与目标,因此一次训练同时教语法、题目结构、答案首位和答案是否结束。

数字 a×100 次目标完全一致
b100 次0~9 各占 10%
a×b=100 次目标完全一致
a×b=答案首位100 次必须读取 a、b
…=首位次位或 ↵100 次必须读取题目与首位
…=两位数58 次只有两位数样本有此步
同向梯度怎样累加
前缀 7 在10道题中完全相同,目标也全是 ×
∂Loss/∂logits = P − one_hot(×)

输入相同、目标相同,所以这10份梯度逐项相同。传到共享参数后就是同一个方向的10倍:凡是能让 P(×) 上升的参数变化,都得到10票支持。

冲突梯度怎样达到平衡
前缀 完全相同,目标却是0~9各一次
Σ ∂Loss/∂logit(c) = 10P(c) − 1

若某个数字概率超过10%,右式为正,梯度下降就把它压低;低于10%则把它抬高。最终平衡是十个数字各约10%,不是“随便挑一个最大”。

同一原则进入注意力
每道 a×b= 都产生自己的连接梯度,再汇入同一张 WQ/WK
ΔWQ ∝ −Σ₍ₐ,ᵦ₎ Xᵀ(∂Loss₍ₐ,ᵦ₎/∂Q)ΔWK ∝ −Σ₍ₐ,ᵦ₎ Xᵀ(∂Loss₍ₐ,ᵦ₎/∂K)

上方真实表格给出单题的 ∂Loss/∂S;这里说明优化器不会只听一道题。能在许多题中反复降低误差的比较方式同向累积,只对个别题有利的方向会被其他题抵消。

因此,注意力追求的不是“最大”,也不保证“最合理”。

它追求的是降低当前训练集平均 Loss。若某个来源的 V 在许多样本中反复提供有利方向,QK 分数会被持续推高,它才可能成为最大连接;若多个来源共同有用,比例可以分散。梯度下降也可能学到数据中的捷径。本教程把全部 100 道题都用于训练,因此 100/100 只证明拟合成功,不单独证明对未见数据的泛化。

正文数值验证 · 用同一题检查上面的每个结论

只追 7×0=、头 2、来源“0”

不换题、不换头、不换来源。先保存随机初始化时的真实数字,再训练同一个模型;训练后仍沿同一条矩阵路线追到 P(0)。只有位置交换验证会把同一个“0”移到 0×7= 的第 0 列。

接收位置“=”注意力头第 1 层 · 头 2来源内容token“0”正确目标下一个 token“0”
正在读取模型状态…训练发生在当前浏览器;刷新即重新从同一随机种子开始。

正在准备同一条路线的真实数字…

正文到这里结束;下面只做核算

正文负责顿悟;附录负责逐格验算

正文已经从问题、训练和矩阵接线推出注意力的作用。附录不再承担概念叙述:需要核对哪个箭头,就从架构图进入同名账本,不必按顺序读完。

附录 A · 与右上架构图一一对应

Decoder 逐层真实矩阵账本

正文只推理注意力为何有效;这里不引入新结论,只把当前输入的同一次前向传播逐层摊开。架构图中的 19 个链接全部落到下面同名位置;修改输入或训练后,所有数值同步更新。

两次查表与一次矩阵加法

TokenEmb [4×8] + PosEmb [4×8] = X [4×8]

返回架构图 ↑
独立使用 L0_* 参数

Decoder 第 1 层

返回架构图 ↑
独立使用 L1_* 参数;输入是第 1 层输出

Decoder 第 2 层

返回架构图 ↑
附录 B1 · 通信前后的对照矩阵

没有跨 token 通信,“=”永远不知道左边是什么题

输入矩阵 X 的一行对应一个位置。逐行 FFN 可以把每一行算得很复杂,却不能把第 0 行的 7 搬到第 3 行的“=”。所以必须添加一个“行与行之间传信息”的运算。

TokenEmb [4×8]+PosEmb [4×8]=X [4×8]
决定性证据

7×8=8×7= 的“=”输入行完全相同;注意力后却不同

两题的“=”是同一字符、同一位置,所以注意力之前最后一行一样。处理后不同,只可能来自左边各行已经通过注意力进入“=”这一行。

先看训练后最容易顿悟的一条关系

7×0=0×7=:0 换了位置,连接的最大列会不会跟着移动?

两题包含同样的数字、运算符和等号,正确答案也同为 0;差别只是 0 与 7 交换位置。因此它适合检查 QK 生成的 A 是否真的随当前输入改变。它展示的是模型学到的一条“遇到 0”子规律,不代表一个头独自实现了完整乘法。

看到“最大列跟着 0 移动”以后,接下来只追问两件事:这条连接怎样由 Q×Kᵀ 算出来?连接确定后,V 中什么数字沿它进入“=”?

14 个 token 的 TokenEmb:初始随机 8 维与当前训练值

每个 token 直接对应 TokenEmb 的一行 8 个 float32。随机数一开始没有含义;训练反复修改它们。PosEmb 是预留位置 0~15 的 16×8 可训练表,因为模型允许最长 16 个 token;这 100 道乘法训练实际只让用到的位置收到有效梯度。

附录 B2 · 注意力内部逐格验算

同样是 XW,Q、K、V 为何作用不同

Q、K、V 在这里先只是三张结果矩阵的名字。它们都由同一个 X_norm 乘一张 8×8 参数矩阵得到;到这一步,三者只有数字不同,计算形式和形状完全相同,不能从名字推出作用。作用只能从它们下一步接到矩阵乘法的哪个位置推出来。

先抓住矩阵中的行列含义
A 的行保留 Q 的行号A 的列来自 K 转置后的列号V 的行与 A 的列按 token 对齐H 的行再次保留 A 的行号

先不解释单词,只跟踪行列怎样继承:Q × Kᵀ → A 决定行与列的连接比例;A × V → H 再让 V 的行进入 H 的行。完整算完以后,我们才解释为什么这三个位置分别被称为 Query、Key、Value。

全文主图 · 先看一条完整路线,再逐步证明

“=”从一个来源读取信息时,数字怎样首尾相接

鼠标移到或点击任一来源 token
1

只得到 Q、K、V;现在还不能从名字解释它们的作用

X_norm 是 X 经过本层第一次 LayerNorm 后的 4×8 结果。三张不同的 8×8 参数矩阵读取同一份输入,产生三张同为 4×8 的结果。WQ、WK、WV 只是代码里的名字;本步骤不要从名字偷取结论。

X_norm [4×8]×WQ [8×8]=Q [4×8]
X_norm [4×8]×WK [8×8]=K [4×8]
X_norm [4×8]×WV [8×8]=V [4×8]
查看 WQ、WK、WV 三张完整 8×8 当前参数矩阵
2

Q×Kᵀ 生成行对列的分数;Softmax 把每行变成比例 A

Q_head [4×4]×K_headᵀ [4×4]÷√4S [4×4]
softmax_rows(S + Mask)=A [4×4]

只取“=”这一行,矩阵形状仍然完整成立

Q(“=”) [1×4]×K_headᵀ [4×4]÷√4S(“=”) [1×4]

下面把 S 最后一行的 4 个格子全部展开。每个格子都使用同一行 Q(“=”),分别乘 Kᵀ 中对应 token 的一列:4 对数字逐项相乘,再把 4 个乘积累加,最后除以 √4。

3

A×V:A 的列与 V 的行对齐,得到头输出 H

A [4×4]×V_head [4×4]=H_head [4×4]

这一个乘法,就是“= 读取 7、×、8、=”的精确含义

A(“=”) [1×4]×V_head [4×4]=H(“=”) [1×4]

上一节把 Q 的一行乘 Kᵀ 的四列逐格展开;这里用完全相同的行乘列规则,把 H(“=”) 的来源账本摊开。A(“=”) 的每个比例分别乘对应 token 的整行 V,得到四条 1×4 贡献向量;四条向量逐列相加,才得到 H(“=”)。

矩阵为什么能搬运信息:A(“=”) 的四列与 V 的四行按同一个 token 顺序对齐。于是 V 中属于 7、×、8、= 的四行,会按照 A(“=”) 给出的四个比例合成为 H(“=”)。H 的行号继承自 A,也就是最初 Q 的“=”行;它的数值却来自 V 的多个 token 行。

到这里才能从计算推出分工:Q 的行号决定谁得到输出;K 的行号经转置变成“可被选择的列”;V 的行是选择完成后真正进入 H 的数字。这个结论来自矩阵位置,不来自 Query、Key、Value 三个英文单词。
4

5 不是 Q、K、V 中的某个格子;它是整条路径的最终选择

concat(H₁,H₂) [4×8]× WOAttentionOut [4×8]+ X上下文状态 [4×8]→ 第2层与 FFNFinal(“=”) [1×8]× LMlogits [1×14]→ SoftmaxP [1×14]
单独盯住答案“5”这一列
Final(“=”) [1×8]×LM[:, “5”] [8×1]=logit(“5”) [1×1]
查看 WO [8×8]、LM [8×14] 与完整中间矩阵
5

一个注意力头,本质上是一条“按当前输入匹配,再读取并写入”的可训练规则

下面每个符号都已经在前面出现。这里只做代入:用 X_norm×WQ_head 替换 Q,用 X_norm×WK_head 替换 K,用 X_norm×WV_head 替换 V;再把这个头对应的 WO_head 接在 H 后面。

① 前面已经算过Q = X_norm × WQ_headK = X_norm × WK_head
② 代入 Q×KᵀQ×Kᵀ = X_norm × WQ_head × WK_headᵀ × X_normᵀ
③ 前面也已经算过V = X_norm × WV_headH = A × V
④ 这个头写回 8 维残差流HeadOut = H × WO_head
这个头的完整结果 = softmax_rows((X_norm × WQ_head × WK_headᵀ × X_normᵀ) ÷ √4 + Mask) × X_norm × WV_head × WO_head
查看当前头把参数链合并后得到的两张真实 8×8 矩阵
左半边只决定“谁读谁”

同一组参数面对不同输入 X_norm,会现场生成不同的 4×4 矩阵 A。它不是背下一张固定连接表,而是学会一种比较两个位置的方法。

右半边只决定“读到什么、怎样写回”

来源位置的 X_norm 先经过 WV_head 形成 V,再经过这个头对应的 WO_head 写回 8 维残差流。A 决定每个来源贡献多少。

这才是注意力的设计核心:它把“关系判断”和“内容传输”拆成两条能分别训练、最后相乘的矩阵链。Q、K 本身不是问题和答案;它们共同生成一次性的连接矩阵。V 也不是最终答案;它是在这张连接矩阵上被搬运的中间内容。
03 · 现在才从矩阵位置推出名字

三次 XW 没有定义作用;后续矩阵位置才定义 Q、K、V

Q、K、V 的生成公式相同。它们之所以后来不同,不是因为名称本身,而是矩阵乘法把它们放在了三个不同的位置,并让行号以不同方式传到结果中。

Q_head [4×4]×K_headᵀ [4×4]A [4×4]
输出 A 的行继承 Q 的行
所以 Q 决定“哪个位置正在收”
输出 A 的列来自 K 的行
所以 K 决定“哪个位置可被选”
A [4×4]×V_head [4×4]H [4×4]
A 的列与 V 的行对齐;V 的数字进入 H
所以 V 决定“选中一个位置后实际取得什么”
Q 的作用从左乘位置推出

它的行留在 A 的行上

Q 位于第一次矩阵乘法左侧。Q 的第 i 行生成 A 的第 i 行,所以第 i 个位置用这一行决定自己怎样连接所有来源。因为它形成接收位置的匹配条件,后来才用 Query 作为简称。

K 的作用从转置位置推出

它的行变成 A 的列

K 转置后位于右侧。K 原来的第 j 行成为 A 的第 j 列,所以第 j 个来源位置用这一行参与全部接收位置的匹配。因为它使来源可以被匹配,后来才用 Key 作为简称。

V 的作用从第二次乘法推出

它的数字真正进入 H

V 完全不参与生成 A;A 确定后,它才位于第二次矩阵乘法右侧。A 的列与 V 的行对齐,因此 V 的数字被组合进 H。因为它是被取得的数值内容,后来才用 Value 作为简称。

关键不在英文解释,而在矩阵接线:Q 位于第一次乘法左侧,K 转置后位于右侧,V 只进入第二次乘法;正是这些位置决定了三者的作用。三张 W 分开训练,是为了让“接收位置怎样形成一行比例”“来源位置怎样形成一列匹配”“被选中后交付哪些数字”能够独立调整,而不是被同一张参数矩阵绑死。
04 · 推理公式不动,用真实反向传播解释它怎样学会

训练只给正确 token“5”,为什么 Q/K 会学出关系

训练数据没有“=”应该关注哪个位置的标签。它只把序列错开一位,告诉模型每个位置真实的下一个 token。关系是预测误差沿矩阵链倒传时自动形成的。

第一段 · 正确答案进入误差

P−Y 告诉 14 个输出列分别应该升还是降

P [1×14]Y [1×14]=G_logits [1×14]
“5”的误差怎样第一次离开词表,进入 8 维状态

G_logits 乘 LMᵀ,把 14 列的奖惩翻译成对 Final(“=”) 8 个数的修改要求

G_logits [1×14]×LMᵀ [14×8]=G_Final [1×8]

这 8 个数继续穿过最终 LayerNorm、第 2 层、FFN 和残差,最终抵达 H=A×V。到这里才分成 G_A 与 G_V,再继续训练 WQ、WK、WV。于是正确 token“5”确实能沿一条没有断点的矩阵链反过来塑造 Q/K/V。

第二段 · 同一个错误自动拆成“路线责任”和“内容责任”

H=A×V 的反向关系正好一分为二

G_T 表示“Loss 对矩阵 T 的梯度”,与 T 形状完全相同。于是 G_H 是 H 收到的错误方向,G_A、G_V、G_Q、G_K 分别是同一错误继续传到 A、V、Q、K 后得到的矩阵;它们都不是新的训练参数。

Aᵀ×G_H=G_V

路线权重越大,错误越多地传给相应 Value,所以 WV 学“被读取时应该提供什么”。

G_H×Vᵀ=G_A

某来源的 Value 越能推动正确答案,它的路线就收到越有利的训练信号,所以 A 学“应该读谁”。

G_A→ Softmax 反向G_S
G_S × K ÷√4=G_Q
G_Sᵀ × Q ÷√4=G_K
第三段 · 信号落到真正会被更新的三张参数表

当前题、当前目标对 WQ、WK、WV 的真实 8×8 梯度

这些表由 TensorFlow.js 对当前模型执行反向传播得到,不是示意数。它们只供观察,不更新参数;训练按钮仍用 100 道题的全部有效位置共同更新。

查看三张完整 8×8 真实梯度矩阵
训练为什么能长出关系

参数保存的不是一张固定连接图,而是“怎样根据当前内容生成连接图”的方法

100 道题的误差共同更新同一组 WQ、WK、WV。能跨很多样本反复降低 Loss 的寻址规律会累积下来:Q 逐渐形成可复用的检索条件,K 形成可复用的地址,V 形成可复用的内容。每次输入仍会重新计算 A,因此连接图随问题改变。

05 · 放回经典 Decoder 架构

只讲透一层;第二层就是换一组参数再走一遍

XLayerNormMasked Multi-Head Self-Attention→ +XLayerNormFFN→ +残差第1层输出第2层重复Final LayerNormLMSoftmax

block_input 只是“本层入口矩阵”的代码名。第 1 层入口是 X;第 2 层入口就是第 1 层输出,没有新数据凭空出现。

LM 是 Language Model 输出矩阵的代码名,也常写 W_out。本模型为 8×14:把 8 维状态映射成词表 14 列分数。

06 · 以小见大

为什么同一个机制放大后能说话、回答问题、写代码

计算套路没有换:训练时永远是预测真实的下一个 token;推理时永远是输出一个 token、接回左侧、再算一次。变化的是词表、维度、层数、头数、参数量和训练文本规模。

海量 token 序列→ 错开一位下一个 token 目标→ 反向传播可复用的路由与特征→ 自回归循环长文本行为
说话

许多局部预测首尾相接

Q/K 连接主语、谓语、指代和远处约束;V 搬运相关内容;FFN 变换组合后的特征。每次只选一个下一个 token,但生成结果被接回输入,连续预测就形成句子。

回答问题

答案位置读取问题中的条件

疑问类型、实体和限制分散在不同位置。多层注意力让答案位置动态读取它们;问答文本的下一个 token 误差会训练能导向正确回答的路线和内容。

写代码

程序依赖也是远距离 token 关系

变量定义与引用、函数签名与调用、左括号与右括号相隔很远。Q/K 按内容寻找相关位置,V 搬运名称、类型和结构特征,LM 选择下一段代码 token。

涌现与 QKV 的准确关系

QKV 是关键基础设施:它让网络拥有输入依赖、可训练、可并行的动态通信。没有它或同类机制,远距离关系很难高效组合。

但涌现不只来自 QKV:多层和多头让简单关系继续组合,FFN 保存与变换特征,残差保留信息,海量数据提供语言、知识和代码模式,规模与优化让可复用规律胜过逐条记忆。这 100 道乘法证明的是机制,不证明这个 8 维模型已经学会人类乘法推理。

最后守住一条边界:A 是运算中的读取比例,不等于模型决定的完整解释

A 能准确告诉我们这一层、这一个头怎样混合 V;但最终输出还经过 V、WO、残差、FFN、后续层和 LM。某列比例最大,只说明这一步从该来源取得的份额最大,不能单独证明该 token 对最终答案最重要。

现在再给注意力下定义

注意力是一种可微分的动态寻址:Q/K 用当前输入生成连接图,A×V 沿图通信;同一个预测误差又能分别训练寻址规则和传输内容。

这套解释依据哪些经典资料与研究边界
附录 C · 页面实际运行的代码

训练、前向、梯度追踪都在这里