08 课 · Transformer 拼装
LESSON 08 · 卷八 拼装

把零件拼成一台机器

到第 7 课为止,注意力这个零件已经齐了:Q/K/V、多头、因果掩码。 可它现在还只是孤零零的一个机制—— 直接把它叠十几层,信号会在路上衰减成一团噪声,数值还会一路涨到溢出。 这一课给它配三个保镖:残差连接(一条直通的高速公路)、LayerNorm(把音量调到标准大小)、FFN(每个字回自己座位独立消化)。 拼完这一课,你手上就有了一个能真的堆起来、跑起来的 Transformer。

STAGE 01
一层长什么样
两条公式,四个零件
STAGE 02
两个保镖
残差管信号,LN 管音量
STAGE 03
FFN
放大 4 倍,再缩回来
STAGE 04
装完对账
4 层、3824 个数

先跟着一条样本走一遍。输入「三加五等于?」, 镜头跟着数据一层层往下走 —— 从切字开始,到最后吐出「词表里每个字的分数」。 点「自动播放」,或者自己一步一步点。

招牌动画一条样本的完整旅程 —— 从 6 个字,到 20 个字的分数
① 切字一句话被切成 6 个字块(第 3 课)
?6 个字,还没变成数字
👆 一步一步往下点:②~⑥ 每一步的形状都是 [6, 8], 所以它能一层一层叠起来 —— 这是「能堆深」的全部秘密。 最后模型给出的是「」,因为第 3 课那个模型只看得到前一个字, 上下文只有 1 个 token(它只会猜一个孤立的字,第 9 课我们拿这个分布去采样)。
注意 ②~⑥ 每一步的形状都是 [6, 8]:进出的形状一模一样, 所以才能一层一层叠起来。最后两步才换形状:先变成 [6, 20], 再挑出最后一个位置。
图 8-1 · 切字 → 查表 → 加位置 → 注意力 → FFN → 重复 4 次 → 最后线性层 → 挑最后一个位置
形状不变,
才能一层一层叠上去

三句话讲完这一课

  • 一层 = 注意力 + FFN,两块都套上「残差 + LayerNorm」这两个保镖:x = x + 注意力(LayerNorm(x))x = x + FFN(LayerNorm(x))
  • 残差管信号、LayerNorm 管音量:前者保证信息不被一层层覆盖 (没有它 12 层后只剩 0.218%), 后者保证数值不越滚越大(没有它 12 层后是 281 倍)。两件事谁也不能替谁。
  • FFN 是「各自消化」8328, 中间夹一个 GELU(负数压向 0、正数原样通过)。 它占了这一层参数的 63.3%。
第 1 站

一层长什么样

把第 5、6 课的注意力,和第 7 课那两个「保镖」写在一起,一层就是下面这两行:

一个 Transformer 层(我们的版本,也是最主流的那种)
x = x + 注意力(LayerNorm(x))  # 全班讨论
x = x + FFN(LayerNorm(x))    # 各自消化

就这两行。它们藏着四个零件:注意力(信息交流)、FFN(自己消化)、残差(那个加号)、LayerNorm(每一块之前那一次)。

读法要念对:先 LayerNorm、再做本层的运算、最后加回原来的 x。 这个顺序叫 Pre-LN(归一化在前),现在的模型基本都用它 —— 原因是它训练更稳(第 12 课讲训练时会再回来提一句)。

核心实验一层的数据流图 —— 把 LayerNorm 从加法前面挪到后面,看直通怎么被稀释
顺序 ✓ 直通原样通过
残差直通 · 高速公路(原样通过,一层都没被改)
直通保留 1.00 倍(100%)
直通 1.00直通 1.00加法之前加法之前
x = x + 注意力(LayerNorm(x))  # 归一化在加法之前
x = x + FFN(LayerNorm(x))
直通那条线:LN 只作用在运算的入口,碰不到它 —— 每层 ×1.000,12 层后还是 1.00 倍(100%)。x 从头到尾原样躺在输出里。
对照:如果干脆没有那个 +x(每层都被覆盖掉),每层只剩 0.6 倍 → 12 层后只剩 0.218%(= 0.6^12)。 这是第 2 站那台仪表管的另一件事:那台问「有没有」,这台问「摆在哪」。
直通信号的保留比例(第 12 层后)
100%
每层 ×1.000
加法不改直通那一份
如果关掉直通(没有 +x)
0.218%
每层 ×0.612 层 = 0.6^12
有残差时是 6.33 倍 —— 一个在塌,一个在长
LayerNorm [6, 8] → [6, 8]
统一音量:把每个 token 自己那一行调成均值 0、方差 1,再乘 γ 加 β。只改数值大小,形状一个数都不变。
它在链上的位置
第 2 站(共 8 站)· 前一个:输入 x。 归一化站在加法的前面 —— 先归一化、再运算、最后把 x 加回来。直通那条线一点没被碰过。
当前是 Pre-LN两个 LayerNorm 都排在加号前面。
👆 点图里任何一个方框,下面那张小卡就换成它的形状和作用。再按上面的按钮切换 Pre-LN / Post-LN:两个 LayerNorm 方块会真的滑到加号后面,方框底下的字从「加法之前」变成「加法之后」, 上面那条高速公路同时变灰、虚掉,加号底下的直通读数从 1.00 掉到 ×0.857。 注意公式也跟着换:Pre-LN 的 LayerNorm 在括号里(加法之前),Post-LN 的在最外面(加法之后)—— 零件一个没换,只是站错了位置,直通就从 100% 掉到 15.8%(12 层)。
先点图里那个 LayerNorm 方块(看它的形状和作用),再按上面两个按钮在 Pre-LN / Post-LN 之间来回切:方块会平移、公式会换、加号底下的直通读数会从 1.00 掉下来。 盯住一件事就够 —— LayerNorm 到底站在加号的前面还是后面
图 8-2 · 同一层的两种写法:Pre-LN 的归一化在加法之前(直通 100%),Post-LN 的挪到加法之后(直通被 LN 稀释)
那个加号 +
残差连接:本层输出 = 原样直通的输入 + 本层新学到的东西。 它是一条高速公路,保证信号和梯度都能直达前面。
那个 LayerNorm (·)
统一音量:每个 token 向量在做运算之前,先被调成均值 0、方差 1, 再乘 γ 加 β(可学)。否则数值会一层层滚大。
那一对括号 注意力 / FFN
两块都是「先归一化、再运算、再残差加回来」。形状全程不变,都是 [6, 8]。
如果把 LayerNorm 挪到「运算之后、残差相加之前」(叫 Post-LN),会发生什么?
第 2 站

两个保镖:残差与 LayerNorm

这两件事经常被混在一起说,其实它们管的完全不是一回事。 下面两台仪表,一台量「信息还剩多少」,一台量「数值有多大」:

核心实验残差与 LayerNorm —— 两个开关,两张图,看信号怎么塌 / 数值怎么爆
假设每层新学的东西是输入长度的 0.6 倍(这个假设写在代码注释里)
① 信息还剩多少(残差管这件事)12 层后:6.33 倍
11.17
21.36
31.59
41.85
52.16
62.52
72.93
83.42
93.99
104.65
115.43
126.33
纵轴 0 ~ 6.33 倍(1.00 那条虚线是「原来的大小」)。开着残差,信号一路不衰减,12 层后还长到 6.33 倍。
② 数值有多大(LayerNorm 管这件事)12 层后:1.00 倍
11.00
21.00
31.00
41.00
51.00
61.00
71.00
81.00
91.00
101.00
111.00
121.00
纵轴 0 ~ 10 倍,顶到天花板就写「爆表」。每层开头都被归一化回标准大小,条子一直是 1.00 —— 稳。
两个保镖管的是两件不同的事,谁也不能替谁:
残差(高速公路)保证「原来的信息还能传到后面」—— 没有它,每层都在覆盖输入,12 层后只剩 0.218%。
LayerNorm(统一音量)保证「数值不会越滚越大」—— 没有它,每层乘 1.612 层后是 281.5 倍。
而且它们必须一起用:只有残差、没有 LayerNorm,数值会一直涨(6.33 倍还在往上走);只有 LayerNorm、没有残差, 数值是稳了,可原始信息早被一层层覆盖光了。
LayerNorm 的实算 —— 拿第 0 个位置「三」那一行 8 个数(γ = 1、β = 0)
归一化前
0.67-0.29-0.31-1.050.240.530.12-0.09
归一化后
1.36-0.52-0.56-2.010.511.080.28-0.13
均值 = -0.022500 方差 = 0.261069 标准差 = 0.510959
第一个数:( 0.67-0.022500 ) ÷ 0.510959 = 1.355295 ← 照着按计算器,一位不差
归一化之后:均值 -0.000000、方差 0.999961 —— 量级从最大 1.05 被压到 ±2.01 附近
👆 两个开关随便关着玩:先只关残差(看第 ① 张图塌下去),再只关 LayerNorm(看第 ② 张图顶破天花板)。 真实模型里这两件事是每层各来两次(注意力和 FFN 之前各一次), 公式就两行:x = x + 注意力(LayerNorm(x))x = x + FFN(LayerNorm(x))
先只关残差:第 ① 张图整排塌下去(12 层后只剩 0.218%)。 再只关 LayerNorm:第 ② 张图第 5 层就顶破天花板(12 层后 281 倍)。
图 8-3 · 左边:残差管「信息传不传得到」;右边:LayerNorm 管「数值大不大」
面试常问 · 残差与 LayerNorm
  • 残差为什么能救深层网络:反向传播时,梯度沿着那个加号原样回传, 不用穿过本层的运算。所以就算某一层学得很差,梯度也不会被它卡死。
  • 残差是加法不是拼接:加法不改变宽度,而且「直通」这件事本身就要求形状一致 —— 拼接会把宽度翻倍,后面每一层都得跟着改。
  • LayerNorm 和 BatchNorm 的区别:LayerNorm 是在一个 token 自己的 各维之间做归一化(跟同一批里别的句子无关),所以推理时不需要攒统计量、 一条样本也能算 —— 这是它在 NLP 里胜出的关键。
  • 为什么用两个:注意力和 FFN 是两套独立的运算, 各自需要「进去之前先调音量」,所以每层有两次 LayerNorm。
第 3 站

FFN:每个字回自己座位消化

注意力是「全班讨论」——每个字都要看别人。可讨论完还得各自消化: 这就是 FFN(也叫 MLP)的活。它一层里只干三件事:放大、筛选、缩回

核心实验FFN 的形状与 GELU —— 拖滑块看激活函数把负数压掉
[8]注意力输出的一个位置
×W1放大 4
[32]中间那层(宽了 4 倍)
GELU把负数压掉
[32]还是 32 个数
×W2缩回来
[8]形状和进来时一样
0xy = x(原样通过)
输入 x1.5
GELU(1.5) = 1.399572
输入是正的 → 输出基本原样通过(1.3995721.5 93%)。
几个能背下来的数:gelu(−2) = -0.045402、gelu(0) = 0.000000、 gelu(1) = 0.841192、gelu(2) = 1.954598
一层里的参数都花在哪儿(演示规模:一层共 872 个数)↓
FFN(8×32 + 32 + 32×8 + 8放大再缩回那两张矩阵
552
注意力(Q/K/V/O 四张 + 偏置)全班讨论
288
两个 LayerNormγ 和 β 各 8
32
FFN 占了这一层的 63.3%552 / 872),是三个零件里最大的那块。
换成真实规格(d = 128、FFN 512),它占 66.4%; 而第 2 课账本里 Qwen2.5-7B 是 87% —— 差在哪儿?差在 K、V: 7B 有 28 个头共用 4 组 K/V,注意力那边被省掉一大截,FFN 的份额自然就上去了。「参数大头在 FFN」这句话是对的,但比例取决于注意力那边省了多少。
👆 拖滑块看 GELU 的脾气:负数被压到 0 附近、正数基本原样通过。 为什么必须有个非线性?如果没有它,两层线性夹在一起还是线性(等于一张矩阵),堆多少层都白搭 —— 这一点和第 2 课「数乘不改变方向」是同一个道理。
形状一路是 8328放大 4 倍铺开,在宽空间里筛选,再压回原宽。 中间那个 GELU 就是「筛选」的执行者。
图 8-4 · FFN = 两层线性 + 一个激活;GELU 让负方向基本不通,正方向原样通过
为什么必须有个非线性

两层线性夹在一起,如果没有激活函数,那还是一层线性

(x · W1) · W2 = x · (W1 · W2)

右边那个 W1·W2 本身就是一张矩阵 —— 堆 100 层也等价于 1 层。 所以 GELU 才是让「深」有意义的那一步:它把一部分方向直接掐断, 让每一层能表达「在这些方向上要、在那些方向上不要」。

顺带一个常被问到的细节:中间为什么是 4 倍? 没有硬道理,是个经验值(原始论文就是 4)。真实模型有 8/3 倍再取整的、 也有靠门控结构(SwiGLU)的,思路一样:先铺开到更宽的空间里挑一遍

第 4 站

装完对账

零件齐了,数一下这个模型到底有多少个数。 公式只有一条:一层 = 注意力 + FFN + 两个 LayerNorm

演示规模 · 一层
872 个数
注意力 288 + FFN 552 + LayerNorm 32
演示规模 · 整个模型
3824 个数
4 层 × 872 + 嵌入 20×8 + 输出层 20×8 + 最后 LN 16
真实规格 · 整个模型
79.9
4 层、d = 128、FFN 512、词表 21798,720 个数
FFN 占一层
63.3%
真实规格 66.4%;7B 模型 87%(K/V 被收窄)
同一套公式,换个规模就是几百万倍
演示:4 × 872 + 20×8 ×2 + 16 = 3824 个数
第 7 阶段那个小模型:4 × 198,272 + 21×128 ×2 + 256 = 798,720 个数 ≈ 79.9
Qwen2.5-7B:28 层、d = 3584 —— 同一套公式,得 76 亿(第 2 课那张账本)

三个规模之间只差几个数字:层数、宽度、FFN 倍数。 公式一条没变,形状规则一条没变 —— 这也是为什么我们敢在这个玩具规模上 把整台机器算一遍,然后再去理解几亿参数的大模型。

现在这一层的输入和输出形状完全一样([6, 8]), 所以可以放心地叠 4 层、40 层、80 层。 下一课我们让它开口说话:把最后吐出来的那 20 个分数变成一个字, 再把这个字接回输入、接着猜下一个 —— 顺便把「温度」「top-k」这些旋钮搞明白。

第 5 站 · 收官

把这一课钉在墙上

本课核心 · TAKEAWAY

一个 Transformer 层就是两条公式x = x + 注意力(LayerNorm(x))x = x + FFN(LayerNorm(x))残差(那个加号)是一条直通的高速公路:本层输出 = 原样直通的输入 + 本层新学的东西,它保证信号和梯度都不会被一层层覆盖掉 (没有它,12 层后信号只剩 0.218%)。LayerNorm 在每个运算之前把 token 向量的音量调成标准大小 (均值 0、方差 1,再乘 γ 加 β),否则数值会一路滚大到溢出 (没有它,12 层后是 281 倍)。FFN 是「各自消化」:8328,中间靠 GELU 把不要的方向掐断 —— 没有非线性,堆多少层都等于一层。 一层 = 注意力 288 + FFN 552 + LayerNorm 32 = 872 个数;整个演示模型 3824 个数。 形状全程不变,所以能一直叠上去。

这一课你亲手做完了

  • 走过一条样本的完整旅程:切字 → 查表 → 加位置 → 注意力 → FFN → 重复 4 次 → 最后线性层 → 挑最后一个位置, 最后吐出 20 个字的分数(模型猜「」,29.5%)。
  • 关过残差:每层只剩 0.6 倍,12 层后 0.218% —— 条子塌到看不见。
  • 关过 LayerNorm:每层乘 1.6,第 5 层就爆表,12 层后 281.5 倍。
  • 手算过 LayerNorm:均值 -0.02、标准差 0.51, (0.67 − (−0.0225)) ÷ 0.510959 = 1.36 —— 按计算器能核对。
  • 量过 FFN8328,占一层参数的 63.3%(真实规格 66.4%)。
  • 对过账:一层 872 个数、演示模型 3824 个数、 真实规格 79.9 万 —— 同一套公式。

学习小测验

已完成 0 / 60.0%答对 0
还没提交过 —— 每题先选一个选项,再点「提交」,答完就能看到诊断。
Q1残差连接为什么是「加法」(x + 本层)而不是「拼接」?
Q2LayerNorm 之后,一个 token 向量各维的均值和方差大约是多少?
Q3没有残差连接时,信号在第 12 层之后还剩多少?
Q4FFN 为什么先把维度放大 4 倍,再缩回来?
Q5如果两层线性之间不夹激活函数(非线性),会怎样?
Q6最后一层线性层吐出来的东西,形状和含义是什么?
NEXT · 第 9 课

大模型解码:温度与采样

模型现在会吐分数了,可它只会吐一次:给一段前文,输出「下一个字」的分布。 要让它开口说话,还得自己接一个循环:挑一个字 → 接到后面 → 再问一次 → 直到吐出结束符。 下一课就把这个循环装起来,顺便搞明白三个旋钮:贪心(永远挑最高分)、温度(敢不敢冒险)、top-k / top-p(先在靠谱的字里抽签)。 最后还要学会一件事:诚实地评估—— 同一份训练,换张考卷分数能差 28 分。

从零手册 —— 下一课:大模型解码:温度与采样