跳到主要内容

拼出整台 GPT — 归一化、GELU、残差,与 1.24 亿参数的账

这一章干的是总装: 把第 02 章的嵌入、第 04 章的多头注意力, 和本章新造的三样零件(层归一化、GELU 前馈、残差连接)拼成一台完整的 GPT。 装完顺手算两笔账:这台机器到底有多少个参数、占多少内存。 最后把它开起来——它会说胡话,而这个「胡话」本身就是重要的一课。

1. 这一章讲什么

到上一章为止,我们手里只有零件。这一章回答:整台 GPT 长什么样、为什么深网络需要两件保险装置、 以及「124M」这个型号数字怎么数出来。原书第 4 章对准的目标是真家伙: GPT-2 small,1.24 亿参数——选它是因为 OpenAI 公开了它的权重(第 07 章要搬进来), 而且笔记本跑得动1

2. 顶层全景

GPT 整机的数据流(以 GPT-2 small 为例):

输入文字 "Every effort moves you"
→ 词元 ID:[6109, 3626, 6100, 345]
→ 词元嵌入 + 位置嵌入(各 768 维,逐位相加)→ dropout
→ 12 个相同的 transformer 块,每块内部:
层归一化 → 多头注意力 → dropout → + 输入(残差)
层归一化 → 前馈网络 → dropout → + 输入(残差)
→ 最终层归一化
→ 输出头:768 维 → 50,257 维(词表大小),每个位置得到一行 logits
→ 生成时:取最后一行 → 挑最大 → 接回输入末尾 → 从头再来

图说:整台机器只有五种零件,12 个块长得一模一样。
「大模型」的「大」不来自零件多,来自同一块重复 12 遍、每遍 768 维。

logits」——模型最后一层直接吐出的原始分数——这个词本章开始频繁出现: 它还没经过 softmax 归一化,所以不是概率,只是「谁的分高谁候选」的打分2

3. 核心原理

3.1 骨架:七个数定一台机器

书里用一个 Python 字典定死 GPT-2 small 的全部规格,后面所有代码都从它取值3:

管什么
vocab_size50,257词表大小(第 02 章的 BPE)
context_length1,024最多看多少个词元(位置嵌入表的行数)
emb_dim768每个词元的向量维度
n_heads12每个块里注意力的头数
n_layers12transformer 块堆几层
drop_rate0.1dropout 丢弃率
qkv_biasFalse注意力的 Q/K/V 投影带不带偏置

什么叫「参数」?书里给了一个最小的数法:一个 2,048×2,048 的权重矩阵, 每个元素是一个参数,共 4,194,304 个4。GPT-2 small 全身这样的矩阵加起来就是 1.24 亿。 参照物:第 01 章说过,传统方法两个参数就能分类鸢尾花——这台机器是它的六千万倍。

书里搭骨架的顺序值得一提:先写一个 DummyGPTModel,transformer 块和归一化都拿「原样返回输入」 的占位类顶替,先把数据流(数据从输入到输出、每一步是什么形状的那条管路)跑通——输入 [2, 4] 的 ID,输出 [2, 4, 50257] 的 logits—— 再回头把占位类逐个换成真零件5。这个「先通管路、再装引擎」的顺序,是自己动手写大代码时该抄的作业。

3.2 保险装置一:层归一化

层叠得越深,训练越容易翻车:梯度(第 03 章说过,是「每个参数该往哪挪多少」的信号) 在往早层传的过程中会失控——要么缩没(梯度消失),要么爆掉6

层归一化(layer normalization)是第一件保险:把每一层输出的一批数调成均值 0、方差 17。 书里用五个数进、六个数出的小层演示:原始输出的均值是 0.13、方差 0.39; 归一化后均值 0.00、方差 1.00(打印出来的 -0.0000000596 是浮点误差(计算机存小数只能存有限位,算到末位总会差一点的那种偏差),不是 bug)8

两个实现细节,后面会用到:

  • 归一化沿特征(每个词元各自那串数的数轴)维做(代码里是 dim=-1),每个词元独立归一,与批里有多少样本无关—— 这正是一般「批归一化」做不到、而 LLM 需要的:批大小经常变,甚至逐条推理9;
  • 层里还有两个可训练的参数(scale 和 shift),归一化之后允许模型自己再学「要不要缩放平移回去」—— 保险装置是扶手,不是镣铐10

3.3 保险装置二:残差连接

残差连接(residual connection,也叫捷径连接)的做法一句话:把一层的输入直接加到它的输出上11。 它来自计算机视觉(让计算机看懂图像的那一行)的 ResNet,为对付梯度消失而生。

书里做了一个干净的对比实验,这是本章的主走查之一:造一个五层小网,喂同一个输入 [1.0, 0.0, -1.0], 打印每层的平均梯度12:

不带残差:0.0002 → 0.0001 → 0.0007 → 0.0014 → 0.0050 (越早的层,信号越弱)
带残差: 0.22 → 0.21 → 0.33 → 0.27 → 1.33 (早层照样收得到信号)

不带残差时,第一层的梯度只有最后一层的二十五分之一——最前面的层几乎收不到「该怎么改」的信号, 等于白站岗13。残差连接等于给梯度修了一条绕过中间层的高速公路: 加法对梯度是「原样放行」的,所以信号能直达早层11

3.4 第三样零件:GELU 与前馈块

transformer 块里,注意力管「词与词互看」,另一半是前馈网络(feed forward network): 对每个词元独立地做两道工序——先扩到 4 倍维(768→3,072),过一个非线性(不是「输入加倍输出就加倍」的直线关系,而是会拐弯的那种)函数,再缩回来(3,072→768)14。 「非线性」是必需的:没有它,多少层矩阵乘叠起来都等价于一层,深网络就白叠了。

夹在中间的非线性函数,老一代用 ReLU(负数归零、正数直传),GPT 用的是 GELU—— 一条在零点附近平滑拐弯的曲线:负数不归零,只压扁15。 为什么要平滑?ReLU 在零点有个尖角,训练时调参信号在负区直接断掉; GELU 让拿到负输入的单元仍能小幅度参与学习,优化更顺16。 书里用的还是和 GPT-2 一致的 tanh 近似版——原模型当年就是用这个近似训的17

前馈块「先扩 4 倍再缩回」的用意:让模型在更宽敞的中间层里做更丰富的变换, 同时进出同维,块才能无限堆叠不用改接口(块与块之间对接的规格:进多少维、出多少维)18。 一个常被忽略的事实(藏在习题答案里):一个 transformer 块里,前馈模块的参数(4,722,432 个) 约是注意力模块(2,360,064 个)的两倍——GPT 的大部分参数其实不在「注意力」里19

3.5 总装与两笔账

把零件按 3.2、3.3 节的顺序装进 TransformerBlock:先归一化、再进注意力/前馈、 dropout 之后把输入加回来。这个「归一化在前」的顺序叫 Pre-LayerNorm; 2017 年原始 Transformer 用的是反过来的 Post-LayerNorm,书里点明:后者训练动态更差20

整机 GPTModel 把块重复 12 次(最大的 GPT-2 重复 48 次), 尾巴上再过一次最终归一化,接输出头映到 50,257 维21

第一笔账:参数到底是 163M 还是 124M? 用 numel 数出来是 163,009,536——比号称的多。 差额来自 weight tying(权重共享):原版 GPT-2 让输出头直接复用词元嵌入层那张 [50257, 768] 的表, 扣掉它正好是 124,412,16022。共享省内存,但作者明说:他的经验是分开训效果更好, 现代 LLM 也都分开;这本书实现里保持分开,但第 07 章加载 OpenAI 权重时会照原样处理 tying23

第二笔账:占多少地方? 每个参数存成 32 位浮点(4 字节), 163M × 4 ≈ 621.83 MB——「最小号」的 GPT 也要占掉六百多兆,这就是规模的实感24

3.6 点火:它说了胡话,而这正是重点

最后把模型跑起来:输入 "Hello, I am",逐词生成。 生成的循环 generate_text_simple 每一步做四件事,第一件是裁窗——模型一次最多看 1,024 个词元, 这个上限叫上下文窗口(context length)。

裁法是从头部(这一串词元里最前面的那些)砍:超出的丢弃,剩下的过模型取最后一行 logits → 挑分最高的词元(这叫贪心解码,greedy decoding) → 接回输入末尾25

一个值得记住的小事实:那步 softmax 其实是多余的——softmax 保持大小顺序(单调), 直接对 logits 取 argmax,选出来的词元一模一样;书里写它只是为了把「分数→概率→挑最大」的链条摆全26

输出的真实结果:"Hello, I am Featureiman Byeswickattribute argue"——纯胡话27。 原因很简单:这台机器从头到尾盘好了,但里面的几亿个数全是随机初始值,还没训练。 书里把这一刻当成一章的收尾,用意很深:架构只决定这台机器「能学什么」, 它「会什么」的全部内容都在训练里——下一章点火。

补充(不在书里,依据我们的 frontier 书架):同样的 GPT-2 架构在 nanoGPT 里有另一个著名实现 (331 行单文件,带从 OpenAI checkpoint 直接导入的 from_pretrained),我们拆过,可当对照读物。 依据: shelf=ai-frontier-reference/nanogpt#01-model.md 事实=model.py 用 331 行摊开 decoder-only Transformer,且 token embedding 与输出头共享权重。

4. 作者的判断与证据

有证据的: 163,009,536 与 124,412,160 两个参数数、621.83 MB 内存账、 五层网梯度对照、未训练模型的胡话输出——全部是可复现的程序输出12222427。 Pre-LayerNorm 优于 Post-LayerNorm 是文献共识,书里标为事实陈述20

作者的判断:

  • 「分开的嵌入/输出层比 weight tying 训得好」——作者明说「in my experience」,是经验判断23
  • 选 GPT-2 当靶子而不是 GPT-3:架构相同,只是规模与数据量不同;且按 Lambda Labs 的估算, 单卡 V100 训 GPT-3 要 355 年—— GPT-2 是能摸到真权重的最大选择1

5. 边界与局限

  • 这台 GPT-2 是 2019 年的配方:绝对位置嵌入、LayerNorm、GELU、完整多头。 2024 年后的主流模型换成 RoPE、RMSNorm、SwiGLU、GQA——骨架未变,零件换了代。 本书的价值在「骨架与训练流程」,零件换代读同族 Grigorov 那本(我们拆过)。
  • 归一化里的方差用「除 n」的有偏估计(unbiased=False),为了和 GPT-2 的 TensorFlow 原始实现对齐, 好让第 07 章的权重能严丝合缝地搬进来——嵌入维度这么大时两者实际无差别28
  • 贪心解码只是起步:它正是「写出来的东西平淡、爱复读」的根源,第 07 章讲怎么松开它。

6. 可带走的

  1. GPT 整机五段:嵌入+位置 → dropout → 12 个相同的 transformer 块 → 最终归一化 → 输出头。
  2. 层归一化把每层输出拉回均值 0 方差 1,沿特征维做,与批大小无关——这是它比批归一化适合 LLM 的原因。
  3. 残差连接 = 把输入加到输出上,给梯度修一条直达早层的短路;没有它,深网络早层收不到训练信号。
  4. 前馈块先扩 4 倍再缩回,参数是注意力模块的两倍——「注意力是全部」是流行误解。
  5. GELU 与 ReLU 的差别在负半轴:平滑留缝,让负输入的单元也能继续学。
  6. 参数数法:矩阵行×列累加;GPT-2 的「124M」口径含 weight tying,全数是 163M,差的就是输出头那张表。
  7. 内存粗算:参数量(参数的总个数)× 4 字节(float32);124M 参数 ≈ 622 MB。
  8. 生成循环四步:裁窗 → 取最后一行 logits → argmax → 接回;softmax 那步对贪心是多余的。
  9. 未训练的完备模型只会说胡话——架构管「能学」,训练管「学会」。

7. 原文地图

主题原书章原文位置
选 GPT-2 的理由与训练 GPT-3 的账4 Implementing a GPT model from scratch to generate texttext/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:110(搜「GPT-2 vs. GPT-3」)
GPT_CONFIG_124M同上text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:125(搜「GPT_CONFIG_124M」)
参数是什么同上text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:108(搜「4,194,304」)
层归一化演示同上text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:349(搜「mean of 0 and a variance of 1」) · text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:483(搜「5.9605e-08」)
LayerNorm vs BatchNorm同上text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:587(搜「batch normalization」)
残差连接实验同上text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:932(搜「0.00020173587836325169」) · text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:952(搜「0.22169792652130127」)
GELU 与前馈同上text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:610(搜「GELU」) · text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:754(搜「four-times larger」)
Pre-LayerNorm同上text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1077(搜「Pre-LayerNorm」)
weight tying 与两笔账同上text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1300(搜「163,009,536」) · text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1336(搜「124,412,160」) · text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1359(搜「621.83」)
生成循环与胡话同上text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1490(搜「monotonic」) · text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1572(搜「Featureiman」)

Footnotes

  1. 出处:「4 Implementing a GPT model from scratch to generate text」第 110 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:110,搜「GPT-2 vs. GPT-3」)。 原文:GPT-3 与 GPT-2 架构相同,只是参数从 15 亿扩到 1750 亿;GPT-3 权重未公开;Lambda Labs 估算单卡 V100 训 GPT-3 要 355 年。 2

  2. 出处:「4 Implementing a GPT model from scratch to generate text」第 1253 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1253,搜「unnormalized probabilities」)。

  3. 出处:「4 Implementing a GPT model from scratch to generate text」第 125 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:125,搜「GPT_CONFIG_124M」)。

  4. 出处:「4 Implementing a GPT model from scratch to generate text」第 108 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:108,搜「4,194,304」)。

  5. 出处:「4 Implementing a GPT model from scratch to generate text」第 159 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:159,搜「DummyGPTModel」)与第 310 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:310,搜「2, 4, 50257」)。

  6. 出处:「4 Implementing a GPT model from scratch to generate text」第 335 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:335,搜「vanishing or exploding gradients」)。

  7. 出处:「4 Implementing a GPT model from scratch to generate text」第 349 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:349,搜「mean of 0 and a variance of 1」)。

  8. 出处:「4 Implementing a GPT model from scratch to generate text」第 473 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:473,搜「0.6159, 1.4126」)与第 483 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:483,搜「5.9605e-08」)。

  9. 出处:「4 Implementing a GPT model from scratch to generate text」第 587 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:587,搜「batch normalization」)。

  10. 出处:「4 Implementing a GPT model from scratch to generate text」第 114 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:114,搜「scale」)。

  11. 出处:「4 Implementing a GPT model from scratch to generate text」第 798 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:798,搜「alternative, shorter path」)。 2

  12. 出处:「4 Implementing a GPT model from scratch to generate text」第 887 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:887,搜「1., 0., -1.」)。 2

  13. 出处:「4 Implementing a GPT model from scratch to generate text」第 932 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:932,搜「0.00020173587836325169」)与第 952 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:952,搜「0.22169792652130127」)。

  14. 出处:「4 Implementing a GPT model from scratch to generate text」第 680 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:680,搜「4 * cfg」)。

  15. 出处:「4 Implementing a GPT model from scratch to generate text」第 610 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:610,搜「Gaussian error linear unit」)。

  16. 出处:「4 Implementing a GPT model from scratch to generate text」第 661 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:661,搜「better optimization properties」)。

  17. 出处:「4 Implementing a GPT model from scratch to generate text」第 618 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:618,搜「computationally cheaper approximation」)。

  18. 出处:「4 Implementing a GPT model from scratch to generate text」第 743 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:743,搜「richer representation space」)。

  19. 出处:「appendix C—Exercise solutions」第 90 段(text/18-apx-c-appendix-c-exercise-solutions.txt:90,搜「4,722,432」)。 习题 4.1 的答案:前馈模块 4,722,432 参数,注意力模块 2,360,064 参数。

  20. 出处:「4 Implementing a GPT model from scratch to generate text」第 1077 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1077,搜「Pre-LayerNorm」)。 原文:Post-LayerNorm「often leads to worse training dynamics」。 2

  21. 出处:「4 Implementing a GPT model from scratch to generate text」第 1151 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1151,搜「repeated 12 times」)。

  22. 出处:「4 Implementing a GPT model from scratch to generate text」第 1300 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1300,搜「163,009,536」)与第 1336 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1336,搜「124,412,160」)。 2

  23. 出处:「4 Implementing a GPT model from scratch to generate text」第 1340 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1340,搜「in my experience」)。 2

  24. 出处:「4 Implementing a GPT model from scratch to generate text」第 1359 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1359,搜「621.83」)。 2

  25. 出处:「4 Implementing a GPT model from scratch to generate text」第 1467 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1467,搜「generate_text_simple」)。

  26. 出处:「4 Implementing a GPT model from scratch to generate text」第 1490 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1490,搜「monotonic」)。 原文:「the softmax step is redundant since the position with the highest score…is the same position in the logit tensor」。

  27. 出处:「4 Implementing a GPT model from scratch to generate text」第 1572 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:1572,搜「Featureiman」)。 2

  28. 出处:「4 Implementing a GPT model from scratch to generate text」第 532 段(text/12-ch04-4-implementing-a-gpt-model-from-scratch-to-gener.txt:532,搜「Biased variance」)。