对应 Maps to:Stanford CS324 Winter 2023 Advances in Foundation Models,Syllabus 主题 “Model Architectures and Training Objectives for FMs” 的阅读:CS324 Winter 2022 课程笔记 Modeling 与 Training、Vaswani et al. Attention Is All You Need(2017)、Clark et al. ELECTRA(2020)、Tay et al. Unifying Language Learning Paradigms(UL2,2022)、Raffel et al. Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer(T5,2019)。补充阅读:W22 笔记 Selective Architectures(混合专家与检索)、Devlin et al. BERT(2018)、Lewis et al. BART(2019)、Wang et al. What Language Model Architecture and Pretraining Objective Work Best for Zero-Shot Generalization?(2022)。 依据说明:Winter 2023 的日历上没有专门讲这个主题的讲座,它是阅读清单里的一个主题;课程网站也没有 slides 或录像。本讲依据上面这些阅读材料来写:分词、上下文嵌入和 Transformer 的部分沿着 W22 笔记 Modeling 展开,三种架构的训练目标沿着 W22 笔记 Training 展开,具体的数字引自各篇论文(文中注明出处);§3 把第 1 讲的
tiny_gpt.py逐块拆开,是本讲义自己的内容;最后一节”2023 年之后”是我补充的。 配套代码 Code:code/lecture03/(PyTorch + numpy;5 个脚本 + 1 个共享模块arch_tools.py。代码 01 只用标准库、约 2 秒;02、03 几秒;04、05 第一次运行各约 80 秒(训练并缓存模型),之后几秒) 预计阅读时间:3.5 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲,你应该能够:
- 说清楚分词器(tokenizer)在做什么、为什么不能按空格切;手算一个 BPE 的例子;解释 unigram 分词为什么是一个”概率模型”;说出第 1 讲用字符级分词的好处和代价。
- 用”上下文嵌入”(contextual embedding)这个抽象概括 encoder、decoder、encoder-decoder 三种架构各自做什么。
- 不看代码地画出第 1 讲 base model 的数据流:每一步的张量形状、注意力里的 q / k / v、为什么除以 、因果掩码、多头、残差流、pre-LayerNorm、MLP、weight tying;并能按公式数出它的 109,056 个参数、用 估算 GPT-3 的参数量。
- 写出三种架构家族的训练目标:GPT 的因果语言建模、BERT 的 masked LM(15%、80/10/10、NSP)、T5 的 span corruption、BART 的去噪;以及介于中间的 prefix LM。
- 解释 ELECTRA 的替换 token 检测为什么被认为更省样本,以及它成立需要什么前提(代码 05 给了一个反例)。
- 说出 UL2 的混合去噪器(R / S / X)和模式 token,以及它”把架构和目标分开”的观点。
- 用代码 04 的实验和文献结论解释:为什么 decoder-only 最终成了默认选择,而 encoder 在哪些地方仍然不可替代。
- 简要说明混合专家(MoE)和检索增强这两种”选择性”架构的动机,以及 2023 年之后的架构演进(RoPE、SwiGLU、RMSNorm、GQA、MoE 的回归)。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面几讲再展开 Previewed only |
|---|---|
| 第 1 讲:语言模型 = 序列上的分布、链式法则(§2.2)、bit/字符(§2.4)、预训练与探针(§3.1、§3.4) | 规模定律:参数、数据、算力怎样决定损失(第 4 讲) |
| 矩阵乘法、softmax、交叉熵 | 微调、LoRA 等适配方法的细节(第 6 讲) |
PyTorch 基础(第 1 讲 tiny_gpt.py 开头的”PyTorch 小知识”) | 注意力的 GPU 实现与 FlashAttention(第 8 讲);MoE 的分布式训练(第 9 讲) |
| 注意力头的机制可解释性(第 12 讲) |
本讲是”往里看一层”:第 1 讲从外面看基础模型能做什么,本讲打开它,看它由什么组成、用什么目标训练,以及不同的选择带来什么不同。如果你学过本目录下的 CS336,它的第 3–4 讲把现代架构的细节(归一化、位置编码、注意力变体、MoE)讲得更深;本讲更侧重”为什么”和 2018–2022 年这段架构之争。
0.3 本讲地图 Map
§1 分词:按空格切的问题;BPE;unigram;字符级的代价 ← 代码 01
§2 上下文嵌入:三种架构的共同抽象;为什么是 Transformer
§3 打开第 1 讲的黑盒:嵌入、注意力、√d、因果掩码、多头、残差流、 ← 代码 02
LayerNorm、MLP、输出层;数参数;手工前向传播;消融
§4 三种架构家族与训练目标:GPT、BERT、T5 / BART、prefix LM;训练信号的密度 ← 代码 03
§5 同样的算力,谁学得快、谁能做什么;ELECTRA(以及它在迷你世界里为什么没奏效) ← 代码 04、05
§6 UL2:混合去噪器与模式切换
§7 为什么 decoder-only 成了默认选择
§8 选择性架构:混合专家与检索
§9 2023 年之后
§10–14 小结、常见误区、练习、延伸阅读、下一讲预告
0.4 记号 Notation
全课统一的记号见第 1 讲 §0.4。本讲新增:
| 符号 | 含义 | 本讲代码里的值 |
|---|---|---|
| () | 每个位置的向量维度(模型宽度) | 64 |
| 层数(Transformer 块的个数);第 1 讲里 也表示序列长度,本讲序列长度写成 | 2 | |
| 序列长度 / 上下文长度(block size) | base model 96 个字符;代码 04、05 是 32 个词 | |
| , | 注意力头数;每个头的维度 | 4 个头,每头 16 维 |
| 注意力里的查询、键、值、输出投影矩阵 | 代码里 qkv、proj | |
| 注意力掩码: 表示位置 可以看位置 , 表示看不到 | 代码里用布尔值,True = 可以看 | |
| 上下文嵌入:把 token 序列映成同样长度的向量序列 | ||
[M]、[S]、<0> | 本讲词级分词器的特殊 token:mask、分隔 / 开始符、T5 式哨兵 | |
| , | UL2 / T5 里被挖掉的片段的平均长度、被挖掉的比例 | T5 基线: = 15% |
| ELECTRA 判别器损失的权重 | 50 |
0.5 关于数字 · About the numbers
讲义里的实验数字都来自配套代码的实际输出(固定种子、PyTorch 单线程,重复运行逐字相同;删掉缓存重训,得到的模型和输出也完全相同,只有计时不同)。代码 02 读取第 1 讲缓存的 base model(不修改它);代码 04、05 用迷你互联网里的评论训练自己的小模型,缓存在 code/lecture03/checkpoints/。这些模型只有 10 万量级的参数,训练只有几十秒,所以它们演示的是机制:哪个目标给的训练信号多、哪种掩码能做什么。真实模型的量级(BERT、T5、ELECTRA、UL2 在基准上的分数)引自原论文,会注明出处。
本讲有好几处实验结果和我写代码前的预想不一样——最明显的是 T5 在这个算力下一直停在”按先验猜”、ELECTRA 没有学起来。讲义按实际结果来写,并解释原因;这些情况都列在代码 README 的”关于实验结果”里。其中一条是我自己的评测错误:最初的版本让 T5 自由生成、要求整段完全匹配,而别的模型只在几个候选里挑一个,于是 T5 被记成了全 0;改成同一种协议后,T5 的分数是”永远猜 ok”的水平(§5.2)。
1. 分词 · Tokenization
语言模型是 token 序列上的概率分布(第 1 讲 §2.1)。可是拿到手的数据是一个字符串。谁来决定”token”是什么?这就是分词器(tokenizer)的工作。它看起来像预处理的小事,其实影响很大:它决定了序列有多长(于是决定了计算量和上下文能装多少内容)、词表有多大、模型能不能处理没见过的词,甚至影响模型会不会算术。
1.1 为什么不能按空格切
最朴素的办法是按空格切。CS324 笔记列了它的几个问题,代码 01 演示 1 把它们打印了出来:
- 中文不用空格:“我今天去了商店。” 按空格切还是一整块。
- 德语有很长的复合词;英语有 father-in-law(和 mother-in-law 没有任何共享的部分)、don’t。
- 标点会粘在词上。
在迷你互联网上,三种粒度的对比是这样的(代码 01 演示 1):
| 粒度 | 词表大小 | token 总数 | 每行 token | 验证集里没见过的 token |
|---|---|---|---|---|
| 字符(第 1 讲) | 44 | 262,986 | 37.5 | 0.00% |
| 按空格切 | 1,874 | 54,705 | 7.8 | 3.05% |
| 词 + 标点 | 350 | 76,524 | 10.9 | 0.00% |
按空格切时,“varno.” 和 “varno” 是两个不同的 token,一整道加法题 “11+21=32.” 是一个 token——验证集里没见过的 78 个 token 全都是这样的加法题,模型对它们一无所知。这就是开放词表(open vocabulary)问题:任何”一个词一个 token”的方案,都会遇到训练时没见过的词(新名字、新数字、拼写错误),只能把它们变成 [UNK]。字符级永远不会遇到没见过的 token,代价是序列长了好几倍。
好的分词在两个极端之间找平衡(W22 笔记的说法):token 不能太多(序列太长,难以建模、计算贵),也不能太少(否则词与词之间没有参数共享——father-in-law 和 mother-in-law 应该共享 “-in-law”),而且每个 token 最好是一个有意义的单位。
1.2 BPE:反复合并最常见的一对
字节对编码(byte pair encoding, BPE)原本是一种压缩算法,Sennrich 等人(2016)把它用到了机器翻译的分词上。算法非常简单:
1. 每个词先拆成单个字符
2. 统计所有相邻 token 对的出现次数,把最常见的一对合并成一个新 token,记下这条"合并规则"
3. 重复第 2 步,直到合并了预定的次数
学到的是一张有序的合并规则表;词表 = 初始字符 + 每条规则产生的新 token。给新文本分词时,按规则学到的先后顺序依次合并。代码 01 演示 2 (a) 逐步复现了 W22 笔记的例子——语料是 the car、the cat、the rat:
| 步 | 合并 | 次数 | 合并后 |
|---|---|---|---|
| 1 | t + h → th | 3 | th e · c a r · c a t · r a t |
| 2 | th + e → the | 3 | the · c a r · c a t · r a t |
| 3 | c + a → ca | 2 | the · ca r · ca t · r a t |
词表是 [a, c, e, h, r, t, th, the, ca]。给新文本 “the ox” 分词,得到 [the, ␣, o, x]:the 是一个 token,ox 没见过,就退回到单个字符——BPE 没有 OOV,最坏情况就是字符级。(这个例子里空格单独成块、不参与合并;并列时取最早出现的一对,和笔记的选择一致。)
在迷你互联网上训练(演示 2 (b))。先按 GPT-2 的方式做”预分词”:空格贴在后面那个词的开头(“␣lives”),字母、数字、标点各成一块;BPE 只在块的内部合并。最先学到的合并是 he → the → ␣w → ␣the → ␣was……正是评论和故事里最高频的片段。合并越多,词表越大,序列越短:
| 合并次数 K | 词表大小 | 字符 / token | 每行 token | 96 个 token 装几行 |
|---|---|---|---|---|
| 0(字符级) | 44 | 1.00 | 37.5 | 2.6 |
| 100 | 144 | 2.11 | 17.8 | 5.4 |
| 300 | 344 | 3.14 | 11.9 | 8.0 |
| 649(合并到底) | 693 | 3.44 | 10.9 | 8.8 |
迷你互联网只有 410 种不同的块,合并 649 次以后每块都成了一个 token。真实文本的块是长尾的,GPT-2 / GPT-3 的词表停在 50,257 个 token,罕见的词仍然被拆成几块。K = 300 时可以清楚地看到 BPE 的几个性格:
zukun lives in quila. → |zu|ku|n|␣lives|␣in|␣quila|.| 高频词是一个 token,名字是碎片
95+42=137. → |95|+|4|2|=|13|7|.| 数字被切得不一致
kari livs in varno. → |k|ar|i|␣li|v|s|␣in|␣varno|.| 拼错一个字母,token 序列就完全不同
数字被切得不一致这一点值得多说一句。K = 300 时,10–198 这些数里 29 个是一个 token、150 个被切成两块、10 个被切成三块:95 是一个 token,45 是 4|5,107 是 10|7,137 是 13|7。同一个数位在不同的数里落在不同的 token 位置上,模型做加法时要”对齐数位”就难了;合并到底以后,训练语料里没出现过的 195、196、198 也被切成两块(1|95 等)。这被认为是 GPT-3 这类模型算术不稳定的原因之一;PaLM、LLaMA 等后来的模型干脆把数字拆成单个数位。
GPT-2 还做了一个重要的改进:字节级 BPE——先把文本变成 UTF-8 字节,再在字节上做 BPE。W22 笔记提到,Unicode 有 144,697 个字符,直接在字符上做会很稀疏;字节只有 256 种,任何语言、任何符号都能表示,永远不会遇到”字母表之外”的字符。
1.3 unigram 分词:分词也是一个概率模型
T5 用的 SentencePiece 默认用另一种算法:unigram 语言模型分词(Kudo 2018)。它把”怎么切”本身看成一个概率模型:每个 token 有一个概率,一种切法的概率是其中各 token 概率的乘积,分词时取概率最大的切法(用动态规划,即 Viterbi 算法)。
W22 笔记的例子:训练数据 “ababc”,切成 ab | ab | c,按这次切分里的频率,,,似然是
(笔记里把结果写成了 4/9,是一个算术笔误;代码 01 演示 3 用精确分数核对过。)如果词表是 {a, b, c, ab}、概率 1/8、1/8、1/4、1/2,“ababc” 有 4 种切法,概率最大的 ab | ab | c 是 1/16,其次两种都是 1/512。训练时从一个很大的候选词表出发,用 EM 算法估计概率,然后删掉”删了以后似然下降最少”的一部分 token(笔记里是保留 80%),反复进行。
和 BPE 相比,unigram 的特别之处是它给出了其他切法的概率。代码 01 在迷你互联网上用 BPE(K = 300)分词后的 token 频率当 unigram 概率:␣zukun 的头两种切法 ␣|zu|ku|n 和 ␣|zu|k|un,概率约 0.66 : 0.33。训练时可以按概率随机采样切法(subword regularization),让模型见识同一个词的不同切法,对分词的偶然性更鲁棒。多数词两种算法切得一样;拼错的 livs 不一样(BPE 按规则先合出 ␣li,而 ␣li 不在 unigram 的词表里)——对没见过的字符串,两种算法可以给出不同的切法。
1.4 第 1 讲为什么用字符,代价是什么
代码 01 演示 4 把字符级的代价摆在了一起:
| 分词方式 | 词表 V | 每行 token n | 注意力矩阵 n²/行 | 96 个 token 装几行 |
|---|---|---|---|---|
| 字符(第 1 讲) | 44 | 37.5 | 1,407 | 2.6 |
| BPE K = 300 | 344 | 11.9 | 142 | 8.0 |
- 序列长:同一行文本,字符级的 token 数是 BPE 的 3.1 倍,注意力矩阵的元素个数是 9.9 倍(自注意力的计算量随 增长,第 8 讲);生成一行也要多走 3 倍的步数。
- 上下文装得少:base model 的 96 个字符只装得下约 2.6 行,换成 BPE 能装约 8 行。上下文学习要在上下文里放示例(第 5 讲),这一点很关键。
- 大部分预测很琐碎:字符级模型的多数位置是在”把单词拼完”,真正有信息量的预测(住在哪个城市、评分是什么)只占一小部分位置。这也是为什么第 1 讲用 bit/字符而不是 token 级困惑度——不同分词方式的困惑度不能直接比较(第 1 讲练习 3)。
好处同样明显:词表只有 44、不用训练分词器、没有 OOV、模型能看到每个字母(包括数字的每一位)。对一个 26 万字符的迷你世界,序列长的代价也承受得起。
真实系统的分词器差别也很大。W22 笔记比较了 GPT-3(BPE,5 万词表)和 Jurassic(SentencePiece,25.6 万词表):同样的文本,Jurassic 的 token 数少 28%,所以快约 1.4 倍,同样 2048 的上下文能多装 39% 的文字——“Abraham Lincoln” 在 Jurassic 里是一个 token。词表大的代价是嵌入表更大,但在大模型里这不算什么:GPT-3 的嵌入表 50,257 × 12,288 只占参数的约 0.35%(代码 01 演示 4)。
1.5 本讲后半部分为什么换成”词级”token
§4、§5 要比较 BERT、T5、ELECTRA 这些”遮住一部分、让模型猜回来”的目标。BERT 的 WordPiece 词表里,常见词都是一个 token:遮住一个 token 就是遮住一个词,模型用一次 softmax 在 3 万个词里挑。字符级模型上照搬会出两个问题(代码 03 演示 4):
- 每个字符独立地以 15% 的概率被遮住时,93% 的被遮字符只看同一个词剩下的字母、查一下词表就能唯一确定(“va_no” 只能是 varno)——模型学会拼写就够了,用不着理解上下文。
- 改成遮住整个词,又碰上 masked LM “各位置独立预测”的问题(§4.3):就算模型把每个位置的字母分布估计得完全正确,逐位置选最可能的字母也会拼出 “crest” 这样的非词。
所以代码 04、05 改用”词 + 标点”的词级分词器:迷你互联网的词表是封闭的(349 个词和标点,验证集没有 OOV),加上换行和 12 个特殊 token,共 362 个。这样我们的 BERT / T5 / ELECTRA 和原版在”一个 token ≈ 一个词”这一点上一致。
2. 上下文嵌入:三种架构的共同抽象 · Contextual embeddings
W22 笔记用一个抽象把所有架构统一起来:上下文嵌入(contextual embedding)
把一个 token 序列映成同样长度的向量序列,其中第 个向量 不只取决于 本身,还取决于它的上下文。“the mouse ate the cheese” 里两个 the 会得到不同的向量。这和 word2vec 那种”每个词一个固定向量”(第 1 讲 §2.6)的根本区别就在这里。
有了 ,三种架构的区别就是怎样用它:
| 架构 | 代表 | 做什么 | 优点 | 缺点 |
|---|---|---|---|---|
| encoder-only | BERT、RoBERTa | ,在表示上做分类等 | 每个位置都看到左右两边(双向) | 不能自然地生成文本;需要专门设计的训练目标(masked LM) |
| decoder-only | GPT-2、GPT-3 | 生成很自然;训练目标就是最大似然,简单 | 每个位置只看到左边(单向) | |
| encoder-decoder | BART、T5 | 输入双向地读,输出一个个地写 | 两套参数;同样需要专门设计的训练目标 |
用什么实现? 早期是 RNN / LSTM:从左到右逐个读,;ELMo 把一个从左到右、一个从右到左的 LSTM 拼起来得到双向的表示。2017 年的 Transformer 用注意力取代了循环。原论文的表 1 说明了它的优势:
| 层的类型 | 每层计算量 | 必须顺序执行的步数 | 任意两个位置之间的最长路径 |
|---|---|---|---|
| 自注意力 | |||
| 循环(RNN) |
RNN 处理一段文本必须一步一步来(第 步要等第 步算完),无法在序列方向上并行;任意两个位置之间的信息要经过 步传递,长程依赖很难学(W22 笔记的说法:LSTM 很少能”干净地”捕捉长程依赖)。自注意力让每个位置一步就能看到所有位置,整段序列可以并行计算——这正是 GPU 擅长的。代价是计算量随序列长度平方增长(第 8 讲的 FlashAttention 就是在这个代价上做文章)。原论文的 big 模型在 8 张 P100 上训练 3.5 天,就在 WMT 2014 英德翻译上达到 28.4 BLEU,比之前包括集成模型在内的最好结果高出 2 分以上。
此后,三种架构用的都是 Transformer,差别只在注意力”谁能看谁”和训练目标——这正是 §4 的主题。先把 Transformer 本身拆开。
3. 打开第 1 讲的黑盒 · Inside tiny_gpt
第 1 讲把 base model 当黑盒用:输入一串字符,输出下一个字符的分布。代码 02 把它拆开。先看全貌:
idx: (T,) 字符编号 例如 "\nbase lives in " 的 15 个编号
│ 字符嵌入 E[idx] + 位置嵌入 P[0:T]
▼
x: (T, d) = (15, 64) "残差流":之后每一层都往它上面加东西
│ ┌──────────────── Transformer 块 × 2 ────────────────┐
│ │ h = LayerNorm(x) │
│ │ q, k, v = h W_Q, h W_K, h W_V 拆成 4 个头 (4, T, 16) │
│ │ A = softmax(q kᵀ / √16 + M) (4, T, T) M = 因果掩码 │
│ │ x = x + concat(A v) W_O 残差 │
│ │ x = x + W₂ GELU(W₁ LayerNorm(x)) 残差,MLP: 64→256→64 │
│ └────────────────────────────────────────────────────┘
▼
LayerNorm → 乘以 Eᵀ(和字符嵌入共享)→ logits: (T, V) = (15, 44)
代码 02 演示 2 只用这些权重矩阵手工做了一遍这个流程(不调用任何模块的 forward),结果和 TinyGPT 的输出最大只差 (logits 本身是十几的量级,差别只是浮点舍入):base model 里没有任何别的东西。GPT-3 的结构和它基本相同,只是 96 层、12,288 维、96 个头、上下文 2048(W22 笔记;另外 GPT-3 交替使用稠密注意力和局部的稀疏注意力)。下面逐块看每一部分为什么存在。
3.1 嵌入:字符嵌入 + 位置嵌入
第一步把每个字符编号变成一个 64 维向量:查表 的第 idx 行(nn.Embedding 就是一个可训练的查找表)。这张表是学出来的,意思相近的 token 会学到相近的向量。
只有字符嵌入是不够的。自注意力本身对顺序不敏感:它只是”每个位置对所有位置按相似度加权平均”,如果把输入打乱,每个位置的输出只会跟着换位置,不会改变。“mouse ate cheese” 和 “cheese ate mouse” 在它眼里是同一组东西。所以要把位置信息加进去:第 个位置加上位置向量 。原论文用固定的正余弦函数
GPT 系列和我们的 base model 用学出来的位置嵌入(一张 的表)。原论文报告两者效果几乎一样(base 模型 BLEU 25.7 vs 25.8),选正余弦是希望能外推到比训练时更长的序列。
代码 02 演示 5 把位置嵌入置零:验证集 bit/字符从 0.409 变成 5.022,接近什么都没学的 ,事实补全从 0.93 掉到 0。顺序信息几乎全靠它。
3.2 自注意力:q、k、v
注意力可以理解成一张”软”的查找表(W22 笔记的说法)。每个位置 由它当前的向量算出三样东西:
- 查询 :“我在找什么?”
- 键 :“我这里有什么?“(每个位置都提供一个)
- 值 :“如果你找的是我,就拿走这个。”
位置 用自己的查询和每个位置的键比相似度(点积),softmax 变成权重,再对值加权平均:
为什么要分成 q、k、v 三个投影,而不是直接用 ?因为”我要找什么”和”我能提供什么”、“我被找到时给出什么”是三件不同的事。例如回答 “q: where does base live? a:” 时,最后一个位置要找的是”问的是谁”,名字所在的位置要能被这样的查询匹配上,而被匹配上以后传过去的应该是”这个名字的身份信息”,不是”我是一个字母 s”。分开的投影让模型可以分别学这三种关系。
在 base model 里, 合成一个 64 × 192 的矩阵 qkv 一次算出来,再切开;注意力的输出再乘一个 64 × 64 的 (proj)放回残差流。
3.3 为什么除以
如果 q 和 k 的每个分量独立、均值 0、方差 1,它们的点积 就是 个方差为 1 的数之和,方差是 、标准差是 (原论文 §3.2.1 的脚注)。维度越大,点积越大,softmax 越”尖”。代码 02 演示 4 用随机向量量了一下(一行 96 个键):
| 每头维度 | 点积的标准差 | 不缩放:平均最大权重 | 不缩放:最大权重 > 0.99 的行 | 除以 :平均最大权重 |
|---|---|---|---|---|
| 4 | 2.0 | 0.239 | 0% | 0.081 |
| 16 | 4.0 | 0.552 | 2% | 0.083 |
| 64 | 8.0 | 0.776 | 18% | 0.083 |
| 256 | 16.0 | 0.893 | 46% | 0.082 |
| 1024 | 32.0 | 0.942 | 69% | 0.082 |
softmax 的梯度是 ;当某个权重接近 1 时,这个矩阵接近 0,梯度几乎传不回去,注意力”卡死”在随机选中的那个位置上。除以 后,分数的尺度和维度无关。注意它的作用是让训练开始时的分布不要太尖——训练好的模型完全可以自己把分数调大:base model 训练后,两层注意力分布的平均熵是 2.42 和 1.92 nat,明显比同长度均匀分布的 3.58 集中。GPT-3 的每头维度是 128,不缩放的话初始分数的标准差约 11,softmax 一开始几乎就是 one-hot。
3.4 因果掩码
decoder-only 模型要”根据前文预测下一个 token”,训练时位置 绝不能看到位置 及以后的内容——否则答案就摆在眼前。做法是在 softmax 之前给”未来”的分数加上 (掩码 的上三角),,这些位置的权重严格为 0。代码 02 演示 3 检查了两层、八个头所有”看向未来”的权重之和:0.0。
这个掩码还带来一个巨大的好处:一次前向传播就得到所有位置的预测。长 的一段文本同时提供了 道”预测下一个 token”的题(第 1 讲 §3.1),每道题都只看到了它该看到的前文。§4.6 会看到,这种”每个位置都是一道题”的密度,是 decoder-only 在训练效率上的一大优势。
3.5 多头
base model 每层有 4 个头,每个头只有 16 维,各自有自己的 ,各算各的注意力,结果拼回 64 维。原论文的解释:多头”让模型能在不同的表示子空间里、同时关注不同位置的信息”;只有一个头时,所有信息被平均到一起,这种能力就受限了。
代码 02 演示 3 看了 “q: where does base live? a:” 最后一个位置(接下来要说出城市)的 8 个头:第 0 层的 4 个头都很”散”,最大的权重不到 0.2;第 1 层的头 1.0 把约 0.39 的权重放在名字 base 上(主要落在最后一个字母),头 1.1 则把 0.58 的权重放在自己身上。不同的头确实在做不同的事——有的负责”把问的是谁搬过来”。演示 5 也印证了这一点:关掉第 1 层的注意力,事实补全从 0.93 掉到 0.05。第 12 讲会更系统地读注意力模式。
3.6 残差流与 pre-LayerNorm
每个块里有两处 :这是残差连接(residual connection,来自 ResNet)。它的作用有两层:
- 梯度的高速公路:反向传播时,梯度可以沿着”+“直接流回前面的层,深层网络才训练得动。
- 残差流(residual stream)的视角:每个位置有一个 64 维的向量贯穿整个网络,嵌入先写下”这是哪个字符、在哪个位置”,每个注意力层把别的位置的信息搬过来”加一笔”,每个 MLP 在原地加工再”加一笔”,输出层读的是所有笔画的总和。代码 02 演示 2 量了最后一个位置上每一笔的长度:嵌入 1.3,第 0 层注意力 1.5、MLP 1.4,第 1 层注意力 1.5、MLP 4.8——“说出哪个城市”的决定主要在最后一层 MLP 里成形。第 12 讲的机制可解释性就是从这个视角出发的。
LayerNorm 把每个位置的向量减去自己的均值、除以自己的标准差,再乘以可学的 、加上 :
它只在”一个位置的 个数”之间归一化,不管残差流里的数值涨到多大,送进注意力和 MLP 的输入都是”标准大小”。放在哪里有讲究:原始 Transformer 是 post-LN,;GPT-2 起改成 pre-LN,——归一化放在分支里面,残差这条主干上没有任何东西挡着,深层模型训练更稳定,对 warmup 也没那么敏感(W22 笔记提到,Transformer 需要学习率 warmup 的一个解释正是 LayerNorm 带来的梯度问题)。我们的 base model 是 pre-LN,最后再加一个 ln_f。
3.7 MLP
注意力是不同位置之间交换信息的唯一地方;MLP 则在每个位置内部独立计算:64 维升到 256 维(4 倍,GPT-3 也是 )、过一个 GELU 非线性、再降回 64 维。如果只有注意力,每层只是”对值做加权平均”再线性变换,表达能力很有限;MLP 提供了逐位置的非线性加工。它也是参数的大头:代码 02 演示 1 算出,每个块里 MLP 占 66% 的参数,注意力占 33%。第 12 讲会看到,很多”事实知识”被认为储存在 MLP 里。
3.8 输出层与 weight tying
最后一层表示经过 ln_f 以后,乘以一个 的矩阵得到每个字符的打分(logits)。base model 让这个矩阵就是字符嵌入矩阵的转置(weight tying;GPT-2 也这样做):“哪个字符的嵌入向量和当前表示最像,下一个就最可能是它”。好处是省参数(这里省 2,816 个,占 2.6%)、并让输入和输出的”词义空间”一致。
3.9 数参数:109,056 从哪里来
代码 02 演示 1 按公式逐项数(,,,):
| 部分 | 公式 | 参数个数 |
|---|---|---|
| 字符嵌入 | 2,816 | |
| 位置嵌入 | 6,144 | |
| 每块:两个 LayerNorm | 256 | |
| 每块: 和偏置 | 12,480 | |
| 每块: 和偏置 | 4,160 | |
| 每块:MLP 两层 | 33,088 | |
| 每块合计 | 49,984 | |
| 最后的 LayerNorm | 128 | |
| 输出层 | 与字符嵌入共享 | 0 |
| 总计 | 109,056 |
和 PyTorch 数出来的一模一样。每个块的参数几乎都在 4 个矩阵里(),所以参数量 ≈ 是估算 Transformer 大小的标准公式。用它算 GPT-3( = 12,288, = 96): 亿,加上嵌入表 亿,合计约 1746 亿,和论文报告的 1750 亿只差一点。第 4 讲的”训练算力 “就建立在这个参数量上。
一个”机制 vs 量级”的小例子:我们的模型里两张嵌入表占 8.2% 的参数,GPT-3 里只占约 0.37%。规模越大,几乎所有参数都在那些 的矩阵里。
3.10 拆掉一块会怎样
代码 02 演示 5 把 base model 装进本讲的 MaskedTransformer(参数名和 TinyGPT 完全相同,只是掩码由调用者给;用因果掩码时输出逐位相同),然后关掉某个组件(把它的输出投影置零,这一层就只剩残差):
| 模型 | 验证集 bit/字符 | 事实补全 |
|---|---|---|
| (参照:均匀分布) | 5.459 | 0.00 |
| 完整的 base model | 0.409 | 0.93 |
| 位置嵌入置 0 | 5.022 | 0.00 |
| 关掉第 0 层注意力 | 6.799 | 0.00 |
| 关掉第 1 层注意力 | 1.348 | 0.05 |
| 关掉第 0 层 MLP | 5.499 | 0.00 |
| 关掉第 1 层 MLP | 4.205 | 0.00 |
| 因果掩码换成双向掩码 | 7.528 | — |
三点观察:
- 在 2 层的小模型里,每一块都不可缺少。关掉第 0 层的注意力或 MLP,结果比什么都没学还差——后面的层是在”第 0 层加工过的残差流”上学的,输入分布一变就失灵。几十层的大模型里,单独去掉中间某一层的影响常常小得多,层与层之间有冗余;小模型没有这种余量。
- 影响最小的是第 1 层注意力:bit/字符只升到 1.348,“拼单词、接模板”的能力大体还在,事实补全却几乎归零——演示 3 里把名字搬到当前位置的头就在这一层。能力和组件之间有相当具体的对应关系。
- 最有意思的是最后一行。把因果掩码换成双向掩码,每个位置都能”看到”它要预测的下一个字符,按理说可以作弊——可是 bit/字符变成了 7.528,比均匀分布还差得多。模型从没学过怎样利用右边的信息,多出来的注意力目标把它学到的模式全打乱了。掩码不是一个可以随便拨动的开关:架构(掩码)和训练目标是一起学进权重里的。 要把一个训练好的因果模型改成双向模型,需要继续训练来适应(§7 的 Wang et al. 2022)。
4. 三种架构家族与训练目标 · Architectures & objectives
4.1 同一个矩阵,三种填法
代码 03 演示 1 把四种注意力模式画成了网格(行 = 当前位置,列 = 被看的位置,# = 可以看)。以 “37 + 45 = 82 .” 为例:
(a) encoder-only(BERT) (b) decoder-only(GPT) (c) prefix LM(前缀 37+45=)
37 + 45 = 82 . 37 + 45 = 82 . 37 + 45 = 82 .
37 # # # # # # 37 # . . . . . 37 # # # # . .
+ # # # # # # + # # . . . . + # # # # . .
45 # # # # # # 45 # # # . . . 45 # # # # . .
= # # # # # # = # # # # . . = # # # # . .
82 # # # # # # 82 # # # # # . 82 # # # # # .
. # # # # # # . # # # # # # . # # # # # #
(d) encoder-decoder(T5、BART)有三块注意力:编码器对输入 “37 + 45 =” 双向自注意力;解码器对输出 “[S] 82 .” 因果自注意力;再加上交叉注意力(cross-attention)——解码器的每个位置用自己的查询去”查”编码器最后一层的所有输出(原论文称之为 encoder-decoder attention;第 14 讲的 Flamingo 用同样的机制让语言模型”看”图像)。
prefix LM 正好介于 (a)(b) 之间:前缀长度为 1 时就是 GPT,前缀等于整段时就是 BERT。它和 (d) 的信息流也几乎一样——输出能看到全部输入、只能看到自己左边的输出——区别在于参数是否共享,以及输出看的是输入的哪一层表示。
掩码决定了模型能做什么:(b)(c)(d) 都能一个一个地生成输出;(a) 的每个位置都看到了整段输入,没有”下一个”的概念,只能给已有的位置打分或填空。
4.2 decoder-only + 因果语言建模(GPT)
目标就是第 1 讲的最大似然:
简单、和生成完全一致(训练时做的事就是使用时做的事),而且因为因果掩码,每个位置都是一道题。缺点是表示是单向的:第 个位置的表示只包含左边的信息。BERT 正是为了解决这一点而提出的。
4.3 encoder-only + masked LM(BERT)
如果不需要生成,就可以让每个位置看到左右两边。但”预测下一个词”在双向注意力下就成了作弊(§3.10 最后一行)。BERT(Devlin et al. 2018)的办法是遮住一部分输入,让模型猜回来——把语言建模变成一个去噪问题:从被破坏的 恢复原来的 。
masked LM 的具体规则(BERT 论文 §3.1):随机选 15% 的 token 位置;被选中的位置
- 80% 换成
[MASK]; - 10% 换成一个随机 token;
- 10% 保持原样(但仍然要预测)。
损失只在被选中的位置上算,每个位置独立地预测。为什么不总是换成 [MASK]?因为下游微调时输入里根本没有 [MASK]:如果模型只在 [MASK] 处认真”思考”,预训练和使用之间就有分布差异。随机替换和保持原样让模型不知道哪些位置被动过,必须对每个位置都保持好的表示。论文附录 C.2 的消融显示,对微调来说这个选择不太敏感(MNLI 上 80/10/10 是 84.2,全用 [MASK] 是 84.3),但对”直接拿表示当特征”的用法,全用 [MASK] 明显更差(NER 特征法 94.9 vs 94.0)。代码 03 演示 2 把一行评论按这个规则变成了训练样本:
原文:review : the soup was tasty and the host was kind . rating : good .
输入:review : the [M] was tasty pilot the host was kind . rating : good .
要预测:位置 3 → soup(换成了 [M]),位置 6 → and(换成了随机词 pilot),位置 13 → :(保持原样)
下一句预测(next sentence prediction, NSP):BERT 还有第二个目标。输入是两段话 A 和 B,50% 的情况 B 真的是 A 的下一句,50% 是随便找的一句;用开头的 [CLS] 位置的表示做二分类。论文认为它对问答和自然语言推理有帮助,但后来的 RoBERTa 发现去掉 NSP 并不变差,再把训练数据从 16GB 增加到 160GB、训练得更久,SQuAD 从 81.8 提高到 89.4(W22 笔记)。
规模与影响:BERT-Base 12 层、768 维、12 个头、1.1 亿参数;BERT-Large 24 层、1024 维、16 个头、3.4 亿参数;训练数据是 BooksCorpus(8 亿词)和英文维基百科(25 亿词)。它把 GLUE 基准推到 80.5,绝对提升 7.7 个点,几乎刷新了当时所有 NLP 任务的最好成绩(第 1 讲 §2.6)。双向有多重要?论文表 5 把 BERT-Base 换成”从左到右”(LTR,等于一个 GPT 式的编码器)后,MRPC 从 86.7 掉到 77.5,SQuAD 从 88.5 掉到 77.8。
masked LM 的两个弱点。第一,只有 15% 的位置提供训练信号(§4.6)。第二,被遮住的多个位置是彼此独立预测的:模型给每个 [MASK] 一个分布,但不建模它们之间的联合关系。当一个”意思单位”被拆成几个 token 时,这会出问题——代码 03 演示 4 的例子:右边的上下文说明空格里是一个 5 字母的正面形容词(tasty、fresh、sweet、crisp、great),即使模型把每个位置的字母分布估计得完全正确,逐位置选最可能的字母也会拼出 “crest”。XLNet 等工作批评的正是这种独立性假设;BERT 后来发布的 whole word masking 版本、SpanBERT(整段遮住)都和它有关;而 T5 的做法是干脆让解码器把被挖掉的片段一个一个地写出来。
4.4 encoder-decoder:T5 与 BART
T5(Raffel et al. 2019)的核心主张是”把所有 NLP 任务都写成文本到文本”(text-to-text):翻译是 “translate English to German: …” → 德文,分类也是输出类别的名字(“positive”),而不是在 [CLS] 上接一个分类器。这样一个模型、一个目标、一套解码方式就能做所有任务。它的预训练目标是 span corruption:随机挖掉 15% 的 token,连续被挖掉的 token 合成一个片段,每个片段在输入里换成一个哨兵(sentinel);目标是”哨兵 + 被挖掉的内容”。论文表 3 的例子:
原文:Thank you for inviting me to your party last week .
输入:Thank you <X> me to your party <Y> week .
目标:<X> for inviting <Y> last <Z>
和 BERT 相比有两个好处:目标很短(只包含被挖掉的片段,省计算);解码器自回归地写出片段,片段里的 token 不再独立。T5 论文对这个设计做了极其系统的比较(全部是先预训练、再在各任务上微调):
- 损坏比例(表 6):10%、15%、25%、50% 差别不大,只有 50% 在 GLUE、SQuAD 上明显变差;保留 15%。
- 片段长度(表 7):平均长度 2、3、5 差别不大,3 在多数非翻译任务上略好;最终模型用平均长度 3。
- 三类目标(表 4):BERT 式去噪 GLUE 82.96,prefix LM 80.69,打乱顺序再还原(deshuffling)73.17。
- 架构(表 2,GLUE 分数; 是一个 12 层栈的参数量, 是计算量):
| 架构 | 参数 | 计算 | 去噪目标 | 语言建模目标 |
|---|---|---|---|---|
| encoder-decoder | 83.28 | 79.56 | ||
| encoder-decoder,编解码器共享参数 | 82.81 | 79.60 | ||
| encoder-decoder,各 6 层 | 80.88 | 78.67 | ||
| decoder-only 语言模型 | 74.70 | 73.78 | ||
| prefix LM | 81.82 | 79.68 |
在”预训练 + 微调”的设定下,encoder-decoder + 去噪最好;去噪目标在每种架构上都好于语言建模目标;纯 decoder-only 最差。注意 encoder-decoder 的参数是 decoder-only 的 2 倍,计算量却相同:输入 token 只走编码器、输出 token 只走解码器,每个 token 经过的层数一样。T5 最终在约 750GB 的 C4 语料上训练,最大的模型 110 亿参数。(T5 的作者之一 Colin Raffel 是第 13 讲的客座讲者。)
BART(Lewis et al. 2019)是另一个 encoder-decoder:编码器读被破坏的文本,解码器写出完整的原文。它比较了五种噪声——token masking、token deletion、text infilling(片段长度服从 Poisson(3),每个片段只换成一个 [MASK],模型还得自己判断缺了几个词)、句子打乱、文档旋转。结论是 text infilling 最稳定;最终的大模型遮住 30% 的 token 并打乱所有句子,编码器、解码器各 12 层、1024 维。BART 在分类上和 RoBERTa 相当,在摘要等生成任务上提升明显(论文摘要说最多提高 6 个 ROUGE 点)。
4.5 prefix LM:两者之间
prefix LM 用一个 decoder 的参数,但前缀部分双向可见、之后因果(§4.1 (c)),损失只算在前缀之后。它天然适合”给定输入、生成输出”的任务:输入是前缀,双向地读;输出照常一个个地写。T5 表 2 里,用去噪目标训练的 prefix LM(81.82)远好于纯 decoder-only(74.70),接近 encoder-decoder。UL2 的 S-denoiser 就是它(§6)。代码 02 演示 5 和代码 04 都说明了一点:prefix LM 和 GPT 用的是同一个网络结构,区别只在掩码。
4.6 训练信号有多密
代码 03 演示 3 用代码 04、05 训练时真正使用的 batch 函数,数了每种目标”每处理 1000 个位置,有多少个位置在算损失”:
| 目标 | 每 1000 个位置的损失项 | 其中是哨兵 | 真正的”内容”预测 |
|---|---|---|---|
| 因果 LM(GPT) | 1000 | 0 | 1000 |
| prefix LM | 533 | 0 | 533 |
| masked LM(BERT,15%) | 137 | 0 | 137 |
| span corruption(单个 Transformer,15%) | 187 | 103 | 84 |
| span corruption(T5 式编码器 + 解码器) | 235 | 131 | 105 |
| ELECTRA 判别器 | 1000 | 0 | 1000(二分类) |
同样的计算量,GPT 得到的训练信号是 BERT 的 7 倍多。T5 的目标里一大半是哨兵——它们按 <0>、<1>、<2> 的顺序出现,几乎不用猜——真正”写出被挖掉的词”的信号只有一百左右。这张表是 §5 所有实验结果的钥匙。
5. 同样的算力,谁学得快、谁能做什么 · A controlled comparison
5.1 实验设置
代码 04 把四种”架构 + 目标”放在同一条起跑线上:
- 数据:迷你互联网训练集里的 1,897 条评论(32,249 个词级 token)。只用评论,是为了让四个模型都能在 2 分钟内训练完;评论里有我们要测的全部能力(评分规则、形容词的正负、生成)。
- 模型:宽度 64、2 层、4 个头;GPT、prefix LM、BERT 都是 125,312 个参数;T5 是编码器 2 层 + 解码器 2 层,260,992 个参数(参数约 2 倍、每个 token 的计算量相同,和 T5 论文表 2 的设定一致)。
- 训练:每段 32 个位置、batch 32、2500 步,同样的学习率调度和种子。每种模型每一步处理的位置数相同,所以算力相同;不同的只是目标。
- 目标:GPT(因果 LM)、prefix LM(随机分界)、BERT(15%、80/10/10)、T5(15% 的 i.i.d. span corruption,即 T5 的基线目标)。
5.2 学习曲线:谁先学会
训练过程中每 500 步,让每个模型用它自己的接口预测 100 条评论的评分:GPT 接着 “rating:” 往下写,prefix LM 把整段当前缀,BERT 填评分位置上的 [M],T5 在评分位置放 <0>、看解码器写到 “[S] <0>” 时的下一个 token。打分协议对四个模型完全相同:取那个空位上的 logits,只在 good / ok / bad 三个候选里取最大的。
| 模型 | 0 步 | 500 步 | 1000 步 | 1500 步 | 2000 步 | 2500 步 |
|---|---|---|---|---|---|---|
| GPT(因果 LM) | 0.47 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| prefix LM | 0.47 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| BERT(masked LM) | 0.26 | 0.47 | 0.73 | 1.00 | 1.00 | 1.00 |
| T5(span corruption) | 0.39 | 0.47 | 0.47 | 0.47 | 0.47 | 0.47 |
| (T5 自由生成,整段完全匹配) | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
(三种评分大约各占 1/4、1/2、1/4;0.47 恰好是这 100 条里 ok 的比例,也就是”永远猜 ok”的水平。最后一行是另一种更苛刻的协议,见下文。)
- GPT 和 prefix LM 在第 500 步就全对。“评分由两个形容词决定”恰好是从左往右读就能学到的规律,而 GPT 的每个位置都是一道题。
- BERT 先在”按先验猜”的水平停了一段(500 步时和”永远猜 ok”一样),1000 步 0.73,1500 步才全对。它每 1000 个位置只有约 140 个训练信号,而且大多数被遮住的词靠附近的语法就能猜对(“the ___ was” 后面是食物名),真正需要”看远处内容”的信号更少。这种”先平台、后突变”的学习曲线在小模型上很常见(第 5 讲还会讨论”涌现”和平台期)。
- T5 从第 500 步起一直停在 0.47,也就是”永远猜 ok”——和 BERT 在第 500 步的状态一样,但它在 2500 步内没有跨过这个平台。在三个评分词里它总是选最常见的 ok,没学会”评分由两个形容词决定”。它在自己的目标上并非什么都没学:训练结束时哨兵预测对 99%,被挖掉的词猜对 66%(BERT 猜被遮住的词是 77%);但”在输入里找到 <0>、再根据 <0> 周围的内容决定写什么”这件事,在这个算力下还没学会。它不只训练信号少,任务也更绕。
- 表里最后一行是一个教训。如果让 T5 自由生成——解码器一直写到下一个哨兵,整段必须正好是评分词——它在每个检查点都是 0.00:最常写出的片段是 “ok .”,把后面本来就在的句号也写了进去(§5.3)。本讲最初的版本就是只用这种方式给 T5 打分、却让别的模型在三个候选里挑,于是把”还没学会规则”错记成了”连评分词都写不出”。比较不同架构时,评测协议必须相同:同样是”在空位上打分”,或者同样是”自由生成”,否则比较的是协议而不是模型。
这些都是机制层面的观察:在同样的、很小的算力下,训练信号越多、任务越直接的目标学得越快。它们不说明 BERT 或 T5 最终学不会,也不说明它们在大规模上更差——T5 论文在大得多的算力下发现 encoder-decoder + 去噪在微调后最好(§4.4)。(写代码时我在副本里把 T5 的训练延长到 6000 步,它学会了往形容词的空里填形容词,但评分仍是”永远猜 ok”,见练习 9。)
5.3 能做什么、不能做什么
训练结束后,用 200 条新评论测三件事(代码 04 演示 2)。“填形容词”:把食物形容词挖掉,右边的 “and/but + 服务 + 评分” 决定了它该是正面还是负面(“填对” = 填出一个食物形容词且正负和右边一致,瞎猜约 0.5)。“生成”:从零采样 200 行,格式合法率 / 内容为真率(第 1 讲的 check_line)。
| 模型 | 预测评分 | 填形容词:做法 → 填对 | 从零生成:合法 / 为真 |
|---|---|---|---|
| GPT | 1.00 | 从左往右写 → 0.55;给 16 个候选打分 → 1.00 | 0.97 / 0.97 |
| prefix LM | 1.00 | 从左往右写 → 0.53 | 0.97 / 0.97 |
| BERT | 1.00 | 填 [M](一次前向传播)→ 1.00 | 0.00 / 0.00 |
| T5 | 0.45 | 填 <0>、第一步取最大 → 0.00;给 16 个候选打分 → 0.46;自由生成 → 0.00 | — |
这张表是三种架构”性格”的缩影:
- GPT 从左往右写,用不上右边的信息。写到 “the soup was” 时,它还没看到后面的 “and the host was kind. rating: good”,只能按先验挑一个形容词,正负对的比例和瞎猜差不多(0.55)。prefix LM 一样——生成的部分是因果的。
- 但 GPT 并不是”不知道”:把 16 个候选逐个填进去、比较整行的概率,它全对。整行的概率里包含了”这个形容词和后面的评分配不配”——右边的信息通过打分间接用上了,代价是 16 次前向传播。GPT-3 论文做多项选择题正是这样(第 1 讲的
completion_logprob)。 - BERT 一次前向传播就能填对:
[M]同时看到左右两边。分类、抽取、填空这类”输入已经完整给出”的任务,正是 BERT 时代的主战场。 - BERT 不会写:没有”下一个词”的概念,用”末尾放一个
[M]、猜出来再放一个”的办法硬凑,200 行里没有一行合法。它写出的是 ”: the was. soggy and the staff was bad. rating: good..” 这样词序混乱、缺词少字的东西——它的训练目标里根本没有”从左往右接着写”这回事:[M]只是”这里少了一个词”,左边和右边对它来说是对称的。 - T5 在这个算力下还在按先验猜。三种做法都按和别人相同的协议打分:评分 0.45(这 200 条里 ok 的比例,即”永远猜 ok”);形容词空位上第一步最可能的词 200 次全是 the(连词性都不对);给 16 个候选打分 0.46,和瞎猜一样。
- T5 的自由生成暴露了另一个问题:片段的边界。在评分的空位上,它最常写出的片段是 “ok .”(105 次)——把后面本来就在的句号也当成了被挖掉的内容,偶尔还接着写 “rating”(11 次);其次是从 “review : the” 开始重新写一条评论(84 次)。span corruption 训练的是”把挖掉的片段写回来”,“片段从哪里开始、到哪里结束”本身也要学。T5 在大规模上的长处恰恰是两者兼得:编码器双向地读,解码器写出任意长度的片段;但要它从零写一整段(“生成”一栏的”—”),需要换一种输入格式或继续训练——Lester et al.(2021)做 prompt tuning 时专门用了”LM-adapted” 版的 T5,就是这个原因(第 6 讲)。
5.4 线性探针:表示里有什么
不看各自的接口,只看表示:冻结模型,用很少的标签训练一个线性探针读出”食物好不好”,测试评论里的食物形容词在标注数据里从没出现过(和第 1 讲代码 03 演示 2 的设定 B 相同)。要做对,表示本身就得编码”这个形容词是正面还是负面”——这只能来自预训练。
| 模型 | 读哪里 | k = 8 | k = 32 | k = 128 |
|---|---|---|---|---|
| GPT | 各位置平均 | 0.844 | 0.838 | 0.851 |
| GPT | 最后一个位置 | 0.853 | 0.936 | 0.948 |
| prefix LM | 各位置平均 | 0.894 | 0.993 | 1.000 |
| BERT | 各位置平均 | 0.731 | 0.802 | 0.943 |
| T5 | 编码器:各位置平均 | 0.541 | 0.506 | 0.503 |
| T5 | 编码器:形容词处 | 0.533 | 0.522 | 0.559 |
| T5 | 解码器:[S] <0> 处 | 0.485 | 0.499 | 0.451 |
- 学会了”形容词决定评分”的三个模型,表示里都带着形容词的正负;还没学会的 T5 换了三种读法(编码器各位置平均;编码器在形容词那个位置;解码器写到 “[S] <0>” 时的状态,输入是”评论 + rating: <0>.”,相当于 GPT 读 “rating:” 处)都在 0.45–0.56 之间。它的输入里没有补齐位置,每个位置都看得到整条评论,所以这不是”读错了地方”——它的表示里确实还没有形容词的正负。能不能把知识带到标签之外,取决于预训练时学到了什么。
- 怎么读也很重要。GPT 的因果表示取平均时,评论前半段的位置还没看到形容词,平均会把信息冲淡(0.838);读最后一个位置(它看到了整条评论)就好得多(0.936)——和第 1 讲代码 03 的发现一致。双向的模型每个位置都看到了整条评论,取平均就很自然。
- prefix LM 的表示最好读:它的前缀部分是双向的,训练信号又比 BERT 多得多——在这个小实验里,它兼得了两边的好处。
5.5 ELECTRA:让每个位置都有信号
既然 BERT 的问题是”只有 15% 的位置有训练信号”,能不能让每个位置都有信号?ELECTRA(Clark et al. 2020)的办法叫替换 token 检测(replaced token detection):
- 一个小的生成器(generator,本身就是一个小 masked LM)在被遮住的 15% 位置上预测原词,并从它的预测分布里采样一个词填回去;
- 一个判别器(discriminator,和 BERT 一样大的双向编码器)对每一个位置判断:这个词是原来的,还是被换过的?生成器碰巧采到原词时,标签算”原词”;
- 总损失 = 生成器的 MLM 损失 + × 判别器的二分类损失( = 50);预训练完扔掉生成器,只用判别器。
代码 03 演示 2 的例子:
原文:review : the soup was tasty and the host was kind . rating : good .
输入:review : the soup was bland and the host was kind . rating : good . (另有 host → staff;good 被采回了 good)
标签:每个位置一个"原 / 换"——16 个位置全部有标签
要发现 bland 被换过,判别器得懂”评分由形容词决定”。几个设计细节:
- 不是 GAN:生成器用最大似然训练,判别器的损失不传回生成器(采样不可导)。论文试过用强化学习训练对抗式生成器,效果更差(生成器的 MLM 准确率只有 58%,最大似然的是 65%)。
- 生成器要小:论文发现生成器是判别器的 1/4 到 1/2 大小最好。生成器太强,替换出来的词太像真的,判别器的任务过难。
- 收益从哪来:论文表 5 的消融(GLUE):ELECTRA 85.0;把判别器的损失只算在被遮住的 15% 位置上(ELECTRA 15%)82.4;BERT 82.2。也就是说,大部分收益来自”所有位置都有信号”。
- 效果:同样的算力( FLOPs、1400 万参数),ELECTRA-Small 在 GLUE 上 79.9,BERT-Small 75.1;只用一张 GPU 训练 4 天,就超过了用约 30 倍算力训练的 GPT(78.8)。在大规模上,它用不到四分之一的算力就和 RoBERTa、XLNet 相当。论文强调,模型越小,ELECTRA 的优势越大。
5.6 在迷你世界里,ELECTRA 没有奏效
代码 05 在代码 04 的同一份评论上比较三个同样大小的双向编码器(判别器 = BERT 的大小,125,312 个参数;生成器 1 层、32 维、25,376 个参数,每步多约 20% 的计算),每 500 步用探针(k = 32,测试形容词没见过)检查一次表示:
| 模型 | 0 步 | 500 步 | 1000 步 | 1500 步 | 2000 步 | 2500 步 |
|---|---|---|---|---|---|---|
| BERT(masked LM 15%) | 0.445 | 0.502 | 0.532 | 0.665 | 0.898 | 0.802 |
| ELECTRA(100% 位置) | 0.445 | 0.518 | 0.516 | 0.519 | 0.503 | 0.512 |
| ELECTRA-15%(只在遮住处) | 0.445 | 0.527 | 0.525 | 0.514 | 0.527 | 0.513 |
我原本预期 ELECTRA 比 BERT 更早学会,而且 ELECTRA-15% 介于两者之间。实际上 ELECTRA 和 ELECTRA-15% 从头到尾都停在瞎猜的水平,BERT 反而在 2000 步达到约 0.9(2500 步回落到 0.8:探针只用 32 个标签,表示也还在变化;两份代码里的 BERT 是同一个模型)。
问题出在哪里?代码 05 演示 2 打开了训练好的 ELECTRA:
- 生成器学得太快。被选中的位置里真被换掉的比例:第 500 步 0.92,第 1500 步 0.45,第 2500 步 0.29。在模板化的评论里,很多被遮住的词几乎是确定的(“rating” 后面是 ”:”),训练后期只有 4.1% 的位置被换掉,判别器看到的标签几乎全是”原词”。
- 被换掉的词里,约一半根本无法分辨:waiter → cook、bread → pie、两个同为正面的形容词互换——意思没变(47%)。另一半改变了意思(48%),例如句子里是 “sour and the waiter was polite. rating: bad”,polite 显然是被换过的;要发现它们,判别器得做”形容词正负 + and/but + 评分”的推理。只有 6% 是词性都不对的明显替换。
- 判别器只学会了抓明显的替换(抓出 79%),对改变意思的替换只抓出 3%,对原词从不误报。它停在”语法检查器”的阶段,没学到探针需要的正负知识。真正有信息量的标签——改变了意思的替换——只占全部位置的约 2%,比 BERT 的约 14% 还少。
教训:ELECTRA 的”每个位置都有信号”,只有当”判断换没换”需要我们想让编码器学的知识时才有用;它的效果取决于生成器犯什么样的错,而这又取决于数据。真实文本比我们的模板丰富得多,生成器的错误五花八门(语法、搭配、事实、指代……),判别器能从中学到大量语言知识。所以这个玩具实验既不能证明、也不能推翻论文的结论;它说明的是一个机制上的前提条件。这也是本讲反复出现的”机制 vs 量级”:小实验适合检验”为什么”,不适合检验”有多好”。
5.7 哪些结论能推广
| 结论 | 在迷你实验里 | 在真实大模型上 |
|---|---|---|
| 掩码决定了”能做什么”(生成 vs 双向填空) | 清楚地看到(§5.3);比较时评测协议要相同 | 成立:这是架构的定义性差别 |
| 同样算力下,训练信号越多学得越快 | GPT、prefix LM ≫ BERT ≫ T5(T5 停在”永远猜 ok”,§5.2) | 方向成立(ELECTRA、decoder-only 的效率论证),但量级取决于规模、数据和训练时长 |
| 双向表示更适合”读” | prefix LM 的探针最好;GPT 要选对读出位置(§5.4) | BERT 论文的 LTR 消融;encoder 至今仍用于检索和分类(§9) |
| encoder-decoder + 去噪最好 | 没能复现:T5 在这个算力下停在先验水平 | T5 论文:在”预训练 + 微调”设定下最好;但 zero-shot 时因果 decoder 最好(§7) |
| ELECTRA 更省样本 | 没能复现,原因清楚(§5.6) | 论文:同算力下 GLUE 明显更好,小模型优势最大 |
6. UL2:把目标混在一起 · Unifying Language Learning Paradigms
到 2022 年,局面有点混乱:GPT 式的因果 LM 适合生成和 few-shot,T5 式的去噪适合微调,不同论文各说各的。UL2(Tay et al. 2022)提出了两个主张。
第一,把架构和目标分开。 论文的原话是”把架构和自监督方案解耦”(disentangle the architecture from the self-supervision scheme):decoder-only 和 encoder-decoder 都可以用任何目标训练,而”选择什么去噪器的影响,比选择什么骨干架构大得多”;骨干的选择主要是各种效率指标之间的取舍。这和 §3.10、§5 的观察一致:prefix LM 和 GPT 是同一个网络,只差一个掩码。
第二,混合去噪器(mixture of denoisers, MoD)。用片段平均长度 、损坏比例 描述一个去噪任务,UL2 混合三类:
| 去噪器 | 含义 | 最终配置 |
|---|---|---|
| R(regular) | 常规的 span corruption,和 T5 相同 | ( = 3, = 0.15)、( = 8, = 0.15) |
| S(sequential) | 顺序去噪,就是 prefix LM:目标在序列末尾,“不依赖未来的信息” | 目标约占 25% |
| X(extreme) | 极端去噪:片段很长(≥ 12 个 token)或损坏比例很高(≥ 30%) | ( = 3, = 0.5)、( = 8, = 0.5)、( = 64, = 0.15)、( = 64, = 0.5) |
7 个去噪器大致等比例混合。这个框架把各种目标看成同一族的特例:当被挖掉的”片段”就是整个序列(、)时,去噪就成了语言建模;prefix LM 是 、( 是前缀长度)。论文因此认为没必要再单独加一个因果 LM 任务——它是 prefix LM 的特例。代码 03 演示 2 (7) 把一段文本按 R、S、X 三种方式变成了训练样本:R 挖掉几个短片段,S 给前半段写后半段,X 一口气挖掉一半,要求模型写出大段内容(更接近生成)。
模式切换(mode switching):预训练时在输入开头加一个模式 token,告诉模型现在做的是哪一类去噪(论文写作 [R]、[S]、[X];公开发布的 UL2 checkpoint 的说明里对应的是 [NLU]、[S2S]、[NLG]);下游微调或 few-shot 时也加上与任务相称的模式 token。论文发现这对 one-shot 摘要这类任务影响很大,对 SuperGLUE 影响较小。
结果:UL2 20B(一个和 T5 很像的 encoder-decoder,在 C4 上训练了 1 万亿 token)在 50 个有监督任务上达到当时最好,zero-shot SuperGLUE 超过了 1750 亿参数的 GPT-3,one-shot 摘要是 T5-XXL 的三倍。之后的 Flan-UL2 是它的指令微调版(第 7 讲)。UL2 的混合目标思路也被用到 decoder-only 模型上(例如对 PaLM 继续训练的 U-PaLM),但到 2023 年以后,大规模预训练基本收敛到了纯因果 LM(§9)。
7. 为什么 decoder-only 成了默认选择 · Why decoder-only won
回头看 2018 年,BERT 风头最盛:几乎所有 NLP 排行榜都被 BERT 及其变体占据,GPT-1 反而不太被注意。几年后,GPT-3、PaLM、LLaMA、以及几乎所有公开了架构细节的大模型都是 decoder-only。为什么?没有单一的原因,而是几股力量叠加:
- 生成是最通用的接口。只要能写字,任何任务都能写成”给定提示、续写答案”:分类是写出类别名,问答是写出答案,翻译是写出译文。GPT-3 的 in-context learning(第 1 讲 §4.2)把这一点推到了极致——不用微调,换个提示就换个任务。encoder-only 模型做不到这一点,每个任务都要接一个专门的”头”再微调。
- 训练效率:每个 token 都是一道题(§4.6)。代码 04 的学习曲线(§5.2)是这一点在小尺度上的直观体现。在”算力就是一切”的规模化时代,这很重要。
- 简单:一个栈、一个目标、一个掩码,没有
[MASK]、哨兵、去噪比例、片段长度这些要调的东西;训练时做的事和使用时做的事完全一样;推理时可以缓存前面位置的键和值(KV cache),每生成一个 token 只算新位置。简单的东西更容易扩展、优化和调试。 - zero-shot 能力。Wang et al.(2022)做了一组很系统的对照:在约 50 亿参数(decoder 4.8B、encoder-decoder 11.0B)、1680 亿 token 的规模上,比较因果 decoder(CD)、非因果 decoder(ND,即 prefix LM 架构)、encoder-decoder(ED)三种架构与全量语言建模、prefix LM、masked LM 三种目标。结论分两半:
- 只做预训练、不做多任务微调时,因果 decoder + 全量语言建模的 zero-shot 表现最好(EAI-Eval:CD 44.2、ND 43.5、ED 39.9,随机水平 32.9);
- 做了多任务提示微调之后,encoder-decoder + masked LM 最好(T0-Eval:ED:MLM 60.6、ND:MLM 55.2、CD:FLM 51.4)。
- 他们还发现两者可以兼得:先训练一个因果 decoder,再把它改成非因果、用 masked LM 继续训练(non-causal MLM adaptation),收敛速度是从头训练 ND:MLM 的 3.3 倍。论文的建议正是”先训练因果 decoder,需要时再适应”——这也是 §3.10 最后一行”掩码要靠训练来适应”的大规模版本。
- 规模化的经验与工程积累。规模定律(第 4 讲)、训练稳定性的技巧、分布式训练(第 9 讲)、推理优化,几乎都是在 decoder-only 上发展起来的。一旦一个方向积累了足够多的经验,换方向的成本就很高。
但这不等于另外两种架构”输了”:
- 在”预训练 + 微调”的固定任务上,encoder-decoder 往往更好(T5 表 2、Wang et al. 的多任务微调结果);T0、Flan-T5 这些早期的指令微调模型都是 encoder-decoder(第 7 讲)。
- 双向编码器在”读”的任务上仍然是首选:句子嵌入、检索、重排序、分类(§9)。
- 架构之争在很大程度上是目标之争(UL2 的观点):同一个网络,因果 LM 训练出的是生成器,masked LM 训练出的是编码器。
8. 选择性架构:混合专家与检索 · Selective architectures
到目前为止,模型对每个输入都动用全部参数:GPT-3 处理每个 token 都要用到全部 1750 亿个参数。W22 笔记 Selective Architectures 介绍了两种打破这一点的思路。
8.1 混合专家(Mixture of Experts, MoE)
把一个 MLP 换成 个”专家”(各自是一个 MLP),再加一个门控(gating)网络决定每个 token 交给哪些专家:
如果每个专家都算,就没有省任何计算。关键是稀疏:只保留门控值最大的 top- 个专家并重新归一化,其余的专家根本不算。W22 笔记的例子:稠密的门控 [0.04, 0.8, 0.01, 0.15],取 top-2 后变成 [0, 0.84, 0, 0.16]。于是参数量和每个 token 的计算量脱钩:参数可以非常多,每个 token 只用到其中一小部分。
难点是负载均衡:如果门控总是选同一个专家,其他专家拿不到梯度、永远学不会。常见的做法是给每个专家设容量上限(例如容量因子 2:超出的 token 直接走残差连接跳过这一层),并在损失里加一项鼓励均衡的辅助损失。几个里程碑(数字引自 W22 笔记):
- GShard(Lepikhin et al. 2021):每隔一个块把 MLP 换成 MoE,top-2。
- Switch Transformer(Fedus et al. 2021):top-1,训练了 1.6 万亿参数的模型。
- GLaM(Du et al. 2021):1.2 万亿参数、64 个专家、top-2,每个 token 只激活约 950 亿参数(约 8%);训练成本约为 GPT-3 的三分之一,zero-shot 和 one-shot 表现好于 GPT-3。
MoE 天然适合分布式:不同专家放在不同的机器上(专家并行,第 9 讲)。
8.2 检索增强(retrieval-augmented)
另一种思路:知识不必全塞进参数里,可以在需要时查。RAG(Lewis et al. 2020)用一个检索器(DPR:用两个 BERT 分别编码问题和文档,按点积检索)从维基百科里取出相关段落 ,再让生成器(BART-large,约 4 亿参数)在 和问题 的条件下生成答案:
RETRO(Borgeaud et al. 2021)把检索做到了预训练里:每 32 个 token 的片段,从 2 万亿 token 的数据库里检索相近的片段,用交叉注意力读进来;70 亿参数,比 GPT-3 少 25 倍,语言建模表现很好。检索的好处是知识可以更新(换数据库即可)、来源可以追溯;第 2 讲的数据问题和第 16 讲的隐私问题都会因此有新的形态。注意这里又出现了 encoder(检索器)和交叉注意力——三种架构在系统层面是互补的。
W22 笔记的总结很中肯:这两条路都是为了”突破稠密 Transformer 的规模”;MoE 让参数可以到万亿级而计算不变,检索模型在知识密集型问答上最强;但它们能否在通用能力上和稠密模型相当,当时还没有定论。
9. 2023 年之后 · Since 2023
本讲的问题在 2023 年之后有了相当明确的答案,但细节一直在演进:
- decoder-only 全面主导。在公开了架构的大模型里,几乎都是 decoder-only + 因果 LM(LLaMA 系列、Mistral、Qwen、DeepSeek、Gemma 等;GPT-4 等闭源模型没有公开架构)。指令微调、RLHF、长上下文、推理模型都建立在这个骨架上。T5 / UL2 一系在 2023 年还很活跃(Flan-T5、Flan-UL2 是当时常用的开放模型),此后在大规模生成模型里逐渐少见。
- “现代 decoder”的标准配方。和第 1 讲的 base model 相比,2023 年的 LLaMA 做了几处如今已成标配的改动(2022 年的 PaLM 已经用了其中的多数,第 10 讲):
- RMSNorm(Zhang & Sennrich 2019)替代 LayerNorm:只除以均方根、不减均值,更省、效果相当;仍然是 pre-norm。
- SwiGLU(Shazeer 2020)替代 GELU MLP:带门控的 MLP,同样参数量下效果更好。
- RoPE(旋转位置编码,Su et al. 2021)替代学出来的位置嵌入:把位置信息以”旋转”的方式乘进 q、k,使注意力分数只依赖相对位置,便于扩展上下文长度。
- GQA(grouped-query attention,Ainslie et al. 2023):多个查询头共享一组键 / 值头(极端情形是 Shazeer 2019 的 multi-query attention),大幅缩小推理时的 KV cache。Llama 2 的大模型就用了它。
- 更大的词表(Llama 3 的分词器约 12.8 万个 token)、数字拆成单个数位等分词上的改进(§1.2)。
- 本讲 §3 的结构骨架——嵌入、注意力、MLP、残差、归一化、输出层——没有变。CS336 第 3–4 讲把这些变体讲得更细。
- MoE 的回归。Mixtral 8x7B(2023 年底)每层 8 个专家、每个 token 选 2 个,总参数约 470 亿、每个 token 激活约 130 亿;DeepSeek-V2 / V3 用更细粒度的专家加共享专家,V3 总参数 6710 亿、每个 token 激活约 370 亿。2024–2025 年,很多旗舰开放模型都是 MoE。§8.1 的问题(负载均衡、专家并行、通信)成了大规模训练的核心工程问题。
- 注意力的替代者。状态空间模型(Mamba,2023)和线性注意力把序列长度的代价从平方降到线性;纯替代者没有取代 Transformer,但”Transformer + Mamba”的混合架构(如 Jamba)出现在一些模型里。
- encoder 并没有消失。检索增强生成(RAG)几乎离不开双向编码器做句子嵌入和重排序;ModernBERT(2024)等工作重新训练了更现代的 encoder。2025 年也有把 decoder-only 模型改造成 encoder-decoder 的尝试(如 Google 的 T5Gemma)。
- 本讲那些”缺点”各自有了补丁。decoder 用不上右边的上下文(§5.3)?代码模型普遍用 fill-in-the-middle(FIM,Bavarian et al. 2022)训练:把”前缀、后缀、中间”重新排列,让因果模型学会填中间。训练信号不够密?多 token 预测(multi-token prediction,DeepSeek-V3 等)让每个位置同时预测后面几个 token。masked LM 不能生成?2025 年出现了 80 亿参数规模的掩码扩散语言模型(如 LLaDA):从全是
[MASK]开始,一轮轮地把最有把握的位置填上——BERT 的目标以一种新的方式变成了生成模型。
哪些结论仍然成立?架构和目标可以分开看(UL2);训练信号的密度决定了预训练效率;掩码决定了模型能做什么;双向表示适合”读”——都还成立。被修正的主要是”哪种组合最好”:在”一个模型、所有任务、靠提示使用”的范式下,因果 decoder 成了默认答案。
10. 小结 · Summary
- 分词决定了 token 是什么。按空格切有开放词表问题;BPE 反复合并最常见的相邻对,没有 OOV;unigram 把分词当作概率模型。第 1 讲用字符级:词表小、透明,代价是序列长约 3 倍、注意力开销约 10 倍、上下文装得少(2.6 行 vs 8 行)。BPE 会把数字切得不一致。
- 上下文嵌入 统一了三种架构:encoder 双向地产生表示,decoder 单向地产生表示并预测下一个 token,encoder-decoder 双向地读、因果地写。Transformer 胜过 RNN,因为它可以在序列方向上并行、任意两个位置一步可达。
- Transformer 块 = pre-LayerNorm → 多头自注意力(q、k、v,除以 ,因果掩码)→ 残差 → pre-LayerNorm → MLP(4 倍宽、GELU)→ 残差。位置嵌入提供顺序;注意力是位置之间交换信息的唯一地方;残差流是贯穿全网的”共享黑板”;参数 ≈ (base model 精确是 109,056,GPT-3 约 1746 亿)。
- 三种目标:GPT 的因果 LM(每个位置一道题);BERT 的 masked LM(15%、80/10/10、独立预测;NSP 后来被证明不必要);T5 的 span corruption(哨兵 + 片段,解码器自回归地写)和 BART 的去噪;prefix LM 介于中间。掩码决定能做什么,目标决定学到什么。
- 同算力的小实验:GPT 和 prefix LM 第 500 步就学会评分,BERT 要到 1500 步,T5 在 2500 步内一直停在”永远猜 ok”(最初误用自由生成给它打分,记成了 0——比较不同架构时评测协议必须相同);GPT 不能用右边的上下文生成、但能靠打分做填空,BERT 一次就能填对、却完全不会写。训练信号的密度(1000 / 533 / 137 / 约 100 每千位置)是主要原因。
- ELECTRA 让判别器在每个位置判断”换没换”,在论文里同算力下明显好于 BERT;但在迷你世界里,生成器很快变准、一半的替换无法分辨、有信息量的标签只占约 2%,判别器停在”语法检查”阶段——方法的收益依赖数据。
- UL2 把架构和目标分开,用 R / S / X 三类去噪器的混合和模式 token 统一了 T5 式与 GPT 式的训练。
- decoder-only 成为默认,原因是:生成是通用接口、每个 token 都是训练信号、简单、zero-shot 最好(Wang et al. 2022)、规模化的工程积累。encoder-decoder 在微调后的固定任务上仍有优势,双向编码器仍是检索和分类的首选。MoE 让参数和计算脱钩,检索让知识可以外置,两者都在 2023 年之后变得更重要。
11. 常见误区 · Common pitfalls
- “BERT 和 GPT 的区别是网络结构不同。” 它们的块几乎一模一样(嵌入、注意力、MLP、残差、LayerNorm);区别在注意力掩码和训练目标。同一组权重换个掩码就是另一种”架构”——只是需要重新训练来适应(代码 02:直接把 base model 改成双向,bit/字符变成 7.528)。
- “masked LM 也是语言模型,所以也能生成。” BERT 没有定义序列上的概率分布,也没有”下一个”的概念;硬凑的生成一行合法的都没有(代码 04)。它擅长的是对已经给出的输入做表示、打分和填空。
- “decoder-only 看不到右边,所以做不了填空。” 生成时确实看不到;但把候选填进去比较整段的概率,右边的信息就用上了(代码 04:16 个候选打分 1.00),代价是多次前向传播;FIM 训练则让它直接学会填中间。
- “除以 是为了让注意力更准确。” 它是为了让初始的分数尺度不随维度增长、softmax 不饱和、梯度传得回来(代码 02 演示 4)。训练好的模型可以自己学出很尖的注意力。
- “注意力是 Transformer 里最重要、参数最多的部分。” 每个块里 MLP 占约 2/3 的参数;注意力的特殊之处是它是位置之间交换信息的唯一通道。
- “位置嵌入只是锦上添花。” 自注意力本身对顺序不敏感;base model 去掉位置嵌入,bit/字符从 0.409 变成 5.022,几乎回到什么都没学。
- “T5 证明了 encoder-decoder 最好。” T5 表 2 的结论是在”预训练 + 微调”设定下;Wang et al. 2022 发现只预训练时,因果 decoder 的 zero-shot 最好。“哪个最好”取决于怎么用。
- “ELECTRA 一定比 BERT 省样本。” 论文在真实文本上是这样,但它依赖”生成器犯的错有信息量”。代码 05 的迷你世界里,ELECTRA 一直停在瞎猜。
- “MoE 模型的参数量就是它的计算量。” MoE 每个 token 只激活一小部分专家:GLaM 1.2 万亿参数、每个 token 约 950 亿;比较模型时要分清”总参数”和”激活参数”。
- “比较不同架构时,各自用最顺手的方式打分就行。” 打分协议本身会造成巨大差别:同一个 T5,在空位上只比较三个候选时是 0.47(“永远猜 ok”),要求它自由生成、整段完全匹配时是 0.00(代码 04)。不同架构要用相同的协议比较——都在空位上打分,或者都自由生成。
- “分词只是预处理,不影响模型能力。” 分词决定了序列长度(于是决定算力和上下文)、模型能不能看到数字的每一位、拼错一个字母时 token 序列变多少;不同分词器下的 token 级困惑度不能直接比较。
12. 练习 · Exercises
练习 1(手算 BPE) 语料是 “low low low lower newest newest”(按空格切成 6 个词,空格不参与合并)。手算前 3 次 BPE 合并(并列时取最早出现的一对),写出每一步之后的词表新增项。再用学到的规则给 “lowest” 分词。
参考答案
初始:l o w(×3)、l o w e r、n e w e s t(×2)。相邻对计数:l o 4、o w 4、w e 3(lower 1 次 + newest 2 次)、e r 1、n e 2、e w 2、e s 2、s t 2。
第 1 次:最多的是 l o 和 o w(各 4 次),l o 先出现 → 合并 lo。之后:lo w(×3)、lo w e r、n e w e s t(×2)。 第 2 次:lo w 4 次最多 → low。之后:low(×3)、low e r、n e w e s t(×2)。 第 3 次:计数 low e 1、e r 1、n e 2、e w 2、w e 2、e s 2、s t 2;最早出现的 2 次的对是 n e → ne。
“lowest”:l o w e s t → 先按规则 1 合并 lo → lo w e s t → 规则 2 合并 low → low e s t → 规则 3(n e)不适用。结果 [low, e, s, t]。没见过的词被拆成学过的片段加单个字符——没有 OOV。
练习 2(unigram 的似然) 用 W22 笔记的例子:词表 {ab, c},,。(a) “ababc” 的似然是多少?(b) 如果词表是 {a, b, c, ab},概率 1/8、1/8、1/4、1/2,“abc” 有哪几种切法、各自的概率是多少?Viterbi 会选哪种?
参考答案
(a) (笔记里写的 4/9 是笔误,代码 01 演示 3 用精确分数核对过)。
(b) 两种:a | b | c,概率 ;ab | c,概率 。Viterbi 选 ab | c。注意 unigram 天然偏爱”更少、更长”的 token:每多切一刀就多乘一个小于 1 的概率。
练习 3(数参数) GPT-2 small:,,词表 50,257,上下文 1024,输出层和嵌入共享。用代码 02 演示 1 的公式估算它的参数量。嵌入表占多少?和我们的 base model(8.2%)、GPT-3(约 0.37%)比较。
参考答案
每块 ;12 块共 85,054,464。嵌入:;位置:;最后的 LayerNorm 。合计约 1.244 亿——正是常说的 “124M”。
嵌入表(含位置)约 3,938 万,占约 32%。规模越小,嵌入表的占比越大:base model 8.2%(词表很小但模型更小),GPT-2 small 约 32%(词表大),GPT-3 约 0.37%(模型极大)。这也是为什么小模型上”大词表”的代价更明显。
练习 4(掩码的形状) 一个 encoder-decoder 模型,输入 5 个 token、输出 3 个 token(解码器输入以 [S] 开头)。分别写出编码器自注意力、解码器自注意力、交叉注意力的掩码矩阵形状和”可以看”的元素个数。如果把它改写成一个共享参数的 prefix LM(序列 = 输入 + [S] + 输出去掉最后一个),掩码是多大、有多少个”可以看”?
参考答案
编码器:5 × 5,全部可以看,25 个。解码器自注意力:3 × 3 的下三角,6 个。交叉注意力:3 × 5,全部可以看,15 个。
prefix LM:序列长 5 + 1 + 2 = 8([S] 加输出的前两个),前缀长 6(输入 + [S])。前缀内部 6 × 6 = 36 个;后面 2 个位置各自看前缀 6 个加上自己和左边的输出位置:第 7 个位置看 7 个、第 8 个看 8 个。共 36 + 7 + 8 = 51 个。两者的信息流几乎一样(输出能看到全部输入和自己左边的输出),区别是参数是否共享、输出看的是输入的哪一层表示。
练习 5(√d 的直觉) 用代码 02 演示 4 的设定:两个 维随机向量,各分量独立 N(0, 1)。(a) 证明点积的方差是 。(b) 如果分量的方差是 而不是 1,应该除以什么才能让分数的方差变成 1?(c) 为什么”训练好的 base model 的注意力比均匀分布集中得多”和”要除以 “不矛盾?
参考答案
(a) 。每一项 的均值是 0,方差是 ;各项独立,方差相加得 。
(b) 每项方差变成 ,总方差 ,要除以 。实践里初始化和 LayerNorm 让分量大致是单位方差,所以除以 就够了。
(c) 缩放管的是训练开始时分数的尺度:避免一开始 softmax 就饱和、梯度传不回去。训练过程中,、 可以学得很大,把需要的分数拉开——base model 训练后两层的注意力熵是 2.42 和 1.92 nat,远低于均匀分布的 3.58。缩放是”给优化一个好的起点”,不是限制模型最终能多尖。
练习 6(80/10/10 的算术) BERT 选 15% 的位置,按 80/10/10 处理。(a) 输入里被换成 [MASK] 的 token 占全部 token 的比例是多少?被换成随机词的呢?(b) 一个没有被换成 [MASK] 的 token,它其实是”被选中、需要预测”的概率是多少?这个数说明了什么?
参考答案
(a) [MASK]:15% × 80% = 12%;随机词:15% × 10% = 1.5%;被选中但保持原样:1.5%。
(b) 没被换成 [MASK] 的 token 占 88%,其中”被选中”的是 1.5% + 1.5% = 3%(随机替换 + 保持原样),条件概率 3% / 88% ≈ 3.4%。模型对任何一个看起来正常的位置,都不能百分之百确定”它是原词、不用管”——有约 1.7% 的概率它其实是被随机替换的错词。这迫使模型在每个位置上都检查”这个词放在这里合不合理”,从而对每个位置都保持有用的表示。ELECTRA 可以看成把这一点推到极致:让模型在每个位置都明确地做这个判断。
练习 7(训练信号的密度) 代码 03 演示 3 里,span corruption 每 1000 个位置有 187 个损失项,其中 103 个是哨兵。(a) 为什么哨兵这么多?(b) 如果把片段平均长度从约 1 改成 3(mean_span=3,T5 最终的设定),损坏比例仍是 15%,“内容”预测的数量和哨兵的数量会怎样变?
参考答案
(a) i.i.d. 地选 15% 的 token,大多数片段只有 1 个 token;每个片段在目标里要配一个哨兵,最后还有一个结束哨兵,所以目标里哨兵和内容差不多一样多,甚至更多。
(b) 预测:被挖掉的内容 token 总数大致不变(还是约 15%),片段数大约变成原来的 1/3,哨兵随之减少;目标变短,“内容 / 哨兵”的比例提高。
我用 span_batch(..., mean_span=3) 在副本里数了一下(同样每 1000 个位置):损失项 135,其中哨兵 61、内容 74;而 mean_span=1 时(换了一个随机数序列,所以和演示 3 差一两个)是 188、104、83。哨兵没有降到 1/3,因为每段末尾的结束哨兵总在、片段太长时会被窗口截断;内容也略少,因为随机放下的片段会互相重叠、换行符不挖,实际挖掉的比例低于 15%。T5 论文表 7 里平均长度 3 略好于 i.i.d.;同时目标更短,训练也更省计算(T5 论文在讨论 span corruption 时也提到了”目标序列更短”这个好处)。
练习 8(改一行:BERT 遮更多) 把 arch_tools.mlm_batch 的默认 rate=0.15 改成 0.40,删掉 checkpoints/l3_bert.pt,重跑代码 04。预测 BERT 的学习曲线(评分准确率)和探针结果会怎样变。
参考答案
预测:训练信号多了约 2.7 倍,平台期应该更短;但每个被遮住的词周围的上下文也更少了,任务更难。
我在一份代码副本里跑了(没有改动课程目录里的文件):BERT 的评分准确率 500 步 0.75、1000 步就是 1.00(原来是 0.47、0.73,1500 步才全对);探针 k = 32 从 0.802 升到 0.914,k = 128 从 0.943 升到 0.960;在自己目标上的准确率 0.79(原来 0.77)。在这个小实验里,多遮一些明显更好。这和 Wettig et al.(2022)“Should You Mask 15% in Masked Language Modeling?” 的发现方向一致:在他们的设定下,遮 40% 比 15% 更好。15% 是 BERT 的历史选择,不是什么最优值。(做完记得改回来、删掉缓存。)
练习 9(改一行:T5 训练更久) 代码 04 里 T5 在 2500 步内一直停在”永远猜 ok”。把 STEPS 改成 6000,只训练 T5,你预测会怎样?这说明了什么?
参考答案
我在代码副本里只训练了 T5(6000 步,学习率调度按 6000 步拉长,每 1000 步记一次,评测协议和正文相同):
- 评分准确率从第 1000 步到第 6000 步的每个检查点都是 0.47(200 条评论上 0.45),始终是”永远猜 ok”,没有出现 BERT 那样的突变;
- 它学会了往形容词的空里填形容词:第一步取最大,填出形容词的比例从 2500 步时的 0.00 升到 0.89;但正负对的只有 0.41,给 16 个候选打分是 0.48——仍是瞎猜;
- 在自己的目标上继续进步:准确率 0.88(哨兵 1.00,被挖掉的词 0.73;2500 步时是 0.84、0.99、0.66);探针(k = 32)三种读法在 0.50–0.59 之间,解码器读法在 k = 128 时 0.623,略高于瞎猜;
- 自由生成的习惯变了:在评分空位上 200 次全写成 “review : ok”。
这说明它学得非常慢,而且学会的顺序是先”这个空里该填哪类词”(6000 步时做到了),后”该填哪个意思”(还没做到)。在这个 2 + 2 层、64 维的小模型上,“按哨兵定位、再根据定位处周围的内容决定写什么”这条路径特别难学。可能的原因(没有一一验证):哨兵 <0> 几乎总是输入里第一个被挖掉的位置,模型可以靠”位置的统计规律”糊弄过去,而不真正去找 <0>;目标里一半是哨兵,有效信号更少;评论模板里很多词靠局部语法就能猜对,进一步降低了”必须看远处”的压力。真实的 T5 最小也有编码器、解码器各 6 层(T5-Small),在海量多样的文本上训练,这些捷径基本不存在。
练习 10(MoE 的账) GLaM 有 1.2 万亿参数、64 个专家、top-2 路由,每个 token 激活约 950 亿参数。(a) 激活参数占总参数的比例?(b) 如果每个专家是一个 MLP、且 MoE 层每隔一层出现一次,粗略地说,为什么激活参数远多于”总参数 × 2/64”?(c) MoE 省的是什么,不省的是什么?
参考答案
(a) 950 / 12000 ≈ 7.9%,即 W22 笔记说的约 8%。
(b) 2/64 ≈ 3.1% 只适用于专家(MLP)部分;注意力、嵌入、以及不是 MoE 的那些层的 MLP 是每个 token 都要用的”共享”参数,它们全部计入激活参数。所以激活比例高于 3.1%。
(c) 省的是每个 token 的计算量(FLOPs):参数可以加到万亿级,计算只按激活参数算,GLaM 的训练成本约为 GPT-3 的三分之一。不省的是内存和通信:所有专家的参数都要存在某处,token 要在机器之间路由到对应的专家(第 9 讲);推理时如果 batch 小,很多专家的权重都要读进来,硬件利用率可能不高。
练习 11(ELECTRA 的前提) 代码 05 里 ELECTRA 失败了。提出一种修改迷你世界或训练设置的办法,使”判断换没换”更需要正负知识,并预测结果。(提示:看演示 2 的三类替换。)
参考答案
几种思路(都没有在本讲的代码里实际运行,结果需要你自己验证):(1) 让生成器更弱或更早停止训练,使替换更多、更随机——但替换太随机时,判别器只需要学语法;(2) 直接构造”难负例”:把被遮住的形容词换成正负相反的形容词(这样每个替换都”改变了意思”),相当于把 ELECTRA 变成一个专门的对比任务;(3) 降低模板的确定性,让更多位置真正有多种可能。预测:方案 (2) 应该能让判别器学会正负,因为有信息量的标签比例从约 2% 提高到所有被替换的位置;但这也说明了问题——好的替换分布要靠设计,而真实文本里生成器的错误”免费”提供了丰富的难负例。
13. 延伸阅读 · Further reading
原课阅读(按建议顺序)
- CS324 Winter 2022 课程笔记:Modeling(分词、上下文嵌入、Transformer)、Training(三种架构的目标函数、优化器、学习率、初始化)。本讲 §1–§4 的主线。
- Vaswani et al., Attention Is All You Need(2017)。重点读 §3(模型结构;§3.2.1 的缩放点积注意力及其脚注)和表 1。
- Raffel et al., Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer(T5,2019)。§3.2(架构比较,表 2)、§3.3(目标比较,表 3–7)是本讲 §4.4 的依据。
- Clark et al., ELECTRA: Pre-training Text Encoders as Discriminators Rather Than Generators(2020)。§2(方法)、§3.4–3.5(小模型结果与效率分析,表 1、表 5)。
- Tay et al., Unifying Language Learning Paradigms(UL2,2022)。§3(混合去噪器与模式切换)。
补充
- Devlin et al., BERT(2018):§3.1 的 masked LM 与 NSP,表 5 的消融。
- Lewis et al., BART(2019):§2.2 的五种噪声,§4 的比较。
- Wang et al., What Language Model Architecture and Pretraining Objective Work Best for Zero-Shot Generalization?(2022):§7 的主要依据。
- CS324 Winter 2022 课程笔记 Selective Architectures:§8 的混合专家与检索。
- Sennrich et al.(2016,BPE 分词);Kudo(2018,unigram / subword regularization)。
2023 年之后
- Touvron et al., LLaMA(2023)——“现代 decoder”配方的代表;Ainslie et al., GQA(2023);Jiang et al., Mixtral of Experts(2024);DeepSeek-AI, DeepSeek-V3 Technical Report(2024);Bavarian et al., Efficient Training of Language Models to Fill in the Middle(2022)。
- 本目录下 CS336 的第 3–4 讲(架构细节与 MoE),以及 CS224N 的预训练一讲。
14. 下一讲预告 · Next lecture
本讲比较的是”同样的算力下,哪种架构和目标学得更好”。但真正让基础模型变强的,是把算力本身加上去:更多参数、更多数据、更长训练。损失怎样随这三者变化?给定预算,参数和数据应该怎么分?为什么 Chinchilla 推翻了 GPT-3 时代的分配?
第 4 讲:规模定律——损失怎样随参数、数据和算力变化。 本讲的参数公式 正是估算训练算力 的起点。那一讲会在迷你互联网上训练一系列不同大小的 tiny GPT,拟合幂律,画出 IsoFLOP 曲线,并看看外推能有多准。