对应 Maps to:CMU 10-423 / 10-623 / 10-723 Generative AI(Spring 2026;Aran Nayebi、Matt Gormley),Lecture 2 · Wed 14-Jan · Transformer LMs。 阅读材料:Goodfellow, Bengio & Courville, Deep Learning 第 10.10–10.12 节(LSTM 与门控 RNN);Vaswani et al. (2017), Attention Is All You Need;Jay Alammar, The Illustrated Transformer(博客)。 依据说明:本讲以原课第 2 讲公开的 slides(lecture2-transformer.pdf)为主线:大语言模型简史(噪声信道、Google n-gram、LLM 规模表)、会遗忘的 RNN、LSTM(Graves 的图和公式)、“为什么不一直用 LSTM”、缩放点积注意力的逐步构建、课堂练习、多头注意力、Transformer LM 的四个子层、层归一化、残差连接、位置嵌入、GPT 规模表、矩阵版(因果)多头注意力。论文里的数字尽量核对过原文。“三难”的分析(§2.3)、“记住开头”任务(§5)、农场语言上的全部实验(§6)是本讲义自己设计的,不代表课堂上讲了什么。 配套代码 Code:
code/lecture02/(6 个脚本 + 2 个模块;只需要 PyTorch CPU 版;05约 90 秒,其余 1–50 秒) 预计阅读时间:4–5 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲,你应该能够:
- 说出语言模型在 2017 年之前的主要用途(噪声信道模型),以及”大”语言模型在训练数据量和参数量上的演进。
- 解释简单 RNN 为什么难以学会长程依赖:收缩会遗忘、不收缩会积累噪声、能稳定存储的结构又让梯度消失(Bengio et al., 1994 的困境)。
- 写出 LSTM 的六个公式,解释输入门、遗忘门、输出门和记忆单元各自的作用,说明”加法式更新”为什么能让梯度传得更远;知道遗忘门偏置初始化的作用和 LSTM 的局限。
- 从”加权平均”出发,一步步推出缩放点积注意力 ,解释查询、键、值,按行 softmax、因果掩码和 各自的理由。
- 说明注意力为什么”不知道顺序”、位置编码怎样补上;区分可学习、正弦、相对位置编码。
- 画出一个 Transformer 层(多头注意力、前馈网络、层归一化、残差连接),会数 GPT 系列的参数量,知道计算量的哪一部分随序列长度平方增长。
- 用实验说明 RNN、LSTM、Transformer 在长程依赖上的差别,并会用”切断注意力连线”的干预来检验模型是否真的在用某个信息。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面再展开 Previewed only |
|---|---|
| 第 1 讲:链式法则、n-gram、RNN-LM、困惑度、BPTT、梯度消失与爆炸、自动微分 | 小批量、填充、KV 缓存、分词、解码策略(第 3 讲) |
| 矩阵乘法、softmax、交叉熵 | 现代 Transformer 的改动:RMSNorm、SwiGLU、RoPE、GQA、滑动窗口(第 4 讲) |
PyTorch 的 nn.Module、训练循环(第 1 讲 §5.8) | 仅编码器 Transformer(BERT)与 ViT(第 5 讲) |
| 张量的形状与视图(前置讲 P1 §4) | FlashAttention 与长上下文(第 18–19 讲) |
如果你对”GPU 为什么喜欢大矩阵乘法”没有概念,可以先读 前置讲 P3 §2–3;不读也不影响理解本讲的主线。
0.3 本讲地图 Map
§1 大语言模型简史:噪声信道 → Google n-gram → 今天的 LLM 有多大
§2 会遗忘的 RNN:slides 的例子;三难;LSTM;遗忘门偏置;深层与双向 ← 代码 01、02
§3 注意力:加权平均 → 缩放点积注意力 → 课堂练习 → √d_k → 矩阵版 → 因果掩码 → 多头 → 位置编码 ← 代码 03
§4 Transformer 语言模型:四个子层;层归一化;残差;pre/post-norm;GPT;参数量;并行训练;计算量 ← 代码 04
§5 实验一:"记住开头"——RNN、LSTM、Transformer 谁能学会 ← 代码 05
§6 实验二:农场语言上的 GPT——困惑度、一致性、注意力、干预、位置编码消融 ← 代码 06
§7–11 小结、误区、练习、延伸阅读、下一讲
0.4 记号 Notation
沿用第 1 讲的记号,新增:
| 记号 | 含义 |
|---|---|
| 第 个位置的输入向量(词嵌入 + 位置嵌入), | |
| 把 按行叠起来的矩阵 | |
| 查询(query)、键(key)、值(value): 等,维度 (值的维度 ,通常 ) | |
| 、 | 第 个位置对第 个位置的注意力分数、注意力权重() |
| 掩码矩阵,元素是 或 | |
| 注意力头数; | |
| Transformer 的层数 | |
| sigmoid 函数 | |
| 逐元素乘法 |
注意 slides 把 既用作 RNN 的隐藏状态、也用作 Transformer 最后一层的输出;本讲保留这个习惯,并在可能混淆的地方说明。
1. 大语言模型简史 · Some history of large language models
1.1 2017 年之前:噪声信道模型
今天我们用语言模型来生成文字。但在 2017 年之前,语言模型最重要的用途是给别的系统打分。slides 举了两个例子:语音识别和机器翻译。它们都可以写成噪声信道模型(noisy channel model):
- 是观察到的东西(语音信号;源语言的句子), 是想恢复的东西(转写文本;目标语言的句子)。
- 是转导模型(transduction model):如果说的是 ,听起来像 的概率有多大。
- 就是语言模型: 本身像不像一句通顺的话。
第二个等号用的是贝叶斯公式(前置讲 P2 §1):分母 和 无关,求 argmax 时可以丢掉。为什么要这样拆? 因为两部分可以分别用不同的数据训练:转导模型需要成对的数据(录音 + 转写),很贵;语言模型只需要大量的纯文本,很便宜。语言模型负责排除”听起来像、但不通顺”的候选,比如把 “recognize speech” 和 “wreck a nice beach” 区分开。
1.2 最早的”大”语言模型:Google n-gram
最早真正”大”的语言模型是 n-gram。slides 引用了 2006 年发布的 Google n-gram 数据:
- 在 1 万亿个 token(约 950 亿个句子)的网页文本上统计;
- 包含 1-gram 到 5-gram:一元组 13,588,391 条、二元组 314,843,401 条、三元组 977,069,902 条、四元组 1,313,818,354 条、五元组 1,176,470,663 条(五种合计约 38 亿条);slides 把它概括为”约 30 亿个参数”的英语模型;
- 2009–2010 年又发布了日、中、瑞典、西班牙等十几种语言的版本。
slides 问了两个问题:“这是一个大的训练集吗?""这是一个大模型吗?“答案都是”是”。1 万亿 token 和今天的预训练数据在同一个数量级(下一节的表),几十亿个”参数”(计数)也不小。但第 1 讲已经看到,n-gram 的根本缺陷不是数据和参数不够,而是看不到窗口之外:再多的计数也无法让 5-gram 知道 10 个词之前的主语是单数还是复数。
1.3 今天的大语言模型有多大
slides 的对比表(训练数据以 token 计):
| 模型 | 机构 | 年份 | 训练数据 | 参数量 |
|---|---|---|---|---|
| GPT-2 | OpenAI | 2019 | 约 100 亿(40 GB) | 15 亿 |
| GPT-3 | OpenAI | 2020 | 3000 亿 | 1750 亿 |
| PaLM | 2022 | 7800 亿 | 5400 亿 | |
| Chinchilla | DeepMind | 2022 | 1.4 万亿 | 700 亿 |
| LaMDA | 2022 | 1.56 万亿 | 1370 亿 | |
| LLaMA | Meta | 2023 | 1.4 万亿 | 650 亿 |
| LLaMA-2 | Meta | 2023 | 2 万亿 | 700 亿 |
| GPT-4 / GPT-5 | OpenAI | 2023 / 2025 | 未公开 | 未公开(slides 引用的传闻:1.76 万亿 / 2–5 万亿) |
| Gemini Ultra | 2023 | 未公开 | 未公开(传闻 1.5 万亿) | |
| LLaMA-3 | Meta | 2024 | 15 万亿 | 4050 亿 |
两个值得注意的趋势:
- 参数量和数据量都在增长,但数据增长得更快。 每个参数分到的训练 token 数:GPT-3 约 1.7,Chinchilla 约 20,LLaMA-3 405B 约 37。Chinchilla 论文(Hoffmann et al., 2022)发现,在固定的计算预算下,GPT-3 这样的模型”参数太多、数据太少”了。第 15 讲的规模定律会详细讨论。
- 闭源模型的规模不再公开。 表中带”传闻”的数字没有官方来源,只能当作量级参考。
1.4 本讲要回答的问题
第 1 讲的 RNN-LM 已经比 n-gram 好得多。那么为什么今天表里的所有模型都是 Transformer,而不是 RNN?slides 的回答分两步:先看 RNN 的”遗忘”问题和它的修补方案 LSTM(§2),再看一个完全不同的思路——注意力(§3–4)。
2. 会遗忘的 RNN · Forgetful RNNs
2.1 回顾:RNN-LM 的关键想法
第 1 讲的 RNN-LM:(1) 把之前的所有词压缩成一个定长向量 ;(2) 用 定义下一个词的分布。 由 和当前输入递推得到:
所以任何信息要从第 步传到第 步,都必须在 里一步一步”接力”下去。slides 把这个问题叫做”会遗忘的 RNN”。
(slides 还回顾了两种画神经网络的方式:神经网络图里节点是隐藏单元、边上标权重;计算图里节点是中间变量和它的计算函数、参数和常量也是节点。本讲的图都按计算图理解。)
2.2 slides 的例子:记住”两个位置都出现过 1”
slides 设计了一个小任务:输入是长度为 2 的 0/1 向量序列,RNN 要在每一步回答”到目前为止,两个位置是否都出现过 1”。设定权重 、、,输入是
slides 上画出的隐藏状态是 、、……——这些数说明这里的激活函数实际上取的是恒等函数(slides 公式里写的是 )。slides 接着问:读出层 该怎样设?然后指出第 100 步的输出会变回 。
01_forgetful_rnn.py 第 1 部分把这条轨迹算了出来,并且给了 RNN 一个最”宽容”的非线性读出:只要 就回答 。
| 真实答案 | 读出 | ||
|---|---|---|---|
| 4 | [0.5, 1] | +1 | +1 ✓ |
| 10 | [0.00781, 0.0156] | +1 | +1 ✓ |
| 14 | [0.000488, 0.000977] | +1 | −1 ✗ |
| 100 | [6.31e-30, 1.26e-29] | +1 | −1 ✗ |
即使用这样的读出,也只能撑到第 12 步。之后 和”什么都没见过”时的 几乎无法区分:**不管读出层怎么设计,最终都会给出和一开始一样的答案。**这就是”遗忘”。在 float32 里,连续乘 ½ 到第 150 次就变成精确的 0(前置讲 P1 §3 的下溢),连浮点数都”忘了”。
根源是 的谱半径 :每一步都把旧的记忆压缩一次。动力系统里把这叫做收缩(contraction)。
2.3 三难:记得久、抗噪声、梯度传得回去
“那就别收缩,把 设成 不就行了?“01 的第 2、3 部分试了两种不收缩的设计,结果说明简单 RNN 面对的是一个三难问题。
积分器()。 只是把所有输入加起来,第 3 步写入的 1 永远保留。但如果每一步的输入带有一点噪声(标准差 0.05),噪声也被一起加了起来:
| 网络 | 噪声标准差 | ||
|---|---|---|---|
| 0.054 | 0.058 | 0.062 | |
| 0.180 | 0.503 | 1.617 |
积分器的噪声按 增长,1000 步后(理论值 1.58)已经超过信号本身。收缩的网络噪声有界(理论值 ),但信号也被压没了。收缩能抗噪声但会遗忘;不收缩不遗忘但会积累噪声。
锁存器(latch)。 用饱和的 sigmoid 可以造出第三种设计:。它有两个稳定点(约 0.007 和 0.993):没见过 1 时停在 0.007,一旦写入 1 就跳到 0.993 并一直停在那里;噪声也会被”吸”回稳定点。这是一个真正的、抗噪声的记忆,100 步后仍然记得。
但梯度呢?在稳定点附近 sigmoid 已经饱和,每一步的导数只有 :
| 距离 | 1 | 5 | 10 | 20 | 50 |
|---|---|---|---|---|---|
| 7.1e-02 | 1.9e-06 | 3.4e-12 | 1.2e-23 | 4.7e-58 |
这就是 Bengio, Simard & Frasconi (1994) 证明的困境:要抗噪声地存住信息(吸引子),状态在稳定点附近必须是收缩的,而收缩恰恰意味着梯度消失。锁存器”记得住”,但梯度下降看不见它该怎样设置——“学不会”。§5 的实验会看到这个困境在真实训练中的样子。
| 结构 | 100 步后还记得? | 抗噪声? | 传回 50 步的梯度 |
|---|---|---|---|
| 线性 RNN,(slides) | ✗ | ✓ | 8.9e-16 |
| 线性 RNN,(积分器) | ✓ | ✗ | 1 |
| sigmoid 锁存器 | ✓ | ✓ | 4.7e-58 |
| LSTM(下一节) | ✓ | ✓ | ≈1 |
2.4 LSTM:门控与加法式记忆
动机。 slides 引用了 Graves (2012) 的图:标准 RNN 对第 1 步输入的敏感度随时间迅速变淡(梯度消失),而 LSTM 可以让这种敏感度保持很久。LSTM(Long Short-Term Memory,Hochreiter & Schmidhuber, 1997)的思路是把”要记住的内容”和”是否写入、是否保留、是否读出的控制”分开:
slides 的说法:输入门”遮住”标准 RNN 的输入;遗忘门”遮住”上一步的记忆;记忆单元存储”输入和旧记忆的混合”,它是 LSTM 的长期记忆,控制信息在时间步之间的流动;输出门”遮住”下一个隐藏状态的值;隐藏状态是 LSTM 单元的输出。
slides 引用的 Graves et al. (2013) 版本里,门还会”窥视”记忆单元(公式里多一项 等,叫窥视孔连接,peephole)。PyTorch 的 nn.LSTM 和大多数现代实现都不用它;Greff et al. (2017) 的大规模比较发现去掉它几乎没有损失。本讲按不带窥视孔的版本实现。
关键是第五行:加法式更新。 对比 Elman RNN 的 ——每一步旧记忆都要穿过一个矩阵乘法和一个会饱和的非线性;LSTM 的 只是把旧记忆乘以一个门、再加上新内容。沿记忆单元回传的梯度是
只要遗忘门接近 1,梯度就几乎不衰减。Hochreiter & Schmidhuber 把这条通路叫做恒定误差传送带(constant error carousel)。
01 第 4 部分手工设定了一个解 slides 任务的 LSTM:输入门 (有 1 才打开,否则约 ),遗忘门、候选值、输出门都固定在约 1。结果:
- 1004 步全部答对,加上噪声也全对;噪声累积 1004 步后记忆单元只漂移了 (积分器的噪声标准差是 1.6)——因为输入门关着时,噪声根本写不进去。
- 沿记忆单元传回 1000 步的梯度是 0.999998(锁存器在 100 步时已经是 )。
但 LSTM 不是”不会遗忘”。 遗忘门小于 1 时,记忆照样按 衰减(练习 3)。LSTM 的真正优势是:它能学会什么时候让 ,而且这只需要调一个门的偏置;Elman RNN 要让整个 矩阵恰好处在”既不收缩也不发散”的刀刃上。
2.5 从零实现 LSTM;遗忘门偏置
lm_models.LSTMCell 把四个门的线性变换拼成一个 的矩阵(和一个 的矩阵),一次算完再切成四段——行的顺序是 ( 即 ),和 torch.nn.LSTM 完全相同。02_lstm.py 第 1 部分复制 nn.LSTM 的权重后对比:前向的 、 和反向的梯度都只差 。
参数量。 循环层的参数是 Elman 的约 4 倍(、 时 25,088 vs 6,208;整个 LSTM-LM 30,520 vs 第 1 讲 RNN-LM 的 11,640)。每一步的计算量也约是 4 倍。
刚初始化时,梯度能传多远? 02 第 3 部分测了 相对于 的比值:
| 循环层 | 每步平均倍数 | |||
|---|---|---|---|---|
| Elman RNN | 7.2e-04 | 1.3e-16 | 1.6e-32 | 0.48 |
| LSTM,遗忘门偏置 0 | 3.8e-03 | 7.3e-12 | 8.2e-23 | 0.60 |
| LSTM,遗忘门偏置 1 | 8.4e-02 | 3.4e-05 | 2.4e-09 | 0.81 |
| LSTM,遗忘门偏置 3 | 4.3e-01 | 2.9e-01 | 3.6e-01 | 0.98 |
- 门本身不会自动让梯度不消失。 遗忘门偏置为 0 时 ,LSTM 的衰减(每步 ×0.60)和 Elman RNN(×0.48)差不多。
- 遗忘门偏置 决定了初始的 ,衰减速度随之改变。这就是 Gers et al. (2000) 和 Jozefowicz et al. (2015) 建议把遗忘门偏置初始化为 1 的原因:一开始就”倾向于记住”。实测的每步倍数比 还大,因为各单元的 不同,梯度范数由 最接近 1 的那些单元主导。
2.6 深层与双向 LSTM;LSTM 的变体
slides 引用了 Graves et al. (2013) 在语音识别上用的深层双向 LSTM(DBLSTM):
- 深层:把多层 LSTM 叠起来,下一层的输出序列是上一层的输入序列。
- 双向:每层有一个从左往右、一个从右往左的 LSTM,输出拼在一起。语音识别里整段话一次性转写,用未来的上下文没有问题。
- slides 的评语:DBLSTM 和深层双向 RNN 拓扑相同,表示能力并不更强,但在实践中更容易学。
02 第 4 部分验证了一件对语言建模很重要的事:改动第 4 步的输入后,单向 LSTM 第 1–3 步的输出完全不变,双向 LSTM 第 1–3 步的输出也变了——它在第 步”偷看”了未来。用它预测下一个词等于把答案告诉模型,所以双向结构不能直接当生成式语言模型(第 5 讲的 BERT 会换一种训练目标来利用双向上下文)。
slides 还问:“这个特定的结构有多重要?“并引用 Jozefowicz et al. (2015):他们评估了一万种类 LSTM 结构,发现有几种变体在多个任务上和 LSTM 一样好。最常见的简化版是 GRU(Cho et al., 2014),它把输入门和遗忘门合成一个”更新门”、去掉了单独的记忆单元。
2.7 为什么不一直用 LSTM?
slides 的回答是”大家确实一度都在用”,但是:
- 它们仍然难以处理长程依赖(§5 的实验会看到具体在哪里难);
- 计算天生是串行的,难以在 GPU 上并行:第 步必须等第 步算完。
02第 5 部分测得手写 LSTM 和nn.LSTM的耗时都和序列长度成正比(nn.LSTM把循环写在 C++ 里,本机快约 1.3 倍,但串行依赖躲不开); - 虽然基本解决了梯度消失,仍可能梯度爆炸(所以训练时仍要做梯度裁剪)。
第 2 点是工程上最致命的。2017 年的 Transformer 用注意力同时解决了第 1、2 点。
3. 注意力 · Attention
3.1 直觉:不传递,直接查
RNN 和 LSTM 的共同点是”一步一步传”:信息从第 步到第 步要经过 次接力。如果换一种思路——第 个位置需要什么信息,就直接去前面所有位置里找——那么任意两个位置之间的路径长度都是 1。
一个类比是查字典(或者查数据库):
- 每个位置 挂出一个键(key):“我这里有什么”,和一个值(value):“如果你需要我,我给你这个”;
- 第 个位置发出一个查询(query):“我在找什么”;
- 查询和每个键比相似度(点积),相似度越高,那个位置的值在结果里占的比重越大。
和真正的字典不同,这里的”查找”是软的:不是只取最匹配的那一项,而是对所有项做加权平均,权重由 softmax 给出。这样整个过程可微,可以用梯度下降学习 、、 应该是什么。
3.2 从加权平均开始
slides 一步一步地搭出注意力。第一步只写输出的形式:第 4 个位置的新表示 是前面(包括自己)所有位置的值的加权平均,
叫注意力权重, 叫分数(score)。对第 1、2、3 个位置也一样(因果版本里,第 个位置只看 )。剩下的问题是: 和 从哪里来?
3.3 缩放点积注意力
slides 接着依次加上值、键、查询和分数:
三个矩阵 、 是要学习的参数。同一个输入 扮演三种角色,所以这叫自注意力(self-attention):查询、键、值都来自同一个序列。(第 13–14 讲的交叉注意力里,查询来自一个序列、键和值来自另一个。)
03_attention.py 里的 attention_loop 逐字照搬了这组公式:对每个 ,对每个 算分数,做 softmax,再加权求和。
3.4 课堂练习:交换 和
slides 的课堂练习:设输入嵌入和第 4 个位置的注意力权重为
则 。现在交换 和 ,新的 是多少?
slides 的答案:**和原来完全一样。**原因是注意力权重来自内容的点积 : 搬到第 2 个位置后,它的分数(从而它的权重 0.6)也跟着搬过去了,加权求和的结果不变。03 第 1 部分用随机的 真算了一遍:交换前后 只差 ,第 4 行的权重从 [0.29, 0.162, 0.499, 0.049] 变成了 [0.29, 0.499, 0.162, 0.049]——数值相同,只是换了位置。
这揭示了注意力的一个根本性质:它把能看到的输入当作一个集合,不知道谁在第几个位置(数学上叫置换等变性,练习 9)。“the dog chased the cat” 和 “the cat chased the dog” 在最后一个词看来是同一个集合——这显然不行,所以 §3.9 要显式加入位置信息。
(同一个实验里, 和 变了:因果掩码让第 2、3 个位置看到的集合不同了。因果掩码本身带有一点位置信息,§6.5 会再遇到这一点。)
3.5 为什么要除以
如果 和 的每个分量都是独立的、均值 0 方差 1 的随机数,那么点积 是 个方差为 1 的项之和,方差为 、标准差为 。03 第 4 部分对 32 个键做 softmax(均匀分布的熵是 ):
| 不缩放:分数标准差 | 最大权重 | 熵 | 除以 后:最大权重 | 熵 | |
|---|---|---|---|---|---|
| 4 | 2.0 | 0.350 | 2.26 | 0.160 | 3.03 |
| 64 | 8.0 | 0.815 | 0.50 | 0.164 | 3.01 |
| 1024 | 31.9 | 0.954 | 0.11 | 0.160 | 3.02 |
不缩放时, 一大 softmax 就几乎变成 one-hot。饱和的 softmax 梯度极小:分数差为 1 时 ,分数差为 32 时只有 。训练一开始注意力就被”锁死”在某个位置上,学不动。Vaswani et al. (2017) 正是用这个理由引入缩放因子 :缩放后分数的标准差约为 1,不管 多大,注意力分布都保持”软”的。(为什么不除以 ?那样分数的标准差变成 ,注意力又会过于均匀,见练习 4。)
3.6 矩阵版:一次算完所有位置
slides 指出:“为了速度,我们用矩阵运算一次算出所有查询。“把 按行叠成 ,则
即 。 的第 行就是 , 的 元素就是 。
slides 的问题:“softmax 是按列还是按行做的?“答案是按行: 的第 行是”第 个位置对所有位置的分数”,第 个位置的注意力要在这一行里归一化。03 第 3 部分显示,按行做时每行之和恰好为 1;按列做时每行之和变成 [1.1, 0.874, 1.054, 0.972],就不是加权平均了。
03 第 2 部分对比了两种写法:结果只差 ,但矩阵版在 时快了两千多倍(⏱ 本机单线程)。原因不只是”Python 循环慢”:矩阵版的每一行互不依赖——第 个位置的分数不需要等第 个位置算完。这正是 slides 说的 Transformer 训练高效的原因之一:注意力分数在不同时间步之间没有依赖,可以完全并行。代价是要算一个 的矩阵,计算量和内存随 增长(§4.8)。
3.7 因果掩码
slides 在矩阵版的图上画满了从每个位置指向所有位置的箭头,然后问:“我们总是需要所有这些箭头吗?“如果训练 Transformer 预测下一个 token,那么让第 个位置看到它后面的 token 就是作弊。不带掩码的版本是标准的 Transformer 块(第 5 讲的编码器);语言模型需要的是带掩码的版本。
slides 的洞察:如果 softmax 的某个输入是 ,对应的输出就是 0。所以在 softmax 之前加一个掩码矩阵 :
slides 给了三个候选矩阵(A:对角线以下为 ;B:对角线以上为 ;C:只保留对角线),问哪个是因果语言模型需要的。03 第 3 部分逐个扰动输入,看哪些位置会影响 :
| 掩码 | 影响 的输入位置 |
|---|---|
| A(对角线以下为 ) | 2, 3, 4(只看自己和未来) |
| B(对角线以上为 ) | 1, 2(只看自己和过去) |
| C(只保留对角线) | 2(只看自己) |
答案是 B。实践中总是先把全部 个分数算出来,再把右上角设成 (slides:“In practice …”);这意味着将近一半的计算被浪费了(练习 8),第 18 讲的 FlashAttention 会跳过这些块。
为什么用”加 “而不是”softmax 之后乘 0”?因为乘 0 之后剩下的权重和就不是 1 了;加 则让被屏蔽的位置权重恰好为 0,其余位置仍然归一化。
3.8 多头注意力
slides 用卷积作类比:卷积层可以有多个通道,注意力层也可以有多个头(head)。每个头有自己的 ,独立做一次注意力,最后把所有头的输出拼起来:
为了让输出维度和输入维度一样,Transformer 通常取 (slides 的约定)。于是:
- 参数量和”一个 维的头”相同。
03第 5 部分: 时 1、2、4、8 个头的参数量都是 1,088(,含输出投影 )。多头不是”更大的模型”,而是把同样的维度分给几个独立的注意力模式。 - 形状(代码里的写法):,然后所有头的矩阵乘法一次批量算完,再变回 并乘 。
为什么要多个头? 一个头的输出是一个加权平均,只能”看一种东西”。03 第 5 部分手工设定了两个头:一个总是看前一个位置,一个总是看第一个位置(两者的注意力矩阵分别是次对角线和第一列)。真实模型里的头也有类似的分工——§6.3 会在训练好的模型里找到一个专门”看主语”的头。
3.9 位置编码
§3.4 说过,注意力本身不知道顺序。slides 的解决办法:为每个位置 准备一个位置嵌入 ,表示”在第 个位置意味着什么”,把它加到词嵌入上:。关键在于:任何出现在第 个位置的词都加同一个 。03 第 6 部分验证:不加位置时交换 、 后 不变(差 ),加上位置编码后变了(差 0.057)。
slides 列出了几种变体:
- 固定的(基于正弦和余弦)或可学习的(像词嵌入一样是一张表);
- 绝对的(如上所述)或相对的(相对于查询的位置)。
正弦位置编码(Vaswani et al., 2017):
每两维是一个以固定频率转动的”指针”:低维转得快,用来区分相邻位置;高维转得慢,用来区分远处的位置。它有一个漂亮的性质: 只依赖距离 。03 第 6 部分实测: 时从 出发的点积都是 15.3137, 时都是 11.6615。也就是说,正弦编码天然携带相对位置的信息。第 4 讲的 RoPE 把这个想法直接用到 和 上,让注意力分数只依赖相对位置。
可学习的位置嵌入(GPT 系列)是一张 的表:更灵活,但表里没有的位置就没有向量——GPT-2 的上下文上限 1024 就是这张表的行数。§6.5 会看到,即使表足够大,训练中没见过的位置也可能出问题。
4. Transformer 语言模型 · The Transformer LM
4.1 从 RNN-LM 到 Transformer-LM
slides 把两者并排画出来:语言模型的部分完全一样——每个位置输出一个向量 ,用 预测下一个词,用交叉熵训练。区别只在 怎样得到:
- RNN-LM: 由 和 递推;
- Transformer-LM: 由若干个 Transformer 层算出,每个隐藏向量回头看前一层里当前和之前所有时间步的隐藏向量。
slides 特别强调:“RNN 的计算图随输入 token 数线性增长;Transformer 的计算图随输入 token 数平方增长”——因为每对位置之间都有一条注意力连线。
4.2 一层里有什么
slides:Transformer LM 的每一层由几个子层组成:
- 注意力(多头、因果)
- 前馈神经网络(feed-forward network, FFN)
- 层归一化(layer normalization)
- 残差连接(residual connections)
前馈网络对每个位置独立地、用同一组参数做 ,中间维度通常是 。为什么需要它?注意力输出只是各位置的值的加权平均,对 是线性的;前馈层提供逐位置的非线性变换。它的参数占一层的 2/3( vs 注意力的 )。(GELU 是 ReLU 的一个平滑版本,GPT 系列都用它;第 4 讲会讲 SwiGLU。)
4.3 层归一化
slides 的说法:深层网络训练时,低层参数的一个小变化,会在高层被放大成一个大变化(他们称之为 internal covariate shift)。层归一化对每一层的激活做归一化,再学习逐元素的增益和偏置:
slides 还指出:这样的归一化允许用更高的学习率(收敛更快),而不会出现梯度发散。04_transformer_block.py 第 1 部分:
- 手写版和
nn.LayerNorm差 (PyTorch 在方差上加了一个小的 防止除以 0)。 - 输入整体乘以 100,输出只变了 (来自 );整体加 7,只有舍入误差;每个 token 各自乘以不同的倍数,输出也几乎不变。前面的层把激活放大多少倍,后面的层看到的都差不多。
- 每个 token 只用自己的统计量:批大小为 1、序列长度不一都没问题。这是 Transformer 用 LayerNorm 而不用 BatchNorm(在批的维度上统计)的主要原因。
4.4 残差连接
slides 的说法:网络非常深时,会出现一种无法用过拟合解释的性能退化——训练误差和测试误差都变差(He et al., 2016)。解决办法是残差连接:让输入的一份副本绕过子层直接加到输出上,。
为什么有用?slides 的回答:” 不必学会对 的整个变换,只需学一个对 的加法修正(即残差)。“从梯度的角度看:
不管 的导数多小,总有一条”恒等”通路让梯度直达底层——和 LSTM 的 是同一个道理:用加法代替连乘。
04 第 2 部分训练普通网络()和残差网络()去拟合一个随机”教师网络”(目标方差 0.2054,一直预测均值就是这个损失):
| 深度 | 普通:初始梯度 | 普通:训练损失 | 残差:初始梯度 | 残差:训练损失 |
|---|---|---|---|---|
| 2 | 1.7e+01 | 0.0044 | 9.8e+01 | 0.0033 |
| 8 | 7.0e-01 | 0.0043 | 1.5e+02 | 0.0021 |
| 32 | 4.8e-07 | 0.2059 | 2.8e+02 | 0.0037 |
| 64 | 2.9e-15 | 0.2059 | 4.5e+02 | 0.0104 |
32 层以上的普通网络什么都没学到(损失 = 目标方差),比 2 层还差——这就是退化。残差网络 64 层照样训练得动;它 500 步内比 2 层略差(0.0104 vs 0.0033),但和”完全学不动”有质的区别。(普通网络用的是 PyTorch 的默认初始化;更仔细的初始化能缓解梯度消失,但残差是更稳健的办法。)
4.5 post-norm 与 pre-norm
子层、残差和层归一化有两种常见的排列:
pre-norm 的残差通路上没有任何变换,梯度可以从输出直接流到输入(上一节的恒等通路);post-norm 的每一层残差都要穿过一个 LN。Xiong et al. (2020) 分析了 post-norm 为什么需要学习率预热(warmup)才能稳定训练,而 pre-norm 可以不用。pre-norm 在最后还要加一个 LN(下面的 )。lm_models.Block 两种都支持,默认 pre-norm。
4.6 整体结构:GPT
slides:“GPT 代表 Generative Pre-trained Transformer。GPT 就是一个 Transformer LM,只是参数非常多。“本讲的 lm_models.GPT:
slides 的 GPT 规模表(“dimension of inner” 即前馈层的中间维度):
| 模型 | 层数 | 隐藏维度 | 前馈维度 | 头数 | 参数量 |
|---|---|---|---|---|---|
| GPT(2018) | 12 | 768 | 4×768 | 12 | 117M |
| GPT-2(2019) | 48 | 1600 | 4×1600 | 12 | 1542M |
| GPT-3(2020) | 96 | 12288 | 4×12288 | 96 | 175000M |
| GPT-4o(2024)/ GPT-5(2025) | ? | ? | ? | ? | 传闻 1–2 万亿 |
(公开的 GPT-2 XL 配置是 25 个头、每头 64 维;头数不影响参数量。)
4.7 数参数
每层的参数(GPT 式:线性层带偏置,LN 有 、):注意力 (),前馈 ,两个 LN ,合计 。再加上词嵌入 、位置嵌入 和最后的 LN (输出层和词嵌入共享权重,不重复计)。04 第 3 部分先在一个小 GPT 上核对公式(PyTorch 数出 105,152,公式 105,152),再算 slides 表里的三个模型:
| 模型 | 词表 | 上下文 | 公式算出 | slides / 论文 | 嵌入占比 |
|---|---|---|---|---|---|
| GPT(post-norm,无最后的 LN) | 40,478 | 512 | 116.5M | 117M | 27.0% |
| GPT-2 XL | 50,257 | 1024 | 1,557.6M | 1542M | 5.3% |
| GPT-3 | 50,257 | 2048 | 174,604.3M | 175B | 0.4% |
GPT 和 GPT-3 和报告吻合。GPT-2 XL 比论文多约 1%;GPT-2 最小的版本(12 层、768 维)按公式是 124.4M,论文写的是 117M——这是 GPT-2 论文的计数和公开权重对不上的已知现象,以按公开结构数出来的为准。
两个规律:每层约 个参数( 翻倍、参数翻 4 倍);模型越大,嵌入层的占比越小。(前置讲 P1 §5 的 param_count 是 Llama 式结构的版本:没有偏置、没有位置嵌入、前馈是 SwiGLU。)
4.8 训练:一次前向算出所有位置
第 1 讲的 RNN-LM 用”教师强制”训练:每一步的输入都是真实的上一个词。Transformer-LM 也一样,而且更进一步:一次前向就同时算出所有位置的预测。04 第 4 部分验证了两件事:
- 把整段序列一次喂进去,第 个位置的输出,和”只喂前 个 token”时最后一个位置的输出逐位相同(float64 下差 )。所以一次前向就得到了 个”根据前缀预测下一个词”的问题, 个交叉熵一起算、一起反向。
- 改动第 7 个 token,前 6 个位置的 logits 完全不变(差恰好为 0),之后的都变了。pre-norm 和 post-norm 都一样。这个因果性是 GPT 能当语言模型用的前提——对比 §2.6 的双向 LSTM。
4.9 计算量: 什么时候成为大头
每层每个序列的前向矩阵乘法 FLOPs:线性层(Q、K、V、O 和前馈)约 ;注意力分数 和加权求和 约 。两者之比是 ,所以注意力的平方项只有在 超过约 时才成为大头。04 第 5 部分:
| 模型 | 注意力占 FLOPs | ||
|---|---|---|---|
| 本讲的小 GPT | 64 | 64 | 14.3% |
| GPT-2 | 768 | 1,024 | 18.2% |
| GPT-3 | 12,288 | 2,048 | 2.7% |
| GPT-3,但 = 128K | 12,288 | 131,072 | 64.0% |
GPT-3 的训练成本几乎全在线性层——这就是 前置讲 P3 §6.4 里 不考虑注意力也够准的原因。上下文很长时情况反过来,第 18–19 讲会处理。
并行性。 在单线程 CPU 上,GPT 并不比 LSTM 快(CPU 上没有并行,耗时就看运算量)。04 第 5 部分改为比较 1 个线程和 4 个线程(⏱ 本机,):GPT 快了 2.0 倍,Elman RNN 和 LSTM 不变或略慢(1.0×、0.8×)。GPT 的计算是几个大矩阵乘法,可以分给多个核心;RNN 是 个串行的小矩阵乘法。GPU 有上万个核心(前置讲 P3 §2),这个差别会被放大成数量级的差距——这是 Transformer 取代 RNN 的主要工程原因之一。slides 也把它列为 Transformer 成功的关键:批处理、可并行的缩放点积注意力、各头独立计算、高效的矩阵乘法硬件。
4.10 代码导读:slides 的公式在代码里在哪
| slides 的公式 | lm_models.py |
|---|---|
| , , | MultiHeadAttention.forward 里的 self.W_q(x) 等(nn.Linear 存的是 ) |
| 切成 个头, | split_heads:view(B, T, h, d_k).transpose(1, 2) |
attention():q @ k.transpose(-2, -1) / math.sqrt(d_k) 加 causal_mask(T) | |
| (按行) | F.softmax(scores, dim=-1) |
out.transpose(1, 2).reshape(B, T, d) 再 self.W_o | |
| 残差 + LN(pre-norm) | Block.forward:x = x + self.attn(self.ln1(x)) |
| 词嵌入 + 位置嵌入 | GPT.embed |
原课 HW1 要实现的就是这样一个 TransformerLM(再加上第 4 讲的 GQA 和 RoPE)。
5. 实验一:“记住开头” · Experiment: long-range recall
5.1 为什么需要一个新任务
第 1 讲的 RNN 在主谓一致上 全对,§6.2 会看到本讲的 LSTM 和 GPT 到 也基本全对。那个任务只需要记 1 个比特、最远 25 步,格式也非常规整——演示不出”遗忘”。05_long_range_recall.py 设计了一个更难、而且距离完全可控的任务:
键 干扰 干扰 … 干扰 ? → 在 ? 处说出开头的键
└─┘ └──── d 个 ────┘
键有 8 种(3 比特),干扰符号是另外 8 种、随机出现。模型必须把键的身份原封不动地带过 步,而中间的输入全是和答案无关的噪声。只有 ”?” 处计损失。随机猜的准确率是 1/8,损失是 。三种模型都很小(Elman RNN 8,018 个参数,LSTM 26,834,2 层 Transformer 29,888),每个训练 1000 步、每步 64 个序列。我们还用预测和真实键的互信息衡量”记住了几个比特”(最多 3 比特)。
5.2 Elman RNN:训练数据的最大距离决定成败
| 训练数据 | 记住的比特 | ||||
|---|---|---|---|---|---|
| 1.000 | 1.000 | 1.000 | 1.000 | 3.00 | |
| 1.000 | 1.000 | 0.998 | 0.998 | 2.98 | |
| 0.639 | 0.506 | 0.482 | 0.518 | 1.76 | |
| 0.281 | 0.232 | 0.258 | 0.215 | 0.97 |
两个和直觉相反的现象:
- **训练数据最远只到 20 步的 RNN,在 上也全对。**它学到了一个能一直保持的”锁存器”(§2.3)。所以 Elman RNN 能表示跨越 100 步的记忆。
- 训练数据最远到 100 步的 RNN 反而失败了,而且连 都做不好。准确率随 几乎是平的——这不是”距离越远忘得越多”,而是训练失败:长序列让梯度信号又弱又乱,模型只学到了一部分(约 1 比特)。
这正是 Bengio et al. (1994) 的困境在真实训练中的样子:能存住信息的结构恰恰让梯度消失,所以从长距离的例子里学出这个结构很难;短例子多的时候才学得会。
5.3 LSTM 与 Transformer
在 Elman RNN 失败的同一份数据()上,LSTM(遗忘门偏置 1)和 Transformer 都在所有距离上全对(记住 3.00 比特)。
5.4 只有长例子时
如果训练数据里只有 的长例子呢?
| 模型 | / | 最后损失 | ||
|---|---|---|---|---|
| Elman RNN | 0.146 | 0.133 / 0.119 | 0.123 | 2.094 |
| LSTM(遗忘门偏置 1) | 0.131 | 0.133 / 0.119 | 0.123 | 2.091 |
| LSTM(遗忘门偏置 3) | 0.201 | 0.133 / 0.119 | 0.123 | 2.090 |
| Transformer | 1.000 | 1.000 / 1.000 | 1.000 | 0.003 |
Elman RNN 和 LSTM 都学不会,损失停在 附近。把遗忘门偏置提高到 3(§2.5 里初始梯度能传回 100 步的设置)也没有用。Transformer 照样全对,连训练中没见过的 也对:它学到的是”找到键这一类 token”,和距离无关。
所以 LSTM 的长程记忆在很大程度上依赖”先从短例子学会、再推广到长例子”;注意力则从训练第一步起就给了任意两个位置之间一条长度为 1 的通路。Vaswani et al. (2017) 的表 1 把这一点概括为:自注意力的最大路径长度是 ,循环网络是 。
5.5 为什么:刚初始化时的梯度
05 第 4 部分测了刚初始化(未训练)时,”?” 处的损失对开头那个键的嵌入向量的梯度:
| 模型(未训练) | ÷ | |||
|---|---|---|---|---|
| Elman RNN | 2.6e-07 | 1.4e-19 | 0(float32 下溢) | 0 |
| LSTM(偏置 1) | 1.4e-05 | 1.1e-08 | 1.7e-12 | 1.3e-07 |
| LSTM(偏置 3) | 1.3e-04 | 8.0e-05 | 6.2e-05 | 0.46 |
| Transformer | 4.0e-04 | 1.0e-04 | 5.7e-05 | 0.14 |
- Elman RNN 和偏置 1 的 LSTM:梯度指数衰减,长例子几乎不提供学习信号。
- Transformer: 从 10 到 100,梯度只缩小到 0.14,大约是 。刚初始化时注意力近似均匀,键只分到约 的权重,所以梯度按 变小——多项式衰减,不是指数衰减;而且一旦学会把注意力集中到键上,这个 就消失了。
- 偏置 3 的 LSTM 是个反例:它的梯度衰减比 Transformer 还慢,却同样学不会。所以”梯度能传回去”并不等于”能学会”。一个可能的解释(我们没有逐项验证):LSTM 要同时学会”写入键、保持遗忘门 ≈ 1、让之后的写入不被干扰符号带偏”几件事,在它们都到位之前,记忆单元里的键被几十次和键无关的写入淹没(类似 §2.3 积分器的噪声累积);Transformer 只要学会一件事——”?” 的查询和键类 token 的键向量点积要大。
5.6 训练好的模型内部
Transformer 在 ”?” 处的注意力(05 第 5 部分):第 1 层的两个头把注意力分散在干扰符号上;第 2 层的两个头都把 0.99 以上的注意力放在开头的键上——、50、100 时分别是 0.999、0.997、0.995,而均匀分配时只有 。这个头学会了”直接去开头找键”,与距离无关。
LSTM( 训练的那个):64 个记忆单元里有 9 个在干扰符号处的遗忘门平均超过 0.99(平均 0.9946),负责”保留”。但它没有像 §2.4 的手工版那样关上输入门(键处 0.71、干扰处 0.62)。它的办法是让干扰处写入的内容几乎只取决于已经记住的键(解释 90% 的方差),几乎不取决于当前是哪个干扰符号(3%):每一步都在”重写”同一份记忆。训练出来的解法和我们手工设计的不一样,但同样依赖遗忘门 ≈ 1 的加法式记忆。
6. 实验二:农场语言上的 GPT · Experiment: a GPT on the farm language
06_farm_transformer.py 在第 1 讲的农场语言(2 万句训练语料,词表 56)上训练一个小 GPT(2 层、4 头、、可学习位置嵌入,111,360 个参数,5 个 epoch)和一个 LSTM(30,520 个参数,5 个 epoch),并读取第 1 讲缓存的 RNN(11,640 个参数,10 个 epoch)。
6.1 困惑度
| 模型 | 参数量 | ⏱ 每个 epoch | 测试集困惑度 | 比下界多的 nat/词 |
|---|---|---|---|---|
| Elman RNN(第 1 讲) | 11,640 | 0.7 秒 | 2.5396 | 0.019 |
| LSTM | 30,520 | 1.1 秒 | 2.5382 | 0.019 |
| GPT | 111,360 | 2.6 秒 | 2.5305 | 0.016 |
| 真实分布(理论下界) | — | — | 2.4913 | 0 |
三种神经网络语言模型都非常接近理论下界,GPT 略好一点。在这个只有约 20 个词长的玩具语言上,三种结构的容量都够用,差别不在困惑度上。
6.2 主谓一致到
干扰名词全部和主语单复数相反(最难的情况),训练集里最多只有 3 个干扰名词:
| 模型 | – | – | |
|---|---|---|---|
| Elman RNN | 1.000 | 1.000 | 1.000 |
| LSTM | 1.000 | 1.000 | 1.000 |
| GPT(种子 0) | 1.000 | 1.000 | 0.990 |
RNN 和 LSTM 一步一步处理,每一步做的事情和位置无关,“跳过一个介词短语”的规则可以无限次重复。GPT 在 时错了 2 句。 时动词要在第 27 个位置上预测,而训练序列最长只有 19 个输入 token——第 20 个位置以后的位置嵌入从来没被训练过。§6.5 会看到,换一个随机种子,这个问题会明显得多。
6.3 GPT 预测动词时,注意力放在哪里
取 的句子,查询位置是最后一个干扰名词(模型在这里预测动词,第 12 个 token),统计每个头分给各组位置的注意力:
| 层 | 头 | <s> | 主语前的 the | 主语名词 | 介词 | 介词后的 the | 前两个干扰名词 | 自己 |
|---|---|---|---|---|---|---|---|---|
| 1 | 1 | 0.00 | 0.00 | 0.00 | 1.00 | 0.00 | 0.00 | 0.00 |
| 1 | 2 | 0.09 | 0.08 | 0.09 | 0.21 | 0.29 | 0.16 | 0.07 |
| 1 | 3 | 0.00 | 0.00 | 0.03 | 0.93 | 0.02 | 0.01 | 0.00 |
| 1 | 4 | 0.00 | 0.00 | 0.99 | 0.00 | 0.00 | 0.00 | 0.00 |
| 2 | 1 | 0.02 | 0.05 | 0.33 | 0.03 | 0.18 | 0.38 | 0.01 |
| 2 | 2–4 | 0.06–0.10 | 0.10–0.13 | 0.04–0.07 | 0.20–0.27 | 0.30–0.42 | 0.08–0.12 | 0.02–0.13 |
第 1 层第 4 个头把 0.99 的注意力放在主语名词上(均匀分配时是 1/12 ≈ 0.08):在要决定动词单复数的地方,模型直接”回头看”了 10 个词之前的主语,只用一跳,不像 RNN 那样要经过中间 10 个时间步的接力。另外两个第 1 层的头几乎只看介词;第 2 层的头注意力分散在多组位置上。
6.4 切断连线:注意力权重大,就说明”用到了”吗?
注意力权重大只是相关性。要检验模型是否真的依赖主语的信息,可以做干预:在两层的所有头里,禁止第 3 个位置(主语名词)之后的所有查询看主语——给注意力加一个附加掩码(MultiHeadAttention.extra_mask)。这样主语的信息就没有任何路径能流到动词的位置。
| 干预(,干扰名词全部和主语相反) | 主谓一致准确率 |
|---|---|
| 不切断 | 1.000 |
| 切断通往主语名词的连线 | 0.050 |
| 切断通往第一个干扰名词的连线 | 1.000 |
| 切断通往第一个介词的连线 | 1.000 |
切断通往主语的连线后,准确率掉到 0.050——远低于乱猜的 0.5:模型改为跟着(单复数相反的)干扰名词走了。切断别的位置几乎没有影响。这才是”模型用主语决定动词形式”的因果证据。解释模型时,注意力权重本身不一定可靠(Jain & Wallace, 2019);像这样做干预更有说服力。机制可解释性(mechanistic interpretability)研究里把这类方法叫做消融(ablation)或激活修补(activation patching)。
6.5 位置编码的三种选择
最后把位置编码换掉:可学习的(GPT 系列)、正弦的(原始 Transformer)、不加(NoPE)。每种训练 3 个随机种子(python3 06_farm_transformer.py --seeds 3,约 2 分钟):
| 位置编码 | 种子 | 测试困惑度 | |||||
|---|---|---|---|---|---|---|---|
| 可学习 | 0 | 2.5305 | 1.000 | 1.000 | 1.000 | 1.000 | 0.990 |
| 可学习 | 1 | 2.5240 | 0.705 | 0.860 | 0.695 | 0.380 | 0.275 |
| 可学习 | 2 | 2.5205 | 0.945 | 0.995 | 0.855 | 0.675 | 0.820 |
| 正弦 | 0 | 2.5344 | 1.000 | 0.985 | 0.380 | 0.325 | 0.495 |
| 正弦 | 1 | 2.5239 | 0.950 | 1.000 | 1.000 | 0.995 | 1.000 |
| 正弦 | 2 | 2.5349 | 0.440 | 0.560 | 0.440 | 0.520 | 0.495 |
| 不加 | 0 | 2.5360 | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 |
| 不加 | 1 | 2.5316 | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 |
| 不加 | 2 | 2.5363 | 1.000 | 0.995 | 0.975 | 0.975 | 0.985 |
( 时所有模型都是 1.000。)
- 训练见过的长度之内,三种做法没有区别:测试困惑度都在 2.521–2.536。
- 超出训练长度后差别出现了,而且很依赖随机种子:可学习和正弦位置编码在 时最低降到 0.275 和 0.325——低于乱猜的 0.5,说明它们开始跟着干扰名词走;同一种编码,不同种子之间差别也很大;不加位置编码的 GPT 反而最稳,3 个种子的最低值是 0.975。
- **为什么不加位置编码也能工作?**因果掩码本身泄露了位置:第 个位置只能看到 个 token,比如对
<s>的均匀注意力是 ,模型可以由此推断”我在第几个位置”(Haviv et al., 2022;练习 9)。而”跳过介词短语、找主语”只需要相对的先后关系,不需要绝对位置;没训练过的绝对位置向量反而成了干扰。 - 这是玩具设定下的结论,不要推广过头:真实语言需要更细的位置信息,现代模型普遍用第 4 讲的 RoPE;长度外推本身是第 19 讲的主题。这里想说明的是:(1) 注意力机制的”位置”完全来自我们怎样设计输入和掩码;(2) 超出训练分布的行为,必须用多个随机种子来测——只看种子 0,我们会以为可学习位置嵌入外推得很好。
7. 小结 · Summary
- 2017 年之前,语言模型主要在噪声信道模型里给语音识别和机器翻译打分;最早的”大”语言模型是 Google n-gram(1 万亿 token、约 38 亿条 n-gram)。今天的 LLM 用 – 个 token 训练,参数 – 以上,每个参数分到的 token 数在增加。
- 简单 RNN 的三难:收缩会遗忘、不收缩会积累噪声、能抗噪声存储的锁存器又让梯度消失。实验上,Elman RNN 能表示100 步的记忆(训练 时学会并外推),但从长例子里学不会。
- LSTM 用门控制一个加法式更新的记忆单元 ,梯度沿它传回 步约为 。门本身不保证梯度不消失(遗忘门偏置 0 时和 Elman 差不多),但 LSTM 能学会让 。它的代价是 4 倍的参数和计算,以及无法并行。
- 注意力让每个位置直接对所有(之前的)位置做加权平均:。查询和键的点积决定权重,值是被取走的内容;按行 softmax;除以 防止 softmax 饱和;因果掩码用 屏蔽未来。任意两个位置之间的路径长度是 1。
- 注意力不知道顺序(置换等变),必须加入位置信息;位置编码可以是可学习的或正弦的、绝对的或相对的。正弦编码的点积只依赖距离。
- 多头把 维切成 个 维的头,参数量不变,每个头学一种注意力模式。
- Transformer 层 = 多头注意力 + 前馈网络,每个子层都有残差连接和层归一化。残差让梯度有恒等通路(和 LSTM 的加法式记忆同理);LayerNorm 对每个 token 单独归一化。GPT = 嵌入 + 位置嵌入 + 个因果 Transformer 层 + 输出层,每层约 个参数。
- 训练时一次前向得到所有位置的预测;因果掩码保证第 个位置的输出不受未来影响。注意力的 项只在 时成为计算的大头;Transformer 的大矩阵乘法可以并行,这是它取代 RNN 的主要工程原因之一。
- 实验:只有长例子时,只有 Transformer 学会了”记住开头”;在农场语言上,GPT 有一个头直接看主语,切断通往主语的注意力连线后一致性从 1.000 降到 0.050;超出训练长度时,不加位置编码的 GPT 反而最稳——但这依赖于玩具任务的性质,也很依赖随机种子。
8. 常见误区 · Common pitfalls
- “LSTM 解决了梯度消失。” 更准确的说法是:LSTM 提供了一条可以不衰减的通路,衰减速度由遗忘门控制。刚初始化时遗忘门约 0.5,梯度照样指数衰减;只有长例子时,LSTM 和 Elman RNN 一样学不会(§5.4)。
- “RNN 记不住长距离,所以准确率随距离下降。” 在我们的实验里,失败的 RNN 准确率随距离几乎是平的;成功的 RNN 外推到了 5 倍的距离。问题出在学习而不是表示。
- “注意力权重大的位置就是模型用到的信息。” 权重是相关性,干预(切断连线、替换输入)才是因果证据(§6.4)。
- “softmax 按列做”或”掩码在 softmax 之后乘 0”。 按行做;在 softmax 之前加 。
- “多头注意力比单头参数多。” 时参数量完全相同。
- “没有位置编码,Transformer 就完全不知道顺序。” 不带掩码的注意力确实如此;因果注意力可以从掩码推断位置(§6.5)。
- “Transformer 的计算量主要在注意力。” 对 的常见设置,主要在线性层(GPT-3 只有 2.7% 在注意力)。长上下文时才反过来。
- “双向 LSTM 更强,所以语言模型也该用双向的。” 双向模型在第 步看到了未来,用它预测下一个词是作弊(§2.6)。
- “pre-norm 和 post-norm 只是写法不同。” 它们的梯度通路不同:pre-norm 的残差通路上没有 LN,深层时更容易训练;post-norm 通常需要学习率预热。
- “一个种子上的结果就是结论。” 超出训练分布的行为对随机种子非常敏感(§6.5 里可学习位置嵌入在 时从 0.990 到 0.275 不等)。
9. 练习 · Exercises
练习 1(课堂练习的变体)。 沿用 §3.4 的 和 ,但这次给出的是分数 而不是权重。(a) 求 和 。(b) 如果分数整体乘以 4(相当于忘了除以 、), 变成什么?
参考答案
(a) ,(第 3 维是 )。 (b) :几乎全部权重给了 ,。分数放大 4 倍,softmax 就从”偏向 “变成了”只看 “——这就是 §3.5 说的饱和。
练习 2(LSTM 的参数量)。 输入维度 、隐藏维度 。分别求 nn.RNN(100, 200) 和 nn.LSTM(100, 200) 的参数个数(PyTorch 的两者都有两份偏置)。
参考答案
nn.RNN:。
nn.LSTM:。恰好 4 倍:四组门各有一套和 Elman RNN 一样大的参数。
练习 3(遗忘门与记忆的半衰期)。 一个记忆单元写入 之后,输入门一直为 0、遗忘门恒为 。(a) 时,50 步后 还剩多少?(b) 要让 100 步后至少还剩 0.5, 至少要多大?如果 (只靠偏置), 至少多大?
参考答案
(a) 。 (b) ;。所以”记住 100 步”需要遗忘门非常接近 1——这只需要一个偏置约为 5,但模型必须学会把它设到那里。
练习 4(为什么是 ,而不是 )。 的分量独立、服从 ,,每个查询对 32 个键做 softmax。分别求分数 的标准差,、、。哪一种会让注意力过于集中、哪一种过于均匀?
参考答案
的方差是 ,标准差 8。乘以 后标准差为 : 时 8, 时 1, 时 0.125。数值实验(20000 个查询): 时最大权重平均 0.815、熵 0.50(过于集中); 时最大权重 0.164、熵 3.01; 时最大权重 0.040、熵 3.458,几乎等于均匀分布的 (过于均匀,注意力”看不出”区别)。 恰好让分数的标准差为 1。
练习 5(层归一化)。 不带 ()、,求 。再求 和 。
参考答案
,,输出 。另外两个输入得到完全相同的结果:LN 对整体缩放和平移不变。
练习 6(数 GPT-2 Medium 的参数)。 GPT-2 Medium:24 层、、词表 50,257、上下文 1024,pre-norm(有最后的 LN),输出层和词嵌入共享。用 §4.7 的公式求参数量。
参考答案
每层 ,24 层共 302,309,376;词嵌入 ;位置嵌入 ;最后的 LN 2,048。合计 354,823,168 ≈ 355M,和公开的 GPT-2 Medium(常写作 355M)一致。
练习 7(注意力的计算与内存)。 (a) 一个 的模型处理 的序列,注意力()占每层前向矩阵乘法 FLOPs 的多少?(b) 批大小 8、8 个头、 时,一层的分数矩阵 有多少个元素?用 float32 存要多少内存? 呢?
参考答案
(a) 占比 。 (b) 个元素,float32 共 4 MiB。 时乘以 ,是 1 GiB——每一层都要。训练时还要为反向传播存下来。这就是第 18 讲 FlashAttention 要避免”把整个 写进显存”的原因。
练习 8(因果掩码浪费了多少)。 时, 的分数矩阵里有多少个位置是”允许看”的()?占多大比例?
参考答案
个,占 的 50.02%。先算完整的 再加掩码,意味着将近一半的分数算出来就被丢掉了。
练习 9(置换等变与”掩码里的位置”)。 (a) 证明:不带掩码、不带位置编码的自注意力满足 ,其中 是任意置换矩阵(打乱输入的行,输出的行也同样被打乱)。(b) 现在加上因果掩码、仍然不加位置编码。假设某个头的查询和所有键的点积都相同(均匀注意力),值向量只有第 1 个位置(<s>)是 1、其余为 0。求第 个位置的输出。这说明了什么?
参考答案
(a) 变成 ,同理 、。。按行 softmax 和”同时置换行和列”可交换:。所以输出 (),即 。 (b) 第 个位置均匀地看前 个位置,输出 :1, 0.5, 0.333, 0.25, 0.2。不同的位置得到不同的输出,所以即使没有位置编码,因果 Transformer 也能”数出”自己在第几个位置——这是 §6.5 里 NoPE 能工作的一个机制。
练习 10(残差的恒等通路)。 (a) 一个 64 层的普通网络,每层的雅可比矩阵把梯度范数缩小到 0.5 倍,传回底层还剩多少?(b) 换成残差网络 ,假设每层 很小、使得每层的放大倍数约为 ,64 层后是多少?(c) 用这两个数解释 §4.4 表格里”初始梯度”两列的差别。
参考答案
(a) 。(b) 。(c) 普通网络的梯度是 64 个小于 1 的因子连乘,指数消失(表中 64 层是 );残差网络每层都有一个"",连乘的是 ,不会消失,反而随深度缓慢增长(表中从 98 增长到 450)。后者的增长是 pre-norm 残差网络的已知现象:残差流里的激活随层数累加;GPT 的初始化把每个残差分支的最后一层再乘 (lm_models.GPT),就是为了控制这种增长。
10. 延伸阅读 · Further reading
- LSTM 的来历:Hochreiter & Schmidhuber (1997), Long Short-Term Memory;Gers, Schmidhuber & Cummins (2000), Learning to Forget(遗忘门);Greff et al. (2017), LSTM: A Search Space Odyssey(各部件的消融);Jozefowicz, Zaremba & Sutskever (2015), An Empirical Exploration of Recurrent Network Architectures。
- 梯度消失的理论:Bengio, Simard & Frasconi (1994), Learning Long-Term Dependencies with Gradient Descent is Difficult;Pascanu, Mikolov & Bengio (2013), On the difficulty of training recurrent neural networks。
- Graves 的两篇:Graves (2012), Supervised Sequence Labelling with Recurrent Neural Networks(书);Graves, Mohamed & Hinton (2013), Speech Recognition with Deep Recurrent Neural Networks(DBLSTM)。
- 注意力的前身:Bahdanau, Cho & Bengio (2015), Neural Machine Translation by Jointly Learning to Align and Translate——第一个在 RNN 编码器-解码器上加注意力的工作。
- Transformer:Vaswani et al. (2017);Alammar 的 The Illustrated Transformer;Rush 的 The Annotated Transformer(逐行实现)。
- 归一化与残差:Ba, Kiros & Hinton (2016), Layer Normalization;He et al. (2016), Deep Residual Learning for Image Recognition;Xiong et al. (2020), On Layer Normalization in the Transformer Architecture(pre-norm vs post-norm)。
- GPT 系列:Radford et al. (2018) GPT;Radford et al. (2019) GPT-2;Brown et al. (2020) GPT-3。
- 位置编码:Shaw, Uszkoreit & Vaswani (2018),相对位置表示;Haviv et al. (2022), Transformer Language Models without Positional Encodings Still Learn Positional Information;Kazemnejad et al. (2023), The Impact of Positional Encoding on Length Generalization in Transformers。
- 注意力 ≠ 解释:Jain & Wallace (2019), Attention is not Explanation;Wiegreffe & Pinter (2019), Attention is not not Explanation。
- 主谓一致作为探针:Linzen, Dupoux & Goldberg (2016),第 1 讲已经引用过。
11. 下一讲预告 · Next lecture
我们有了模型,但还没认真讨论怎样训练它、怎样用它生成。第 3 讲回答:
- 不等长的句子怎样组成一个批?填充、截断、块大小和损失掩码;
- 生成第 个 token 时,前面 个位置的键和值为什么不必重算——KV 缓存(它正是本讲 GPT 接口里那个空着的
state); - 词表从哪里来:字符、词、子词,以及 BPE 算法;
- 从分布里”挑”出一段文字:贪心、束搜索、温度、top-k、top-p,各自怎样失败;
- GPT-1 的”生成式预训练 + 判别式微调”。
第 3 讲会直接读取本讲 06 缓存的农场语言 GPT(checkpoints/farm_gpt.pt)。