从0训练LLM · 现代大模型体系 阅读 + 实验 ≈ 35 分钟 页内实验 ×3
C4 一个 Transformer 块:FFN、残差、归一化、门控 第 2 课

把注意力和 FFN 一层层直接叠起来,叠深了会怎样?

残差:让每一层只学一个增量
直接堆叠时,深网络比浅网络还难训,纯注意力堆叠还会让所有 token 变成同一个向量。残差把「什么都不做」设成默认,并给梯度留出一条不经过任何子层的直通路——代价是残差流会越加越大。

本课一问:把注意力和 FFN 一层层直接叠起来,叠深了会怎样?

这一课先不碰:把残差流拉回稳定尺度的归一化(L3)、Post-LN 为什么离不开 warmup(C8)、把一条残差流加宽成 4 条的新做法(C15)。

② 从已有出发

从已有出发 · 累计数据流 点已学环节看形状与参数
解码层

「小明把苹果给了小王」的 5 个 token,在 Qwen3.8 里是一个 [5 × 5120] 的矩阵。一个块里有两种子层:注意力([T × d] → [T × d])和 FFN([T × d] → [T × d]),形状进出一样,所以可以一层接一层地叠——Qwen3.8 叠了 64 层,这 5 行要连续穿过 128 个子层。最直接的叠法是:上一个子层的输出,就是下一个子层的输入。

下面的模拟器不用这 5 个 token,而用随机向量和随机初始化的网络,原因是:这一课要看的是「堆叠方式」本身的性质(梯度、token 会不会趋同),和具体内容无关;随机初始化正是训练开始那一刻网络的真实状态。

③ 先自己设计:直接堆叠行不行?

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案直接堆叠:x ← F(x),上一层的输出就是下一层的输入

这个实验不在 Transformer 上,而在图像分类网络上——但它第一次把「深了反而更差」量化了出来(ImageNet 验证集 top-1 错误率,10-crop):

18 层34 层
直接堆叠(plain)27.94%28.54%

这两个网络都带 BatchNorm,作者专门检查过:前向信号和反向梯度都没有消失 。深度本身成了障碍。

Transformer 里有两个同样的问题,可以在浏览器里亲手复现(下面的网络都是随机初始化、没有训练,用来看结构本身的性质):

  1. 梯度能不能传到底层:直接堆 32 个子层,把初始化尺度 gain 从 0.5 拖到 2.0,第 1 层收到的梯度与顶层之比从 1.4 × 10⁻²⁰ 一路变到 2.9 × 10³——23 个数量级,只有很窄的一段既不消失也不爆炸。
  2. token 会不会变成一样:只用注意力、直接堆叠时,每一层都在对前面的 token 取加权平均。6 个 token 离「全部相同」的相对距离从 0.924 起步,8 层后剩 5.8 × 10⁻³,32 层后只剩 3.2 × 10⁻¹⁰——每个位置都成了同一个向量,后面再多的层也分不出谁是谁 。
改进方案:

口径:浏览器实时计算。权重由固定种子生成(随机初始化,没有训练),所以每次打开结果相同;换 N 或 gain 会重新生成整套权重。

🎯

试三件事:① 先只看红色(朴素堆叠)那一排,把 gain 从 0.5 拖到 2.0,看右边梯度柱从「底层几乎没有」变成「底层大得离谱」,再看最下面那条红线的形状——它就是「刀刃」;② 绿色那一排只开「残差」,再拖一遍 gain:绿线始终在 1 以上,梯度不会消失,但会越传越大,左边的残差流 RMS 也从 1 涨到 6 以上;③ 切到「token 会不会变成一样」:N = 32 时红线一路掉到 3.2 × 10⁻¹⁰(把 N 拖到 8,是 5.8 × 10⁻³);只开残差时绿线掉得慢得多,但 32 层后仍到 0.026;再把「写回乘 1/√N」和「子层输入先 RMSNorm」都勾上,是 0.828,6 个 token 基本保持不同。

④ 改进到现代做法

第 1 步:残差,x + F(x)

残差连接与它的展开式
= + ⟹ ⟹
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
展开式和雅可比矩阵里的 I 都是直接从定义推出来的,不依赖任何实验。

极小真数字:两层、标量。 设每个子层都是 F(x) = 0.5·x,输入 x₀ = 1。

右边四项正好是梯度的四条路:两层都跳过(1)、只经过第 1 层(0.5)、只经过第 2 层(0.5)、两层都经过(0.25)。直接堆叠只剩最后一条;残差把「都跳过」那条留住了,所以梯度不会消失 。

「高速公路」这个说法怎么用才不出错。

第 2 步:新的失败——残差流越加越大

x_L = x₀ + Σ F_l 这个展开式也说明了代价:每个子层都在往同一条流里加东西。如果 128 次写入各自方差为 1、彼此独立,方差会相加:最后的标准差是 √(1 + 128) = √129 = 11.36,比输入大一个数量级 。组件里只开残差时也是这样:32 个子层之后残差流 RMS 从 1 变成 6.3,梯度也被放大到 20 倍。

后果:越往上的层,看到的输入尺度越大;同一个 F 在不同层面对的是完全不同量级的数。两种补救一起用:

第 3 步:回到 token 趋同

切到组件的「token 会不会变成一样」:只开残差,32 层后相对距离是 0.026(两两余弦平均 0.999),比直接堆叠的 10⁻¹⁰ 好得多,但仍在收敛;三个开关全开(残差 + 先 RMSNorm + 写回乘 1/√N)时是 0.828,余弦平均 0.162,6 个 token 基本保持各自的样子。Dong 等还证明 MLP 也能阻止这种退化——FFN 和残差在这里是互相配合的 。

残差在一个块里的样子

回看这个块:主路上只有两次加法
残差流(主路):只做加法 xₗ[T × d] RMSNorm 注意力或 Gated DeltaNet Δ₁ [T × d] + RMSNorm FFNSwiGLU Δ₂ [T × d] + xₗ₊₁[T × d]

    演进图:本课在第几代

    一个 Transformer 块的演进:每一代都被上一代的某个真数字逼出来
    零件 \ 年代
    2017 · 原版Vaswani 等
    2019 · GPT-2OpenAI
    2020–2023 · Llama 式Shazeer 2020 起
    2025–2026 · Qwen3.8本课程锚点
    2026 · 分歧C15 展开
    FFN
    两矩阵 + ReLU,d_ff = 4d(512 → 2048) 卡在d_ff 取 1024 / 2048 / 4096,开发集困惑度 5.12 / 4.92 / 4.75:越宽越好,只能拿参数换
    两矩阵 + GELU,768 → 3,072 卡在参数对齐后仍输给门控版:GELU 1.983 对 SwiGLU 1.944(T5-base)
    SwiGLU 三矩阵,中间维 8d/3:DeepSeek-LLM-67B 为 8,192 → 22,016 卡在8d/3 只是公平对照时的换算,真实模型按预算另取宽度
    SwiGLU,5,120 → 17,408(3.4d),占全模型参数 61.6% 卡在输出没有上界:SiLU(10) × 10 = 99.995,低精度下会溢出
    给 SwiGLU 截断(gpt-oss 7、DeepSeek V4 10)或软封顶(Kimi K3 的 SiTU-GLU);再往后拆成很多小 FFN(MoE,C11)
    残差 本课
    每个子层 x + F(x)(ResNet 2015 的想法搬进 Transformer) 卡在残差流越加越大:128 次单位方差写入后,标准差 √129 = 11.36
    残差输出投影按 1/√N 初始化(我们的项目:0.02 ÷ √8 = 0.00707) 卡在一条 d 维向量要承载所有层的读写,层数一多成了瓶颈(各家自报)
    普通残差,同 GPT-2
    普通残差:64 层 × 2 个子层 = 128 次写入
    加宽成 4 条流(mHC、Gated Residual),或让每层对前面所有层做注意力(AttnRes)
    LayerNorm(2016,逐 token 统计;取代按批统计的 BatchNorm)放在加法之后:Post-LN 卡在离不开学习率 warmup(原因在 C8 兑现)
    LayerNorm 移到子层入口(Pre-LN)+ 最终 LayerNorm 卡在还要减均值、带 β;Zhang & Sennrich 认为平移不变可以不要
    Pre-RMSNorm:只除以均方根,没有 β 卡在标准增益 g 被 weight decay 拉向 0
    zero-centered RMSNorm:乘 1 + w,w 初始化 0,eps 1e-6;另有 QK-Norm
    OLMo 2 把归一化挪到子层输出端;DeepSeek V4 用 QKV RMSNorm 取代 QK-Clip(C8)
    没有(GLU 在 2016 年已提出,用在卷积语言模型里)
    没有
    FFN 里的门:SwiGLU = SiLU(x·W_gate) ⊙ (x·W_up) 卡在注意力 softmax 必须分出 100%:首 token 平均占 46.7%(Qwen 的 15B MoE 实验)
    再加注意力输出门:q_proj 5,120 → 12,288 = 2 × 6,144,一半过 sigmoid 当门(首 token 占比降到 4.8%)
    Kimi K3 的 Gated MLA;Qwen3.8-Flash-Next 把线性注意力的输出门也换成 sigmoid(C11)
    数字口径:中间维、比例、FFN 占比、q_proj 宽度、写入次数由 anchor-model.json / frontier-models.json 的官方 config 在构建时现算;4 倍宽的对照出自 Vaswani 2017 表 3(本地 PDF),GELU 对 SwiGLU 出自 Shazeer 2020 表 1,首 token 占比出自 Qiu 等 2025 表 4(Qwen 自报),截断值出自各家 config(R3 §3.6);√129 假设每次写入独立、方差为 1。

    Qwen3.8-27B 仍然用普通残差;把残差加宽是 2026 年正在形成的共识苗头,具体怎么加宽各家分歧很大 ,放到 C15 讲。

    ⑤ 项目实测

    真训练日志 · 我们自己的模型去掉残差之后

    项目基线是 4 层、每层 2 个子层;对照组只把两处 x + 去掉,其余(Pre-Norm、最终 norm、数据、种子、步数)全部相同。另有 12 层的一对,看深度加大后差距怎么变。

    项目实测 · 残差有 / 无(4 层与 12 层)

    项目实测 · 初始化时每一块收到多大的梯度

    项目实测 · 残差流 RMS 随写入次数的变化

    读图(2026-09-26 导出的数据,以组件显示为准):

    ⑥ ⚠ 常见误解

    ⚠️

    「残差只是为了防梯度消失」✗ —— He 2015 的 plain 网络带 BatchNorm,梯度范数正常,34 层仍然比 18 层差。残差首先改变的是「默认解」:F = 0 就是恒等,每层只需要学一个增量;梯度直通路是它的另一个好处,而且只开残差时梯度还可能被放大。

    「残差是把输入复制一份拼在后面」✗ —— 是逐元素相加,不是拼接(拼接是 DenseNet 的做法)。所以每个子层的输出必须和输入一样宽:Qwen3.8 的注意力先升到 6,144 维、FFN 先升到 17,408 维,最后都要投影回 5,120 维才能加回去。

    「有了残差,层数越多越好」✗ —— 残差流越加越大,靠后的层写入的增量在总和里占比越来越小;Pre-Norm 结构下深层的贡献会被稀释,这是 C8 和 C15 的话题。

    ⑦ 证据三问

    ⑧ 一句话

    残差让每层只学一个增量:输出 = 输入 + 改动,梯度因此总有一条不经过任何子层的直通路。

    ⑨ 下一个缺口

    残差留住了直通路,却让残差流越加越大:128 次写入之后标准差可以到 11.36,越往上的层看到的输入越大。能不能让每个子层在读残差流之前,先把它的尺度拉回同一个范围?下一课:归一化在做什么。

    ⑩ 你可能会问