从平均值推导 Attention 与 transformer block
从最便宜的 context 摘要——平均值——出发,衡量它失败在哪里,再从修复中自然推出 attention 公式。
本页内容
你带着 Chapter 7 的 tokenizer、Chapter 8 的 embedding 表来到这里,以及与它们相配的目标:给定到目前为止的 tokens,给下一个 token 赋一个概率。
缺少的是中间部分。为了预测 token ,模型需要一个向量来总结它之前的一切,而你已经构建的东西都不会产生这个向量。token 的 embedding 不是它——那只是一个 bigram 模型,无法知道句子一开始是个问题。把所有之前的 embeddings 拼接起来也不是:它们的数量每一步都会变,而固定的权重矩阵不能接收变长输入。
所以:一个固定大小的向量,总结数量可变的一组向量。这就是整个问题,而 attention 就是用最偷懒的方式解决它、再修补两个会坏掉的地方之后得到的东西。
当时领域里的答案,以及为什么我们不构建它
链接到此部分:当时领域里的答案,以及为什么我们不构建它从 1997 年到大约 2017 年,摘要是一个循环状态:保留一个向量 ,并在每个 token 处更新它,。固定大小、可变输入,形状完全正确。
它以三种方式失败,而本章的架构回应了这三点。Backpropagating 穿过 步会相乘 个 Jacobians,于是 gradient 消失或爆炸——这正是 Chapter 5 在单个 节点内部测量过的病。LSTM1 正是为对抗它而设计的,把可用范围从几十步推到几百步,但并没有改变这样一个事实:来自 token 5 的信息要到达 token 500,只能在 495 次顺序更新中幸存下来。整个源序列必须塞进一个向量:在 sequence-to-sequence 翻译2中,encoder 把输入压缩到它的最终状态。Bahdanau、Cho 和 Bengio 在 2014 年,也就是 transformer 出现前三年,指出了这个瓶颈并修复了它:让 decoder 对所有 encoder states 做加权和,而权重由 decoder 自己计算。3 下文所有内容都是这个想法:由一个序列应用到自身,并删除 recurrence。以及更新在构造上就是顺序的: 需要 ,而一块有一万个核心的 GPU 对此无能为力。最终胜出的架构并不显然更聪明;它只是把昂贵步骤变成矩阵乘法的那个。
另一个经典 inductive bias,convolution——让一个小 filter 滑过整个输入,使任何位置检测到的 feature 都能在任何位置被检测到——这里也不构建;它几乎正适合图像,因此留给视觉课程。recurrence 和 convolution 在这一页之后都不会再出现,这就是为什么它们都没有单独一章:Chapter 1 承诺过,省略会被明说,而不是悄悄跳过。
最便宜的摘要
链接到此部分:最便宜的摘要对数量可变的一组向量,返回一个向量,最显然的函数就是平均值:
任意数量的输入、固定输出大小、可微、免费。embedding 表加上这个平均值,再加一个到词表的线性层,就是一个十五行写完的完整语言模型。它也糟糕透顶,而它如何糟糕,就是整个推导。
下面的语料是 1 MB Shakespeare,1,115,394 个字符,经过 Chapter 7 中那类 byte-level BPE tokenizer,词表大小 1024:459,760 个 tokens,平均每个 2.43 个字符,按 90/10 划分。每个模型宽度为 128,看 128 个 tokens,用 AdamW 训练 3000 步,学习率 ,batch 为 64。Perplexity 在留出划分上计算。4
| model | parameters | validation perplexity |
|---|---|---|
| 只看当前 token,完全没有 context | 263,168 | 59.71 |
| 加上之前所有内容的均匀平均值 | 263,168 | 248.07 |
| 再加 learned position embeddings | 279,552 | 245.93 |
| 均匀平均值不是替换 token,而是加到 token 上 | 263,168 | 60.45 |
把第二行读两遍。对 context 求平均不是稍微没用;它让模型变得比完全忽略 context 差四倍。原因有两个,而且都可证明,并非经验现象。
平均值看不见顺序。 加法可交换,所以打乱 window 不会改变摘要——不是近似不变:
A = torch.tril(torch.ones(T, T))
A = A / A.sum(1, keepdim=True) # rows of the averaging matrix
y = x[torch.randperm(T)] # the same tokens, shuffled
print((A[-1] @ x - A[-1] @ y).abs().max().item())2.9802322387695312e-08重新排序求和产生的浮点噪声:两个摘要是同一个向量。一个只能通过平均值观察 context 的模型,无法区分 the dog bit the man 和 the man bit the dog。第三行证明了这不能靠给输入加位置来修好——在平均之前给每个 token 加 learned position embedding,只从 188 点里买回了 2.14 点。位置进入了和里,而和忘掉了它们。
而且平均值淹没了当下。 在位置 100,当前 token 只占摘要的一百分之一。这有一个你已经拥有的廉价修复:保留 token,并把摘要加到它上面——也就是 Chapter 6 的 residual connection,第四行显示了它的效果。稀释问题修好以后,均匀平均值完全没有贡献:60.45 对 59.71 的 baseline。每个 token 都在里面,权重相等,而相等权重等同于没有信息。
问题不在平均。问题在权重。
平均值是矩阵乘法,mask 是 softmax
链接到此部分:平均值是矩阵乘法,mask 是 softmax对不断增长的 prefix 求平均看起来像一个循环。它其实是一次乘以行和为一的下三角矩阵——而且也完全等价于一个 softmax:
loop = torch.stack([x[:t + 1].mean(0) for t in range(T)]) # the obvious version
A = torch.tril(torch.ones(T, T))
A = A / A.sum(1, keepdim=True)
mat = A @ x # the same thing
S = torch.zeros(T, T).masked_fill(torch.tril(torch.ones(T, T)) == 0, float("-inf"))
soft = F.softmax(S, dim=-1) @ x # and the same thing againloop vs matmul max |diff| = 5.960464477539063e-08
loop vs softmax max |diff| = 5.960464477539063e-08
the averaging matrix A (rows sum to 1, upper triangle is zero):
1.000 0.000 0.000 0.000 0.000 0.000
0.500 0.500 0.000 0.000 0.000 0.000
0.333 0.333 0.333 0.000 0.000 0.000
0.250 0.250 0.250 0.250 0.000 0.000
0.200 0.200 0.200 0.200 0.200 0.000
0.167 0.167 0.167 0.167 0.167 0.167transformer 的三个有名组件现在已经出现在屏幕上。三角形是 causal mask,由目标强制产生:如果位置 能看见位置 ,答案就已经在输入里了——这就是 Chapter 6 让你审计的泄漏,只是这次发生在架构内部。softmax 是实现 mask 的方式:把被禁止的 entries 设为 会让它们精确变成零,并归一化剩余部分,所以 masking 和 normalising 是同一个操作。(用 ,不要用 -1e9:这是 masking 意味着的值,cast 到 float16 后仍是 ,也让你不必判断自己选的常数对当前范围是否足够大——那是 Chapter 2 的浮点盒子在问一个你无需回答的问题。)而 scores 是自由参数。 当每个允许的 score 都是同一个数字时,你得到的就是均匀平均;放入任意数字,softmax 都会把它们变成合法权重。
本章剩下的内容只有一个问题:这些数字从哪里来?
Query, key, value
链接到此部分:Query, key, value它们不能是普通参数。一个 learned 矩阵对每个句子都相同——它可以编码「向后看四个 tokens」,但永远不能编码「看这个代词所指的名词」。连接位置 与位置 的权重必须取决于两个位置上的内容,因为相关性是一种关系,不是一种属性:单词 it 本身并不内在相关,它是相对于某个东西相关。
对两个向量返回一个数字的最便宜函数,是 Chapter 1 的点积。把位置 对位置 的 score 写成 ,机制就能工作——但很糟糕,而且会以两种方式逼出后面的全部内容。一个向量和自己的点积是它的范数平方,所以每个 token 大多会 attend 自己。而且这个关系会是对称的:如果 it 强烈 attend animal,那么 animal 也会强烈 attend it,这并不符合语言:形容词需要名词,远多于名词需要形容词。
所以给每个 token 两个角色,作为它的两个 learned linear maps:这个位置在寻找什么,,即 query;以及它提供什么供别人找到,,即 key。score 写作 ,对称性就消失了,因为 :一个 token 可以宣传一件事,同时搜索另一件事。
还有一件事不对。加权和原本是对 本身求的,这强迫被复制的东西必须也是被匹配的东西。匹配需要识别 token 的 features;复制需要对下游有用的 features。所以再学习第三个映射,,即 value,然后对它们求和。
公式现在只是记账:
其中 是 causal mask,对角线及以下为零,上方为 。代码里是三十行,其中二十行都是形状:
class Head(nn.Module):
"""One head of causal self-attention."""
def __init__(self, d_model, d_head, block):
super().__init__()
self.q = nn.Linear(d_model, d_head, bias=False)
self.k = nn.Linear(d_model, d_head, bias=False)
self.v = nn.Linear(d_model, d_head, bias=False)
self.d_head = d_head
self.register_buffer("mask", torch.tril(torch.ones(block, block)).bool())
def forward(self, x):
T = x.shape[1]
q, k, v = self.q(x), self.k(x), self.v(x)
s = q @ k.transpose(-2, -1) / math.sqrt(self.d_head)
s = s.masked_fill(~self.mask[:T, :T], float("-inf"))
w = F.softmax(s, dim=-1)
return w @ v score、mask、normalise、mix。其他一切都是 projection。
除以平方根,以及它防御的东西
链接到此部分:除以平方根,以及它防御的东西几乎所有对 的解释都会说「为了防止 softmax 饱和」,这是真的,但没有解释任何东西。论证只需要 Chapter 2 的方差两行。如果 和 的 entries 独立,均值为零、方差为一,那么每个乘积 的方差为一,而独立项的方差相加:
所以 scores 的标准差是 。在二万对随机向量上测量:
d Var(q.k) std sqrt(d)
4 3.975 1.994 2.000
16 16.071 4.009 4.000
64 64.249 8.016 8.000
256 253.065 15.908 16.000
1024 1015.562 31.868 32.000这为什么重要:softmax 对尺度敏感,而线性层不是。把线性层输入翻倍,输出也翻倍;在 softmax 前把 scores 乘以十,会把柔和混合变成硬选择。一行 64 个 scores,有除法和没有除法:
| 最大权重,未除 | 熵 | effective tokens | 最大权重,已除 | 熵 | effective tokens | |
|---|---|---|---|---|---|---|
| 4 | 0.205 | 2.944 | 19.0 | 0.081 | 3.758 | 42.9 |
| 16 | 0.438 | 1.692 | 5.4 | 0.075 | 3.849 | 46.9 |
| 64 | 0.489 | 0.874 | 2.4 | 0.085 | 3.673 | 39.4 |
| 256 | 0.9999 | 0.0007 | 1.0 | 0.143 | 3.547 | 34.7 |
| 1024 | 1.0000 | 0.0000 | 1.0 | 0.132 | 3.644 | 38.3 |
「Effective tokens」是熵的指数:这一行实际上在多少个位置上取平均。未除时,在 ,一个刚初始化的 head 会从 64 个 token 中精确 attend 一个,而选择只由随机抽样决定。
这在 forward 时很糟,backward 时更糟,其形状 Chapter 5 已经在一个 上测过。一个已经押注到单个 entry 的 softmax 几乎没有导数:它的 Jacobian 对角线是 ,两端都是零。在二千行随机 scores 上:
| 未除 | 已除 | 饱和行(最大权重大于 0.99) | |
|---|---|---|---|
| 4 | 0.8427 | 0.9568 | 0.2 % → 0.0 % |
| 64 | 0.2940 | 0.9609 | 17.9 % → 0.0 % |
| 256 | 0.1406 | 0.9609 | 49.1 % → 0.0 % |
| 1024 | 0.0681 | 0.9611 | 70.4 % → 0.0 % |
在 ,十行里有七行在训练开始前就被冻结,而一个一开始就被冻结的 head 学不会该看哪里。除过以后,这个量在每个宽度都平坦地保持在 0.96,且没有任何饱和。
现在是没人发表的部分:它会改变最终 perplexity 吗? 删除这个除法,在四种 head 宽度下训练:
| head width | 未除 | 除以 | 除以 |
|---|---|---|---|
| four heads, | 37.29 | 38.07 | 37.89 |
| one head, | 48.51 | 46.10 | 45.99 |
| one head, | 65.37 | 47.53 | — |
| one head, | 67.06 | 49.15 | — |
| one head, | 76.69 | 59.17 | — |
前两行来自上面的 3000 步预算;后三行是更短的运行——1500 步、batch 32、一个 head、projections 前没有 normalisation——两个变体设置完全相同。
在 ,除法毫无价值,没有它的运行还略微领先。这不是丢掉它的许可证,因为在 256 宽时它值 18 点 perplexity,在 1024 宽时值 17 点。机制在 scores 本身里可见:
| 初始化时 score std | 1500 步后,未除 | 1500 步后,已除 | 饱和行,未除 | 已除 | |
|---|---|---|---|---|---|
| 256 | 10.49 | 121.67 | 2.13 | 91.9 % | 0.8 % |
| 512 | 15.13 | 836.85 | 2.66 | 98.7 % | 1.3 % |
| 1024 | 21.15 | 5147.46 | 3.44 | 99.9 % | 16.5 % |
未除的 head 不会恢复。它会失控:scores 的标准差从初始化的 21 增长到 5147,attention 熵降为零,99.9% 的行把超过 0.99 的权重放到单个 token 上。一旦 head 变成硬选择器,它的 gradient 几乎为零,没有东西把它拉回来,所以坍缩是稳定的。已除的 head 在相同训练后 score 标准差为 3.44,仍是一个可以被改变的柔和混合。
Vaswani 等人说的正是这个,也仅此而已——他们怀疑乘积在 很大时会「magnitude 变大」,于是做了除法。large 这个词承担了重量,而表格说明了 large 从哪里开始:32 时什么都没有,256 时一切都发生了。
不止一种意见,以及没人谈的三分之二
链接到此部分:不止一种意见,以及没人谈的三分之二一个 head 对每个位置只有一行 softmax,因此它只能持有一个对「这里什么相关」的答案。在 the animal that crossed the wet street 中预测 the 之后的词,需要同时知道句法槽位、主语和前一个 token,而一个概率分布不可能同时集中在三个地方。所以并行运行多个 heads,每个宽度为 ,拼接,然后再用一个矩阵 混合:你是在划分宽度,而不是增加宽度。
Attention 也只做一件事——在位置之间移动信息。上面代码里的每个操作沿 feature 轴都是线性的,而 Chapter 5 已经证明了线性映射堆叠会变成什么。所以每个 block 还带有一个小 MLP,独立地应用到每个位置上,把宽度扩展四倍再回来,中间有一个 GELU。这个分工值得记住:attention 在位置之间混合,feed-forward network 在一个位置内部计算。
完整阶梯,每一行都在上一行基础上加一个部件:
| model | parameters | validation perplexity |
|---|---|---|
| 均匀平均值,加上去 | 279,552 | 60.45 |
| 一个 attention head,替换 token | 328,704 | 55.47 |
| 一个 attention head,加上去 | 328,704 | 46.10 |
| 四个 heads 而不是一个 | 345,216 | 43.21 |
| 加上 feed-forward network | 476,928 | 39.87 |
| 加上 LayerNorm——完整 block | 477,696 | 38.07 |
learned weights 比均匀权重好 14 点 perplexity,这一行就是本章的全部论点。四个 heads 用额外 16,512 个参数再买 3 点。而同一个 head 加上去比替换多值 9 点:attention 把信息带进来,它不决定一个位置是什么。
现在看参数实际在哪里,这会让只见过图的人感到意外:
| width | heads | attention | feed-forward | total per block |
|---|---|---|---|---|
| 128 | 4 | 65,664 (33.2 %) | 131,712 (66.6 %) | 197,888 |
| 768 | 12 | 2,360,064 (33.3 %) | 4,722,432 (66.6 %) | 7,085,568 |
| 4096 | 32 | 67,112,960 (33.3 %) | 134,238,208 (66.7 %) | 201,367,552 |
每个 transformer block 的三分之二都是 feed-forward network,在任何尺度都是如此,因为 attention 有四个 矩阵,而 MLP 等价于八个。无论一个模型知道什么,承载这些知识的大多数参数都在逐位置 MLP 里。
Residuals 与 LayerNorm,继承自 Chapter 6
链接到此部分:Residuals 与 LayerNorm,继承自 Chapter 6LayerNorm 已在 Chapter 6 构建并测量,本章按它在那里留下的样子使用它;residual connections 也在那里被命名并做过 ablation,这里直接构建。上面「加上去,而不是替换」的行就是 residual connections,对平均值值 188 点 perplexity,对一个 head 值 9 点。LayerNorm6 对每个样本在其 features 上做 normalise,而 Chapter 6 给出了它而不是 BatchNorm 在这里存活的理由——不依赖 batch、没有 running statistics、训练和推理完全一致、对序列长度不敏感——当你一次为一个用户生成一个 token 时,每一条都会变成要求,而 Chapter 13 最终就会到那里。它花费 768 个参数,买来 1.8 点 perplexity。
class Block(nn.Module):
def forward(self, x):
x = x + self.att(self.ln1(x))
x = x + self.ff(self.ln2(x))
return x看看 normalisation 放在哪里:在每个 sub-layer 的输入上,而从输入到输出的 residual path 从不被 normalise。这就是 pre-norm。2017 年论文做的是相反的,x = LayerNorm(x + Att(x))——post-norm,它把 LayerNorm 放在 residual path 本身上。
Xiong 等人通过初始化时的 gradient 解释了差异:在 post-norm 网络里,gradient 会随深度严重缩放不当——这就是原始 transformer 需要 learning-rate warmup 才能训练的原因。7 12 个 blocks,1000 步,学习率 :
gradient norm per block at initialisation, before any step
pre-norm block 1 0.0498 ... block 12 0.0657 ratio last/first 1.32
post-norm block 1 0.0977 ... block 12 0.1613 ratio last/first 1.65
pre-norm, no warmup perplexity 37.82
pre-norm, 200-step warmup perplexity 37.62
post-norm, no warmup perplexity 308.05
post-norm, 200-step warmup perplexity 37.88没有 warmup 的 post-norm 糟糕八倍,而带 warmup 的 post-norm 与 pre-norm 完全匹配。这里 warmup 不是一种普遍良好实践;它是针对 normalisation 某种具体摆放方式的补丁,移动 LayerNorm 就移除了对它的需求。这就是为什么自 2019 年以来几乎所有模型都是 pre-norm,也就是为什么 2017 年的图应该被当作历史,而不是规范。
一个 token 在哪里?
链接到此部分:一个 token 在哪里?删除 position embeddings,模型仍会训练;它只是无法分辨任何东西在哪里,而这是一个对称性问题,不是训练失败。attention score 里没有任何东西提到 或 本身,所以置换输入会置换输出:self-attention 是置换等变的。这是平均值的顺序盲性换了个更好的伪装——causal mask 恢复了一部分顺序,因为每个位置看到不同 prefix,但在一个 prefix 内部,所有排序都一样。
四种注入位置的方法,在 64-token windows 上训练,并在 64、128 和 256 上评估——超过它们见过的任何长度:
| positions | perplexity at 64 | at 128 | at 256 |
|---|---|---|---|
| 完全没有 | 48.79 | 52.63 | 57.52 |
| learned absolute embeddings | 38.63 | 108.47 | 181.94 |
| fixed sinusoids | 42.96 | 95.26 | 152.25 |
| RoPE | 44.12 | 50.52 | 84.84 |
| ALiBi | 44.95 | 43.51 | 42.49 |
Learned absolute embeddings——每个位置一个向量,加到 token 上——在训练长度上获胜,然后一头跌落悬崖,因为位置 100 从未出现在 batch 里,它的 embedding 仍是初始化时的随机向量。Sinusoids 是原始选择,它们不是 learned,而是从按几何间隔排列的频率上的正弦和余弦计算出来;2017 年论文希望这能外推,而表格说不能——这个函数在位置 200 上有定义,但模型从没学过在那里读取它。RoPE8 不添加任何东西,而是在二维切片中按与位置成比例的角度旋转 query 和 key;由于等量旋转点积两边不会改变点积,score 最终只依赖 ,于是位置免费变成相对的,也没有会耗尽的表。它会退化,但只是退化。ALiBi9 是这里最简单、也最奇怪的结果:在 score 上加一个与距离成比例的线性惩罚,每个 head 的斜率不同。它的 perplexity 随着 window 超过训练长度反而改善,从 44.95 到 42.49,因为惩罚在任意距离都有定义,每个 head 都继续做它训练时做的事。
这个教训比表格更长寿:一个架构无法表示某个东西,和它从未学过某个范围,是两个不同问题,而咬人的通常是第二个。它也是每个「我们把 context 扩展到 128K」公告背后的机制——那些几乎总是 rotary encoding 的重缩放,也正因如此,Chapter 16 才说 context 限制会移动,而不是消失。
Dropout 以同样方式继承:它出现在 softmax 之后的 attention weights 上,出现在每个 sub-layer 输出、residual addition 之前,也出现在 embedding sum 上,做的正是 Chapter 6 描述过的事情。在大型预训练运行中,它常被设为零,因为一个每个 token 只见一次的模型并没有过拟合的条件。
它的成本
链接到此部分:它的成本层里有两个张量形状为 ,其中 是 tokens 数:scores,以及 softmax 之后的 weights。其他一切——每个 projection,整个 MLP——都对 线性。
一个 attention 层,宽度 512,8 heads,batch 为一,float32,在笔记本 GPU 上。两个毫秒列只看它们的比例:它们是 8 GB 笔记本显卡上的 wall clock,这张卡发热后会从 1,785 MHz 降到 300 MHz 以下,所以同一段代码冷启动会快七到十倍,忙时还会更慢。MB 列是 allocator 字节数,不会移动。
tokens ms total ms x4 ms projections attn matrix MB peak MB MB x4
128 2.246 - 1.324 0.5 14.6 -
256 2.855 1.27 2.113 2.0 19.2 1.31
512 5.761 2.02 3.105 8.0 34.4 1.79
1024 16.414 2.85 4.008 32.0 89.1 2.59
2048 51.573 3.14 9.989 128.0 296.1 3.32
4096 225.432 4.37 20.176 512.0 1100.1 3.72
8192 832.838 3.69 40.106 2048.0 4300.1 3.91
16384 OUT OF MEMORY 8192.0
fitted exponent (log-log slope, last four rows): time ~ n^1.91 memory ~ n^1.87x4 列是相对于上一行的比值, 每翻倍一次,时间和内存都会收敛到精确的 4——最后一步是 3.91,理论值为 4。projections 列是对照:从 1024 tokens 的 4.0 ms 到 8192 的 40.1 ms,token 数增八倍,耗时增十倍。正如所称,是线性的。
然后是最后一行。一个 attention 层、一个序列、周围没有模型,在 8 GB GPU 上到 16,384 tokens 就会耗尽内存——仅 score 矩阵就是 8 GB,因为它是 8 heads 乘以 16,384 乘以 16,384 再乘以 4 字节。不是模型;只是一个层里的一个中间张量。
这就是后面三章之下的物理事实。它解释了为什么 context window 会有上限,Chapter 16 会把它转化为价格。它解释了 FlashAttention 为什么存在:以 tile 方式计算同一个结果,而从不存储矩阵——它首先是内存优化,其次才是速度优化。10 它也是 long prompt 价格背后的算术,Chapter 24 会在 agent loop 里支付这笔成本——这与该章另一个发现不同:模型也会更差地使用长 context;该章会测量它,并拒绝把锅甩给这个公式。
查看详情
两个缩小 cache 的变体,在这里命名,在 Chapter 13 付款。
生成会缓存已经处理过的 tokens 的 keys 和 values——每个 token 一个 key 和一个 value,每个 head、每一层。Multi-query attention11 保留 个 query projections,但所有 heads 共享一个 key projection 和一个 value projection,将这个 cache 除以 。Grouped-query attention12 介于两者之间:heads 被分组,每组共享一个 key 和 value,所以 是普通 attention, 是 multi-query。自 2023 年以来,几乎每个 open model 都用它,并配 4 或 8 个 groups。两者都不是为了质量而存在;它们都为这个 cache 的大小而存在,而 Chapter 13 会做算术,把它变成「哪个模型能装进你的 GPU」。
两种形状,以及一种的大小
链接到此部分:两种形状,以及一种的大小2017 年论文描述的是 encoder-decoder:一个 stack 用 unmasked attention 读取源序列,第二个 stack 以 causal 方式生成目标序列,中间还有第三种 attention,让 decoder 的 queries 遇到 encoder 的 keys。这对翻译是正确的,因为输入和输出是两个序列。
胜出的是 decoder-only 的那一半——一个 stack,全程 causal,输入和输出在同一个序列里——原因并不是优雅。「预测下一个 token」可以运行在任何文本上,所以训练集是互联网,而不是 parallel corpus,一切都变成这一个任务:翻译是一个包含源文本再包含目标文本的文档,问题和答案是一个文档,中间带 tool call 的对话也是一个文档。Chapter 11 讲的是最后一种如何被制造出来。Encoders 没有消失——它一次看到整个输入,当任务是表示一段文本而不是延续它时,这正是你想要的,也解释了为什么 Chapter 19 的 retrieval embeddings 来自 encoders,而不是来自正在聊天的模型。
block 定义好后,模型大小就是算术。每个 block,宽度 ,四倍扩展: 用于 ,四者都有 biases,就像 GPT-2 那样——上面的表把其中三个的 bias 省掉了,所以在 时每个 block 少 2,304 个; 用于 MLP; 用于两个 LayerNorms——,再加一个 的 token 表,以及对 absolute positions 的 。对 GPT-2 small 的形状——,12 个 blocks,词表 50,257,context 1024,输出层共享 embedding weights:
token embeddings 50,257 x 768 = 38,597,376
position embeddings 1,024 x 768 = 786,432
one block 7,087,872
12 blocks 85,054,464
final LayerNorm 2 x 768 = 1,536
total (weights tied) 124,439,808这就是该模型发布的大小。这个公式不是近似;它就是模型。还要注意,小模型里近三分之一是 embedding 表,这就是为什么词表大小是架构决策,而不是预处理决策——Chapter 7 已经建立了这个 trade-off。
一个 head 实际看哪里
链接到此部分:一个 head 实际看哪里Perplexity 是关于语料的一个数字。一个 head 做什么是另一个问题,而在 1 MB Shakespeare 上训练的模型并不是合适工具:对一个 500,000 参数模型的 attention map,诚实的说法是它大多不可解释。所以:换成一种问题有正确答案的语言。
经典示例是 the animal did not cross the street because it was too tired,其中 it 指 animal;与 …because it was too wet 对照,一个词把指代对象移到了 street。这些是 Winograd schemas13——句子对只有一个词不同,而这个词决定代词指向什么。
它们也可以靠作弊解决,这是教程常跳过的部分。如果两个候选是一个动物和一个地点,tired 与 wet 会通过类别识别指代对象,而一个只知道哪些词出现过的模型,在完全不知道顺序的情况下也能答对。在这个版本任务上测量,并使用留出的 animal/place 词对:
uniform causal average held-out referent accuracy 100.0 %
one transformer block held-out referent accuracy 91.7 %bag of words 打败了 transformer。任何建立在那句话上的演示,都不能证明关于 attention 的任何事。
所以堵上漏洞:把两个候选都从同一个 16 个名词的池子里抽取,任意一个都可以出现在任意槽位,并按角色而不是类别划分形容词——四个让 it 指向 crosser(tired, scared, slow, weak),四个让它指向 crossed(wet, wide, busy, steep)。
the {x} did not cross the {y} because it was too {adj} , so the {ref} waited .按普通 next-token predictor 训练,只评分一个位置——so the 之后的词——并用那些反向顺序出现在训练中的名词对构建留出集,所以任何只知道出现了哪两个名词、却不知道谁先出现的东西,都会答反。
| model | parameters | held-out | 命名另一个名词 |
|---|---|---|---|
| 只看当前 token | 5,796 | 5.2 % | 5.2 % |
| 均匀 causal 平均 | 5,796 | 27.9 % | 50.0 % |
| 一个 learned attention head | 18,084 | 35.4 % | 64.6 % |
| 四个 heads | 22,244 | 75.0 % | 15.6 % |
| 一个 transformer block | 55,716 | 92.7 % | 4.2 % |
| 两个 transformer blocks | 105,508 | 100.0 % | 0.0 % |
在出现的两个名词中随机猜,chance 是 50%。均匀平均达到 27.9%,并且恰好一半时间回答这一对里的错误名词——这正是一个只知道哪些词在那里、却不知道顺序的东西的签名,和三节前的 shuffle 测试预测一致。
现在看图:在必须命名指代对象的位置上的 attention,对每个 block 的四个 heads 取平均,比较只差一个词的两个句子。均匀平均会给十五个可见 tokens 各放 0.067。
the animal did not cross the street because it was too tired , so the animal waited .
blk 1 the:0.00 animal:0.70 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.06
because:0.00 it:0.00 was:0.00 too:0.00 tired:0.00 ,:0.05 so:0.00 the:0.19
blk 2 the:0.00 animal:0.00 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.00
because:0.00 it:0.00 was:0.00 too:0.00 tired:1.00 ,:0.00 so:0.00 the:0.00
the animal did not cross the street because it was too wet , so the street waited .
blk 1 the:0.00 animal:0.70 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.06
because:0.00 it:0.00 was:0.00 too:0.00 wet:0.00 ,:0.05 so:0.00 the:0.19
blk 2 the:0.00 animal:0.00 did:0.00 not:0.00 cross:0.03 the:0.00 street:0.49
because:0.00 it:0.00 was:0.00 too:0.20 wet:0.03 ,:0.00 so:0.00 the:0.25Block 1 在两个句子里完全相同——无论形容词是什么,都给第一个名词 0.70。这不是失败,而是证明:在第一层,一个位置上的 query 是该位置自身 token 和 index 的函数,而位置 14 的 the 在两个句子里是同一个 token。第一层 head 不能基于一个它还没取回的词来 condition。 所以 block 1 做了唯一可用的有用事,把第一个名词拖到前面来。
Block 2 是两个句子分叉的地方,而在所有八个形容词上同一行展示了模型找到的规则:
| adjective | block 2 在 animal 上 | 在 street 上 | 在 adjective 上 | answer |
|---|---|---|---|---|
| tired, scared, slow, weak | 0.000 | 0.000 | 1.000 | animal |
| wet, wide, busy, steep | 0.000 | 0.491 | 0.00–0.03 | street |
对 crosser-adjective,第二个 block 把全部权重放在形容词上,因为答案已经在 residual stream 里了——block 1 把它放在那里——它只需要确认。对 crossed-adjective,它会去取另一个名词。这是一个 two-hop circuit:一个 head 把候选向前移动,后一层的一个 head 读取一个 token,以决定是否保留它。跨层组合就是机制,也正因如此,一个 block 到了 92.7%,两个到了 100%。
这也是真实模型中记录最充分的 circuit 的形状。Induction heads——一个 previous-token head 喂给下一层中完成模式 [A][B] … [A] → [B] 的 head——是 Anthropic interpretability 工作识别出的、in-context learning 很大一部分背后的东西,而且它们会在预训练期间某个可识别的时刻形成。本章不尝试做那个分析:它被委托给参考文献中的两篇论文,因为从真实模型中读出 circuits 是一个研究领域,不是一节内容。
最后是实现。上面三十行,把权重从 PyTorch 自己那里复制过来:
ours vs nn.MultiheadAttention max |diff| = 1.7881393432617188e-07
ours vs F.scaled_dot_product_attention max |diff| = 1.7881393432617188e-07输出平均幅度为 0.159 时的 :同一套算术以不同顺序执行,在 float32 精度下。
接下来去哪里
链接到此部分:接下来去哪里你已经拥有本课程其余部分中每个模型所基于的架构,而且它比名声显得更小:一个权重可学习的加权平均,一个承载三分之二参数的逐位置 MLP,两个 normalisations 和两个 additions,堆叠起来。
你还没有的是一个知道任何东西的模型,而单纯堆叠不会修好这一点。在这个语料上,两个 blocks 达到 14.49 的训练 perplexity 和 40.57 的 validation perplexity,而一个 block 是 18.77 和 38.07——容量更多,对见过的内容更好,对没见过的内容更差,这就是 Chapter 6 的表,只是里面放了一个 transformer。这个模型与 Chapters 14 到 30 会对话的那些模型之间的距离,不在架构上。它是同一个 block,重复更多次,跑在多得多的文本上。
这让它变成一个会计问题,而这套会计比看上去更奇怪。需要多少文本,任何人又从哪里得到它?需要多少算术,如何在花钱之前估算它?在固定预算下,是把模型做大更好,还是给它看更多数据更好——有正确答案吗,还是只有潮流?Chapter 10 会用测量回答这三个问题,并给这个问题最便宜且有用的形式标价:今天,从零训练一个 GPT-2 这样的模型要花多少钱?
Sources and method
链接到此部分:Sources and method关于这份材料,有三种解释在各自目标上比本文更好,本章应与它们一起阅读。Jay Alammar 的 The Illustrated Transformer 是迄今画得最好的数据流图。Harvard NLP 的 The Annotated Transformer 是 2017 年论文,并逐行穿插可运行代码。Andrej Karpathy 的 Let's build GPT: from scratch, in code, spelled out 在两个小时直播中构建同一个模型,而上面的 ablation 阶梯就是同一条主线,只是在另一个语料上测量。至于本章只轻触的 interpretability 问题,主要来源是 Anthropic interpretability group 的 Elhage et al., A Mathematical Framework for Transformer Circuits (2021) 和 Olsson et al., In-context Learning and Induction Heads (2022)。
参考资料
链接到此部分:参考资料-
Hochreiter, S. and Schmidhuber, J. Long Short-Term Memory. Neural Computation 9(8), pp. 1735–1780 (1997). ↩
-
Sutskever, I., Vinyals, O. and Le, Q. V. Sequence to Sequence Learning with Neural Networks. arXiv:1409.3215 (2014). 其单个 context 向量正是瓶颈的 encoder-decoder。 ↩
-
Bahdanau, D., Cho, K. and Bengio, Y. Neural Machine Translation by Jointly Learning to Align and Translate. arXiv:1409.0473 (2014). Attention,比 transformer 早三年。 ↩
-
Perplexity 是每 token 平均 cross-entropy 的指数,来自 Chapter 8。这里每个数字都使用同一个 tokenizer 和同一个 validation split,而这也是两个 perplexities 能够相互比较的唯一条件。 ↩
-
Shazeer, N., Mirhoseini, A., Maziarz, K., Davis, A., Le, Q., Hinton, G. and Dean, J. Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer. arXiv:1701.06538 (2017). ↩
-
Ba, J. L., Kiros, J. R. and Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). 在 Chapter 6 中引入并测量;这里原样使用。 ↩
-
Xiong, R., Yang, Y., He, D., Zheng, K., Zheng, S., Xing, C., Zhang, H., Lan, Y., Wang, L. and Liu, T.-Y. On Layer Normalization in the Transformer Architecture. arXiv:2002.04745 (2020). pre-norm 背后的 gradient 分析,以及 warmup 是一种症状的论证。 ↩
-
Su, J., Lu, Y., Pan, S., Murtadha, A., Wen, B. and Liu, Y. RoFormer: Enhanced Transformer with Rotary Position Embedding. arXiv:2104.09864 (2021). ↩
-
Press, O., Smith, N. A. and Lewis, M. Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation. arXiv:2108.12409 (2021). 上面复现的外推结果。 ↩
-
Dao, T., Fu, D. Y., Ermon, S., Rudra, A. and Ré, C. FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness. arXiv:2205.14135 (2022). ↩
-
Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019). ↩
-
Ainslie, J., Lee-Thorp, J., de Jong, M., Zemlyanskiy, Y., Lebrón, F. and Sanghai, S. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023). ↩
-
Levesque, H. J., Davis, E. and Morgenstern, L. The Winograd Schema Challenge. KR (2012). 每个 attention 教程都会用的 animal / street 句子背后的构造。 ↩