对应原课:CMU 11-667 Fall 2025 · Lecture 2 · Thu 28-Aug-2025 · Transformer architecture and pre-training learning objectives(Jaromir Savelka、Yubin Kim) 原课 slides:Neural Language Model Architectures(课表上标为 lm architectures);预训练目标那部分在第 3 讲 slides 的最后一节(Pre-training Learning Objectives),本讲义按标题把它放在这里 阅读材料:Wang et al. (2022), What Language Model Architecture and Pretraining Objective Work Best for Zero-Shot Generalization?(ICML 2022) 配套代码:code/lecture02/(3 个模块 + 4 个演示脚本,全部在 CPU 上运行,最长的一个首次约 50 秒) 本讲的写法:骨架来自原课 slides;注意力的推导、三种结构的实测、花园世界上的训练实验和 Wang et al. 的玩具版复现是补充的。所有数字都来自配套代码的实际输出。
目录
- 0. 本讲导读
- 1. 从 RNN 说起:编码器、解码器与信息瓶颈
- 2. 注意力:让每一步都能回头查
- 3. Transformer:只用注意力
- 4. 三种结构:掩码决定一切
- 5. 在花园世界上训练一个 Transformer
- 6. 预训练目标:答案藏在文本里
- 7. 小结、常见误区、练习
0. 本讲导读
0.1 这一讲要回答的问题
第 1 讲把语言模型拆成了六块积木:分词 → 嵌入 → 神经网络 → logits → softmax → 解码。其中第三块被我们当成黑盒,用了一个最简单的实现:把最近 5 个词的嵌入拼起来,过一个两层 MLP。它在大多数地方表现不错,却在一个地方栽了跟头:
the children grow the fresh pumpkin from ___ own garden
空格里应该填 their,因为主语是 the children。但主语离空格有 7 个词,窗口只看得到 grow the fresh pumpkin from,模型只好在 my / our / their 之间瞎猜(our 0.477、my 0.374、their 0.104)。更糟的是,这类错误处在分布的头部,top-p 截断也去不掉它。
这一讲打开黑盒,回答三个问题:
- 网络该怎样”看”前文? 窗口看得太短;P2 的 RNN 能看完整的历史,但信息要一步一步往后传,而且只能串行计算。Transformer 换了一种思路:每个位置直接去”查”前文里的任意一个位置——这就是注意力。
- 同样是 Transformer,为什么有 BERT、GPT、T5 三种? 它们的积木完全相同,区别几乎全在一张”谁能看见谁”的表(注意力掩码)上。
- 没有人工标注,模型从哪里学? 预训练目标把一段文本拆成”输入”和”答案”。拆法不同(预测下一个词、填空、填一段),学到的东西也不同。Wang et al. (2022) 用 5B 参数的模型做了系统比较,我们在花园世界上做一个能精确打分的玩具版。
0.2 学完这一讲你应该能
- 写出缩放点积注意力的公式,解释 Q、K、V 各自的角色,以及为什么要除以 ;
- 画出 encoder-only、decoder-only、前缀 LM、encoder-decoder 的注意力掩码,并说出各自适合的任务;
- 解释为什么因果掩码让 KV cache 成为可能,以及为什么 decoder-only 生成文本特别划算;
- 说清楚残差连接、LayerNorm、FFN、位置编码在一个 Transformer 块里各自起什么作用;
- 写出 FLM、PLM、MLM(BERT 的 80/10/10)、span corruption(T5 的哨兵)四种目标的输入和目标,算出每种目标”有多少 token 在学”;
- 复述 Wang et al. (2022) 的两个主要发现,以及”改装(adaptation)“的思路。
0.3 本讲的路线
§1 RNN 语言模型 ──► RNN encoder-decoder:整句输入挤进一个向量(瓶颈)
│
§2 注意力(Bahdanau 2014):解码每一步都回头查 encoder 的每个位置
│
§3 Transformer(2017):干脆去掉循环,只用注意力(+ 残差、LayerNorm、FFN、位置编码)
│
§4 三种结构 = 三种掩码:encoder-only / decoder-only / encoder-decoder(+ 前缀 LM)
│
§5 花园世界实验:窗口外的物主代词 0.700 → 0.982;一个注意力头学会了"回头找主语"
│
§6 四种预训练目标:FLM / PLM / MLM / span corruption;Wang et al. (2022) 与玩具复现
1. 从 RNN 说起:编码器、解码器与信息瓶颈
1.1 神经语言模型回顾
slides 开头先复习了神经语言模型的定义:给定前文 I eat the,一个神经网络输出下一个词的分布,apple、banana 的概率高,其余的低。第 1 讲的窗口模型就是这样一个网络,只是它只看最近几个词。
slides 接着把问题推广成更一般的形式:输入一个序列,逐个预测目标序列的下一个 token。当输入和目标是同一条序列(目标比输入错开一位)时,这就是普通的语言模型;当输入是一句法语、目标是它的英译时,这就是第 1 讲 §4 说的”有条件语言模型”。为了处理这种一般形式,人们把网络拆成两半:
- 编码器(encoder):读入长度不定的输入序列,输出一个(或一串)“摘要”向量,目的是把输入编码成解码器能用的形式;
- 解码器(decoder):一个 token 一个 token 地处理目标序列、预测下一个 token,同时把编码器的输出当作上下文。
slides 留了一个问题:编码器和解码器具体是什么网络? 2013 年前后的答案是 RNN。
1.2 RNN 作为语言模型
循环神经网络(RNN)维护一个隐藏状态 ,每读一个 token 就更新一次(前置讲 P2 §8 详细讲过):
- 输入是一串嵌入 ;
- 循环单元接收上一步的隐藏状态和当前 token 的嵌入,输出新的隐藏状态,再由它预测下一个 token;
- 第一个隐藏状态通常初始化为零向量;
- 多层 RNN 把下面一层的隐藏状态当作上面一层的输入。
RNN 当 encoder-decoder 用时,最简单的连接方式是:用编码器最后一个隐藏状态去初始化解码器的第一个隐藏状态。整句输入的全部信息,都要挤进这一个向量里。
1.3 两个问题:瓶颈与串行
这个设计有两个根本问题(slides “Why drop the recurrence”):
- 长距离信息会被冲淡。如果两个 token 相隔 个位置,前一个 token 的信息要经过 次状态更新才能影响后一个的预测,每一次更新都有机会把它”擦掉”。P2 §8 用雅可比矩阵的连乘量化过这件事:梯度随距离指数衰减或爆炸。在 encoder-decoder 里更糟——一整句话被压缩成一个固定长度的向量,句子越长丢得越多。
- 无法并行。位置 的计算依赖位置 的结果,一条长度为 的序列必须串行算 步。GPU 擅长的是同时做大量相同的运算,串行的 RNN 浪费了它。
slides 里展示了 2013 年 RNN 生成的文本:局部通顺,几句之后就前言不搭后语。要让模型写出连贯的长文本,必须先解决”远处的信息传不过来”这个问题。
2. 注意力:让每一步都能回头查
2.1 动机:翻译时的”对齐”
Bahdanau、Cho 和 Bengio (2014) 在机器翻译里提出了注意力。slides 的例子:把 L’hippopotame a mangé mes devoirs 翻成英语,解码器在写 hippopotamus 时,最该看的是源句里的 hippopotame;写 ate 时最该看 a mangé。人类译者也是这样做的——翻到哪里,就回头看原文的哪里。
于是他们不再把整句输入压成一个向量,而是保留编码器每个位置的隐藏状态 ,解码器在每一步 计算一个上下文向量:
表示”预测第 个目标词时,源句第 个位置有多重要”,归一化成和为 1。解码器的预测由它自己的隐藏状态和 共同决定。打分函数最简单的选择是点积:。
为什么这解决了瓶颈:信息不再需要挤进一个向量。解码器每一步都能直接访问输入的每个位置,路径长度是 1,和距离无关。
2.2 注意力 = 软的字典查询
把注意力想成一次”字典查询”最容易理解:
- 每个位置挂着一个键(key) 和一个值(value) ;
- 查询方拿着一个问题(query) ;
- 和每个 做点积得到匹配分数,softmax 变成权重,输出是各个 的加权平均。
普通字典是”硬”查询:只取键完全匹配的那一项。注意力是”软”查询:按相似度把所有项混合起来。正因为它处处可导,“该问什么(q)、该挂什么标签(k)、该交出什么内容(v)“都可以用梯度下降学出来。
代码 01 §1 手工做了一个例子。句子 she picks her ripe plum from ___,空格要填物主代词,最有用的信息是主语 she。给每个词一个 3 维的 key(“是不是主语""是不是动词""是不是名词”),value 用每个词自己的 one-hot。query 是”我要找主语”,即 :
| query 的大小 | she | picks | her | ripe | plum | from |
|---|---|---|---|---|---|---|
| 0 | 0.167 | 0.167 | 0.167 | 0.167 | 0.167 | 0.167 |
| 1 | 0.263 | 0.147 | 0.147 | 0.147 | 0.147 | 0.147 |
| 3 | 0.531 | 0.094 | 0.094 | 0.094 | 0.094 | 0.094 |
| 6 | 0.865 | 0.027 | 0.027 | 0.027 | 0.027 | 0.027 |
时 query 什么都不问,权重平均分配; 越大,权重越集中到 she。查询的”锐利程度”由 的长度控制——这一点马上会在 的讨论里再出现。§5.4 会看到,在花园世界上训练出来的 Transformer 自己学会了几乎一模一样的查询。
2.3 自注意力:Q、K、V 都从同一串向量来
Bahdanau 注意力里,query 来自解码器,key 和 value 来自编码器。Transformer 把它推广成自注意力(self-attention):一串向量 用三个可学习的矩阵分别投影出 Q、K、V,然后每个位置去查同一串里的所有位置:
其中 是掩码(允许的位置为 0,禁止的位置为 ,§2.5)。同一个词在不同位置会投影出不同的 q、k、v,因为 里已经混进了位置信息(§3.5)和前面各层的上下文。
为什么要三个不同的矩阵,而不直接用 和 做点积?因为”我要找什么”和”我能提供什么”不是一回事。物主代词的位置要找的是主语,而它自己并不是主语;如果 q 和 k 用同一个投影,一个词最匹配的往往就是它自己。分开投影让”提问”和”被查”各有各的表示。
slides 用 bank 举了一个例子:She has withdrawn the money from the bank account 和 She stopped by the river bank 里,bank 的意思完全不同。自注意力让 bank 的表示可以从 money、account 或者 river 那里”取”信息,于是同一个词在不同句子里得到不同的向量——这就是”上下文相关的表示”。第 1 讲的嵌入表做不到这一点:查表得到的向量和上下文无关。
2.4 为什么要除以
设 q、k 的每个分量独立、均值 0、方差 1,那么
分数的标准差是 ,随维度增大。§2.2 的表已经说明:分数越大,softmax 越尖。尖到极致,softmax 就变成 argmax,而 argmax 几乎处处梯度为 0。
代码 01 §2 实测了这一点。每个 query 对 64 个 key 打分,统计 2000 个 query 的平均值(均匀分布的熵是 ; 是 softmax 雅可比矩阵 的范数,衡量梯度能多大程度传回分数):
| 分数标准差 | 最大权重 不缩放 / 缩放 | 熵 不缩放 / 缩放 | 不缩放 / 缩放 | |
|---|---|---|---|---|
| 4 | 1.95 | 0.271 / 0.100 | 2.861 / 3.730 | 0.256 / 0.177 |
| 16 | 4.14 | 0.594 / 0.111 | 1.327 / 3.660 | 0.289 / 0.186 |
| 64 | 8.02 | 0.777 / 0.104 | 0.620 / 3.690 | 0.224 / 0.183 |
| 256 | 16.14 | 0.898 / 0.109 | 0.264 / 3.675 | 0.125 / 0.185 |
| 1024 | 32.03 | 0.945 / 0.108 | 0.134 / 3.684 | 0.073 / 0.184 |
不缩放时,分数标准差 ≈ ,最大权重趋近 1、熵趋近 0。雅可比范数在 很小时还略大于缩放后的值,从 起迅速变小( 时不到缩放后的一半)。缩放后三项指标几乎不随 变化。除以 就是为了让”注意力有多尖”不取决于模型有多宽,尖不尖交给模型自己通过 q、k 的长度去学。
2.5 掩码:决定谁能看见谁
有些位置不该被看见:
- 语言模型预测第 个词时,不能看第 个词本身(否则就是抄答案);
- 一批序列补齐(padding)到同样长度后,补出来的位置不是真实内容。
做法是在 softmax 之前把禁止位置的分数设为 :,这些位置的权重恰好为 0,softmax 在剩下的位置上重新归一化。代码 01 §3 打印了因果掩码下的权重矩阵:上三角全是 0,每行和为 1,第 0 行只能看自己,所以是 。
一个容易忽略的边界情况:如果某一行全部被禁止,softmax 会算出 ,而 NaN 会通过后续的矩阵乘法传染到别的位置。这种行只会出现在补齐的位置上(它们的输出反正不计入损失),我们的实现(transformer.py 的 scaled_dot_product_attention)把这种行的权重置 0。
3. Transformer:只用注意力
3.1 为什么干脆扔掉循环
既然注意力已经让解码器能直接访问输入的每个位置,Vaswani et al. (2017) 走了一步更激进的棋:编码器和解码器内部也不要 RNN 了,全部用注意力。论文标题就是 Attention Is All You Need。好处正好对应 §1.3 的两个问题:
| RNN | 自注意力 | |
|---|---|---|
| 位置 的信息传到位置 | 经过 次状态更新 | 1 步直达 |
| 训练时的串行步数 | 1(所有位置同时算) | |
| 每层的计算量 |
代价是注意力矩阵有 个元素。这个 项到底什么时候要命?一层 Transformer 的前向乘加次数()大约是:Q、K、V、O 四个投影 ,FFN ,注意力分数 与加权求和 共 。 项的占比是
代码 01 §7:
| = 512 | 2048 | 8192 | 32768 | 131072 | |
|---|---|---|---|---|---|
| 768 | 10.0% | 30.8% | 64.0% | 87.7% | 96.6% |
| 4096 | 2.0% | 7.7% | 25.0% | 57.1% | 84.2% |
| 8192 | 1.0% | 4.0% | 14.3% | 40.0% | 72.7% |
7B 级别的模型()在 时, 项只占约四分之一;要到 才占一半。所以”注意力是平方复杂度”这句话在长上下文(第 21 讲)时才真正要命。不过激活显存里的 (每个头存一张 的注意力矩阵)出现得早得多,P3 §4 实测过;FlashAttention(第 19 讲)就是为了不存这张矩阵。
3.2 三种注意力
原版 Transformer 里有三种注意力,区别只在 Q、K、V 从哪里来、用什么掩码(slides 第 31~35 页):
- 编码器自注意力:Q、K、V 都来自编码器,没有掩码,每个输入位置看见整条输入;
- 解码器的带掩码自注意力(masked self-attention):Q、K、V 都来自解码器,因果掩码,每个位置只看自己和左边(也叫因果自注意力,causal self-attention);
- 编码器-解码器注意力(交叉注意力,cross-attention):Q 来自解码器,K、V 来自编码器的输出,没有掩码——这就是 Bahdanau 注意力的直接后代。
3.3 多头注意力
一行注意力权重只能表达一种关注方式。但一个位置常常要同时关心好几件事:物主代词的位置既要找主语(决定 my / her / their),也要看前一个词(from 后面接物主代词,in 后面接 the)。
**多头注意力(multi-head attention)**把 维的 Q、K、V 各切成 份,每份 维, 个头各自独立地做注意力,最后拼回 维、再乘一个输出矩阵 把各头的结果混合:
代码 01 §4 验证了几件事:
- 我们的实现和”一个头一个头地循环算”一致到 ,和 PyTorch 的
F.scaled_dot_product_attention一致到 ,和nn.MultiheadAttention(复制同样的权重)一致到 ; - 参数量和头数无关: 时 1 个头和 8 个头都是 4224 个参数()。切成多个头不增加参数,只是把 维分成 组,各算各的 softmax;
- 一个真实的坑:
nn.MultiheadAttention的attn_mask约定是 True = 屏蔽,而F.scaled_dot_product_attention是 True = 允许。把我们的掩码直接传给前者,批里每条序列的最后一行都变成 NaN(原本”全都能看”,现在”全都不许看”),其余位置的输出最多差 0.98——整个过程不报任何错。写注意力代码时,掩码约定值得专门写一行注释。
3.4 一个 Transformer 块
slides 第 37 页画了完整的块。除了注意力,还有三样东西:
前馈网络(FFN):对每个位置单独做一次两层 MLP,,中间层通常是 宽。为什么需要它?注意力只做加权平均,它把信息从别的位置”搬”过来,但加权平均本身是线性的。FFN 对每个位置做非线性加工,各位置共享同一组参数。一个常见的说法是:注意力负责”交流”,FFN 负责”思考”或”查记忆”(第 14 讲会看到,很多事实知识存在 FFN 的权重里)。从参数量看,FFN 是大头:§4.4 的配置里,一个块 315 万参数,FFN 占 210 万。
残差连接:。每个子层只需要学”在原来的基础上改一点什么”;更重要的是,梯度可以沿着""这条捷径直接流回前面的层,几十上百层的网络因此能训练(ResNet 的思想)。
层归一化(LayerNorm):把每个位置的向量归一化到均值 0、方差 1,再乘一个可学习的缩放、加一个可学习的偏置。它让每一层输入的数值范围保持稳定。归一化放在哪里有两种做法:
- post-norm(原版 2017):;
- pre-norm(GPT-2 起):,最后再补一个 LN。
本讲的代码默认用 pre-norm,因为它更容易训练。为什么一个小小的位置变化会影响训练稳定性,是第 4 讲的主题之一。
3.5 位置编码:注意力对词序视而不见
注意力有一个奇怪的性质:它不关心顺序。把输入的词打乱,每个词的输出只是跟着换了位置,数值完全不变——数学上叫”排列等变”。原因是:分数只取决于 q、k 的内容,加权求和也不在乎各项的排列顺序。
代码 01 §5 用随机初始化的两层模型验证了这一点:
| 结构 | 打乱输入后,输出与”原输出按同样方式打乱”的最大差 |
|---|---|
| 双向注意力,不加位置编码 | (浮点误差,即完全相同) |
| 双向注意力,正弦位置编码 | 1.59 |
| 因果注意力,不加位置编码 | 0.93 |
不加位置编码的双向模型把句子当成”一袋词”:she feeds the dog 和 the dog feeds she 对它没有区别。所以要把位置信息加进去。注意第三行:因果掩码本身就打破了对称——位置 只看得到前 个词,“看得到几个词”就泄露了位置。§5.5 会看到,在花园世界上,不加位置编码的因果模型(NoPE)一点也不差。
原版 Transformer 用的是正弦位置编码,直接加到词嵌入上:
直觉:每对维度 是一根以不同速度旋转的”时钟指针”,低维转得快(分辨相邻位置),高维转得慢(分辨远距离),很像二进制计数里低位变得快、高位变得慢。代码 01 §6 验证了它的两个好性质:
- 点积只取决于距离: 在 时完全相同( 时都是 30.917, 时都是 21.052, 时都是 15.674),距离越远大体越小;
- 平移是一个固定的旋转:存在一个只依赖 的分块旋转矩阵 ,使 对所有 成立(实测最大误差 )。
原论文的说法是,选正弦是因为”假设它能让模型容易学会按相对位置关注”。第 4 讲的 RoPE 把这个想法做彻底:不再把位置加到嵌入上,而是直接把 q、k 旋转一个和位置成正比的角度,让注意力分数严格只依赖相对距离。另一种做法是 GPT-2、BERT 用的可学习位置嵌入:每个位置一个向量,和词嵌入一样训练出来;它的缺点是没见过的长度没有嵌入可查。
3.6 输出层
最后一层的输出向量和嵌入矩阵做点积,得到每个词的 logit(slides 第 29 页),这和第 1 讲 §5.4 的”输出嵌入与词嵌入的点积、权重共享”完全一样。我们的 TransformerLM 默认共享输入和输出嵌入。
4. 三种结构:掩码决定一切
4.1 四张掩码图
slides 最后三页介绍了 encoder-only、decoder-only、encoder-decoder 三种结构。它们用的是同一种块,区别几乎全在掩码上。代码 02 §1 把它们画了出来(■ = 能看见,行是 query,列是 key):
(a) encoder-only(BERT) (b) decoder-only(GPT) (c) 前缀 LM(prefix_len=2)
I eat the apple . I eat the apple . I eat the apple .
I ■ ■ ■ ■ ■ I ■ · · · · I ■ ■ · · ·
eat ■ ■ ■ ■ ■ eat ■ ■ · · · eat ■ ■ · · ·
the ■ ■ ■ ■ ■ the ■ ■ ■ · · the ■ ■ ■ · ·
apple ■ ■ ■ ■ ■ apple ■ ■ ■ ■ · apple ■ ■ ■ ■ ·
. ■ ■ ■ ■ ■ . ■ ■ ■ ■ ■ . ■ ■ ■ ■ ■
(d) encoder-decoder 有三张:encoder 自注意力是全 ■(双向),decoder 自注意力是下三角(因果),交叉注意力是全 ■(decoder 的每个位置看 encoder 的全部输出)。
- encoder-only(BERT,2018):每个位置看见整句,适合”理解”类任务——分类、抽取、填空。它的输出是每个位置的上下文表示,不直接生成文本。
- decoder-only(GPT 系列、Llama、Qwen、DeepSeek……):因果掩码,一次前向同时给出 个”下一个词”的预测。今天的主流 LLM 几乎都是这种。
- 前缀 LM(也叫 non-causal decoder):仍是”一叠块”,只把掩码改成”前缀内部双向、之后因果”。条件部分(例如问题、要翻译的句子)可以双向编码,生成部分保持因果。
- encoder-decoder(原版 Transformer、T5、BART):两叠块,输入只进 encoder,输出只进 decoder,中间靠交叉注意力连接。
4.2 实测:掩码真的决定了信息流
“叠了好几层之后,信息会不会从别的路径绕过来?“代码 02 §2 直接测:把输入的第 个 token 换掉,看第 个位置的输出变没变,得到一张”依赖矩阵”。对三层的随机初始化模型:
- 因果模型的依赖矩阵是严格的下三角,与掩码完全一致;
- 双向模型是全 1;
- 前缀 LM(前缀长 3)与它的掩码完全一致:前 3 个位置互相依赖,之后因果。
叠多少层都不会越界:每一层都只看左边,左边的左边还是左边。这正是因果语言模型能用一次前向同时训练 个位置的前提——位置 的预测绝对碰不到 以后的答案。
encoder-decoder 也测了:改动源序列的任何一个 token,decoder 的全部 4 个位置都会变(交叉注意力没有掩码);改动目标序列的第 个 token,只有位置 及之后的输出会变(decoder 内部严格因果)。
4.3 生成的代价:KV cache 的来历
生成文本时,模型每次追加一个 token,再预测下一个。代码 02 §3 测了”追加一个 token 后,原来 8 个位置的隐藏状态变了多少”:
| 结构 | 最大变化 |
|---|---|
| 因果 | 0 |
| 双向 | 0.233 |
| 前缀 LM(新 token 在前缀之后) | 0 |
因果模型里,前面每个位置每一层的计算结果都不会因为后面多了 token 而改变。所以可以把每层的 K、V 存起来(KV cache),生成新 token 时只算新位置那一行。双向模型就不行:新 token 会改变所有位置的表示,每生成一个 token,整条序列的每一层都得重算。
用”过一遍网络的位置数”粗略比较生成 个 token(提示长 )的代价:因果 + KV cache 是 ;双向每步重算是 :
| 因果 + KV cache | 双向(每步重算) | 倍数 | ||
|---|---|---|---|---|
| 100 | 100 | 200 | 15,050 | 75× |
| 1000 | 200 | 1,200 | 220,100 | 183× |
| 4000 | 1000 | 5,000 | 4,500,500 | 900× |
(这里只数位置,没算”新位置对前面所有 K 做注意力”的代价,第 19 讲会细算。)KV cache 是 decoder-only 结构在推理上的天然优势,P3 §5 算过它的显存账。
4.4 参数量与计算量
比较不同结构时,必须说清楚是”同参数量”还是”同计算量”。代码 02 §4 取 、、8 个头、:一个普通块 3,152,384 个参数;decoder 块多了交叉注意力和一个 LayerNorm,4,204,032 个(多 33%)。
| 结构 | 总参数 | 一个输入 token 经过的块参数 | 一个目标 token 经过的 |
|---|---|---|---|
| decoder-only,8 层 | 41,604,096 | 25,219,072 | 25,219,072 |
| encoder-only,8 层 | 41,604,096 | 25,219,072 | —(不生成) |
| encoder-decoder,8+8 层 | 75,237,376 | 25,219,072 | 33,632,256 |
| encoder-decoder,4+4 层 | 45,811,712 | 12,609,536 | 16,816,128 |
每个 token 的前向计算量约等于它经过的参数量的两倍(P3 §6)。encoder-decoder 8+8 的块参数是 decoder-only 8 层的 2.3 倍(总参数 1.8 倍,因为嵌入共享),但每个 token 只经过其中一叠:输入 token 只过 encoder(计算量相同),目标 token 只过 decoder(多了交叉注意力,1.33 倍)。Wang et al. (2022) 按每个 token 的计算量对齐,所以他们的 encoder-decoder 是 11B 参数,decoder-only 是 4.8B,前者因交叉注意力”贵约 10%”。
4.5 为什么今天几乎都是 decoder-only
把本节和 §6 的内容合起来,decoder-only 成为主流有几条理由:
- 简单:一叠块、一种掩码、一个目标。条件和输出拼成一条序列即可(第 1 讲 §4.2),不需要区分”输入”和”输出”。
- 训练信号最多:每个 token 都产生损失(§6.3),同样的数据学得最快。
- 推理划算:KV cache 让生成的代价线性增长。
- 零样本能力最好:Wang et al. 的发现 1(§6.5)。所有任务都可以写成”续写”,正好是它的训练目标。
encoder-only 模型(BERT 系列)仍然在检索、分类、嵌入等”不需要生成”的场景里大量使用(第 11 讲的 RAG 就要用到);encoder-decoder(T5 系列)在翻译、摘要等输入输出分得很清楚的任务上依然有竞争力。
5. 在花园世界上训练一个 Transformer
5.1 设置与总成绩
代码 03 在第 1 讲的花园世界上训练一个因果 Transformer:,4 个头,2 层,,正弦位置编码,pre-norm,输入输出共享嵌入,共 106,496 个参数。训练 6 遍(每遍 250 个批次、每批 32 篇文档),Adam 峰值学习率 0.003,预热 200 步后余弦衰减,按验证集困惑度挑最好的一遍(第 5 遍;6 遍的验证集困惑度依次是 4.495、4.300、4.088、3.909、3.864、3.875)。在作者机器上每遍约 1.5 秒。
| 模型 | 参数 | 测试困惑度 | 物主代词·窗口内 | 窗口外 |
|---|---|---|---|---|
| 真实分布(下界) | — | 3.655 | 1.000 | 1.000 |
| 第 1 讲 固定窗口 MLP(看 5 个词) | 27,706 | 3.846 | 0.988 | 0.700 |
| P2 普通 RNN | 15,002 | 3.807 | 0.963 | 0.939 |
| P2 GRU | 27,546 | 3.824 | 0.988 | 0.988 |
| 本讲 Transformer(2 层) | 106,496 | 3.851 | 0.987 | 0.982 |
(物主代词一列是”模型给正确答案的平均概率”,窗口内 543 个、窗口外 437 个。RNN、GRU 两行引用前置讲 P2 的结果。)
两件事值得注意:
- 困惑度上,Transformer 并没有赢。四种神经网络都在 3.81~3.85 之间。§5.5 会看到,光是换一个随机种子,Transformer 的测试困惑度就从 3.851 变成 3.817——这几个模型在困惑度上的差别和随机波动是同一量级。
- 长距离依赖上,注意力解决了问题。窗口外的物主代词从 0.700 升到 0.982。第 1 讲的那个例子 the children grow the fresh pumpkin from ___,Transformer 给 their 0.998,其余物主代词都约等于 0(窗口模型给的是 our 0.477、my 0.374、their 0.104)。
另外,ext_vocab() 在 90 个花园世界 token 之外多了 10 个结构用的特殊符号(<pad>、<mask>、8 个哨兵),因果模型从不需要预测它们。训练后它们平均只分到 的概率,对困惑度的影响可以忽略,所以这里的困惑度可以直接和第 1 讲比较。
5.2 剩下的差距从哪里来
3.851 和 3.655 之间的差距是结构的问题吗?代码 03 §2 把测试集的 NLL 按句子拆开:
| 句子类型 | token 数 | 模型 NLL/词 | 真实 NLL/词 | 多出的 NLL/词 | 占总差距 |
|---|---|---|---|---|---|
| 不含”没见过的搭配” | 23,453 | 1.307 | 1.293 | 0.015 | 26.3% |
| 含 5 种没见过的搭配之一 | 1,379 | 2.048 | 1.356 | 0.692 | 73.7% |
只有 5.6% 的 token 落在”含没见过搭配”的句子里(pick plum、grow tulip 等 5 种组合在训练集里被刻意剔除,第 1 讲 §6.3),却贡献了差距的 74%。在其余句子上,模型每个词只比真实分布多 0.015 nat——几乎完美。剩下的差距主要是数据问题,不是结构问题:训练集里一次都没出现过的组合,任何结构都只能靠”相似的词”去猜;再加上 Zipf 长尾里罕见名词的概率估不准。这和第 1 讲 §6.6 的结论一致,也预告了第 3 讲的主题:数据决定了模型能学到什么。
5.3 生成:头部的错误消失了
第 1 讲 §7.9 有一个让人印象深刻的结果:窗口模型用 top-p 0.95 采样时,剩下的 74 句不合法的句子全部错在窗口外的物主代词上。这类错误处在分布的头部,截断去不掉。代码 03 §3 用 Transformer 各采样 2000 句:
| 方法 | 合法率 | 不同句子 | 不合法的句子 |
|---|---|---|---|
| 第 1 讲窗口模型 纯采样 | 93.5% | 80.8% | 129 句,其中 91 句错在尾巴里的物主代词 |
| 第 1 讲窗口模型 top-p 0.95 | 96.3% | 78.3% | 74 句,全部是物主代词 |
| Transformer 纯采样 | 95.9% | 80.5% | 83 句:名词类别 22、主语 15、形容词后的名词 12、尾巴的选择 11、限定词 10、动词 7、尾巴里的其他词 4、尾巴里的物主代词 2 |
| Transformer top-p 0.95 | 100.0% | 77.9% | 0 句 |
纯采样时,Transformer 仍会犯错,但错误散落在各个地方,都是”长尾里抽中了小概率的词”。这正是 top-p 擅长处理的:top-p 0.95 把它们全部去掉,2000 句全部合法,多样性几乎不变。第 1 讲说”模型很有把握的错误只能靠更好的模型来解决”,这里就是这句话的兑现:结构改进把”头部的错误”变成了”尾部的错误”,解码策略就能处理后者了。
5.4 打开看:一个注意力头学会了”回头找主语”
模型是怎样做到的?代码 03 §4 统计了预测尾巴里的物主代词时(query 是 from / with 那个位置),每个头分给”本句主语”的平均注意力:
| 头 0 | 头 1 | 头 2 | 头 3 | |
|---|---|---|---|---|
| 第 1 层 | 0.435 | 0.785 | 0.061 | 0.696 |
| 第 2 层 | 0.621 | 0.396 | 0.755 | 0.857 |
每个 query 平均能看到 21 个位置,主语只占其中一两个;而第 2 层的头 3 平均把 0.857 的注意力放在主语上。看一个具体的例子,I grow my pretty tulip from ___(正确答案 my),这个头的注意力:
<bos> 0.000
I 0.998 ████████████████████████████████████████
grow 0.000
my 0.000
pretty 0.000
tulip 0.000
from 0.001
0.998 的注意力落在 I 上。这和 §2.2 我们手工设计的”找主语”的查询几乎一模一样,只不过这里的 q、k 是模型从数据里自己学出来的。注意它并没有被 my(限定词位置的物主代词)吸引——它找的确实是主语,不是”长得像答案的词”。
这种”打开模型看注意力”的分析是可解释性研究(第 14 讲)的起点。要提醒的是:注意力权重高只说明”信息从那里来”,不一定说明”模型因为它而做出了这个预测”;要下因果结论,还需要干预实验。练习 7 做了这个实验,结果出人意料:把这个头关掉,窗口外的准确率只从 0.982 降到 0.973;影响最大的反而是第 1 层的头 1(关掉后降到 0.893)。
5.5 消融:位置编码与层数
代码 03 §5 改动一处结构,其余不变:
| 变体 | 参数 | 测试困惑度 | 物主·窗口内 | 窗口外 |
|---|---|---|---|---|
| 基线:2 层 + 正弦位置编码 | 106,496 | 3.851 | 0.987 | 0.982 |
| 2 层 + 可学习的位置嵌入 | 139,264 | 3.835 | 0.985 | 0.976 |
| 2 层 + 不加位置编码(NoPE) | 106,496 | 3.812 | 0.988 | 0.986 |
| 1 层 + 正弦位置编码 | 56,512 | 3.861 | 0.983 | 0.972 |
| 基线换一个随机种子 | 106,496 | 3.817 | 0.990 | 0.983 |
这张表最重要的是最后一行:换一个随机种子,困惑度就变化 0.034,比前三行之间的任何差别都大。所以这里能下的结论只有:
- 位置信息的形式在这个任务上几乎不重要,不加位置编码也完全可以。§3.5 说过,因果掩码本身就泄露了顺序;“找最近的主语”也不需要精确的位置,只需要知道”哪个主语离我最近”。Haviv et al. (2022) 在真实语料上也发现,不加位置编码的因果语言模型和加了的差不多。(双向模型就不行了,练习 6。)
- 1 层也基本够用(窗口外 0.972)。一层注意力就能直接从 from 查到主语,只要 key 里有”我是主语”的信息、再加上一点”越近越好”的偏好。
- 可学习的位置嵌入多出 32,768 个参数(512 个位置 × 64 维),没有带来好处;而且训练集里最长的文档只有 207 个 token,后面三百多个位置的嵌入从来没被训练过。
做实验的教训:小模型的结论很容易被随机波动淹没。比较两个配置之前,先看看”同一个配置换个种子”会差多少;第 5 讲会系统地讨论评估中的不确定性。
6. 预训练目标:答案藏在文本里
6.1 自监督
预训练的数据是海量的无标注文本。没有人告诉模型”正确答案”是什么——答案就藏在文本本身里:把文本的一部分藏起来,让模型根据剩下的部分去猜。这叫自监督学习。不同的”藏法”就是不同的预训练目标。第 3 讲 slides 的最后一节列了三种(引用 Wang et al. 2022 的图):因果 decoder 通常用完整语言建模(FLM),encoder-decoder 常用掩码语言建模(MLM),非因果 decoder 可以用前缀语言建模(PLM)。
6.2 四种目标长什么样
代码 04 §1 把同一篇文档 she picks her ripe plum from her own garden . <eos> 变成四种训练样本(objectives.py):
FLM(完整语言建模,GPT):每个位置预测下一个词,11 个位置都有损失。
输入:<bos> she picks her ripe plum from her own garden .
目标:she picks her ripe plum from her own garden . <eos>
PLM(前缀语言建模):随机选一个切分点,前缀(方括号)双向可见、只当条件;只预测前缀之后的部分,这里 11 个位置中有 8 个有损失。
输入:[<bos> she picks her] ripe plum from her own garden .
目标:只预测 ripe plum from her own garden . <eos>
为什么前缀里不能算损失?前缀内部是双向注意力,位置 看得见位置 的输入,而位置 的目标恰好就是 的输入——答案就在眼前。练习 5 会看到,如果把因果掩码去掉还照样算 FLM 损失,模型会”作弊”。
MLM(掩码语言建模,BERT):随机选约 15% 的位置;选中的位置 80% 换成 <mask>、10% 换成随机词、10% 保持不变;只在这些位置算损失。
输入:<bos> she picks her ripe <mask> from her own garden <mask> <eos>
目标:位置 5、10 上的原词 plum 和 .(12 个位置中有 2 个有损失)
为什么不把选中的位置全换成 <mask>?因为预训练时输入里有 <mask>,微调和使用时却没有。如果模型只在看到 <mask> 时才认真预测,它在没有 <mask> 的输入上就学不到好的表示。混进随机词和原词,逼模型对每个位置都保持警惕(Devlin et al. 2018)。
span corruption(T5):随机选约 15% 的 token,组成平均长 3 的若干段,每段在输入里换成一个哨兵(sentinel)<x_i>;目标只写出被遮的部分,每段前面加上对应的哨兵(Raffel et al. 2020)。
输入:she picks her ripe plum from <x0> garden . <eos>
目标:<x0> her own <eos>
目标序列很短(只有 4 个 token),decoder 的计算量很小;哨兵告诉模型”这一段该填到哪里”。和 BERT 相比,一段可以有好几个 token,模型还得学会”填多长”,更接近生成。
6.3 每种目标有多少 token 在学
预训练的预算通常按”看过多少 token”算。同样看过一个 token,各种目标得到的训练信号差别很大。代码 04 §1 在 8000 篇训练文档(201,244 个 token)上统计:
| 目标 | 产生损失的 token | 占比 |
|---|---|---|
| FLM | 201,244 | 100.0% |
| PLM(随机切分点) | 105,405 | 52.4% |
| MLM(15%) | 30,354 | 15.1% |
| span corruption | 48,607 | 24.2%(其中被遮的原词 29,074 个,其余是哨兵和 <eos>) |
Wang et al. 的数字是:FLM 100%,PLM 平均 50%,他们的 MLM(span corruption,626 个 token 的样本里约 512 个输入、114 个目标)约 18%。FLM 的数据效率最高:同样的文本,它的每个 token 都在提供训练信号。
6.4 同样的预算,学到的东西一样吗
代码 04 §2 用同样的数据、同样的 6 遍、同样的学习率训练四个模型:FLM 用因果 decoder(就是 §5 的模型),PLM 用前缀 decoder,MLM 用 encoder-only,span corruption 用 2+2 层的 encoder-decoder(参数 240,128,每个 token 只经过其中 2 层,和 Wang et al. 的对齐方式相同)。每遍结束时验证集上”目标自己的损失”:
| 目标 | 第 1 遍 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| FLM | 1.503 | 1.459 | 1.408 | 1.363 | 1.352 | 1.354 |
| PLM | 1.507 | 1.419 | 1.356 | 1.309 | 1.291 | 1.292 |
| MLM | 3.036 | 2.316 | 1.751 | 1.304 | 1.071 | 1.006 |
| span corruption | 2.101 | 1.844 | 1.560 | 1.373 | 1.258 | 1.196 |
四种损失的难度不同,横向不能比;要看的是形状:FLM、PLM 第 4~5 遍就基本走平了,MLM 和 span corruption 到第 6 遍还在明显下降——每遍得到的训练信号少,学得慢。
然后在三类任务上比较。花园世界的好处是每个任务都有精确的标准答案:
- (a) 续写:给测试文档的第一句,预测第二句,算每个词的 NLL(真实分布是 1.330)。这是 Wang et al. “零样本生成”的玩具版。
- (b) 完形填空:在第一句里随机挖掉一个词,两边都给,看模型给正确词的概率。真实后验 可以对 87 个候选词逐一精确算出(真实后验下正确词的 NLL 是 0.792,因为很多位置本来就有多个合理答案)。
- (c) 从零生成句子:纯采样 1000 句,看合法率。
每个模型都用它”自然”的方式做这些任务:
| 模型 | (a) 续写 NLL/词 | (b) 填空 NLL | KL(真‖模) | 众数一致 | 每题前向 |
|---|---|---|---|---|---|
| FLM(因果 decoder) | 1.371 | 0.857 | 0.062 | 92.2% | 87 |
| PLM(前缀 decoder) | 1.438 | 0.851 | 0.061 | 90.0% | 87 |
| MLM(encoder) | 3.191 | 0.873 | 0.102 | 86.8% | 1 |
| span corruption(enc-dec) | 9.812 | 1.784 | 1.022 | 54.6% | 1 + 87 条短序列 |
| 真实分布 | 1.330 | 0.792 | 0 | 100% | — |
| 模型 | (c) 合法率 | 停在句号 | 停在 <eos> |
|---|---|---|---|
| FLM | 96.2% | 99.7% | 0.2% |
| PLM | 93.3% | 99.1% | 0.9% |
| MLM | 0.3% | 6.6% | 93.3% |
| span corruption | 0.0% | 5.1% | 94.9% |
逐项读:
续写和生成:因果模型完胜。 FLM 的续写 NLL 1.371,离真实的 1.330 很近;PLM 略差(它的训练信号只有一半)。MLM 和 span corruption 差得多。原因很有启发性:
- MLM 不是生成模型。要从左往右写,只能”在末尾放一个
<mask>,猜它,再放下一个”。可是 MLM 的训练序列永远以 <eos> 结尾,于是它学到了”最后一个位置是 <eos>“:93% 的样本第一步就写出了 <eos>。双向模型要生成,必须先知道”还剩多长”——这是 MLM 不适合开放式生成的根本原因。 - span corruption 练的是”填 3~4 个词的段”。续写要它一口气写出一整句(8 个词以上),远超它见过的段长,它在两三个词之后就想结束。
填空:MLM 的优势是快,不是准。 这是本讲最出乎意料的结果。MLM 一次前向就能读出填空的分布;因果模型只会从左往右算概率,填空要用贝叶斯公式——把 87 个候选词各填进去,算整篇文档的概率,再归一化:
代价是 87 次前向,但它做得比 MLM 还略好(0.857 vs 0.873,KL 0.062 vs 0.102)。道理是:因果语言模型是一个完整的联合分布,原则上任何条件概率都能从它算出来,只是不一定便宜;而它每篇文档得到的训练信号是 MLM 的约 6.6 倍。MLM 的真正优势在于”一次前向就得到所有位置的双向表示”——这对分类、检索这类需要”理解整句”的任务、对推理成本敏感的场景很重要。
span corruption 在这里最差。 填空题是”恰好挖掉一个词”,而它练的多是 3~4 个词的段——预训练时空多长,它就擅长填多长。(我们已经按”这一段恰好是 “来打分:。)配套代码的 README 还记录了一次更戏剧化的翻车:第一版实现照搬了 T5 的切分方式,被遮的段总在文档末尾,结果模型完全不会填句子中间的空(填空 NLL 6.1)。预训练数据怎么造,模型就学会什么。
要强调的是,这是一个 10 万参数、训练 6 遍的玩具实验。真实规模下 MLM / span corruption 模型远没有这么差,但”不同目标擅长不同任务”的方向和 Wang et al. 的结论一致。
6.5 Wang et al. (2022) 的结论
本讲的阅读材料做的正是”大规模版本”的这件事:三种结构(因果 decoder、非因果 decoder、encoder-decoder)× 三种目标(FLM、PLM、MLM),每个模型约 5B 参数(encoder-decoder 11B),在 C4 上预训练 168B token,然后在 30 个任务上做零样本评估(T0 的评估集和 EleutherAI 的 lm-evaluation-harness)。按”看过的 token 数”对齐预算,而不是按”产生损失的 token 数”。
- 发现 1:只做自监督预训练时,因果 decoder + FLM 的零样本能力最好;非因果 decoder(PLM)在一个百分点以内;encoder-decoder 远远落后。(MLM 训练的模型不适合做零样本的提示式评估,没有参与这一项比较。)
- 发现 2:预训练之后再做多任务微调(在大量”用提示改写过的任务”上微调,T0 的做法,第 6 讲),结论反过来:用 MLM 预训练的非因果模型最好,encoder-decoder + MLM 第一,非因果 decoder + MLM 紧随其后;因果 decoder + FLM 反而较差。
- 两头兼顾:改装(adaptation)。既然两种组合各有所长,能不能先训一个、再便宜地改成另一个?他们试了两个方向:
- 把 MLM 训练的非因果 decoder 改成因果、继续用 FLM 训练(language modeling adaptation):收敛到同样的 FLM 损失快 1.6 倍;
- 把 FLM 训练的因果 decoder 改成非因果、继续用 MLM 训练(non-causal MLM adaptation):快 3.3 倍,得到的模型多任务微调后排第二。
改装之所以可能,是因为三种结构用的是同一套参数:只换掩码和目标,一个参数都不用加。代码 04 §5 在花园世界上试了两个方向,各训练 1 遍:
| MLM 模型(完形填空 NLL,越低越好) | |
|---|---|
| 从零训练 1 遍 | 2.513 |
| 从零训练 6 遍 | 0.873 |
| 因果 FLM 模型改成双向,MLM 训练 1 遍 | 1.669 |
| 因果模型(测试集困惑度,越低越好) | |
|---|---|
| 从零训练 1 遍 | 4.296 |
| 从零训练 6 遍 | 3.851 |
| MLM 模型改成因果,FLM 训练 1 遍 | 3.875 |
两个方向都远好于从零训练 1 遍。MLM → 因果的改装尤其有效:1 遍之后就几乎追上了从零训练 6 遍的因果模型(3.875 vs 3.851)——MLM 预训练已经学到了大部分”语言知识”,改装只是教它”从左往右用”。T5 后来发布的”LM-adapted”版本(Lester et al. 2021)走的就是这条路:在 span corruption 预训练之后,再用 prefix LM 目标训练 10 万步,让它能做生成。
6.6 小结:选结构和目标时在权衡什么
| 因果 decoder + FLM | 前缀 decoder + PLM | encoder + MLM | encoder-decoder + span corruption | |
|---|---|---|---|---|
| 训练信号 | 100% | ~50% | ~15% | ~15%(+ 哨兵) |
| 生成 | 天然 | 天然 | 很难 | 需要改装 |
| 条件部分的表示 | 单向 | 双向 | 双向 | 双向 |
| KV cache | 可以 | 可以 | 不行 | encoder 算一次,decoder 可以 |
| 代表 | GPT、Llama | UniLM、GLM | BERT、RoBERTa | T5、BART |
今天的主流选择是第一列,原因见 §4.5。但”双向编码条件部分”的价值并没有消失:多任务微调后它更好(Wang et al. 发现 2),检索和分类用的嵌入模型大多仍是双向的(第 11 讲)。
7. 小结、常见误区、练习
7.1 小结
- RNN 的两个问题:远处的信息要经过很多步才能传到(还会被冲淡),encoder-decoder 里整句输入被压进一个向量;计算必须串行。
- 注意力 = 软的字典查询:。q 的长度控制查询的锐利程度;除以 让 softmax 的”尖锐程度”不随维度变化(实测:不缩放时 的最大权重 0.945、熵 0.134)。掩码把禁止的分数置为 。
- Transformer 块 = 多头自注意力(交流)+ FFN(加工)+ 残差 + LayerNorm;多头不增加参数。注意力对顺序视而不见,必须加位置编码(或依靠因果掩码泄露的顺序);正弦编码的点积只依赖距离。
- 三种结构 = 三种掩码:双向(encoder-only)、因果(decoder-only)、前缀;encoder-decoder 多了交叉注意力。实测依赖矩阵与掩码完全一致。因果掩码让前面的计算在追加 token 后不变,KV cache 因此成立。
- 花园世界实验:两层 Transformer 的困惑度和 RNN、GRU 同一水平(差距主要来自训练集缺失的搭配),但窗口外的物主代词从 0.700 升到 0.982;top-p 0.95 采样 100% 合法;第 2 层的一个头把 0.857 的注意力放在主语上。NoPE 在这里不比正弦编码差,换随机种子的波动和各种结构差异同一量级。
- 预训练目标:FLM(100% 的 token 有损失)、PLM(~50%)、MLM(~15%,80/10/10)、span corruption(~15% + 哨兵)。同预算下,因果模型续写和生成最好,用贝叶斯公式做填空也不输 MLM(只是要 87 次前向);MLM 不会从左往右生成;span corruption 只擅长填它练过的长度。Wang et al.:只预训练时 FLM 最好,多任务微调后 MLM 最好;改装可以两头兼顾。
7.2 常见误区
- “注意力权重就是解释”:权重高说明信息从那里来,不说明预测是因为它做出的。要下因果结论需要干预(练习 7、第 14 讲)。
- “多头注意力的参数是单头的 h 倍”:参数量是 (加偏置),和头数无关;多头只是把 维切成 份。
- “Transformer 是平方复杂度,所以总比 RNN 慢”:训练时 Transformer 完全并行,RNN 必须串行; 项占比是 ,在 远小于 时并不主要。(推理时每一步的情况不同,见第 19、21 讲。)
- “没有位置编码的 Transformer 不知道词序”:双向的确实不知道;因果的能从掩码里推断出位置(§3.5、§5.5)。
- “BERT 和 GPT 是两种完全不同的网络”:积木完全相同,区别在掩码和训练目标;参数甚至可以互相”改装”(§6.5)。
- “MLM 模型更擅长理解,所以填空一定比因果模型准”:因果模型是完整的联合分布,用贝叶斯公式也能填空,在本讲的实验里还略好;MLM 的优势是一次前向。
- “预训练目标只影响训练效率”:它决定了模型擅长什么。span corruption 只擅长填它练过的长度;第一版实现里被遮段总在末尾,模型就只会填末尾(§6.4)。
- “换了一个结构,困惑度低了 0.03,说明新结构更好”:本讲的模型换个随机种子就差 0.034。比较之前先量一量噪声有多大。
7.3 练习
练习 1(手算注意力):;三个 key 分别是 、、,value 与 key 相同;。算出注意力权重和输出。
参考答案
分数 。,,和为 5.056,权重 。输出 。用 scaled_dot_product_attention 验证:权重 [0.4011, 0.1978, 0.4011],输出 [0.8022, 0.5989]。注意第 2 个 key 和 q 正交(点积为 0),它仍然分到了 0.198 的权重——softmax 不会给任何位置精确的 0,除非用掩码。
练习 2(为什么是 ):(a)证明:若 的各分量相互独立、均值 0、方差 1,则 。(b)如果改成除以 而不是 ,会发生什么?
参考答案
(a)。各项独立,,。独立项的方差相加,得 。
(b)分数的标准差变成 ,维度越大分数越接近 0,softmax 越接近均匀分布——变成了另一个极端:注意力”什么都看一点”,很难集中。模型当然可以学着把 q、k 放大来补偿,但初始化时注意力几乎是平均池化。除以 恰好让初始分数的标准差为 1,两头都不偏。
练习 3(数参数):、8 个头、,线性层都带偏置。(a)算出一个多头注意力子层、一个 FFN、两个 LayerNorm 的参数量,验证一个块是 3,152,384。(b)decoder 块为什么是 4,204,032?
参考答案
(a)注意力:4 个 矩阵加 4 个偏置,。FFN:。两个 LayerNorm:。合计 3,152,384,与代码 02 §4 一致。FFN 占 2/3。
(b)多一个交叉注意力子层(1,050,624)和一个 LayerNorm(1024):。
练习 4(掩码):(a)写出 、前缀长 3 的前缀 LM 掩码。(b)生成时,如果新追加的 token 落在前缀里面(比如交互式地往”问题”里补充内容),KV cache 还能用吗?(c)为什么 BERT 不能用 KV cache?
参考答案
(a)前三行(前缀内的位置)是 1 1 1 0 0;第 4 行 1 1 1 1 0;第 5 行 1 1 1 1 1。
(b)不能直接用:前缀内部是双向的,新 token 进入前缀后,前缀里所有位置的表示都会改变(它们现在能看见这个新 token),之前缓存的 K、V 全部作废。前缀 LM 的 KV cache 只在”前缀固定、往后生成”时成立。
(c)BERT 每个位置都能看见所有位置,任何位置的变动(包括追加)都会改变所有位置的表示,§4.3 实测前面位置的隐藏状态变化 0.233。
练习 5(动手:忘了因果掩码):用 TransformerLM(attn="bidirectional") 搭配 clm_loss(即”双向模型 + 预测下一个词”)训练 1 遍。预测会发生什么,再运行验证。
参考答案(作者机器上的实际结果)
训练 1 遍后,用同样的双向模型在验证集上算”困惑度”,得到 2.09——比真实分布的 3.655 还低,这对诚实的模型是不可能的(交叉熵 ≥ 熵)。原因是位置 能看见位置 的输入,而那正是它要预测的答案:模型学会了”抄下一个词”。把同一个模型改成因果掩码再评估(这才是真正生成时的情况),困惑度是 23.6——它什么也没学会。
这是一类真实存在的 bug:写训练代码时掩码出错,损失会降得”好得不像话”。验证损失低于理论下界(或者低得可疑)时,首先检查有没有信息泄露。
练习 6(思考 + 动手:双向模型需要位置编码吗):§5.5 说因果模型不加位置编码也行。对双向的 MLM 模型呢?用 pos="none" 训练一个 MLM 模型,预测它在完形填空上的表现。
参考答案(作者机器上的实际结果)
双向 + 无位置编码的模型对词序完全不敏感(§3.5 的排列等变性)。对一个 <mask>,它只知道”句子里有哪些词”,不知道 <mask> 在哪里——she picks her <mask> plum 和 she <mask> picks her plum 对它是同一个输入,填空分布也必然相同。所以它只能根据”句子里还缺什么词”来猜。
把 MLM 模型的结构改成 dict(BASE_ARCH, pos="none")、其余不变训练 6 遍:验证集 MLM 损失停在 2.544(有位置编码时是 1.006);完形填空 NLL 从 0.873 升到 1.807,KL 从 0.102 升到 1.036,与真实众数一致的比例从 86.8% 降到 68.4%。这个对比说明:“因果模型可以不要位置编码”靠的是因果掩码,不是注意力本身。
练习 7(动手:关掉一个头):把 §5.4 找到的第 2 层头 3 的输出置零(提示:在 MultiHeadAttention.forward 里,o_proj 之前把对应的 16 维切片乘 0),重新算物主代词准确率。如果准确率几乎不变,说明什么?
参考答案(作者机器上的实际结果)
窗口外物主代词的准确率(基线 0.982):
| 关掉的头 | 它分给主语的注意力 | 窗口内 | 窗口外 |
|---|---|---|---|
| 无(基线) | — | 0.987 | 0.982 |
| 第 2 层头 3 | 0.857(最高) | 0.981 | 0.973 |
| 第 2 层头 2 | 0.755 | 0.980 | 0.968 |
| 第 1 层头 3 | 0.696 | 0.968 | 0.970 |
| 第 1 层头 1 | 0.785 | 0.855 | 0.893 |
| 第 2 层头 2 + 头 3 | 0.950 | 0.927 | |
| 第 2 层全部 4 个头 | 0.919 | 0.903 |
注意力最集中在主语上的那个头(第 2 层头 3),关掉之后准确率几乎不变(0.982 → 0.973);影响最大的反而是第 1 层的头 1。就算把第 2 层的 4 个头全关掉,窗口外仍有 0.903——第 1 层已经把主语的信息搬到了 from 的位置上,第 2 层的几个头只是”又看了一遍”。
结论:这个功能是冗余实现的,“注意力权重最高”和”这个头最重要”是两回事。这正是第 14 讲要讨论的:相关性的观察(看权重)需要因果性的干预(消融、激活修补)来确认。(实现方法:临时替换某个 MultiHeadAttention 实例的 forward,在 o_proj 之前把 out[:, 头号] 置零。)
练习 8(思考:预算怎么对齐):Wang et al. 按”看过的 token 数”对齐预算。如果改成按”产生损失的 token 数”对齐,MLM 能多看多少数据?你认为哪种对齐方式更公平?
参考答案
MLM 只有约 15%(span corruption 约 18%)的 token 产生损失,按损失 token 对齐,它能多看约 5.6~6.7 倍的文本。但每多看一个 token 都要做一次完整的前向和反向(被遮的位置之外的 token 也要经过整个网络),计算量是按看过的 token 数算的。所以按”看过的 token 数”对齐,基本等价于按计算量对齐,这是更公平的做法——在计算受限的现实里(预训练几乎总是计算受限),“每单位计算得到多少学习信号”才是要比的东西。反过来,如果数据才是瓶颈(高质量数据快用完了,第 3、23 讲),每个 token 被”榨干”的程度就变得重要,结论可能不同。
练习 9(动手:更便宜的贝叶斯填空):§6.4 里因果模型做填空时,每个候选词都把整篇文档跑一遍。只跑到第一句的句号为止,结果会变吗?还能怎样进一步省计算?
参考答案(作者机器上的实际结果)
在花园世界里,第一句之后的内容与第一句条件独立(句号之后状态机回到起点),所以真实后验只取决于第一句。对模型来说,后面的句子也几乎不受影响:只算到句号时,500 题的平均填空 NLL 是 0.8577,算整篇是 0.8567,几乎相同,而计算量减少了约三分之二。
进一步:87 个候选句子在空格之前的部分完全相同,可以只算一次、把 K、V 缓存起来(§4.3),每个候选只需算空格及之后的位置。这就是”共享前缀 + KV cache”,在第 5 讲的多选题评估、第 19 讲的推理加速里都会再遇到。
7.4 延伸阅读
- 原课阅读:Wang et al. (2022), What Language Model Architecture and Pretraining Objective Work Best for Zero-Shot Generalization?, ICML
- 注意力的起源:Bahdanau, Cho & Bengio (2014), Neural Machine Translation by Jointly Learning to Align and Translate
- Transformer:Vaswani et al. (2017), Attention Is All You Need;Alammar, The Illustrated Transformer(原课第 1 讲的阅读)
- 三种结构的代表:Devlin et al. (2018), BERT;Radford et al. (2018/2019), GPT / GPT-2;Raffel et al. (2020), Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer(T5,span corruption 与大量结构 / 目标对比)
- 前缀 LM:Liu et al. (2018), Generating Wikipedia by Summarizing Long Sequences;Dong et al. (2019), Unified Language Model Pre-training(UniLM)
- 改装:Lester et al. (2021), The Power of Scale for Parameter-Efficient Prompt Tuning(LM-adapted T5)
- 不加位置编码:Haviv et al. (2022), Transformer Language Models without Positional Encodings Still Learn Positional Information
- 本仓库的相关内容:CMU10-423 第 2 讲(Transformer 语言模型)与第 5 讲(BERT);CS336 第 3 讲(结构与超参数的工程选择)
7.5 下一讲预告
本讲 §5.2 发现,模型离真实分布的差距大部分来自数据:训练集里缺了什么,模型就学不会什么。第 3 讲转向预训练数据本身:几万亿 token 的数据从哪里来(Common Crawl、C4、FineWeb……),“清洗”到底删了什么、又误删了什么,怎样去重,分词器是怎样从数据里训练出来的,以及怎样把文档打包成批次。我们会造一个”脏网页”版本的花园世界,从零搭一条清洗流水线。