读到「猫很累,所以它睡了」里的「它」,你会自动回头看「猫」。上一章 《Seq2Seq 与注意力》里的
第 2 节那张图可以拖:改一个词的问题,看它「看谁」的分布整个重排。
上一章的注意力只有三步:打分 → softmax 归一化 → 加权求和。 三步能背下来,可一追问就卡住:分数拿什么打?为什么是点积?那个 √d 从哪来? 下面五个问题,这一章一个个答掉。①③⑤ 都能自己动手,②④ 用一张图和一笔数字讲清。
这一章回答的五个问题,各在哪一节
先放下公式。下面这句话有 6 个词,每个词身上挂着两串数字: Q(它想问的问题)和 K(它挂出来的标签)。 每个词还带着第三串数 V(它携带的内容)——小例子里先让 V = K,第 3 节讲清它从哪来。 拿一个问题去跟所有标签比一遍,谁像就看谁——就这么简单。
互动 · 真的算 QKᵀ、真的过 softmax、真的加权求和
右边那张 6×6 热力图,每一格都是现场算出来的:先做 4 次乘加得到点积,
除以 √dk,再过 softmax。每行横着加起来永远等于 1.000——那是 softmax 保证的。
操作:点左边任意一行切换「谁在提问」→ 拖下面的滑块改它的 Q →
看它的那一行热力图重排。也可以按下面那排按钮,直接把它的 Q 换成另一个词的标签。
① 为什么有 Q 和 K 两串?因为「我要找什么」和「我是什么」是两件事。同一个词可以问一件事、
挂牌另一件事。
② 为什么要除以 √dk?它治的不是「你故意拖出的极端」——那种情况 softmax 本来就会让一格吃掉全部。
它治的是另一件事:维度一高,点积是 dk 个数相加,数值自己就会变大,softmax 被推成 one-hot——只有一个位置拿到权重。
第 5 节真的量给你看。
③ 那一行权重最后干什么用?它们是配比:按这个比例把每个词的 V 混起来,就是输出。
读数区最后一行那个向量,就是混出来的结果。
三个记号先说清:x 是这个位置进来时的那串数(词向量,前面章节讲过); WQ / WK / WV 是训练出来的三张表:训练时只看最后的损失(答错多少), 梯度一路传回这三张表,把有用的格子推高、没用的压低。 n 是「几个词在提问」,m 是「几个词被查」:自注意力里 m = n,翻译时 m 是原文长度。
| 在图书馆里 | 在注意力里 | 在第 2 节那张图上 | 形状 |
|---|---|---|---|
| 你脑子里要问的问题 | Q(query,查询):这个位置想要什么。由 W_Q · x_i 学出来 |
左边表里标着 Q 的那 4 个数——你拖的滑块改的就是它 | n × dk |
| 每本书书脊上的标签 | K(key,键):这个位置挂出来什么。由 W_K · x_j 学出来 |
同一行标着 K 的那 4 个数——它是「货架」,不被拖动 | m × dk |
| 书里的内容 | V(value,值):匹配上之后真正被搬走的东西。由 W_V · x_j 学出来 |
小例子里让 V = K(省一组数字)。真实模型里 V 是另外一组投影 | m × dv |
| 每本书的匹配度 → 借阅比例 | softmax(QKᵀ):分数变成加起来等于 1 的配比 | 右边那张 6×6 热力图,每一行加起来 = 1.000 | n × m |
| 按比例带回家的内容 | AV:把几本书的内容按比例混成一份 | 读数区最后那个 4 维向量 | n × dv |
Q 是问题,K 是书脊,V 是书的内容。
拿问题去比对书脊(Q · K),按匹配度决定借哪几本(softmax),
把借来的那几本按比例合起来(· V)。
为什么不能只有一组向量?因为「我在找什么」和「我提供什么」是两件事——
就像书脊上的字和书里的内容不必一样。
把第 2 节那张图上真跑的那套流程写下来,就是这一行:
它不是四条规则,是一台机器。下面这张图是它的数据流——注意每一站的形状, 这就是暗线 A(信息流动)在这一章的答案。
| 形状 | 是什么 | 那个数是怎么来的 |
|---|---|---|
| n × dk | Q,n 个位置的查询 | n 个词,每个投影成 dk 个数 |
| dk × m | Kᵀ,m 个位置的键,转置过来 | 转置只是为了对齐 —— 矩阵乘法要求内维相等 |
| n × m | 分数矩阵 QKᵀ | n×m 个格子,每个是一次长度 dk 的点积。 第 8 节的 O(n²) 就是这一项 |
| n × m | 注意力矩阵 A | 对分数矩阵逐行过 softmax,形状不变 |
| n × dv | 输出 | A(n×m) · V(m×dv) —— 又一次矩阵乘法 |
整个注意力只有两次矩阵乘法和一个 softmax 第一次 QKᵀ 算「谁看谁」,softmax 把它变成配比,第二次 AV 把内容混起来。
没有循环、没有逐时刻依赖——所以 n 个位置可以同时算。
这就是 Transformer 能把 RNN 整个删掉的底气。
互动 · 一条分数从点积到「配比」的完整旅程
这是注意力里最容易被当成「经验技巧」带过去的一步。它其实是一个可以直接算出来的事实。
| 第几步 | 算的是什么 | 得到什么 |
|---|---|---|
| 1 | 点积是 dk 项相加:q·k = Σt qt kt |
一个数 |
| 2 | 假设每个分量独立、均值 0、方差 1(初始化就是这么设计的,见初始化那一章) | Var(qtkt) = 1·1 = 1 |
| 3 | 独立项的方差可以相加 | Var(q·k) = dk ← 方差随维度线性增长 |
| 4 | 那把点积除以它的标准差 | Var(q·k/√dk) = 1 —— 尺度被拉回和维度无关 |
为什么方差大是灾难?因为 softmax 对尺度极端敏感。分数一拉开,
它就把几乎全部权重压到最大的那一格上(退化成 one-hot),
于是「只有一格拿到梯度,其余全被压成 0」——
互动 · 拖 dk,看 softmax 的熵怎么塌下去
每次换维度,下面都是真的重新生成 dk 维的 q 和 6 个 k、真的做 dk 次乘加、
真的过 softmax。左图是其中一次抽样的 6 个权重,右图是 300 次抽样的平均熵。
熵量的是权重有多平均:六个位置完全均分时最大,等于 ln6 = 1.7918;全压在一格上时为 0。
熵越接近 0,权重越像 one-hot。
下表每一个数字都是打开这一章时现场跑 300 次抽样算出来的。 看第二列和第三列:实测方差一路跟着 dk 涨,而除以 √dk 之后,熵几乎不动。
| dk | 实测方差(300 次) | 理论 dk | 原始分数的极差 | softmax 平均熵 | ÷√dk 后的平均熵 | 最大权重 |
|---|
第二列不会和第三列完全相等。 它是 300 次抽样的估计,自己就带采样误差(每次抽样只有 6 个数,估出来的方差本身很抖)。 但趋势是铁的:方差随维度线性长,而熵只在我们除掉 √dk 之后才稳得住。
「除以 √dk 是为了让 softmax 不至于太尖锐」——这句话对,但不够。
它准确的意思是:让分数的方差和维度无关。
这也是 Transformer 论文里的原话:小维度下点积和加性打分差不多;维度一大、又没除 √dk,
点积才明显变差(§3.2.1,引 Britz et al. 2017 的对照)。
所以它不是一个调参技巧,而是把「dk 变了,行为不该变」这条要求翻译成了数学。
这也解释了另一个现象:模型里那些「除以 √某维度」的地方(比如初始化时按维度定权重尺度)都是同一个道理。
互动 · 真抽 300 次,量一量点积的方差是不是真的等于 dk
上面那一整套,里头的 Q/K/V 都是一组投影。多头做的事只有一件: 把它做 h 遍,每一遍用另一组投影矩阵,最后把 h 份结果拼起来再线性变换一次。
先说一句免得误会:下面这 4 组投影矩阵是用固定随机数生成的,不是训练出来的, 看它们给出什么,再回来说真实模型里这些矩阵从哪来。
互动 · 4 组不同的投影,4 张完全不同的注意力图
这 4 个头看的是同一批 6 个词,用的也是同一个问题——差别只在投影矩阵。
(真实模型常用 8 个或更多头,这里只画 4 个。)
每一格都是现场算的:投影 → 点积 → ÷√dk → softmax,一步不少。
下面那四行数字是真的权重。注意最后一行:4 个头给出的答案彼此差多远。
一张图看懂:一个维度 d 怎么切成 h 个头
那么真实模型里这几张表是怎么来的?训练出来的——训练会自动把每一组推到某个有用的方向上。 训练之后,模型里也真的会分化出不同性格的头:有的盯住固定的位置,有的摊在整句上 (Clark et al. 2019《What Does BERT Look At?》在 BERT 里数到过这些模式)。 这里用固定随机数,只是为了让这一页每次打开的数字都一样, 矩阵乘法、点积、softmax 一步都没少。
这一点很关键:多头之所以有用,不是因为 8 个头各自被指派了任务(没人指派), 而是因为给模型 h 个互相独立的通道,训练就有机会让它们分工。 这正好是「没有免费午餐 → 必须有归纳偏置」的一种写法: 你不需要事先知道该看什么关系,你只需要提供足够多的「看的方式」。
为什么是拼接而不是平均 平均等于「大家一起拿主意」,谁也说不清话; 拼接(concat)是「每个人都说完,再让下一层去挑」,决定权交给后面那层可学的线性变换。 好处是参数量和计算量不随头数增加:每个头的维度取 dk = d/h, 投影矩阵总量和单头一样大——所以「8 个头」几乎不额外花钱,这是它被普遍采用的原因。
上面所有例子里,每个词都能看全部 6 个词,包括它自己右边的。 这对「理解一句话」没问题,对生成下一个词是致命的——GPT 就是这一类一个词一个词往外说的模型。
看一眼就明白的漏洞 训练时模型要预测「第 5 个词是什么」。如果第 5 个位置能直接看到第 6 个词,
那它抄答案就行——损失会飞快降到 0,但模型什么都没学会。
更糟的是,真正拿来用(推理)时第 6 个词还不存在,模型会直接崩。这就是数据泄漏。
做法粗暴但有效:把分数矩阵的上三角(j > i 的格子)设成 −∞, 过 softmax 之后它们就真的变成 0。剩下的格子会被重新归一化,所以每行仍然加起来等于 1。
互动 · 遮住上三角之后,权重去哪了
左边是掩码本身(灰色 = 被遮住,权重真的等于 0)。右边是当前这一行的真实算术: 分子哪些被丢掉、分母剩下多少、归一化之后每个权重是多少。点左边任意一行切换。
互动 · 遮住之后,这张表还剩多少格真的在干活
像一个考场上不许翻后面的章的考试。你可以看已经答过的部分,不能看还没答的。 原因不是道德,是因为后面那几章在考场上根本不存在—— 训练时允许你看,考试时却没有,模型就学废了。
这个掩码让「生成」变成可能:一行一行往下算,每一行只依赖上面的行。 这也是 KV Cache(见《推理与 KV Cache》)能成立的前提。
答案在第 4 节那张形状表里已经出现了:分数矩阵是 n × m 的。 序列长度翻一倍,这张表就大四倍。这就是全部来源,没有别的。
互动 · 把 n 拖大,看这张表怎么吃掉显存
每一个数字都是现场算的:每个头一张 n×n 的表,这一层 32 个头就是 32 张;
每个头只分到 dhead = 128 维(就是第 5 节那个 dk),
32 × 128 = 4096 是这一层的总维度 D。按 FP16 存(16 位浮点,一个数 2 字节)。显存就是显卡上的内存。
右图是同一批数画在对数坐标上——虚线是「和 n 成正比」,实线是真的 n²。
注意下面那条 KV Cache 的对照:它只随 n 线性增长。
前面都是现象。这一节把公式从零推出来——全站三处完整推导之一 (另两处是 backprop 的链式法则、softmax 与交叉熵的求导)。 Transformer 里几乎所有变体(线性注意力、FlashAttention、多头、掩码)都只是在这条链的某一步上做替换。
撇开术语,先问一个笨问题:「让一个词看到别的词」该输出什么? 最简单的是把整句话取平均——每个位置拿到的都是同一份东西。
这个式子是集合操作,不知道谁是谁:输入倒过来,输出完全一样。 要改的只有一件事——把永远等于 1/n 的权重,换成每个位置自己算的 w(i, j),并要求非负、每行加起来等于 1。
条件 α ≥ 0, Σα = 1 保证输出是凸组合(输入的某种平均),
不会外推到训练时从没见过的区域。到这一步,只剩一个问题:α 从哪里来。
最自然的想法:越像就看它越多。「像」用什么量?候选有距离、余弦、内积——选内积的理由很功利:
| 为什么选内积 | 说明 |
|---|---|
| 它是一次矩阵乘法 | 所有 n×m 个匹配度一次算完。GPU 最擅长的事就是它,而「逐对算距离」要写两层循环 |
| 它可以微调「像」的定义 | 距离是死的(欧氏距离就是欧氏距离),但内积可以先把向量投影一下再比——那就是参数 |
| 它是双线性的 | 对 q 和 k 都是线性的,梯度好看,也匀给两个位置各一半 |
| 代价:它没有上界 | 这就是第 5 节那个 √dk 的由来——余弦相似度没有这个问题,但要先把每个向量各自除以长度,多一步 |
一张图看懂:加性打分和乘性打分差在哪
直接用 xi · xj 也有问题:它是死的,没有参数可学。
所以给每个位置先投影一下:
常见的说法是:「一个矩阵做不到不对称,所以要拆成 Q 和 K」。这句话是错的。
xiᵀ M xj 里的 M 完全可以是不对称矩阵,它能表达任何「我→你」和「你→我」不同的关系。
真正让它们分开的,是三个工程与训练上的事实:
① V 必须独立:「拿什么去匹配」和「匹配上之后搬什么」是两件事。 连着用同一个向量,一个词就不能「广泛地匹配、精确地提供内容」——这是分开 V 的全部理由,而它是必需的。
② 拆开更省:WQᵀ WK 相当于一个秩最多 dk 的 M(秩就是「这张表最多只有 dk 个独立方向」:两张中间只有 dk 的窄表乘出来的 d×d 大表,不会有比这更多的独立方向)。 每个头只要 2·d·dk 个参数,一个完整的 M 要 d² 个;头一多,这笔账差得很远。
③ K 和 V 要被缓存、Q 不用:生成下一个词时,前面所有位置的 K/V 可以存下来重复用, Q 只算一次就丢。混在一个矩阵里,就缓存不动了(见 KV Cache)。
所以三个矩阵不是数学上的必需,它们是工程需要的形状。
现在有一串分数 sj,要变成「非负、和为 1」的权重。
三种做法听起来都合理,用同一组真实分数算一遍。
一张图看懂 · 三种归一化各算一遍,看哪一种坏了
六个位置的分数固定为 e = [2.0, 0.5, −1.0, 1.5, −0.5, 0.8]
(点积本来就可正可负)。下面三种做法各算一遍、共五行,每一行都是现场算出来的。
最后两行是同一道题:把所有分数同时加上 10 再算一次。
| 把分数变成权重的方法 | 六个位置的权重 | Σ | 结论 |
|---|
最后两行是分水岭:softmax 对「全体加一个常数」不敏感,而其他几种全变了。
一张图看懂 · 实心是原样,空心是全体加 10
exp 还藏着后面两章的根:exp(a + b) = exp(a) · exp(b)——把加法变成乘法。
所以 softmax 可以分块算:先算一块的指数和,碰到更大的分数就把这块按比例重缩放, 不用回头改前面的结果——FlashAttention 的在线归一化靠的就是它。
第 5 节已经把这个算出来了:点积的方差是 dk,除掉 √dk 之后是 1。 把四步串起来,就是那个公式:
写清楚就是两次数值:QKᵀ 得到 n×m 个分数(每个分数是长度 dk 的点积);
逐行 softmax 得到配比 A;AV 把内容按配比混起来。
回到第 2 节:你拖的每个滑块,影响的都是这套算式里的一个数。
这条推导牺牲了什么 为了好读,这条推导有三处没说满:
① 第 2 步的方差假设 q、k 各分量独立且方差为 1——初始化时的近似,真实训练会漂移。
② 「必须选内积」是动机,不是定理:有人用余弦、有人用加性(Bahdanau)、有人用 MLP 打分,都能跑。 内积赢在硬件和参数化。
③ softmax 也不是唯一答案:sparsemax、entmax 同样非负、和为 1,某些任务上更好。 对照只能说「三种最简单的候选里,softmax 最不坏」。
这么做是有意的:这三处不影响理解机器怎么转,严格版本会把它变成一篇论文—— 这就是暗线 B「什么被牺牲了」。
第 4 节给了那一行公式,第 9 节把它推了一遍。但它到底怎么算成一个数? 最好的办法是跟着「一个格子」走一趟。 这里换一台更小的例子:4 个词(它、很、快、。)、dk = 3—— 机器和前面几节一样是真算的,只是小到每一格都看得清。
下面这张图从 Q 的一行出发,经过点积、除以 √dk、softmax(它要偷看同一行的其他格子), 最后落到输出里的一个数。公式里出现的每个符号,图上都有它的一块。
数学节 · 一格分数是怎么算出来的
公式里每个符号,在图上都有一块 Q 的一行和 K 的一行逐项相乘再相加,就是一个分数——图里选中的那两行就是它们。
÷ √dk 是把整张分数表一起缩小,第 5 节量过为什么。
softmax 不是逐格算的:它要先把同一行的所有分数取指数加起来当分母——
所以拖滑块时你会看到一整行一起亮,而不只是那一个格子。
V 最后按配比加权:那一行权重乘上去,才混出输出的一个数。
这个 n × dv 的输出就交给下一层继续加工(Transformer 里是前馈层)——
这一路上还有哪些零件,下一章讲。
微型图解 · 「加权求和」到底在混什么
「注意力」不是一种方法,是一个槽位:Q 从哪来、K/V 从哪来、要不要遮—— 换这三个旋钮,就得到家族里不同的成员。前面四章讲的每一样,都是这张表里的一行。
| 成员 | Q ← 从哪来 → K / V | 遮不遮 | 它解决什么 |
|---|---|---|---|
| 加性注意力 Bahdanau 2015 |
Q: K/V: | 不遮 | 第一次把 RNN 的固定长度瓶颈拆开(上一章已经看过) |
| 乘性注意力 Luong 2015 |
同上,但改用点积打分 | 不遮 | 同样的效果,便宜得多。它赢了,才有后面的故事 |
| 交叉注意力 cross-attention |
Q:序列 X K/V:另一个序列 Y | 不遮 | 两个序列之间通信:翻译、语音、多模态对齐、检索增强生成(RAG)把资料拼进上下文 |
| 自注意力 self-attention |
都来自同一个序列 X | 不遮 | 让每个词综合全句上下文。BERT 那一类「能看全句、不遮」的模型的主体(下一章) |
| 因果自注意力 causal |
都来自同一个序列 X | 遮上三角 | 可以逐词生成、可以并行训练。GPT 那一类模型的主体 |
| 多头 multi-head |
上面任何一种,都可以横着切成 h 份并行做,再拼起来 | 同时提供多种「看的方式」。现代模型几乎都用 | |
| 滑动窗口 / 稀疏 | 自己 / 上面几种 | 看情况 | 只允许看附近 k 个位置或固定的几格,把 n² 变成 n·k |
| 线性注意力 | 自己 | 看情况 | 把 n² 整个从算式里挪走。代价是容量固定(单独一章) |
| FlashAttention | 不是新的注意力,是把标准注意力算得更省显存(单独一章) | ||
表里没列出来的还有两个小旋钮:dk 的大小(它决定分数尺度,所以 √dk 得跟着换,也影响单个头的表达能力) 和归一化函数(softmax 换成 sparsemax / entmax 就更稀疏;换成核函数就成了线性注意力)。
一张图看懂:注意力不是一种方法,是一个槽位
互动 · 现代模型为什么不用「每个头都有自己一套 K/V」
| 代价 | 说明 | 什么时候真的会痛 |
|---|---|---|
| O(n²) 的时间和显存 | n 个位置两两都要算一次。n=8192 时,单层单头就是 6700 万个分数 | 长上下文(> 8K)、高分辨率图像、长视频 |
| 没有天然的局部性和平移不变性 | 卷积免费得到「相邻像素相关」「平移后一样」,注意力要从数据里学 | 小数据集、低分辨率视觉任务——这也是 ViT 必须先在超大图库上 |
| 丢掉语序 | QKᵀ 是集合运算,把输入倒过来结果不变。位置信息必须另外注入 | 任何语言任务。(位置编码单独一章) |
| 推理时是带宽受限的 | 最贵的动作不是乘加,是把 K/V 从显存搬到芯片上。 | batch 小、序列长的在线推理——KV Cache 涨得比算力快 |
| 注意力权重不是「解释」 | 权重高不等于原因。同一个权重可能有完全不同的作用,甚至可以被改掉而结论不变(Jain & Wallace 2019《Attention is not Explanation》换一组权重、模型照样给出同一个答案) | 任何想拿热力图当证据的场合(机械可解释性就是打开网络看某个头到底在算什么,那一章专门讲) |
| softmax 会把差距放大成陷阱 | 一旦某格吃掉全部权重,其余位置的梯度就是 0——第 5 节那个图不是装饰 | dk 大、初始化不当,或 |
一张图看懂 · 倒过来念,权重一个数都没变
这一章属于七条线里的一条——而那条线在这里第一次不是口号,是可以拖的东西。
回到第 2 节,选中「它」那一行,按「把它的 Q 换成「很」的书脊」:它对「很」的权重从 3.5% 跳到 71.9%, 对「猫」从 46.7% 掉到 4.5%。别的词的 K 一个都没动。
那一瞬间你看到的就是「归纳偏置换自由度」:没有哪条规则说「它」该看「很」, 是训练去挑。代价是 6 个词就有 6×6 个分数要算,真模型里是 n×n 个。
暗线不占任何一章,但每一章都要回答它们。
| 暗线 | 这一章的回答 |
|---|---|
| A 信息流动 | 输入 (n×d) → 三组投影 → Q(n×dk)、K(m×dk)、V(m×dv) → 分数矩阵 n×m → softmax 逐行(形状不变)→ AV → n×dv。 序列长度这一维从头走到尾,一次都没变。 |
| B 什么被牺牲了 | 算力和位置先验。O(n²) 换来了「任意两位置一步直达」; 平移不变性被放弃,换来「关系由数据决定」。 并且必须先算完 QKᵀ 整张表才能做 softmax——这是 FlashAttention 要绕开的可依赖关系。 |
| C 参数账本 | 三组投影:3 × d × dk × h = 3d²,再加输出投影 WO 一共 4d²,和一层 FFN(8d²)比还小。 真正的大头不是参数,是中间那张 n×n 的矩阵——第 8 节算给你看了。 |
| D 跑在什么上 | 长序列上带宽受限,短序列上算力受限。 最贵的动作是把 K/V 从显存搬进芯片——一次搬运只配上很少的乘加(算术强度,就是每搬一个数能做多少次乘加,很低)。 n 小时这张表装得下,反而纯算力更快(那一章量过这个交叉点)。 |
| E 它假设了什么 | ① 「该看谁」写成一个双线性形式(打分器可以换); ② 权重非负、和为 1——是平均,不能外推; ③ dk 维分量近似独立同分布(√dk 靠这一条); ④ 没有位置信息,顺序必须由外面注入。 |
| F 违背了哪个直觉 | 「越相似越该多看」是错的。第 2 节里「它」看自己最多的地方是 另一个词——注意力问的是「我需要的」,不是「我像的」。 而 Q≠K 的全部意义就在这里:提问的方向和自身的坐标可以是两回事。 |
它接住了上一章的什么:《Seq2Seq 与注意力》给出了 「打分 → 归一化 → 加权求和」三步,但没回答「分怎么打」。这一章把每一步都推到了底。
它给下一章留了什么:现在你有了注意力这一个零件。 但一个零件不等于一栋楼——残差怎么接、LayerNorm 放哪、FFN 那三分之二的参数干什么、 三种拓扑(BERT / GPT / T5)差在哪?这些都是 《Transformer 自注意力》的事。
注意力就是把「加权平均」里的权重,从写死的 1/n 换成学出来的 softmax(QKᵀ/√dk)。
Q 是问题、K 是书脊、V 是内容:拿问题比书脊,按匹配度借哪几本,把内容按比例合起来。
除以 √dk 不是调参技巧——点积的方差等于 dk,不除掉 softmax 就会退化成 one-hot,梯度直接消失。
多头是给模型 h 个独立的「看的方式」,几乎不额外花参数;
因果掩码是让模型不许抄答案,也是生成模型成立的前提。
所以呢:你以后看到的注意力变体,都在这条链的某一步上动刀——
线性注意力动的是 softmax 的形状,FlashAttention 动的是算的顺序,多头动的是投影的数量。
这一章的推导就是那把尺子——下一章 《Transformer 自注意力》拿它搭楼。
上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。