📍 路标 :本篇位于《从零构建 Transformer》系列 第 13/16 章 · 预训练篇(收官) 。
系列主线:注意力思想 → 自注意力 → 多头 → 位置编码 → 编解码架构 → 手撕实现 → 预训练模型 → 实战微调 → 知识收官 。
进度:▸ 基石篇(1-5) ▸ 架构篇(6-10) ▸ 预训练篇(11-13·本篇) ▸ 实战篇(14-16)
摘要:GPT 是"会写文章"的那位:它用自回归语言模型(预测下一个词)把 Decoder 训练成能逐词生成文本的机器。本章回答:为什么生成必须看左边?下一个词预测怎么训练?生成时怎么逐词拼回?temperature 与 top-k 怎么控制随机性?全章手写:因果掩码验证、自回归生成循环、温度对分布的影响、top-k 截断,以及"小 GPT"从零训练到自动生成循环短句(loss 2.68→0.47、40 步全对)。全文 10 张图居中,示例均有真实运行输出。
上一篇(四十一):BERT:双向编码器与掩码语言模型
引言:上一章我们认识了"懂",这一章认识"写"
第 12 章我们手撕了 BERT------一台"读懂语言"的机器:双向、会猜词、擅长分类与理解,但不会写 。这一章的主角是 BERT 的镜像:GPT,一台"会写文章"的机器。你现在在用的各种 AI 助手、ChatGPT、Copilot,底层都是它的后代。理解 GPT,就是理解"生成式 AI"的心脏。
GPT 的路线与 BERT 截然相反:它不猜被遮住的词,而是老老实实地预测下一个词;它不看右边,只盯着左边已经写出的部分。看起来更"笨",却意外地能写出通顺的文章------因为"写文章"这件事,本质就是"一个词接一个词地往下憋",而"预测下一个词"恰恰是这个过程的数学化。
这一章要回答四个问题,全部用手写代码 + 真实输出验证:
- 为什么生成必须"看左边"? 因为生成是时间单向的------第 t 个词写完之前,第 t+1 个词还不存在(demo1 用因果掩码验证"每个位置只能看自己及左边");
- "预测下一个词"怎么训练? 把句子右移一位,让模型在所有位置同时预测下一位(teacher forcing),用第 10 章的交叉熵当老师(图 3);
- 生成时怎么"逐词拼回"? 没有答案,只能一个词一个词地憋:预测 → 接到末尾 → 再预测(demo2 用最简单的统计模型演示这个循环);
- temperature 和 top-k 是什么? 让生成在"确定"和"随机"之间找平衡的两个旋钮(demo3、demo4)。
如果你是顺着本系列读下来的,这一章会有极强的"复用感":因果掩码是第 8 章的旧零件、右移对齐是第 8 章解码器的旧动作、交叉熵是第 10 章的老朋友、小模型训练是第 12 章的同款流程。GPT 真正"新"的东西只有两件:一是把架构砍到只剩 Decoder(Decoder-only),二是把"猜词"换成"预测下一个词"(自回归目标)。理解到这一层,你就明白为什么有人调侃"GPT 论文读起来像一篇工程报告"------它的创新不在结构,而在"敢把最简单的想法做到极致"。
最后,第 12 章那套"小模型训练"的流程再次登场:一个 640 参数的小 GPT,用数值梯度训练 40 步,学会循环语言后自己生成了 "a b c d a b c d"------亲眼看到"机器学会说话"的那一刻(demo5)。
顺带预告本章的"家族观":GPT 不是一个模型,而是一整个家族(GPT-1/2/3/4)。它们共享同一个架构模板(本章的"小 GPT"就是最小版),差别只在规模 (参数从 640 到 1750 亿)和数据 (从 4 条样本到 45TB 全网)。理解这一点后,你会明白为什么深度学习圈常说"GPT 的厉害之处不在架构创新,而在于把已知架构推到极致"------本章学的是那个"已知架构",值钱的思路是"规模放大"。
🎯 本章目标
- 说清"生成为什么必须单向",复用第 8 章因果掩码验证 GPT 的"只看左边"(demo1);
- 理解自回归与"下一个词预测"任务,看懂 teacher forcing 的右移对齐(图 3);
- 手写自回归生成循环:预测 → 接回 → 再预测,直到结束标记(demo2);
- 手写 temperature 采样,验证"温度越低越尖锐、越高越均匀"(demo3);
- 手写 top-k 采样,验证"低概率词被截断后重归一化"(demo4);
- 训练一个小 GPT:40 步 loss 2.68→0.47、预测全对,并让它自回归生成短句(demo5);
- 看懂 GPT 家族演进(GPT-1/2/3)与 GPT vs BERT 的对比(图 9、图 10)。
一、从"懂"到"写":生成需要什么?
1.1 一句话定义 GPT
GPT(Generative Pre-trained Transformer,生成式预训练 Transformer)是一个只保留 Decoder 的 Transformer :输入一串词,输出"下一个词的概率分布"。就这么简单。它没有 Encoder(不需要"编码记忆")、没有交叉注意力(不"查"任何外部信息),只有一个被压扁成自回归的 Decoder------所以叫 Decoder-only。
1.3 Decoder-only 与 Encoder-Decoder 的区别 。第 8 章的完整 Transformer 是 Encoder-Decoder(编码器读源句、解码器生成目标句,中间有交叉注意力);GPT 把编码器和交叉注意力全部去掉,只留解码器的"自回归外壳"(掩码自注意力 + FFN)。为什么敢这样"砍"?因为 GPT 的任务里没有"源句"------它只做一件事:给定"已经写出的",续写"下一个"。交叉注意力是"查外部记忆"的通道,而 GPT 不需要外部记忆,它的记忆就是"已经写出的全部"(都在自注意力的可见范围里)。Encoder-Decoder 适合"翻译"这类有两端输入的任务,Decoder-only 适合"续写"这类单流任务------GPT 证明了后者在大规模下足够强大,甚至能覆盖前者的很多场景(把翻译也当"续写"来训)。
1.2 为什么生成必须"看左边"?
第 8 章我们学过:解码器生成第 t 个词时,不能偷看第 t+1 个词(那是未来)。GPT 把这条原则贯彻到整个模型:每一个位置都只能看到自己及左边的词。这不是设计者的保守,而是"生成"这件事的物理约束------你写文章时,第 3 个词写出来之前,第 4、5 个词根本不存在,你不可能"参考"不存在的东西。数学上,自回归模型把一句话的概率分解成:
P ( w 1 , w 2 , . . . , w S ) = P ( w 1 ) ⋅ P ( w 2 ∣ w 1 ) ⋅ P ( w 3 ∣ w 1 w 2 ) ⋯ P ( w S ∣ w 1 . . . w S − 1 ) P(w_1, w_2, ..., w_S) = P(w_1)\cdot P(w_2 | w_1)\cdot P(w_3 | w_1 w_2)\cdots P(w_S | w_1...w_{S-1}) P(w1,w2,...,wS)=P(w1)⋅P(w2∣w1)⋅P(w3∣w1w2)⋯P(wS∣w1...wS−1)
每一项都是"给定已经写出的全部,预测下一个词"------链条的每一步都只看左边。这个分解式值得多看两秒:整句话的概率被拆成了"一步步的条件概率之积" ,这正是"自回归"(auto-regressive,自我回归)的名字来源------每一步的概率"回归"到(依赖于)前面所有步的输出。把概率拆开是训练的关键:模型不需要一次性"写出整句话",只需要学会"每一步给出下一个词的条件概率",整句的概率自然由连乘得到。训练时我们最大化这个连乘(等价于最小化负对数似然,第 10 章的交叉熵),推理时按每一步的条件概率采样。"拆成一步步"这个动作,同时成就了训练(teacher forcing 并行)和推理(逐词生成)。demo1 用真实的因果掩码验证了这一点:
python
S = 5
causal = np.triu(np.full((S, S), -1e9), k=1) # 第8章的上三角掩码
Q = np.random.RandomState(0).randn(S, 4) * 0.5
w = softmax_rows((Q @ Q.T) / np.sqrt(4) + causal)
真实输出:
【demo1】因果掩码:GPT 的注意力只能看左边
因果注意力权重矩阵(每行和=1,右上角全是 0):
[[1. 0. 0. 0. 0. ]
[0.4417 0.5583 0. 0. 0. ]
[0.4037 0.243 0.3533 0. 0. ]
[0.2875 0.2569 0.2224 0.2333 0. ]
[0.1846 0.2515 0.1371 0.1851 0.2417]]
第 3 行(pos3)对未来位置 4 的权重: [0.0]
第 3 行(pos3)对过去位置 0~3 的权重和: 1.0
→ 每一行都严格是"下三角":当前位置只能看自己及左边(第8章 lookahead mask 的复用)
这张 5×5 的权重矩阵就是 GPT 注意力的"世界观":第 i 行的权重全部落在前 i+1 列(自己及左边),右上角清一色的 0------第 3 行对未来位置 4 的权重精确为 0.0,对过去位置的权重和为 1.0。与 BERT 的全连接注意力一对比,一目了然:


🎤 一句话总结 :BERT 的双向靠"猜被遮住的词"训练,GPT 的单向靠"预测下一个词"训练------任务定义了看得到什么,架构忠实执行。因果掩码(第 8 章的 lookahead mask)就是 GPT 的"世界观"。
二、训练任务:下一个词预测
2.1 右移一位:把"预测下一位"变成"每个位置一个标签"
训练时我们有完整句子(比如 "我 爱 猫"),怎么把它变成"预测下一个词"的训练样本?答案是右移一位 :输入 [<s> 我 爱 猫],标签 [我 爱 猫 <e>]------位置 0 的目标是位置 1 的词、位置 1 的目标是位置 2 的词......这样每个位置都有一个"下一词"标签,可以所有位置同时计算交叉熵(teacher forcing,第 8 章学过:训练时老师直接给答案,并行且快)。
注意"右移"这步和第 8 章解码器的输入构造一模一样 ------第 8 章我们手动往目标句前面补 <BOS>(开始标记)得到解码器输入,这里补的是 <s>;第 8 章的目标句末尾是 <EOS>(结束标记),这里补 <e>。GPT 就是"把解码器从机器翻译里拆出来单独用" :不要编码器、不要交叉注意力,只剩"输入右移 + 预测下一个词"。如果你对第 8 章"训练能并行、推理必须串行"还有印象,这里就是它的单机版。而 <s>/<e> 正是第 11 章讲过的"协议 token"家族成员------它们不表达词义,只表达"开始"和"结束"这两个协议动作。

2.2 和 BERT 的 MLM 有什么不同?
| 维度 | BERT(MLM) | GPT(下一个词) |
|---|---|---|
| 任务 | 猜被遮住的词 | 预测下一个词 |
| 可见范围 | 左右全部 | 只有左边 |
| 训练标签 | 被遮位置的原词 | 右移一位的下一词 |
| 能力 | 理解 | 生成 |
一张表看清:GPT 的训练目标就是"预测下一个词",这既是训练任务、又是推理任务------训练时用真实句子监督,推理时用自己的输出接下去。任务和架构完全同构,这是 GPT 最优雅的地方。
2.3 为什么不用 MLM 训练生成模型? 有人会问:BERT 的 MLM 效果那么好,为什么生成模型不用它?答案在"生成"本身:MLM 猜被遮住的词需要"看到左右两边",而生成时右边的词根本不存在 ------训练用双向、推理用单向,行为不一致,生成的词会崩坏。这就是为什么 GPT 必须用"只看左边"的下一个词预测:训练和推理的"视野"严格一致(都只看左边),模型学到的东西在生成时 100% 用得上。BERT 与 GPT 的差异,归根结底是"理解任务可以看全句,生成任务只能看过去"这一物理约束。同样的逻辑也解释了为什么 T5 这类模型要在 Encoder(双向)和 Decoder(单向)之间做严格分工。
三、推理:自回归生成循环
训练完,模型手里只有"给定一串词 → 下一个词的概率分布"。推理(生成)就是反复执行这个步骤:
- 喂当前序列 (开始时只有
<s>或几个提示词); - 前向传播,取最后一个位置输出的词表概率分布;
- 采样选一个词(argmax / temperature / top-k,下一节讲);
- 把选中的词接到序列末尾 ;若是结束标记
<e>则停止; - 回到第 1 步------自己的输出成为自己的输入,这就是"自回归"(auto-regressive)。

demo2 用一个最简单的 bigram 统计模型(统计"词 A 后面通常跟词 B"的频率)把这个循环跑一遍------它没有神经网络,但生成循环的逻辑分毫不差:
python
corpus = ['the cat sat', 'the dog ran', 'the cat sleeps', 'a bird flies']
bigram = {}
for s in corpus:
ws = s.split() + ['<e>'] # 句尾接结束标记
for j in range(len(ws) - 1):
bigram.setdefault(ws[j], {}).setdefault(ws[j + 1], 0)
bigram[ws[j]][ws[j + 1]] += 1
def bigram_dist(w):
d = bigram.get(w, {})
if not d:
return ['<e>'], np.array([1.0]) # 句尾词 → 只能结束
...
真实输出:
【demo2】自回归生成循环:用 bigram 统计模型逐步生成(演示"逐词接回"逻辑)
生成起点: ['the']
第1步: 根据当前词 'the' 采样 → 'cat' (候选 ['cat', 'dog'],概率 [0.667, 0.333])
第2步: 根据当前词 'cat' 采样 → 'sleeps' (候选 ['sat', 'sleeps'],概率 [0.5, 0.5])
第3步: 根据当前词 'sleeps' 采样 → '<e>' (候选 ['<e>'],概率 [1.0])
第4步: 遇到结束标记 <e>,生成终止
完整生成序列: the cat sleeps <e>
→ 自回归的本质:上一步的输出(词)接回输入,再预测下一步,直到结束标记
每一步都看得清清楚楚:"the" 后面候选是 cat/dog(概率 2:1),采到 cat;"cat" 后面候选 sat/sleeps,采到 sleeps;"sleeps" 在语料里永远是句尾,只能结束。一个统计表 + 一个循环,就完成了"机器写句子"------神经网络版的 GPT 只是把"查表"换成"神经网络预测",循环逻辑一模一样。
3.3 bigram 模型 vs 神经网络:差在哪? 值得点破的一层:demo2 的 bigram 模型"只看上一个词"(一阶马尔可夫),所以它生成 "the cat sleeps" 这种通顺句靠的是运气------真正写文章需要"看过很多个前面的词"(二阶、三阶甚至整段上下文)。神经网络的 GPT 用因果注意力把"所有已写出的词"都纳入视野 (demo1 的下三角矩阵),再经过多层非线性变换,能力远超"只看上一个词"。bigram 是"生成逻辑"的最小演示,GPT 是"生成逻辑 + 长上下文建模"的工业实现------demo2 教你循环怎么转,demo5 教你神经网络怎么填进循环里。
🎤 一句话总结生成循环 :生成 = "预测 → 接回 → 再预测" 的循环,直到结束标记。训练时 teacher forcing 并行喂整句,推理时只能串行逐词------第 8 章的知识在这里完美落地。
3.1 生成什么时候停?两个终止条件 。循环不能无限跑下去(会一直生成下去),业界用两个条件控制长度:①结束标记 :模型预测出 <e>(或 <eos>)就停------demo2 里 "sleeps" 后只有 <e> 可选,生成自然终止;②最大长度 :即使模型一直不吐结束标记(比如它没学会"该结束了"),也强制在 max_len 步后截断。真实场景两者结合:while 未到结束标记 and 已生成步数 < max_len。demo5 的小 GPT 因为训练语料里没有 <e> 作为"输出目标",学会了循环却不会停------所以它无限生成 "a b c d a b c d..."------这恰好是"训练数据决定生成行为"的活教材:想让模型学会"说人话说完就停",训练语料里就得有"说完"(结束标记)这个动作。
3.2 生成为什么比训练"贵"? 一次前向的时间是固定的(模型大小决定),但生成要跑 S 次前向 (每生成一个词一次),而训练时整句只跑一次前向(teacher forcing 并行)。所以:生成长度为 100 的句子 = 100 次前向;训练一个 100 词的样本 = 1 次前向。这就是为什么 GPT 推理慢、且"生成长度 × 模型大小"直接决定推理成本------ChatGPT 回答一个问题之所以要等几秒,就是它在串行跑几十次大模型前向。另一个推论:生成的每一步都只能看到"已生成的词",随着序列变长,每步前向的输入变长,KV 缓存(缓存注意力里的 K/V)等技术就是为了让"每步只算新词的注意力"而不是重算全部,这是工业推理加速的核心。
四、采样策略:temperature 与 top-k
模型给每个词一个概率,但"按概率选词"有讲究。直接用 argmax(永远选概率最大的)会生成死板重复的文本;完全随机(按概率均匀采)会生成胡言乱语。两个旋钮在中间地带工作:temperature(温度) 和 top-k。
4.0 为什么不能直接 argmax? 先想清楚"为什么需要采样"。如果每步都选概率最大的词,生成会退化成:①重复 ------模型对同一上下文大概率给出同一答案,"今天天气很好,今天天气很好......"地绕圈;②呆板 ------概率第二、第三大的词虽然合理,但永远没机会出场,生成缺乏多样性和"灵感"。而完全按概率随机采样(多项式采样)又会把低概率的"胡话词"放进来。所以需要控制随机性的旋钮:既不要"永远选最大",也不要"什么都可能",在"确定性"和"多样性"之间找平衡------temperature 和 top-k 就是这两颗旋钮。
4.1 temperature:温度改的是"分布的尖锐程度"
公式极简:把 logits 除以温度 T 再做 softmax:
p j = e z j / T ∑ k e z k / T p_j = \frac{e^{z_j / T}}{\sum_k e^{z_k / T}} pj=∑kezk/Tezj/T
- T < 1:logits 被放大 → 分布更尖锐 → 高概率词更突出,生成更"保守确定";
- T = 1:原样 softmax;
- T > 1:logits 被缩小 → 分布更平坦 → 低概率词也有机会,生成更"随机发散"。
demo3 用同一份 logits 实测三种温度(真实输出):
【demo3】temperature:同一份 logits,温度越低越尖锐、越高越均匀
logits = [1.0, 2.0, 3.0, 0.5]
T=0.5 → [0.0158, 0.1166, 0.8618, 0.0058] 熵=0.4742
T=1.0 → [0.0854, 0.2321, 0.6308, 0.0518] 熵=0.9930
T=2.0 → [0.1627, 0.2683, 0.4423, 0.1267] 熵=1.2710
T=0.5:最大概率 0.8618(几乎"只选最可能的词",接近确定性)
T=2.0:最大概率 0.4423(分布变平,低概率词也常被选到)
三行数据里,熵(不确定性)从 0.4742 涨到 1.2710------温度每翻一倍,分布更平一分。T=0.5 时概率几乎全押在最可能的词 2(0.8618),T=2.0 时它只剩 0.4423,词 3 也有 0.1267 的机会。画成图,三条曲线一目了然:

💡 直觉:T 就像"敢不敢冒险"的旋钮。写诗、编故事想要新奇 → 调高 T;写代码、查资料想要准确 → 调低 T。ChatGPT 界面上的"温度"滑块就是这个。
4.2 top-k:直接砍掉低概率词
temperature 改变"形状",top-k 改变"候选名单":只保留概率最高的 k 个词,其余概率清零,再对保留的词重归一化。这样"偶尔冒出的怪词"(拼写错误、胡言乱语)永远没机会被选中。demo4 实测(真实输出):
【demo4】top-k 采样:只保留概率最高的 k 个词,其余清零重归一化
logits = [1.0, 2.0, 3.0, 0.5, 0.2]
完整分布(5 个词): [0.0822, 0.2235, 0.6075, 0.0499, 0.0369]
k=2:保留词 [1, 2](其余概率被清零),重归一化后 [0.0, 0.2689, 0.7311, 0.0, 0.0]
k=3:保留词 [0, 1, 2](其余概率被清零),重归一化后 [0.09, 0.2447, 0.6652, 0.0, 0.0]
→ top-k 把"偶尔冒出低概率怪词"的概率直接抹掉,生成更可控
k=2 时词 3、4 的概率被清零,词 1、2 重新归一化成 0.2689/0.7311;k=3 时多放行词 0(0.09)。"候选名单"由 k 控制,名单外的词连抽签资格都没有。画成图:

🎤 一句话总结采样 :temperature 管"分布胖瘦",top-k 管"候选名单长短"------两个旋钮配合使用(业界常见 T=0.7~1.0 配 top-k=40~100),是控制生成质量的标配。
4.4 实用指南:三个旋钮怎么配 。给一个"经验配方"(具体任务再调):写诗/编故事/头脑风暴 (要多样新奇)→ T=0.81.2,top-k=50100,top-p=0.9;问答/翻译/写代码 (要准确一致)→ T=0.30.7,top-k=2040;数学/事实性回答 (要严格)→ 直接 argmax(等价 T→0、k=1)。一条通用原则:任务越"开放"越敢放开随机性,越"封闭"越要收紧 。另外提醒:temperature 和 top-k 是"每次采样"用的;如果你希望多次生成再挑一个最好的(业界叫"最佳 N 采样",best-of-N),可以每次用中等的 T 生成,再按打分挑------这是 ChatGPT 这类产品常用的技巧,底层仍然是本章这几行代码。
4.3 采样的完整配方与一个变体(top-p) 。实际生成时两个旋钮一起用:先除以温度 T 得到分布,再做 top-k 截断,最后采样。一个重要的细节:top-k 的 k 是固定的 (永远保留 40 个词),但不同上下文的分布"胖瘦"差很多------固定 k 可能在简单上下文里保留太多"废词"、在复杂上下文里又不够。于是有了 top-p(核采样,nucleus sampling) :不是"保留前 k 个",而是"从概率最高的词开始累计,直到累计概率超过 p 才停"------候选名单随分布自适应伸缩 。举个例子:若某个位置概率高度集中在 2 个词上(累计 0.95),top-p=0.9 就只保留这 2 个;若分布很平,就要保留更多词才能凑够 0.9。HuggingFace 的 generate() 里,do_sample=True, temperature=0.7, top_p=0.9 就是这套配方的工业实现。记住:temperature、top-k、top-p 三个旋钮都是为了同一个目的------在"质量"(选最可能的词)和"多样性"(不重复不说废话)之间找平衡。
五、小 GPT 从零训练:让机器学会"自己写"
理论齐了,动手。用第 12 章"小 BERT"的同款流程训练一个小 GPT(demo5):
- 词表 :8 个 token(
<pad> <s> <e> a b c d x); - 训练数据 :4 条循环序列
abcd / bcda / cdab / dabc------模型要学的规律是"a 后面接 b、b 后面接 c......d 后面接 a"(循环语言); - 模型 :单层 Decoder(d=8,因果注意力 + 残差 + LN + FFN)+ 输出层,共 640 参数;
- 任务 :输入
[w0 w1 w2],预测[w1 w2 w3](右移一位,3 个位置同时算平滑 CE); - 训练:数值梯度 + AdamW + warmup(第 10 章三件套)。
核心前向(与第 9 章 Encoder 唯一的不同是打分矩阵加了因果掩码):
python
def forward_ids(ids, P):
x = P['emb'][ids] + P['pe'][:ids.shape[1]][None, :, :]
q = x @ P['Wq']; k = x @ P['Wk']; v = x @ P['Wv']
scores = q @ k.transpose(0, 2, 1) / np.sqrt(dM)
causal = np.triu(np.full((ids.shape[1], ids.shape[1]), -1e9), k=1)
w = softmax_rows(scores + causal[None]) # 因果掩码:只看左边
a = w @ v
m = layer_norm(x + a, P['g1'], P['b1'])
f = np.maximum(0.0, m @ P['W1'] + P['b1f']) @ P['W2'] + P['b2f']
h = layer_norm(m + f, P['g2'], P['b2'])
logits = h @ P['Wo'] + P['bo'] # (B,S,V) 每个位置预测下一位
return softmax_rows(logits), logits
真实输出:
【demo5】小 GPT 训练(40 步,数值梯度 + AdamW + 平滑 CE,然后自回归生成)
总参数: 640
训练前:样本 1 输入 [a,b,c] 时,第 3 位置预测下一个词的 argmax = <e> (应学成 d)
step loss 预测正确
0 2.6839 -
10 0.7674 11/12
20 0.5218 12/12
30 0.4853 12/12
40 0.4735 12/12
训练后:样本 1 的每个位置预测下一个词 = ['b', 'c', 'd'] (应 [b,c,d])
训练后自回归生成(从 a 开始,逐词接回):
生成序列: a b c d a b c d
生成起点 a 时,"下一个词"的概率分布(8 个词):
训练前: [0.153, 0.2368, 0.3413, 0.0181, 0.0683, 0.0717, 0.0808, 0.03]
训练后: [0.0117, 0.0122, 0.011, 0.0093, 0.9235, 0.0099, 0.0094, 0.0131]
三段输出,见证"学会写作"的全过程:
- 训练日志:loss 从 2.6839 猛降到 0.7674(10 步),40 步收敛到 0.4735;"预测正确"从 11/12 到 12/12------模型记住了"a→b→c→d→a"的循环规则;
- 训练后预测 :输入
[a,b,c],三个位置分别预测[b,c,d]------完全正确; - 自回归生成 :从
a出发,逐词接回,生成出a b c d a b c d------它不仅会"回答",还会"自己接着写",把学到的循环规律无限续写下去(真实的大模型会一直续写直到<e>或长度上限); - 起点概率 :训练前
a的下一位概率几乎均匀(0.34 最高),训练后 b 高达 0.9235------模型"学明白了:a 后面就是 b"。
loss 曲线与生成效果画成图:


🎤 一句话总结 demo5 :640 参数的小 GPT,40 步学会循环语言并自己续写出 "a b c d a b c d" ------这就是"生成式预训练"的最小闭环。ChatGPT 只是把 640 换成 1750 亿、把 4 条样本换成 45TB 语料、把循环语言换成人类语言,原理一行不改。
5.1 从这张训练日志还能读出什么? 三件小事:①loss 从 2.6839 起步 (高于均匀分布的 ln(8)≈2.08)------和第 12 章一样,随机初始化让模型"错得很自信",训练第一步是打散错误自信;②预测正确率在 10 步时就到 11/12、20 步 12/12 ------比 loss 收敛(0.52 左右)更快,说明"猜对哪个词"这个粗糙能力先出现,"把概率分配精确"(loss 精调)后完成,符合"先学对错、再学程度"的规律;③生成比预测更难 :训练时 12/12 全对,但自回归生成时如果某一步选错(比如温度高时采样到 b 以外的词),错误会一路累积------所以真实生成时要用低温或 argmax 保证"第一步就别错"。读懂训练日志,你就知道该在什么时候放心、什么时候警惕。
5.2 数值梯度为什么又撑住了? 和第 12 章一样算笔账:640 参数 × 每步 2 次前向 × 40 步 ≈ 5 万次前向,每次前向是 d=8 的小矩阵,Python 单次约 0.3 毫秒,总计约 30~60 秒------教学刚好。但换成 1750 亿参数的 GPT-3:数值梯度需要"参数个数 × 2 次前向",那是 10^11 量级的每次更新------完全不可能 。所以工业界用反向传播(loss.backward(),计算量 ≈ 一次前向)加张量并行、流水并行。这一章再次强调数值梯度的定位:它是"验证手撕代码"的秤,不是"训练大模型"的车 ------第 14 章切 PyTorch 后,backward() 会替我们做这件事。
六、GPT 家族:规模的三级跳
GPT 不是一夜之间变强的。三代模型印证了同一个规律:架构不变,规模与数据量就是力量。

从 GPT-1 到 GPT-3,参数从 1.17 亿涨到 1750 亿(1500 倍),数据从书籍语料涨到 45TB 全网------但网络结构始终是本章这个"Decoder-only + 因果注意力 + 下一个词预测" 。最值得注意的是能力的涌现:GPT-1 需要微调才能用,GPT-2 零样本能续写,GPT-3 只给几个示例(in-context learning,上下文学习)就能执行新任务------同样的算法,规模上去了,能力质变了。这就是"大力出奇迹"的实证,也是深度学习"规模法则"(scaling law)的起点。
6.1 从 GPT-3 到 ChatGPT:一次关键的"对话微调" 。GPT-3 会续写,但不会"好好聊天"------它可能答非所问、夹带偏见、不遵循指令。ChatGPT 的改进是加了一个对话微调 阶段:先收集人类写的"指令-回答"对(监督微调 SFT),再让模型生成多个回答、人类排序打分,用第 10 章的偏好优化(RLHF 家族)把模型训练成"说人话、守规矩"。注意:这个阶段没有改变架构,只改变了训练目标 ------基础能力(语言、知识、逻辑)在预训练里已经有了,对话微调只是"把这些能力接到对话这个格式上"。所以理解 ChatGPT 的正确姿势是:GPT-3 式预训练(本章)+ 对话微调(第 10 章的技术),两步缺一不可。这也解释了为什么说"预训练是地基,微调是装修"。
6.2 预训练篇三章,一条主线串起来 。回头看第 11-13 章,它们其实是同一条流水线的三个工位:第 11 章造"字典" (BPE 把文字变成 token id,解决"模型吃什么");第 12 章教"理解" (BERT 用 MLM 双向猜词,解决"模型懂什么");第 13 章教"表达" (GPT 用下一个词预测单向续写,解决"模型会写什么")。三者共享同一个底层(Transformer + 第 10 章训练三件套),只是"任务设计"不同------预训练的本质,就是用海量无标注文本 + 一个精心设计的"自监督任务",把语言知识写进参数。这个认知,是接下来理解微调、理解一切大模型的最重要基石。
七、GPT vs BERT:一张表收束预训练篇
第 12、13 章讲完了预训练篇的两大模板,最后用一张表把它们彻底钉住:

| 维度 | GPT | BERT |
|---|---|---|
| 架构 | Decoder-only(单向) | Encoder-only(双向) |
| 注意力 | 因果:只看左边 | 双向:左右都看 |
| 训练任务 | 预测下一个词 | 猜被遮住的词(MLM) |
| 能否生成 | 能(自回归) | 不能 |
| 适合任务 | 续写、对话、写代码 | 分类、问答、抽取 |
| 代价 | 少看右边信息 | 结构上无法生成 |
🎤 一句话收束 :任务决定架构,架构决定能力------"想生成"选 GPT 模板,"想理解"选 BERT 模板;而 T5 这类 Encoder-Decoder 模型想两者通吃,代价是更大的计算量。本系列讲透这两个模板后,其余所有模型(ChatGPT、RoBERTa、T5、Llama)都只是它们的变体或组合。
7.1 预训练篇到此收官,下一章开始"真刀真枪" 。第 11-13 章我们一直在"原理 + 手撕"层面:BPE 分词、BERT 的 MLM、GPT 的自回归------每一个都用纯 NumPy 验证过。但从这一章起,我们手里已经有两大模板(Encoder 家族 + Decoder 家族)的完整认知。第 14 章开始进入实战篇:用真实数据集(IMDB 影评)+ PyTorch 训练一个真正的 Transformer 文本分类器 ,对比 RNN 系列的成绩;第 15 章微调预训练 BERT。到时候你会发现:真实训练里每一个环节(分词、padding、mask、loss、优化器、采样)都是本系列手撕过的------你已经不是一个"看过 Transformer 的人",而是一个"造过 Transformer 的人"。
八、常见坑与自查
- 以为 GPT 能"看右边" :不能。因果掩码是铁律------第 i 行权重只落在前 i+1 列(demo1 验证过)。写手撕代码时,忘了给打分矩阵加
np.triu(..., k=1)掩码,模型就"偷看答案",训练和推理行为不一致; - 把 temperature 和 top-k 混为一谈:temperature 改"分布形状"(除以 T 再 softmax),top-k 改"候选名单"(清零后重归一化)。两者独立,配合使用;
- temperature 方向记反 :T 越大越随机(分布平坦),T 越小越确定(分布尖锐)。demo3 的熵 0.4742 → 1.2710 就是铁证;
- 生成时忘了"接回":自回归的关键是"上一步的输出接到输入末尾再预测下一步"(demo2/demo5 的循环)。只预测一步就停,那不叫生成;
- 把 teacher forcing 用到推理:训练时喂完整句子并行预测(快),推理时没有答案必须逐词生成(慢)------两者生成方式不同(第 8 章再复习);
- 数值梯度只适合教学:640 参数 × 40 步已经要跑一会儿;GPT-3 的 1750 亿参数必须反向传播 + 大规模并行,从第 14 章起切 PyTorch;
- 以为"预测下一个词"很弱 :正是这个看似简单的目标,在规模和数据的加持下涌现出翻译、问答、写代码等能力------目标简单不等于能力简单,规模会放大一切;
- 把
<s>/<e>当普通词 :它们是"协议 token"(第 11 章的特殊 token 家族),<s>标记"开始"、<e>标记"结束",训练时右移对齐要用它们,推理时遇到<e>要停------忘了它们,右移对齐和终止判断都会错位; - 采样时概率没归一化就硬选 :top-k 截断后必须重新归一化(demo4 里 k=2 后词 1、2 变成 0.2689/0.7311 而不是原来的 0.2235/0.6075)------截断不归一化,抽样分布就不是合法概率,采样会偏向错误。
小结
预训练篇(第 11-13 章)收官。本章的核心收获八条:
- GPT 定义:Decoder-only + 因果注意力 + 下一个词预测------一台"会写文章"的机器,也是生成式 AI 的心脏;
- 因果掩码:GPT 的世界观是"只看左边"------真实输出显示权重矩阵严格下三角,pos3 对未来权重精确 0、对过去和 1(demo1、图 2);
- 训练任务:下一个词预测 + 右移一位 + teacher forcing,所有位置并行算交叉熵(图 3);
- 生成循环:预测 → 接回 → 再预测,直到结束标记------bigram 模型真实跑出 "the cat sleeps "(demo2、图 4);
- temperature:除以 T 再 softmax,T=0.5 最大概率 0.8618、T=2.0 只有 0.4423,熵 0.47→1.27(demo3、图 5);
- top-k:只留概率最高的 k 个词重归一化,k=2 时低概率词精确清零(demo4、图 6);
- 小 GPT 训练 :640 参数 40 步学会循环语言,loss 2.68→0.47、预测全对,自回归生成 "a b c d a b c d"(demo5、图 7、图 8);
- 家族与对比:GPT-1/2/3 规模三级跳、能力质变;GPT(会写)vs BERT(会懂)两张表收束(图 9、图 10);
- 生成的最小闭环:640 参数的小 GPT 学会循环语言并自己续写------"预训练 → 生成"的完整链条在一台机器上跑通,这是全章所有概念的汇合点;
- 一句话记住预训练篇 :第 11 章把文字变成数字(BPE),第 12 章让机器学会理解(BERT 双向),第 13 章让机器学会写作(GPT 单向)------理解与生成,至此双剑合璧。
到这里,预训练篇完结 :BPE 分词(第 11 章)→ BERT(第 12 章)→ GPT(第 13 章),从"文字怎么变数字"到"怎么学语言"到"怎么生成语言",两大模板全部手撕并验证过。下一章进入实战篇:用真实数据集 + PyTorch 真正训练一个 Transformer 分类器,和 RNN 系列的成绩一较高下。原理篇到此为止,接下来每一章都是"真刀真枪的代码"。
下一篇(四十三):从零训练一个小型 Transformer 做文本分类