第 2 讲 · Transformer 语言模型 · Transformer Language Models

Generative AI · Lecture 02。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

对应 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

学完这一讲,你应该能够:

  1. 说出语言模型在 2017 年之前的主要用途(噪声信道模型),以及”大”语言模型在训练数据量和参数量上的演进。
  2. 解释简单 RNN 为什么难以学会长程依赖:收缩会遗忘、不收缩会积累噪声、能稳定存储的结构又让梯度消失(Bengio et al., 1994 的困境)。
  3. 写出 LSTM 的六个公式,解释输入门、遗忘门、输出门和记忆单元各自的作用,说明”加法式更新”为什么能让梯度传得更远;知道遗忘门偏置初始化的作用和 LSTM 的局限。
  4. 从”加权平均”出发,一步步推出缩放点积注意力 softmax(QK⊤/dk+M)V\mathrm{softmax}(QK^\top/\sqrt{d_k} + M)V,解释查询、键、值,按行 softmax、因果掩码和 dk\sqrt{d_k} 各自的理由。
  5. 说明注意力为什么”不知道顺序”、位置编码怎样补上;区分可学习、正弦、相对位置编码。
  6. 画出一个 Transformer 层(多头注意力、前馈网络、层归一化、残差连接),会数 GPT 系列的参数量,知道计算量的哪一部分随序列长度平方增长。
  7. 用实验说明 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 讲的记号,新增:

记号含义
xt∈Rdx_t \in \mathbb{R}^d第 tt 个位置的输入向量(词嵌入 + 位置嵌入),d=dmodeld = d_\text{model}
X∈RT×dX \in \mathbb{R}^{T \times d}把 x1,…,xTx_1, \dots, x_T 按行叠起来的矩阵
qt,kt,vtq_t, k_t, v_t查询(query)、键(key)、值(value):qt=Wq⊤xtq_t = W_q^\top x_t 等,维度 dkd_k(值的维度 dvd_v,通常 =dk= d_k)
st,js_{t,j}、at,ja_{t,j}第 tt 个位置对第 jj 个位置的注意力分数、注意力权重(∑jat,j=1\sum_j a_{t,j} = 1)
MM掩码矩阵,元素是 00 或 −∞-\infty
hh注意力头数;dk=d/hd_k = d / h
LLTransformer 的层数
σ(⋅)\sigma(\cdot)sigmoid 函数 1/(1+e−z)1/(1+e^{-z})
⊙\odot逐元素乘法

注意 slides 把 hth_t 既用作 RNN 的隐藏状态、也用作 Transformer 最后一层的输出;本讲保留这个习惯,并在可能混淆的地方说明。


1. 大语言模型简史 · Some history of large language models

1.1 2017 年之前:噪声信道模型

今天我们用语言模型来生成文字。但在 2017 年之前,语言模型最重要的用途是给别的系统打分。slides 举了两个例子:语音识别和机器翻译。它们都可以写成噪声信道模型(noisy channel model):

y^=arg⁡max⁡yp(y∣x)=arg⁡max⁡yp(x∣y) p(y)\hat{y} = \arg\max_y p(y \mid x) = \arg\max_y p(x \mid y)\, p(y)
  • xx 是观察到的东西(语音信号;源语言的句子),yy 是想恢复的东西(转写文本;目标语言的句子)。
  • p(x∣y)p(x \mid y) 是转导模型(transduction model):如果说的是 yy,听起来像 xx 的概率有多大。
  • p(y)p(y) 就是语言模型:yy 本身像不像一句通顺的话。

第二个等号用的是贝叶斯公式(前置讲 P2 §1):分母 p(x)p(x) 和 yy 无关,求 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-2OpenAI2019约 100 亿(40 GB)15 亿
GPT-3OpenAI20203000 亿1750 亿
PaLMGoogle20227800 亿5400 亿
ChinchillaDeepMind20221.4 万亿700 亿
LaMDAGoogle20221.56 万亿1370 亿
LLaMAMeta20231.4 万亿650 亿
LLaMA-2Meta20232 万亿700 亿
GPT-4 / GPT-5OpenAI2023 / 2025未公开未公开(slides 引用的传闻:1.76 万亿 / 2–5 万亿)
Gemini UltraGoogle2023未公开未公开(传闻 1.5 万亿)
LLaMA-3Meta202415 万亿4050 亿

两个值得注意的趋势:

  1. 参数量和数据量都在增长,但数据增长得更快。 每个参数分到的训练 token 数:GPT-3 约 1.7,Chinchilla 约 20,LLaMA-3 405B 约 37。Chinchilla 论文(Hoffmann et al., 2022)发现,在固定的计算预算下,GPT-3 这样的模型”参数太多、数据太少”了。第 15 讲的规模定律会详细讨论。
  2. 闭源模型的规模不再公开。 表中带”传闻”的数字没有官方来源,只能当作量级参考。

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) 把之前的所有词压缩成一个定长向量 ht=fθ(wt−1,…,w1)h_t = f_\theta(w_{t-1}, \dots, w_1);(2) 用 p(wt∣ht)p(w_t \mid h_t) 定义下一个词的分布。hth_t 由 ht−1h_{t-1} 和当前输入递推得到:

ht=σ(Whhht−1+Whxxt+bh)h_t = \sigma(W_{hh} h_{t-1} + W_{hx} x_t + b_h)

所以任何信息要从第 ss 步传到第 tt 步,都必须在 hs,hs+1,…,hth_s, h_{s+1}, \dots, h_t 里一步一步”接力”下去。slides 把这个问题叫做”会遗忘的 RNN”。

(slides 还回顾了两种画神经网络的方式:神经网络图里节点是隐藏单元、边上标权重;计算图里节点是中间变量和它的计算函数、参数和常量也是节点。本讲的图都按计算图理解。)

2.2 slides 的例子:记住”两个位置都出现过 1”

slides 设计了一个小任务:输入是长度为 2 的 0/1 向量序列,RNN 要在每一步回答”到目前为止,两个位置是否都出现过 1”。设定权重 Whx=IW_{hx} = I、Whh=12IW_{hh} = \tfrac12 I、bh=0b_h = 0,输入是

x1=x2=[0,0],x3=[1,0],x4=[0,1],x5=⋯=x100=[0,0]x_1 = x_2 = [0, 0],\quad x_3 = [1, 0],\quad x_4 = [0, 1],\quad x_5 = \dots = x_{100} = [0, 0]

slides 上画出的隐藏状态是 h3=[1,0]h_3 = [1, 0]、h4=[12,1]h_4 = [\tfrac12, 1]、h5=[14,12]h_5 = [\tfrac14, \tfrac12]……——这些数说明这里的激活函数实际上取的是恒等函数(slides 公式里写的是 σ\sigma)。slides 接着问:读出层 yt=sign(Wyhht+by)y_t = \mathrm{sign}(W_{yh} h_t + b_y) 该怎样设?然后指出第 100 步的输出会变回 −1-1。

01_forgetful_rnn.py 第 1 部分把这条轨迹算了出来,并且给了 RNN 一个最”宽容”的非线性读出:只要 min⁡(h1,h2)>0.001\min(h_1, h_2) > 0.001 就回答 +1+1。

tthth_t真实答案读出
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 步。之后 hth_t 和”什么都没见过”时的 h=0h = 0 几乎无法区分:**不管读出层怎么设计,最终都会给出和一开始一样的答案。**这就是”遗忘”。在 float32 里,连续乘 ½ 到第 150 次就变成精确的 0(前置讲 P1 §3 的下溢),连浮点数都”忘了”。

根源是 WhhW_{hh} 的谱半径 12<1\tfrac12 < 1:每一步都把旧的记忆压缩一次。动力系统里把这叫做收缩(contraction)。

2.3 三难:记得久、抗噪声、梯度传得回去

“那就别收缩,把 WhhW_{hh} 设成 II 不就行了?“01 的第 2、3 部分试了两种不收缩的设计,结果说明简单 RNN 面对的是一个三难问题。

积分器(Whh=IW_{hh} = I)。 ht=ht−1+xth_t = h_{t-1} + x_t 只是把所有输入加起来,第 3 步写入的 1 永远保留。但如果每一步的输入带有一点噪声(标准差 0.05),噪声也被一起加了起来:

网络噪声标准差 t=10t=10t=100t=100t=1000t=1000
Whh=12IW_{hh} = \tfrac12 I0.0540.0580.062
Whh=IW_{hh} = I0.1800.5031.617

积分器的噪声按 0.05t0.05\sqrt{t} 增长,1000 步后(理论值 1.58)已经超过信号本身。收缩的网络噪声有界(理论值 0.05/1−1/4=0.0580.05/\sqrt{1 - 1/4} = 0.058),但信号也被压没了。收缩能抗噪声但会遗忘;不收缩不遗忘但会积累噪声。

锁存器(latch)。 用饱和的 sigmoid 可以造出第三种设计:ht=σ(10 ht−1+10 xt−5)h_t = \sigma(10\, h_{t-1} + 10\, x_t - 5)。它有两个稳定点(约 0.007 和 0.993):没见过 1 时停在 0.007,一旦写入 1 就跳到 0.993 并一直停在那里;噪声也会被”吸”回稳定点。这是一个真正的、抗噪声的记忆,100 步后仍然记得。

但梯度呢?在稳定点附近 sigmoid 已经饱和,每一步的导数只有 10 σ′(⋅)≈0.07110\,\sigma'(\cdot) \approx 0.071:

距离 dd15102050
∂ht+d/∂ht\partial h_{t+d} / \partial h_t7.1e-021.9e-063.4e-121.2e-234.7e-58

这就是 Bengio, Simard & Frasconi (1994) 证明的困境:要抗噪声地存住信息(吸引子),状态在稳定点附近必须是收缩的,而收缩恰恰意味着梯度消失。锁存器”记得住”,但梯度下降看不见它该怎样设置——“学不会”。§5 的实验会看到这个困境在真实训练中的样子。

结构100 步后还记得?抗噪声?传回 50 步的梯度
线性 RNN,Whh=12IW_{hh} = \tfrac12 I(slides)✗✓8.9e-16
线性 RNN,Whh=IW_{hh} = I(积分器)✓✗1
sigmoid 锁存器✓✓4.7e-58
LSTM(下一节)✓✓≈1

2.4 LSTM:门控与加法式记忆

动机。 slides 引用了 Graves (2012) 的图:标准 RNN 对第 1 步输入的敏感度随时间迅速变淡(梯度消失),而 LSTM 可以让这种敏感度保持很久。LSTM(Long Short-Term Memory,Hochreiter & Schmidhuber, 1997)的思路是把”要记住的内容”和”是否写入、是否保留、是否读出的控制”分开:

it=σ(Wxixt+Whiht−1+bi)输入门:这一步写入多少ft=σ(Wxfxt+Whfht−1+bf)遗忘门:保留多少旧记忆c~t=tanh⁡(Wxcxt+Whcht−1+bc)候选内容:想写入什么ot=σ(Wxoxt+Whoht−1+bo)输出门:露出多少记忆ct=ft⊙ct−1+it⊙c~t记忆单元(cell)ht=ot⊙tanh⁡(ct)隐藏状态 / 输出\begin{aligned} i_t &= \sigma(W_{xi} x_t + W_{hi} h_{t-1} + b_i) && \text{输入门:这一步写入多少}\\ f_t &= \sigma(W_{xf} x_t + W_{hf} h_{t-1} + b_f) && \text{遗忘门:保留多少旧记忆}\\ \tilde{c}_t &= \tanh(W_{xc} x_t + W_{hc} h_{t-1} + b_c) && \text{候选内容:想写入什么}\\ o_t &= \sigma(W_{xo} x_t + W_{ho} h_{t-1} + b_o) && \text{输出门:露出多少记忆}\\ c_t &= f_t \odot c_{t-1} + i_t \odot \tilde{c}_t && \text{记忆单元(cell)}\\ h_t &= o_t \odot \tanh(c_t) && \text{隐藏状态 / 输出} \end{aligned}

slides 的说法:输入门”遮住”标准 RNN 的输入;遗忘门”遮住”上一步的记忆;记忆单元存储”输入和旧记忆的混合”,它是 LSTM 的长期记忆,控制信息在时间步之间的流动;输出门”遮住”下一个隐藏状态的值;隐藏状态是 LSTM 单元的输出。

slides 引用的 Graves et al. (2013) 版本里,门还会”窥视”记忆单元(公式里多一项 Wcict−1W_{ci} c_{t-1} 等,叫窥视孔连接,peephole)。PyTorch 的 nn.LSTM 和大多数现代实现都不用它;Greff et al. (2017) 的大规模比较发现去掉它几乎没有损失。本讲按不带窥视孔的版本实现。

关键是第五行:加法式更新。 对比 Elman RNN 的 ht=tanh⁡(Whhht−1+… )h_t = \tanh(W_{hh} h_{t-1} + \dots)——每一步旧记忆都要穿过一个矩阵乘法和一个会饱和的非线性;LSTM 的 ctc_t 只是把旧记忆乘以一个门、再加上新内容。沿记忆单元回传的梯度是

∂ct∂ct−1=diag(ft)(只看这条直接通路)⇒∂cT∂cT−d≈∏s=T−d+1Tdiag(fs)\frac{\partial c_t}{\partial c_{t-1}} = \mathrm{diag}(f_t) \quad(\text{只看这条直接通路}) \qquad\Rightarrow\qquad \frac{\partial c_T}{\partial c_{T-d}} \approx \prod_{s=T-d+1}^{T} \mathrm{diag}(f_s)

只要遗忘门接近 1,梯度就几乎不衰减。Hochreiter & Schmidhuber 把这条通路叫做恒定误差传送带(constant error carousel)。

01 第 4 部分手工设定了一个解 slides 任务的 LSTM:输入门 it=σ(40xt−20)i_t = \sigma(40 x_t - 20)(有 1 才打开,否则约 2×10−92 \times 10^{-9}),遗忘门、候选值、输出门都固定在约 1。结果:

  • 1004 步全部答对,加上噪声也全对;噪声累积 1004 步后记忆单元只漂移了 1.2×10−51.2 \times 10^{-5}(积分器的噪声标准差是 1.6)——因为输入门关着时,噪声根本写不进去。
  • 沿记忆单元传回 1000 步的梯度是 0.999998(锁存器在 100 步时已经是 2×10−1152 \times 10^{-115})。

但 LSTM 不是”不会遗忘”。 遗忘门小于 1 时,记忆照样按 fdf^d 衰减(练习 3)。LSTM 的真正优势是:它能学会什么时候让 f≈1f \approx 1,而且这只需要调一个门的偏置;Elman RNN 要让整个 WhhW_{hh} 矩阵恰好处在”既不收缩也不发散”的刀刃上。

2.5 从零实现 LSTM;遗忘门偏置

lm_models.LSTMCell 把四个门的线性变换拼成一个 4H×E4H \times E 的矩阵(和一个 4H×H4H \times H 的矩阵),一次算完再切成四段——行的顺序是 i,f,g,oi, f, g, o(gg 即 c~\tilde c),和 torch.nn.LSTM 完全相同。02_lstm.py 第 1 部分复制 nn.LSTM 的权重后对比:前向的 hh、cc 和反向的梯度都只差 10−1610^{-16}。

参数量。 循环层的参数是 Elman 的约 4 倍(E=32E = 32、H=64H = 64 时 25,088 vs 6,208;整个 LSTM-LM 30,520 vs 第 1 讲 RNN-LM 的 11,640)。每一步的计算量也约是 4 倍。

刚初始化时,梯度能传多远? 02 第 3 部分测了 ∥∂(u⊤hT)/∂xT−d∥\lVert \partial (u^\top h_T)/\partial x_{T-d} \rVert 相对于 d=0d = 0 的比值:

循环层d=10d=10d=50d=50d=100d=100每步平均倍数
Elman RNN7.2e-041.3e-161.6e-320.48
LSTM,遗忘门偏置 03.8e-037.3e-128.2e-230.60
LSTM,遗忘门偏置 18.4e-023.4e-052.4e-090.81
LSTM,遗忘门偏置 34.3e-012.9e-013.6e-010.98
  • 门本身不会自动让梯度不消失。 遗忘门偏置为 0 时 f≈σ(0)=0.5f \approx \sigma(0) = 0.5,LSTM 的衰减(每步 ×0.60)和 Elman RNN(×0.48)差不多。
  • 遗忘门偏置 bfb_f 决定了初始的 f≈σ(bf)f \approx \sigma(b_f),衰减速度随之改变。这就是 Gers et al. (2000) 和 Jozefowicz et al. (2015) 建议把遗忘门偏置初始化为 1 的原因:一开始就”倾向于记住”。实测的每步倍数比 σ(bf)\sigma(b_f) 还大,因为各单元的 ff 不同,梯度范数由 ff 最接近 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 步的输出也变了——它在第 tt 步”偷看”了未来。用它预测下一个词等于把答案告诉模型,所以双向结构不能直接当生成式语言模型(第 5 讲的 BERT 会换一种训练目标来利用双向上下文)。

slides 还问:“这个特定的结构有多重要?“并引用 Jozefowicz et al. (2015):他们评估了一万种类 LSTM 结构,发现有几种变体在多个任务上和 LSTM 一样好。最常见的简化版是 GRU(Cho et al., 2014),它把输入门和遗忘门合成一个”更新门”、去掉了单独的记忆单元。

2.7 为什么不一直用 LSTM?

slides 的回答是”大家确实一度都在用”,但是:

  1. 它们仍然难以处理长程依赖(§5 的实验会看到具体在哪里难);
  2. 计算天生是串行的,难以在 GPU 上并行:第 tt 步必须等第 t−1t-1 步算完。02 第 5 部分测得手写 LSTM 和 nn.LSTM 的耗时都和序列长度成正比(nn.LSTM 把循环写在 C++ 里,本机快约 1.3 倍,但串行依赖躲不开);
  3. 虽然基本解决了梯度消失,仍可能梯度爆炸(所以训练时仍要做梯度裁剪)。

第 2 点是工程上最致命的。2017 年的 Transformer 用注意力同时解决了第 1、2 点。


3. 注意力 · Attention

3.1 直觉:不传递,直接查

RNN 和 LSTM 的共同点是”一步一步传”:信息从第 ss 步到第 tt 步要经过 t−st - s 次接力。如果换一种思路——第 tt 个位置需要什么信息,就直接去前面所有位置里找——那么任意两个位置之间的路径长度都是 1。

一个类比是查字典(或者查数据库):

  • 每个位置 jj 挂出一个键(key)kjk_j:“我这里有什么”,和一个值(value)vjv_j:“如果你需要我,我给你这个”;
  • 第 tt 个位置发出一个查询(query)qtq_t:“我在找什么”;
  • 查询和每个键比相似度(点积),相似度越高,那个位置的值在结果里占的比重越大。

和真正的字典不同,这里的”查找”是软的:不是只取最匹配的那一项,而是对所有项做加权平均,权重由 softmax 给出。这样整个过程可微,可以用梯度下降学习 qq、kk、vv 应该是什么。

3.2 从加权平均开始

slides 一步一步地搭出注意力。第一步只写输出的形式:第 4 个位置的新表示 x4′x_4' 是前面(包括自己)所有位置的值的加权平均,

x4′=∑j=14a4,j vj,a4=softmax(s4),∑ja4,j=1x_4' = \sum_{j=1}^{4} a_{4,j}\, v_j,\qquad a_4 = \mathrm{softmax}(s_4),\qquad \sum_j a_{4,j} = 1

a4,ja_{4,j} 叫注意力权重,s4,js_{4,j} 叫分数(score)。对第 1、2、3 个位置也一样(因果版本里,第 tt 个位置只看 j≤tj \le t)。剩下的问题是:vjv_j 和 st,js_{t,j} 从哪里来?

3.3 缩放点积注意力

slides 接着依次加上值、键、查询和分数:

vj=Wv⊤xj值kj=Wk⊤xj键qj=Wq⊤xj查询st,j=kj⊤qt / dk分数at=softmax(st)注意力权重xt′=∑jat,j vj输出\begin{aligned} v_j &= W_v^\top x_j && \text{值}\\ k_j &= W_k^\top x_j && \text{键}\\ q_j &= W_q^\top x_j && \text{查询}\\ s_{t,j} &= k_j^\top q_t \,/\, \sqrt{d_k} && \text{分数}\\ a_t &= \mathrm{softmax}(s_t) && \text{注意力权重}\\ x_t' &= \textstyle\sum_j a_{t,j}\, v_j && \text{输出} \end{aligned}

三个矩阵 Wq,Wk∈Rd×dkW_q, W_k \in \mathbb{R}^{d \times d_k}、Wv∈Rd×dvW_v \in \mathbb{R}^{d \times d_v} 是要学习的参数。同一个输入 xjx_j 扮演三种角色,所以这叫自注意力(self-attention):查询、键、值都来自同一个序列。(第 13–14 讲的交叉注意力里,查询来自一个序列、键和值来自另一个。)

03_attention.py 里的 attention_loop 逐字照搬了这组公式:对每个 tt,对每个 j≤tj \le t 算分数,做 softmax,再加权求和。

3.4 课堂练习:交换 x2x_2 和 x3x_3

slides 的课堂练习:设输入嵌入和第 4 个位置的注意力权重为

x1=[1,0,0,0], x2=[0,1,0,0], x3=[0,0,2,0], x4=[0,0,0,1];a4=[0.1,0.2,0.6,0.1], Wv=Ix_1 = [1,0,0,0],\ x_2 = [0,1,0,0],\ x_3 = [0,0,2,0],\ x_4 = [0,0,0,1];\qquad a_4 = [0.1, 0.2, 0.6, 0.1],\ W_v = I

则 x4′=0.1x1+0.2x2+0.6x3+0.1x4=[0.1,0.2,1.2,0.1]x_4' = 0.1 x_1 + 0.2 x_2 + 0.6 x_3 + 0.1 x_4 = [0.1, 0.2, 1.2, 0.1]。现在交换 x2x_2 和 x3x_3,新的 x4′x_4' 是多少?

slides 的答案:**和原来完全一样。**原因是注意力权重来自内容的点积 s4,j=kj⊤q4s_{4,j} = k_j^\top q_4:x3x_3 搬到第 2 个位置后,它的分数(从而它的权重 0.6)也跟着搬过去了,加权求和的结果不变。03 第 1 部分用随机的 Wq,Wk,WvW_q, W_k, W_v 真算了一遍:交换前后 x4′x_4' 只差 2×10−162 \times 10^{-16},第 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 要显式加入位置信息。

(同一个实验里,x2′x_2' 和 x3′x_3' 变了:因果掩码让第 2、3 个位置看到的集合不同了。因果掩码本身带有一点位置信息,§6.5 会再遇到这一点。)

3.5 为什么要除以 dk\sqrt{d_k}

如果 qq 和 kk 的每个分量都是独立的、均值 0 方差 1 的随机数,那么点积 q⊤k=∑i=1dkqikiq^\top k = \sum_{i=1}^{d_k} q_i k_i 是 dkd_k 个方差为 1 的项之和,方差为 dkd_k、标准差为 dk\sqrt{d_k}。03 第 4 部分对 32 个键做 softmax(均匀分布的熵是 ln⁡32=3.47\ln 32 = 3.47):

dkd_k不缩放:分数标准差最大权重熵除以 dk\sqrt{d_k} 后:最大权重熵
42.00.3502.260.1603.03
648.00.8150.500.1643.01
102431.90.9540.110.1603.02

不缩放时,dkd_k 一大 softmax 就几乎变成 one-hot。饱和的 softmax 梯度极小:分数差为 1 时 max⁡∣∂p2/∂s∣=2.9×10−2\max|\partial p_2/\partial s| = 2.9 \times 10^{-2},分数差为 32 时只有 1.3×10−141.3 \times 10^{-14}。训练一开始注意力就被”锁死”在某个位置上,学不动。Vaswani et al. (2017) 正是用这个理由引入缩放因子 1/dk1/\sqrt{d_k}:缩放后分数的标准差约为 1,不管 dkd_k 多大,注意力分布都保持”软”的。(为什么不除以 dkd_k?那样分数的标准差变成 1/dk1/\sqrt{d_k},注意力又会过于均匀,见练习 4。)

3.6 矩阵版:一次算完所有位置

slides 指出:“为了速度,我们用矩阵运算一次算出所有查询。“把 x1,…,xTx_1, \dots, x_T 按行叠成 X∈RT×dX \in \mathbb{R}^{T \times d},则

Q=XWq,K=XWk,V=XWv,S=QK⊤/dk,A=softmax(S),X′=AVQ = X W_q,\quad K = X W_k,\quad V = X W_v,\quad S = QK^\top / \sqrt{d_k},\quad A = \mathrm{softmax}(S),\quad X' = AV

即 X′=softmax(QK⊤/dk) VX' = \mathrm{softmax}(QK^\top / \sqrt{d_k})\,V。QQ 的第 tt 行就是 qt⊤q_t^\top,SS 的 (t,j)(t, j) 元素就是 st,js_{t,j}。

slides 的问题:“softmax 是按列还是按行做的?“答案是按行:SS 的第 tt 行是”第 tt 个位置对所有位置的分数”,第 tt 个位置的注意力要在这一行里归一化。03 第 3 部分显示,按行做时每行之和恰好为 1;按列做时每行之和变成 [1.1, 0.874, 1.054, 0.972],就不是加权平均了。

03 第 2 部分对比了两种写法:结果只差 10−1510^{-15},但矩阵版在 T=256T = 256 时快了两千多倍(⏱ 本机单线程)。原因不只是”Python 循环慢”:矩阵版的每一行互不依赖——第 tt 个位置的分数不需要等第 t−1t-1 个位置算完。这正是 slides 说的 Transformer 训练高效的原因之一:注意力分数在不同时间步之间没有依赖,可以完全并行。代价是要算一个 T×TT \times T 的矩阵,计算量和内存随 T2T^2 增长(§4.8)。

3.7 因果掩码

slides 在矩阵版的图上画满了从每个位置指向所有位置的箭头,然后问:“我们总是需要所有这些箭头吗?“如果训练 Transformer 预测下一个 token,那么让第 tt 个位置看到它后面的 token 就是作弊。不带掩码的版本是标准的 Transformer 块(第 5 讲的编码器);语言模型需要的是带掩码的版本。

slides 的洞察:如果 softmax 的某个输入是 −∞-\infty,对应的输出就是 0。所以在 softmax 之前加一个掩码矩阵 MM:

X′=softmax ⁣(QK⊤/dk+M)V,Mt,j={0j≤t−∞j>tX' = \mathrm{softmax}\!\left(QK^\top / \sqrt{d_k} + M\right) V,\qquad M_{t,j} = \begin{cases} 0 & j \le t \\ -\infty & j > t \end{cases}

slides 给了三个候选矩阵(A:对角线以下为 −∞-\infty;B:对角线以上为 −∞-\infty;C:只保留对角线),问哪个是因果语言模型需要的。03 第 3 部分逐个扰动输入,看哪些位置会影响 x2′x_2':

掩码影响 x2′x_2' 的输入位置
A(对角线以下为 −∞-\infty)2, 3, 4(只看自己和未来)
B(对角线以上为 −∞-\infty)1, 2(只看自己和过去)
C(只保留对角线)2(只看自己)

答案是 B。实践中总是先把全部 T×TT \times T 个分数算出来,再把右上角设成 −∞-\infty(slides:“In practice …”);这意味着将近一半的计算被浪费了(练习 8),第 18 讲的 FlashAttention 会跳过这些块。

为什么用”加 −∞-\infty“而不是”softmax 之后乘 0”?因为乘 0 之后剩下的权重和就不是 1 了;加 −∞-\infty 则让被屏蔽的位置权重恰好为 0,其余位置仍然归一化。

3.8 多头注意力

slides 用卷积作类比:卷积层可以有多个通道,注意力层也可以有多个头(head)。每个头有自己的 Wq(i),Wk(i),Wv(i)W_q^{(i)}, W_k^{(i)}, W_v^{(i)},独立做一次注意力,最后把所有头的输出拼起来:

X′(i)=softmax ⁣(Q(i)(K(i))⊤dk+M)V(i),X′=concat(X′(1),…,X′(h)) WoX'^{(i)} = \mathrm{softmax}\!\left(\frac{Q^{(i)} (K^{(i)})^\top}{\sqrt{d_k}} + M\right) V^{(i)},\qquad X' = \mathrm{concat}(X'^{(1)}, \dots, X'^{(h)})\, W_o

为了让输出维度和输入维度一样,Transformer 通常取 dk=dmodel/hd_k = d_\text{model} / h(slides 的约定)。于是:

  • 参数量和”一个 dmodeld_\text{model} 维的头”相同。 03 第 5 部分:d=16d = 16 时 1、2、4、8 个头的参数量都是 1,088(4d2+4d4d^2 + 4d,含输出投影 WoW_o)。多头不是”更大的模型”,而是把同样的维度分给几个独立的注意力模式。
  • 形状(代码里的写法):(B,T,d)→Wq(B,T,d)→view(B,T,h,dk)→transpose(B,h,T,dk)(B, T, d) \xrightarrow{W_q} (B, T, d) \xrightarrow{\text{view}} (B, T, h, d_k) \xrightarrow{\text{transpose}} (B, h, T, d_k),然后所有头的矩阵乘法一次批量算完,再变回 (B,T,d)(B, T, d) 并乘 WoW_o。

为什么要多个头? 一个头的输出是一个加权平均,只能”看一种东西”。03 第 5 部分手工设定了两个头:一个总是看前一个位置,一个总是看第一个位置(两者的注意力矩阵分别是次对角线和第一列)。真实模型里的头也有类似的分工——§6.3 会在训练好的模型里找到一个专门”看主语”的头。

3.9 位置编码

§3.4 说过,注意力本身不知道顺序。slides 的解决办法:为每个位置 tt 准备一个位置嵌入 ptp_t,表示”在第 tt 个位置意味着什么”,把它加到词嵌入上:xt=wt+ptx_t = w_t + p_t。关键在于:任何出现在第 tt 个位置的词都加同一个 ptp_t。03 第 6 部分验证:不加位置时交换 x2x_2、x3x_3 后 x4′x_4' 不变(差 6×10−176 \times 10^{-17}),加上位置编码后变了(差 0.057)。

slides 列出了几种变体:

  • 固定的(基于正弦和余弦)或可学习的(像词嵌入一样是一张表);
  • 绝对的(如上所述)或相对的(相对于查询的位置)。

正弦位置编码(Vaswani et al., 2017):

PE[p,2i]=sin⁡ ⁣(p100002i/d),PE[p,2i+1]=cos⁡ ⁣(p100002i/d)\mathrm{PE}[p, 2i] = \sin\!\left(\frac{p}{10000^{2i/d}}\right),\qquad \mathrm{PE}[p, 2i+1] = \cos\!\left(\frac{p}{10000^{2i/d}}\right)

每两维是一个以固定频率转动的”指针”:低维转得快,用来区分相邻位置;高维转得慢,用来区分远处的位置。它有一个漂亮的性质:PE[p]⋅PE[p+k]=∑icos⁡(k ωi)\mathrm{PE}[p] \cdot \mathrm{PE}[p+k] = \sum_i \cos(k\,\omega_i) 只依赖距离 kk。03 第 6 部分实测:k=1k = 1 时从 p=0,10,30p = 0, 10, 30 出发的点积都是 15.3137,k=4k = 4 时都是 11.6615。也就是说,正弦编码天然携带相对位置的信息。第 4 讲的 RoPE 把这个想法直接用到 qq 和 kk 上,让注意力分数只依赖相对位置。

可学习的位置嵌入(GPT 系列)是一张 max_len×d\text{max\_len} \times d 的表:更灵活,但表里没有的位置就没有向量——GPT-2 的上下文上限 1024 就是这张表的行数。§6.5 会看到,即使表足够大,训练中没见过的位置也可能出问题。


4. Transformer 语言模型 · The Transformer LM

4.1 从 RNN-LM 到 Transformer-LM

slides 把两者并排画出来:语言模型的部分完全一样——每个位置输出一个向量 hth_t,用 p(wt∣ht)=softmax(Wht)p(w_t \mid h_t) = \mathrm{softmax}(W h_t) 预测下一个词,用交叉熵训练。区别只在 hth_t 怎样得到:

  • RNN-LM:hth_t 由 ht−1h_{t-1} 和 xtx_t 递推;
  • Transformer-LM:hth_t 由若干个 Transformer 层算出,每个隐藏向量回头看前一层里当前和之前所有时间步的隐藏向量。

slides 特别强调:“RNN 的计算图随输入 token 数线性增长;Transformer 的计算图随输入 token 数平方增长”——因为每对位置之间都有一条注意力连线。

4.2 一层里有什么

slides:Transformer LM 的每一层由几个子层组成:

  1. 注意力(多头、因果)
  2. 前馈神经网络(feed-forward network, FFN)
  3. 层归一化(layer normalization)
  4. 残差连接(residual connections)

前馈网络对每个位置独立地、用同一组参数做 FFN(x)=W2 GELU(W1x+b1)+b2\mathrm{FFN}(x) = W_2\, \mathrm{GELU}(W_1 x + b_1) + b_2,中间维度通常是 4d4d。为什么需要它?注意力输出只是各位置的值的加权平均,对 VV 是线性的;前馈层提供逐位置的非线性变换。它的参数占一层的 2/3(8d28d^2 vs 注意力的 4d24d^2)。(GELU 是 ReLU 的一个平滑版本,GPT 系列都用它;第 4 讲会讲 SwiGLU。)

4.3 层归一化

slides 的说法:深层网络训练时,低层参数的一个小变化,会在高层被放大成一个大变化(他们称之为 internal covariate shift)。层归一化对每一层的激活做归一化,再学习逐元素的增益和偏置:

μ=1K∑k=1Kak,σ=1K∑k=1K(ak−μ)2,b=γ⊙a−μσ+β\mu = \frac1K \sum_{k=1}^K a_k,\qquad \sigma = \sqrt{\frac1K \sum_{k=1}^K (a_k - \mu)^2},\qquad b = \gamma \odot \frac{a - \mu}{\sigma} + \beta

slides 还指出:这样的归一化允许用更高的学习率(收敛更快),而不会出现梯度发散。04_transformer_block.py 第 1 部分:

  • 手写版和 nn.LayerNorm 差 10−1510^{-15}(PyTorch 在方差上加了一个小的 ϵ=10−5\epsilon = 10^{-5} 防止除以 0)。
  • 输入整体乘以 100,输出只变了 1.8×10−61.8 \times 10^{-6}(来自 ϵ\epsilon);整体加 7,只有舍入误差;每个 token 各自乘以不同的倍数,输出也几乎不变。前面的层把激活放大多少倍,后面的层看到的都差不多。
  • 每个 token 只用自己的统计量:批大小为 1、序列长度不一都没问题。这是 Transformer 用 LayerNorm 而不用 BatchNorm(在批的维度上统计)的主要原因。

4.4 残差连接

slides 的说法:网络非常深时,会出现一种无法用过拟合解释的性能退化——训练误差和测试误差都变差(He et al., 2016)。解决办法是残差连接:让输入的一份副本绕过子层直接加到输出上,b=f(a)+ab = f(a) + a。

为什么有用?slides 的回答:”f(a)f(a) 不必学会对 aa 的整个变换,只需学一个对 aa 的加法修正(即残差)。“从梯度的角度看:

∂hl+1∂hl=I+∂f∂hl\frac{\partial h_{l+1}}{\partial h_l} = I + \frac{\partial f}{\partial h_l}

不管 ff 的导数多小,总有一条”恒等”通路让梯度直达底层——和 LSTM 的 ct=ft⊙ct−1+…c_t = f_t \odot c_{t-1} + \dots 是同一个道理:用加法代替连乘。

04 第 2 部分训练普通网络(h←tanh⁡(Wh)h \leftarrow \tanh(Wh))和残差网络(h←h+tanh⁡(W LN(h))h \leftarrow h + \tanh(W\,\mathrm{LN}(h)))去拟合一个随机”教师网络”(目标方差 0.2054,一直预测均值就是这个损失):

深度普通:初始梯度普通:训练损失残差:初始梯度残差:训练损失
21.7e+010.00449.8e+010.0033
87.0e-010.00431.5e+020.0021
324.8e-070.20592.8e+020.0037
642.9e-150.20594.5e+020.0104

32 层以上的普通网络什么都没学到(损失 = 目标方差),比 2 层还差——这就是退化。残差网络 64 层照样训练得动;它 500 步内比 2 层略差(0.0104 vs 0.0033),但和”完全学不动”有质的区别。(普通网络用的是 PyTorch 的默认初始化;更仔细的初始化能缓解梯度消失,但残差是更稳健的办法。)

4.5 post-norm 与 pre-norm

子层、残差和层归一化有两种常见的排列:

post-norm(原始 Transformer,slides 的画法):x←LN(x+Attn(x)),x←LN(x+FFN(x))\text{post-norm(原始 Transformer,slides 的画法):}\quad x \leftarrow \mathrm{LN}(x + \mathrm{Attn}(x)),\quad x \leftarrow \mathrm{LN}(x + \mathrm{FFN}(x)) pre-norm(GPT-2 起的主流):x←x+Attn(LN(x)),x←x+FFN(LN(x))\text{pre-norm(GPT-2 起的主流):}\quad x \leftarrow x + \mathrm{Attn}(\mathrm{LN}(x)),\quad x \leftarrow x + \mathrm{FFN}(\mathrm{LN}(x))

pre-norm 的残差通路上没有任何变换,梯度可以从输出直接流到输入(上一节的恒等通路);post-norm 的每一层残差都要穿过一个 LN。Xiong et al. (2020) 分析了 post-norm 为什么需要学习率预热(warmup)才能稳定训练,而 pre-norm 可以不用。pre-norm 在最后还要加一个 LN(下面的 LNfinal\mathrm{LN}_\text{final})。lm_models.Block 两种都支持,默认 pre-norm。

4.6 整体结构:GPT

slides:“GPT 代表 Generative Pre-trained Transformer。GPT 就是一个 Transformer LM,只是参数非常多。“本讲的 lm_models.GPT:

xt(0)=E[wt]+P[t]词嵌入 + 位置嵌入x(ℓ)=Blockℓ(x(ℓ−1)),ℓ=1,…,L每个块的注意力都带因果掩码p(wt+1∣w≤t)=softmax ⁣(Wout LNfinal(xt(L)))\begin{aligned} x^{(0)}_t &= E[w_t] + P[t] && \text{词嵌入 + 位置嵌入}\\ x^{(\ell)} &= \mathrm{Block}_\ell(x^{(\ell-1)}),\quad \ell = 1, \dots, L && \text{每个块的注意力都带因果掩码}\\ p(w_{t+1} \mid w_{\le t}) &= \mathrm{softmax}\!\left(W_\text{out}\, \mathrm{LN}_\text{final}(x^{(L)}_t)\right) \end{aligned}

slides 的 GPT 规模表(“dimension of inner” 即前馈层的中间维度):

模型层数隐藏维度前馈维度头数参数量
GPT(2018)127684×76812117M
GPT-2(2019)4816004×1600121542M
GPT-3(2020)96122884×1228896175000M
GPT-4o(2024)/ GPT-5(2025)????传闻 1–2 万亿

(公开的 GPT-2 XL 配置是 25 个头、每头 64 维;头数不影响参数量。)

4.7 数参数

每层的参数(GPT 式:线性层带偏置,LN 有 γ\gamma、β\beta):注意力 4d2+4d4d^2 + 4d(Wq,Wk,Wv,WoW_q, W_k, W_v, W_o),前馈 2⋅d⋅4d+4d+d2 \cdot d \cdot 4d + 4d + d,两个 LN 4d4d,合计 12d2+13d12d^2 + 13d。再加上词嵌入 VdVd、位置嵌入 TmaxdT_\text{max} d 和最后的 LN 2d2d(输出层和词嵌入共享权重,不重复计)。04 第 3 部分先在一个小 GPT 上核对公式(PyTorch 数出 105,152,公式 105,152),再算 slides 表里的三个模型:

模型词表上下文公式算出slides / 论文嵌入占比
GPT(post-norm,无最后的 LN)40,478512116.5M117M27.0%
GPT-2 XL50,25710241,557.6M1542M5.3%
GPT-350,2572048174,604.3M175B0.4%

GPT 和 GPT-3 和报告吻合。GPT-2 XL 比论文多约 1%;GPT-2 最小的版本(12 层、768 维)按公式是 124.4M,论文写的是 117M——这是 GPT-2 论文的计数和公开权重对不上的已知现象,以按公开结构数出来的为准。

两个规律:每层约 12d212d^2 个参数(dd 翻倍、参数翻 4 倍);模型越大,嵌入层的占比越小。(前置讲 P1 §5 的 param_count 是 Llama 式结构的版本:没有偏置、没有位置嵌入、前馈是 SwiGLU。)

4.8 训练:一次前向算出所有位置

第 1 讲的 RNN-LM 用”教师强制”训练:每一步的输入都是真实的上一个词。Transformer-LM 也一样,而且更进一步:一次前向就同时算出所有位置的预测。04 第 4 部分验证了两件事:

  1. 把整段序列一次喂进去,第 tt 个位置的输出,和”只喂前 tt 个 token”时最后一个位置的输出逐位相同(float64 下差 10−1610^{-16})。所以一次前向就得到了 TT 个”根据前缀预测下一个词”的问题,TT 个交叉熵一起算、一起反向。
  2. 改动第 7 个 token,前 6 个位置的 logits 完全不变(差恰好为 0),之后的都变了。pre-norm 和 post-norm 都一样。这个因果性是 GPT 能当语言模型用的前提——对比 §2.6 的双向 LSTM。

4.9 计算量:T2T^2 什么时候成为大头

每层每个序列的前向矩阵乘法 FLOPs:线性层(Q、K、V、O 和前馈)约 24 Td224\,T d^2;注意力分数 QK⊤QK^\top 和加权求和 AVAV 约 4 T2d4\,T^2 d。两者之比是 T/(6d)T / (6d),所以注意力的平方项只有在 TT 超过约 6d6d 时才成为大头。04 第 5 部分:

模型ddTT注意力占 FLOPs
本讲的小 GPT646414.3%
GPT-27681,02418.2%
GPT-312,2882,0482.7%
GPT-3,但 TT = 128K12,288131,07264.0%

GPT-3 的训练成本几乎全在线性层——这就是 前置讲 P3 §6.4 里 C≈6NDC \approx 6ND 不考虑注意力也够准的原因。上下文很长时情况反过来,第 18–19 讲会处理。

并行性。 在单线程 CPU 上,GPT 并不比 LSTM 快(CPU 上没有并行,耗时就看运算量)。04 第 5 部分改为比较 1 个线程和 4 个线程(⏱ 本机,T=512T = 512):GPT 快了 2.0 倍,Elman RNN 和 LSTM 不变或略慢(1.0×、0.8×)。GPT 的计算是几个大矩阵乘法,可以分给多个核心;RNN 是 TT 个串行的小矩阵乘法。GPU 有上万个核心(前置讲 P3 §2),这个差别会被放大成数量级的差距——这是 Transformer 取代 RNN 的主要工程原因之一。slides 也把它列为 Transformer 成功的关键:批处理、可并行的缩放点积注意力、各头独立计算、高效的矩阵乘法硬件。

4.10 代码导读:slides 的公式在代码里在哪

slides 的公式lm_models.py
Q=XWqQ = XW_q, K=XWkK = XW_k, V=XWvV = XW_vMultiHeadAttention.forward 里的 self.W_q(x) 等(nn.Linear 存的是 W⊤W^\top)
切成 hh 个头,dk=d/hd_k = d/hsplit_heads:view(B, T, h, d_k).transpose(1, 2)
S=QK⊤/dk+MS = QK^\top/\sqrt{d_k} + Mattention():q @ k.transpose(-2, -1) / math.sqrt(d_k) 加 causal_mask(T)
A=softmax(S)A = \mathrm{softmax}(S)(按行)F.softmax(scores, dim=-1)
X′=concat(X′(i))WoX' = \mathrm{concat}(X'^{(i)}) W_oout.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 在主谓一致上 k=0..6k = 0..6 全对,§6.2 会看到本讲的 LSTM 和 GPT 到 k=8k = 8 也基本全对。那个任务只需要记 1 个比特、最远 25 步,格式也非常规整——演示不出”遗忘”。05_long_range_recall.py 设计了一个更难、而且距离完全可控的任务:

键  干扰 干扰 … 干扰  ?    →   在 ? 处说出开头的键
└─┘ └──── d 个 ────┘

键有 8 种(3 比特),干扰符号是另外 8 种、随机出现。模型必须把键的身份原封不动地带过 dd 步,而中间的输入全是和答案无关的噪声。只有 ”?” 处计损失。随机猜的准确率是 1/8,损失是 ln⁡8=2.079\ln 8 = 2.079。三种模型都很小(Elman RNN 8,018 个参数,LSTM 26,834,2 层 Transformer 29,888),每个训练 1000 步、每步 64 个序列。我们还用预测和真实键的互信息衡量”记住了几个比特”(最多 3 比特)。

5.2 Elman RNN:训练数据的最大距离决定成败

训练数据d=1d=1d=20d=20d=60d=60d=100d=100d=100d=100 记住的比特
d∈[1,20]d \in [1, 20]1.0001.0001.0001.0003.00
d∈[1,40]d \in [1, 40]1.0001.0000.9980.9982.98
d∈[1,60]d \in [1, 60]0.6390.5060.4820.5181.76
d∈[1,100]d \in [1, 100]0.2810.2320.2580.2150.97

两个和直觉相反的现象:

  1. **训练数据最远只到 20 步的 RNN,在 d=100d = 100 上也全对。**它学到了一个能一直保持的”锁存器”(§2.3)。所以 Elman RNN 能表示跨越 100 步的记忆。
  2. 训练数据最远到 100 步的 RNN 反而失败了,而且连 d=1d = 1 都做不好。准确率随 dd 几乎是平的——这不是”距离越远忘得越多”,而是训练失败:长序列让梯度信号又弱又乱,模型只学到了一部分(约 1 比特)。

这正是 Bengio et al. (1994) 的困境在真实训练中的样子:能存住信息的结构恰恰让梯度消失,所以从长距离的例子里学出这个结构很难;短例子多的时候才学得会。

5.3 LSTM 与 Transformer

在 Elman RNN 失败的同一份数据(d∈[1,100]d \in [1, 100])上,LSTM(遗忘门偏置 1)和 Transformer 都在所有距离上全对(记住 3.00 比特)。

5.4 只有长例子时

如果训练数据里只有 d∈[50,100]d \in [50, 100] 的长例子呢?

模型d=1d=1d=40d=40 / d=60d=60d=100d=100最后损失
Elman RNN0.1460.133 / 0.1190.1232.094
LSTM(遗忘门偏置 1)0.1310.133 / 0.1190.1232.091
LSTM(遗忘门偏置 3)0.2010.133 / 0.1190.1232.090
Transformer1.0001.000 / 1.0001.0000.003

Elman RNN 和 LSTM 都学不会,损失停在 ln⁡8\ln 8 附近。把遗忘门偏置提高到 3(§2.5 里初始梯度能传回 100 步的设置)也没有用。Transformer 照样全对,连训练中没见过的 d<50d < 50 也对:它学到的是”找到键这一类 token”,和距离无关。

所以 LSTM 的长程记忆在很大程度上依赖”先从短例子学会、再推广到长例子”;注意力则从训练第一步起就给了任意两个位置之间一条长度为 1 的通路。Vaswani et al. (2017) 的表 1 把这一点概括为:自注意力的最大路径长度是 O(1)O(1),循环网络是 O(n)O(n)。

5.5 为什么:刚初始化时的梯度

05 第 4 部分测了刚初始化(未训练)时,”?” 处的损失对开头那个键的嵌入向量的梯度:

模型(未训练)d=10d=10d=50d=50d=100d=100d=100d=100 ÷ d=10d=10
Elman RNN2.6e-071.4e-190(float32 下溢)0
LSTM(偏置 1)1.4e-051.1e-081.7e-121.3e-07
LSTM(偏置 3)1.3e-048.0e-056.2e-050.46
Transformer4.0e-041.0e-045.7e-050.14
  • Elman RNN 和偏置 1 的 LSTM:梯度指数衰减,长例子几乎不提供学习信号。
  • Transformer:dd 从 10 到 100,梯度只缩小到 0.14,大约是 12/10212/102。刚初始化时注意力近似均匀,键只分到约 1/(d+2)1/(d+2) 的权重,所以梯度按 1/d1/d 变小——多项式衰减,不是指数衰减;而且一旦学会把注意力集中到键上,这个 1/d1/d 就消失了。
  • 偏置 3 的 LSTM 是个反例:它的梯度衰减比 Transformer 还慢,却同样学不会。所以”梯度能传回去”并不等于”能学会”。一个可能的解释(我们没有逐项验证):LSTM 要同时学会”写入键、保持遗忘门 ≈ 1、让之后的写入不被干扰符号带偏”几件事,在它们都到位之前,记忆单元里的键被几十次和键无关的写入淹没(类似 §2.3 积分器的噪声累积);Transformer 只要学会一件事——”?” 的查询和键类 token 的键向量点积要大。

5.6 训练好的模型内部

Transformer 在 ”?” 处的注意力(05 第 5 部分):第 1 层的两个头把注意力分散在干扰符号上;第 2 层的两个头都把 0.99 以上的注意力放在开头的键上——d=10d = 10、50、100 时分别是 0.999、0.997、0.995,而均匀分配时只有 1/(d+2)1/(d+2)。这个头学会了”直接去开头找键”,与距离无关。

LSTM(d∈[1,100]d \in [1, 100] 训练的那个):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 头、d=64d = 64、可学习位置嵌入,111,360 个参数,5 个 epoch)和一个 LSTM(30,520 个参数,5 个 epoch),并读取第 1 讲缓存的 RNN(11,640 个参数,10 个 epoch)。

6.1 困惑度

模型参数量⏱ 每个 epoch测试集困惑度比下界多的 nat/词
Elman RNN(第 1 讲)11,6400.7 秒2.53960.019
LSTM30,5201.1 秒2.53820.019
GPT111,3602.6 秒2.53050.016
真实分布(理论下界)——2.49130

三种神经网络语言模型都非常接近理论下界,GPT 略好一点。在这个只有约 20 个词长的玩具语言上,三种结构的容量都够用,差别不在困惑度上。

6.2 主谓一致到 k=8k = 8

干扰名词全部和主语单复数相反(最难的情况),训练集里最多只有 3 个干扰名词:

模型k=0k=0–33k=4k=4–77k=8k=8
Elman RNN1.0001.0001.000
LSTM1.0001.0001.000
GPT(种子 0)1.0001.0000.990

RNN 和 LSTM 一步一步处理,每一步做的事情和位置无关,“跳过一个介词短语”的规则可以无限次重复。GPT 在 k=8k = 8 时错了 2 句。k=8k = 8 时动词要在第 27 个位置上预测,而训练序列最长只有 19 个输入 token——第 20 个位置以后的位置嵌入从来没被训练过。§6.5 会看到,换一个随机种子,这个问题会明显得多。

6.3 GPT 预测动词时,注意力放在哪里

取 k=3k = 3 的句子,查询位置是最后一个干扰名词(模型在这里预测动词,第 12 个 token),统计每个头分给各组位置的注意力:

层头<s>主语前的 the主语名词介词介词后的 the前两个干扰名词自己
110.000.000.001.000.000.000.00
120.090.080.090.210.290.160.07
130.000.000.030.930.020.010.00
140.000.000.990.000.000.000.00
210.020.050.330.030.180.380.01
22–40.06–0.100.10–0.130.04–0.070.20–0.270.30–0.420.08–0.120.02–0.13

第 1 层第 4 个头把 0.99 的注意力放在主语名词上(均匀分配时是 1/12 ≈ 0.08):在要决定动词单复数的地方,模型直接”回头看”了 10 个词之前的主语,只用一跳,不像 RNN 那样要经过中间 10 个时间步的接力。另外两个第 1 层的头几乎只看介词;第 2 层的头注意力分散在多组位置上。

6.4 切断连线:注意力权重大,就说明”用到了”吗?

注意力权重大只是相关性。要检验模型是否真的依赖主语的信息,可以做干预:在两层的所有头里,禁止第 3 个位置(主语名词)之后的所有查询看主语——给注意力加一个附加掩码(MultiHeadAttention.extra_mask)。这样主语的信息就没有任何路径能流到动词的位置。

干预(k=3k = 3,干扰名词全部和主语相反)主谓一致准确率
不切断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 分钟):

位置编码种子测试困惑度k=4k=4k=5k=5k=6k=6k=7k=7k=8k=8
可学习02.53051.0001.0001.0001.0000.990
可学习12.52400.7050.8600.6950.3800.275
可学习22.52050.9450.9950.8550.6750.820
正弦02.53441.0000.9850.3800.3250.495
正弦12.52390.9501.0001.0000.9951.000
正弦22.53490.4400.5600.4400.5200.495
不加02.53601.0001.0001.0001.0001.000
不加12.53161.0001.0001.0001.0001.000
不加22.53631.0000.9950.9750.9750.985

(k≤3k \le 3 时所有模型都是 1.000。)

  • 训练见过的长度之内,三种做法没有区别:测试困惑度都在 2.521–2.536。
  • 超出训练长度后差别出现了,而且很依赖随机种子:可学习和正弦位置编码在 k≥4k \ge 4 时最低降到 0.275 和 0.325——低于乱猜的 0.5,说明它们开始跟着干扰名词走;同一种编码,不同种子之间差别也很大;不加位置编码的 GPT 反而最稳,3 个种子的最低值是 0.975。
  • **为什么不加位置编码也能工作?**因果掩码本身泄露了位置:第 tt 个位置只能看到 tt 个 token,比如对 <s> 的均匀注意力是 1/t1/t,模型可以由此推断”我在第几个位置”(Haviv et al., 2022;练习 9)。而”跳过介词短语、找主语”只需要相对的先后关系,不需要绝对位置;没训练过的绝对位置向量反而成了干扰。
  • 这是玩具设定下的结论,不要推广过头:真实语言需要更细的位置信息,现代模型普遍用第 4 讲的 RoPE;长度外推本身是第 19 讲的主题。这里想说明的是:(1) 注意力机制的”位置”完全来自我们怎样设计输入和掩码;(2) 超出训练分布的行为,必须用多个随机种子来测——只看种子 0,我们会以为可学习位置嵌入外推得很好。

7. 小结 · Summary

  1. 2017 年之前,语言模型主要在噪声信道模型里给语音识别和机器翻译打分;最早的”大”语言模型是 Google n-gram(1 万亿 token、约 38 亿条 n-gram)。今天的 LLM 用 101210^{12}–101310^{13} 个 token 训练,参数 101010^{10}–101110^{11} 以上,每个参数分到的 token 数在增加。
  2. 简单 RNN 的三难:收缩会遗忘、不收缩会积累噪声、能抗噪声存储的锁存器又让梯度消失。实验上,Elman RNN 能表示100 步的记忆(训练 d≤20d \le 20 时学会并外推),但从长例子里学不会。
  3. LSTM 用门控制一个加法式更新的记忆单元 ct=ft⊙ct−1+it⊙c~tc_t = f_t \odot c_{t-1} + i_t \odot \tilde c_t,梯度沿它传回 dd 步约为 ∏f\prod f。门本身不保证梯度不消失(遗忘门偏置 0 时和 Elman 差不多),但 LSTM 能学会让 f≈1f \approx 1。它的代价是 4 倍的参数和计算,以及无法并行。
  4. 注意力让每个位置直接对所有(之前的)位置做加权平均:softmax(QK⊤/dk+M)V\mathrm{softmax}(QK^\top/\sqrt{d_k} + M)V。查询和键的点积决定权重,值是被取走的内容;按行 softmax;除以 dk\sqrt{d_k} 防止 softmax 饱和;因果掩码用 −∞-\infty 屏蔽未来。任意两个位置之间的路径长度是 1。
  5. 注意力不知道顺序(置换等变),必须加入位置信息;位置编码可以是可学习的或正弦的、绝对的或相对的。正弦编码的点积只依赖距离。
  6. 多头把 dd 维切成 hh 个 d/hd/h 维的头,参数量不变,每个头学一种注意力模式。
  7. Transformer 层 = 多头注意力 + 前馈网络,每个子层都有残差连接和层归一化。残差让梯度有恒等通路(和 LSTM 的加法式记忆同理);LayerNorm 对每个 token 单独归一化。GPT = 嵌入 + 位置嵌入 + LL 个因果 Transformer 层 + 输出层,每层约 12d212d^2 个参数。
  8. 训练时一次前向得到所有位置的预测;因果掩码保证第 tt 个位置的输出不受未来影响。注意力的 T2T^2 项只在 T>6dT > 6d 时成为计算的大头;Transformer 的大矩阵乘法可以并行,这是它取代 RNN 的主要工程原因之一。
  9. 实验:只有长例子时,只有 Transformer 学会了”记住开头”;在农场语言上,GPT 有一个头直接看主语,切断通往主语的注意力连线后一致性从 1.000 降到 0.050;超出训练长度时,不加位置编码的 GPT 反而最稳——但这依赖于玩具任务的性质,也很依赖随机种子。

8. 常见误区 · Common pitfalls

  1. “LSTM 解决了梯度消失。” 更准确的说法是:LSTM 提供了一条可以不衰减的通路,衰减速度由遗忘门控制。刚初始化时遗忘门约 0.5,梯度照样指数衰减;只有长例子时,LSTM 和 Elman RNN 一样学不会(§5.4)。
  2. “RNN 记不住长距离,所以准确率随距离下降。” 在我们的实验里,失败的 RNN 准确率随距离几乎是平的;成功的 RNN 外推到了 5 倍的距离。问题出在学习而不是表示。
  3. “注意力权重大的位置就是模型用到的信息。” 权重是相关性,干预(切断连线、替换输入)才是因果证据(§6.4)。
  4. “softmax 按列做”或”掩码在 softmax 之后乘 0”。 按行做;在 softmax 之前加 −∞-\infty。
  5. “多头注意力比单头参数多。” dk=d/hd_k = d/h 时参数量完全相同。
  6. “没有位置编码,Transformer 就完全不知道顺序。” 不带掩码的注意力确实如此;因果注意力可以从掩码推断位置(§6.5)。
  7. “Transformer 的计算量主要在注意力。” 对 T<6dT < 6d 的常见设置,主要在线性层(GPT-3 只有 2.7% 在注意力)。长上下文时才反过来。
  8. “双向 LSTM 更强,所以语言模型也该用双向的。” 双向模型在第 tt 步看到了未来,用它预测下一个词是作弊(§2.6)。
  9. “pre-norm 和 post-norm 只是写法不同。” 它们的梯度通路不同:pre-norm 的残差通路上没有 LN,深层时更容易训练;post-norm 通常需要学习率预热。
  10. “一个种子上的结果就是结论。” 超出训练分布的行为对随机种子非常敏感(§6.5 里可学习位置嵌入在 k=8k=8 时从 0.990 到 0.275 不等)。

9. 练习 · Exercises

练习 1(课堂练习的变体)。 沿用 §3.4 的 x1,…,x4x_1, \dots, x_4 和 Wv=IW_v = I,但这次给出的是分数 s4=[0,1,2,0]s_4 = [0, 1, 2, 0] 而不是权重。(a) 求 a4a_4 和 x4′x_4'。(b) 如果分数整体乘以 4(相当于忘了除以 dk\sqrt{d_k}、dk=16d_k = 16),a4a_4 变成什么?

参考答案

(a) a4=softmax([0,1,2,0])=[1,e,e2,1]/(2+e+e2)=[0.0826,0.2245,0.6103,0.0826]a_4 = \mathrm{softmax}([0,1,2,0]) = [1, e, e^2, 1]/(2 + e + e^2) = [0.0826, 0.2245, 0.6103, 0.0826],x4′=[0.0826,0.2245,1.2206,0.0826]x_4' = [0.0826, 0.2245, 1.2206, 0.0826](第 3 维是 2×0.61032 \times 0.6103)。 (b) softmax([0,4,8,0])=[0.0003,0.0180,0.9814,0.0003]\mathrm{softmax}([0,4,8,0]) = [0.0003, 0.0180, 0.9814, 0.0003]:几乎全部权重给了 x3x_3,x4′≈[0.0003,0.018,1.9627,0.0003]x_4' \approx [0.0003, 0.018, 1.9627, 0.0003]。分数放大 4 倍,softmax 就从”偏向 x3x_3“变成了”只看 x3x_3“——这就是 §3.5 说的饱和。

练习 2(LSTM 的参数量)。 输入维度 E=100E = 100、隐藏维度 H=200H = 200。分别求 nn.RNN(100, 200) 和 nn.LSTM(100, 200) 的参数个数(PyTorch 的两者都有两份偏置)。

参考答案

nn.RNN:H⋅E+H⋅H+2H=20,000+40,000+400=60,400H \cdot E + H \cdot H + 2H = 20{,}000 + 40{,}000 + 400 = 60{,}400。 nn.LSTM:4×(H⋅E+H⋅H+2H)=4×60,400=241,6004 \times (H \cdot E + H \cdot H + 2H) = 4 \times 60{,}400 = 241{,}600。恰好 4 倍:四组门各有一套和 Elman RNN 一样大的参数。

练习 3(遗忘门与记忆的半衰期)。 一个记忆单元写入 c=1c = 1 之后,输入门一直为 0、遗忘门恒为 ff。(a) f=0.9f = 0.9 时,50 步后 cc 还剩多少?(b) 要让 100 步后至少还剩 0.5,ff 至少要多大?如果 f=σ(bf)f = \sigma(b_f)(只靠偏置),bfb_f 至少多大?

参考答案

(a) 0.950=0.005150.9^{50} = 0.00515。 (b) f100≥0.5⇒f≥0.51/100=0.99309f^{100} \ge 0.5 \Rightarrow f \ge 0.5^{1/100} = 0.99309;bf=ln⁡f1−f=ln⁡0.993090.00691≈4.97b_f = \ln\frac{f}{1-f} = \ln\frac{0.99309}{0.00691} \approx 4.97。所以”记住 100 步”需要遗忘门非常接近 1——这只需要一个偏置约为 5,但模型必须学会把它设到那里。

练习 4(为什么是 dk\sqrt{d_k},而不是 dkd_k)。 q,kq, k 的分量独立、服从 N(0,1)N(0, 1),dk=64d_k = 64,每个查询对 32 个键做 softmax。分别求分数 q⊤k⋅cq^\top k \cdot c 的标准差,c=1c = 1、1/641/\sqrt{64}、1/641/64。哪一种会让注意力过于集中、哪一种过于均匀?

参考答案

q⊤kq^\top k 的方差是 dk=64d_k = 64,标准差 8。乘以 cc 后标准差为 8c8c:c=1c = 1 时 8,c=1/8c = 1/8 时 1,c=1/64c = 1/64 时 0.125。数值实验(20000 个查询):c=1c = 1 时最大权重平均 0.815、熵 0.50(过于集中);c=1/dkc = 1/\sqrt{d_k} 时最大权重 0.164、熵 3.01;c=1/dkc = 1/d_k 时最大权重 0.040、熵 3.458,几乎等于均匀分布的 ln⁡32=3.466\ln 32 = 3.466(过于均匀,注意力”看不出”区别)。1/dk1/\sqrt{d_k} 恰好让分数的标准差为 1。

练习 5(层归一化)。 不带 γ,β\gamma, \beta(γ=1,β=0\gamma = 1, \beta = 0)、ϵ=0\epsilon = 0,求 LN([1,2,3,4])\mathrm{LN}([1, 2, 3, 4])。再求 LN([10,20,30,40])\mathrm{LN}([10, 20, 30, 40]) 和 LN([101,102,103,104])\mathrm{LN}([101, 102, 103, 104])。

参考答案

μ=2.5\mu = 2.5,σ=(2.25+0.25+0.25+2.25)/4=1.25=1.1180\sigma = \sqrt{(2.25 + 0.25 + 0.25 + 2.25)/4} = \sqrt{1.25} = 1.1180,输出 [−1.342,−0.447,0.447,1.342][-1.342, -0.447, 0.447, 1.342]。另外两个输入得到完全相同的结果:LN 对整体缩放和平移不变。

练习 6(数 GPT-2 Medium 的参数)。 GPT-2 Medium:24 层、d=1024d = 1024、词表 50,257、上下文 1024,pre-norm(有最后的 LN),输出层和词嵌入共享。用 §4.7 的公式求参数量。

参考答案

每层 12d2+13d=12,582,912+13,312=12,596,22412d^2 + 13d = 12{,}582{,}912 + 13{,}312 = 12{,}596{,}224,24 层共 302,309,376;词嵌入 50,257×1024=51,463,16850{,}257 \times 1024 = 51{,}463{,}168;位置嵌入 1024×1024=1,048,5761024 \times 1024 = 1{,}048{,}576;最后的 LN 2,048。合计 354,823,168 ≈ 355M,和公开的 GPT-2 Medium(常写作 355M)一致。

练习 7(注意力的计算与内存)。 (a) 一个 d=4096d = 4096 的模型处理 T=8192T = 8192 的序列,注意力(4T2d4T^2 d)占每层前向矩阵乘法 FLOPs 的多少?(b) 批大小 8、8 个头、T=128T = 128 时,一层的分数矩阵 SS 有多少个元素?用 float32 存要多少内存?T=2048T = 2048 呢?

参考答案

(a) 占比 =4T2d/(24Td2+4T2d)=T/(6d+T)=8192/(24576+8192)=25%= 4T^2 d / (24 T d^2 + 4 T^2 d) = T/(6d + T) = 8192/(24576 + 8192) = 25\%。 (b) 8×8×128×128=1,048,5768 \times 8 \times 128 \times 128 = 1{,}048{,}576 个元素,float32 共 4 MiB。T=2048T = 2048 时乘以 162=25616^2 = 256,是 1 GiB——每一层都要。训练时还要为反向传播存下来。这就是第 18 讲 FlashAttention 要避免”把整个 SS 写进显存”的原因。

练习 8(因果掩码浪费了多少)。 T=2048T = 2048 时,T×TT \times T 的分数矩阵里有多少个位置是”允许看”的(j≤tj \le t)?占多大比例?

参考答案

T(T+1)/2=2,098,176T(T+1)/2 = 2{,}098{,}176 个,占 4,194,3044{,}194{,}304 的 50.02%。先算完整的 QK⊤QK^\top 再加掩码,意味着将近一半的分数算出来就被丢掉了。

练习 9(置换等变与”掩码里的位置”)。 (a) 证明:不带掩码、不带位置编码的自注意力满足 f(PX)=Pf(X)f(PX) = P f(X),其中 PP 是任意置换矩阵(打乱输入的行,输出的行也同样被打乱)。(b) 现在加上因果掩码、仍然不加位置编码。假设某个头的查询和所有键的点积都相同(均匀注意力),值向量只有第 1 个位置(<s>)是 1、其余为 0。求第 t=1,…,5t = 1, \dots, 5 个位置的输出。这说明了什么?

参考答案

(a) Q=XWqQ = XW_q 变成 PXWq=PQPXW_q = PQ,同理 K→PKK \to PK、V→PVV \to PV。S→PQK⊤P⊤/dk=PSP⊤S \to PQK^\top P^\top / \sqrt{d_k} = P S P^\top。按行 softmax 和”同时置换行和列”可交换:softmax(PSP⊤)=P softmax(S) P⊤\mathrm{softmax}(PSP^\top) = P\,\mathrm{softmax}(S)\,P^\top。所以输出 →PAP⊤PV=PAV\to P A P^\top P V = P A V(P⊤P=IP^\top P = I),即 f(PX)=Pf(X)f(PX) = Pf(X)。 (b) 第 tt 个位置均匀地看前 tt 个位置,输出 =1/t= 1/t:1, 0.5, 0.333, 0.25, 0.2。不同的位置得到不同的输出,所以即使没有位置编码,因果 Transformer 也能”数出”自己在第几个位置——这是 §6.5 里 NoPE 能工作的一个机制。

练习 10(残差的恒等通路)。 (a) 一个 64 层的普通网络,每层的雅可比矩阵把梯度范数缩小到 0.5 倍,传回底层还剩多少?(b) 换成残差网络 hl+1=hl+f(hl)h_{l+1} = h_l + f(h_l),假设每层 ∂f/∂h\partial f/\partial h 很小、使得每层的放大倍数约为 1.011.01,64 层后是多少?(c) 用这两个数解释 §4.4 表格里”初始梯度”两列的差别。

参考答案

(a) 0.564=5.4×10−200.5^{64} = 5.4 \times 10^{-20}。(b) 1.0164=1.891.01^{64} = 1.89。(c) 普通网络的梯度是 64 个小于 1 的因子连乘,指数消失(表中 64 层是 2.9×10−152.9 \times 10^{-15});残差网络每层都有一个"II",连乘的是 I+∂f/∂hI + \partial f/\partial h,不会消失,反而随深度缓慢增长(表中从 98 增长到 450)。后者的增长是 pre-norm 残差网络的已知现象:残差流里的激活随层数累加;GPT 的初始化把每个残差分支的最后一层再乘 1/2L1/\sqrt{2L}(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 讲回答:

  • 不等长的句子怎样组成一个批?填充、截断、块大小和损失掩码;
  • 生成第 tt 个 token 时,前面 t−1t-1 个位置的键和值为什么不必重算——KV 缓存(它正是本讲 GPT 接口里那个空着的 state);
  • 词表从哪里来:字符、词、子词,以及 BPE 算法;
  • 从分布里”挑”出一段文字:贪心、束搜索、温度、top-k、top-p,各自怎样失败;
  • GPT-1 的”生成式预训练 + 判别式微调”。

第 3 讲会直接读取本讲 06 缓存的农场语言 GPT(checkpoints/farm_gpt.pt)。

← 返回课程目录