第二章 GPT-2
第一章把 LLM 收成五个直觉。这一章对着 GPT-2 把积木拆开:字怎么切成编号、编号怎么做成训练对、注意力怎么互相看、前馈怎么自己想、训练循环四步怎么拧参数。最后:别人已经走完下山,你可以把现成的权重装进去。
- 一tiktoken 把文本切成编号。token 不是字,也不是单词。
- 二Dataset 用滑动窗口切出(输入,目标);DataLoader 一次取出一个 batch。
- 三注意力:Q 提问、K 名牌、V 口袋。因果掩码不许偷看未来。
- 四前馈:每个位置自己消化。开会时先听,再想。
- 五训练一步 = 前向 → 损失 → backward → 更新。
书 §2.1–2.3 · p.21–43 · 代码在 gpt_model.py、gpt_train.py
2.1.1 分词:tiktoken 在干什么
是什么。模型不吃汉字,也不吃英文字母。它吃整数。分词器(tokenizer)负责两件事:文本 → 编号列表(encode),编号列表 → 文本(decode)。GPT-2 用的库叫 tiktoken,词表大约 50257 项。
为什么要切。如果每个汉字、每个英文单词都独占一项,词表会爆炸,罕见词还没有编号。BPE(Byte Pair Encoding)的办法:先按字节/字符切开,再把经常一起出现的两段反复合并。常见词合成一块,罕见词拆开拼。空格在 GPT-2 里常常粘在下一个英文词前面,所以「␣I」是一个 token,不是「空格 + I」。
书上图 2.1 就是这一步:一段话变成一串 id。下面的演示是教学用近似,用来建立直觉,不是完整的 tiktoken 词表。
书 §2.1.1 · 图 2.1 · p.21–22
输入一句话,看它被切开
改文字或点例句。每个芯片是一个 token,右上角是教学用的假 id。
真正的 tiktoken.get_encoding("gpt2") 按 UTF-8 字节做 BPE。中文一个字常常不止一个 token。本章中文例句仍按字切开,只为看清后面的窗口,不假装自己是官方词表。
合并是怎么发生的
BPE 训练时:数一数哪两个相邻片段最常一起出现,把它们合成一个新符号,再数、再合。推理时按合并表从长到短匹配。点「下一步合并」,看 lower 怎么从五个字母收成一个词。
BPE 合并:lower
经典小例子。真实词表有几万条合并规则,道理一样。
接下节。编号有了。怎么切成模型要吃的(输入,目标)对?滑动窗口 + DataLoader。
2.1.1 Dataset 和 DataLoader
是什么。第一章表 1.1 已经切过一对一对。代码里这件事由 GPTDatasetV1 做:先 tokenizer.encode 整段文本,再用滑动窗口切成重叠的短序列。每一条样本是两串同样长的 id:input 和向右错开一位的 target。
为什么要重叠(stride)。max_length 是窗口里能看见的 token 数,对应模型的上下文长度。stride 是每次窗口往右跳几步。stride 小于 max_length,窗口会重叠,同一段话能切出更多样本,上下文的衔接也更密。DataLoader 再按 batch_size 把几条样本叠成一摞,送给 GPU。
人话:Dataset 是一本切好的练习册;DataLoader 是每次从练习册抽几页订成一本小册子递到桌上。
书 §2.1.1 · 图 2.2 · p.22–23
滑动窗口切练习册
文本仍用「君不见黄河之水天上来」,按字编号 0–9。拖窗口长度和步长,看切出几条、会不会重叠。
代码里:input_chunk = token_ids[i : i+max_length],target_chunk = token_ids[i+1 : i+max_length+1]。和第一章图 1.2 是同一件事,只是窗口可以重叠,并且一次取出一个 batch。
接下节。编号进模型之前先变成向量。真正让两个相同的「我」分道扬镳的,是注意力。
2.1.2 注意力:Q、K、V 和因果掩码
是什么。词嵌入只取决于 token id,位置嵌入只取决于下标。书上举过:「我是小明」和「我是莎士比亚」里,两个「我」的嵌入加位置是一样的。要让它们后来不一样,得靠后面的字来改前面的表示。这件事叫自注意力。
为什么是 Q、K、V。把每个位置想成一个人。
- Q(query):我现在在找什么。提问。
- K(key):我胸前的名牌。别人拿问题来对名牌。
- V(value):我口袋里真正要分享的内容。
对名牌(Q 点乘 K),得到「该听谁多少」;再用这些权重去口袋里拿东西(加权 V)。多头 = 同时开好几组目光,有的看邻字,有的看远距搭配。公式在书 2.1.2.5,这里先把比喻玩会。
书 §2.1.2.2–2.1.2.5 · p.26–32
点一个字,看它在问谁
先点 Q / K / V 看比喻,再点句子里的一个字当提问者。条形是它分给每个人的注意力(教学用小数,不是真实 GPT-2)。
句子:岸边有银行
「行」提问时,票会更多地投给「银」——把「银行」合成一个意思。这就是书里 bank / river 例子的中文版:注意力先找到该听谁,前馈再决定把它理解成什么。
两个「我」怎样被分开
嵌入阶段,两句里的「我」完全相同。点「看一眼后面」,让「我」去注意句尾那个名字。表示就被后面的字拉开了。
我是小明 / 我是莎士比亚
同一套嵌入。差别来自注意力看见了不同的后面。
书 p.26:相同 token id、相同位置 → 嵌入一样。语义差在 Transformer 模块里被注意力拉开。
因果掩码:不许偷看还没写的字
Decoder-Only 的规矩:写到第 k 个位置,只能看 0 到 k,不能看 k 以后。实现上,把上三角的注意力分数填成 −∞,softmax 之后那些位置的权重就是 0。关掉掩码,模型就会在考试时带着答案进场。
5 × 5 的目光
行是提问者,列是被看的人。点一行。斜纹格子是未来,被掩掉。
代码:attn_scores.masked_fill_(mask_bool, -inf),然后 softmax(scores / sqrt(head_dim))。除以根号是为了点积不要太大,softmax 才分得开。
接下节。听完别人,每个位置还要自己想一想。那是前馈网络。
2.1.2 前馈网络:听完之后自己消化
是什么。Transformer Block 里第二件大事。注意力让 token 之间通信;FFN 对每个位置单独做一次非线性变换。同一套参数,互不交谈。
为什么需要它。没有 FFN,模型更像在反复混合上下文,缺少「想一想」的非线性。书里的比喻:Attention 是开会时听取大家意见,FFN 是自己消化一下。只听不想,会议纪要热闹,决策不一定靠谱。
常见结构:把维度从 dmodel(768)升到大约 4 倍(3072),过 GELU,再压回 768。升维是为了在更大的空间里修细节,不是为了变大。FFN 的参数量大约是注意力的两倍,所以它往往也承担很多「模式记忆」。
书 §2.1.2.6 · 式 (2.2)–(2.6) · p.33–34
一个 token 走过 FFN
点「加工一次」。左边是注意力刚聚合完的「行」,右边被推到「银行机构」那一侧。
例句切换
书上英文例子:The bank is near the river vs I deposited money in the bank。注意力找到该听谁,FFN 把表示推进「河岸」或「金融机构」。这是简化,但直觉有用。
每个 Block 的顺序,对照图 1.3 和代码:先 LN → 注意力 → Dropout → 加残差;再 LN → FFN → Dropout → 加残差。12 层重复。形状始终是 (batch, 长度, 768),所以才叠得起来。
接下节。积木会算了。训练时这套积木怎么被拧?四行 Python。
2.2 训练循环:前向 → 损失 → backward → 更新
是什么。书里 train_model_simple 的核心只有四行:把上一批梯度清掉,前向算出损失,反向求出每个参数的梯度,优化器按梯度改参数。周而复始。
为什么是这个顺序。不清梯度,两批的导数会叠在一起。不 backward,就不知道往哪拧。不 step,数字原地不动。AdamW 是第一章点过的那种「带记忆的下山」;这里只要看见它被调用。
loss = calc_loss_batch(input_batch, target_batch, model, device)
loss.backward()
optimizer.step() 书 §2.2 · p.38。损失仍是第一章的交叉熵:logits 拉平后对 target 算 −log。
书 §2.2 · p.36–41
跑一步训练
点「跑一步」,四块依次亮。损失会掉一点,旋钮会拧一点。这是示意,不是真的在训 GPT-2。
- optimizer.zero_grad()
- loss = calc_loss_batch(...)
- loss.backward()
- optimizer.step()
完整脚本还会定期在验证集上算损失、隔一阵用 generate_text_simple 看补全像不像话,最后 torch.save(model.state_dict(), "model.pth")。那是工程包装,核心仍是这四行。
接下节。从零训 GPT-2 很贵。OpenAI 把预训练好的权重公开了,可以直接装上。
2.3 加载预训练权重
是什么。预训练 = 别人已经用海量文本走完第一章的下山。你下载一份 .pth,新建一个空的 GPTModel,load_state_dict 把数字填进去,再 eval()。模型立刻会补全句子,不用自己从随机参数开始。
为什么这一节短。结构在 2.1 已经定义好了。权重只是那些 nn.Embedding、Linear、LayerNorm 的 γβ 里填上训好的数。同一套房子,换一套家具。
书里列出四档 GPT-2。点一档看层数、头数、嵌入维度。small 就是第一章图 1.3 那张:12 层 × 12 头 × 768 维。
书 §2.3 · p.41–43
四档现成的 GPT-2
点一档。参数量是约数。加载之后,生成还可以拧温度和 top-k——那是采样旋钮,不是训练。
| 项 | 这一档 |
|---|
加载三步:按配置建模型 → load_state_dict(torch.load(file)) → eval()。书上生成函数还加了温度缩放和 top-k:温度高更胡写,top-k 只从分数最高的 k 个里抽。先记住「可以装现成的」,细节以后用到再翻。
合上。从 token 编号到现成权重,GPT-2 这一章的地图走完了。强化学习从第 47 页另起一部分。
合上这一章
对照刚才玩过的控件:
- tiktoken。文本变成编号。BPE 把常一起出现的片段合并。教学演示不是完整词表。
- Dataset / DataLoader。滑动窗口切出(输入,错开一位的目标);stride 控制重叠;batch 一次叠几条。
- 注意力。Q 提问、K 名牌、V 口袋。两个「我」靠看后面的名字才分开。因果掩码遮住未来。
- FFN。每个位置自己消化:升维 → GELU → 压回。听完再想。
- 训练四行。zero_grad → 前向算交叉熵 → backward → AdamW.step。
- 预训练权重。同一套结构,填上别人下山走完的数字。
下一章起是书的第二部分:强化学习。智能体、环境、奖励,换一套语言讲「怎么学」。
章末自测
讲完了。下面几题核对本讲刚说过的概念。点选立刻看对错;做错也不会锁下一章。