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

一个 Transformer 块:FFN、残差、归一化、门控

注意力让每个 token 取回了上下文,可一个块里不只有注意力。这一章把剩下的零件一个个「逼」出来:FFN、残差、归一化、门控,最后算清一个块、一整个模型的参数花在哪。

本章问题:一个 Transformer 块里,除了注意力还有什么?为什么偏偏是这几样?

C3 结束时,「小明把苹果给了小王」的 5 个 token 已经各自从别的 token 那里取回了信息。可如果你打开 Qwen3.8-27B 的权重,一层里的参数大头根本不在注意力:一层 FFN 有 267,386,880 个参数,是一层全注意力的 2.55 倍;全模型 61.6% 的参数都在 FFN 里。每层还有两个归一化、两次「加回去」,注意力的 q_proj 还多输出了一倍。

这一章不直接端出这些零件,而是每次只问一个问题:只有注意力行不行?直接往上叠行不行?数值越叠越大怎么办?怎么让模型自己决定放行多少?每个零件都是被上一个方案在某个真实数字上的失败逼出来的。

本章问题链

课本课一问你会亲手做什么
L1 FFN注意力取回信息之后,每个 token 自己还要做什么加工?让线性方案在「好吃 / 不好吃 / 难吃 / 不难吃」上全给 0.500,再用两个神经元的 FFN 精确分开
L2 残差把注意力和 FFN 一层层直接叠起来,叠深了会怎样?在种子固定的深层网络里看梯度逐层缩小或放大、token 逐层变得一模一样,再打开残差
L3 归一化残差流越加越大,怎么让每一层看到尺度稳定的输入?看 BatchNorm 在生成时把「苹果」归一成全 0;对照 LayerNorm 与 RMSNorm 的平移、缩放不变性
L4 门控门控:为什么现代 FFN 有三个矩阵,注意力的 q_proj 还要多输出一倍?在门 × 值平面上读 SwiGLU;用真实 config 对账「8d/3」规则;数清 Qwen3.8 的输出门
↳ 岔路 B1参数到底花在哪?对照 safetensors 实测分项;拖上下文长度 T,找到注意力计算反超 FFN 的那一点
实验课在我们自己的模型上,把块里的零件一个个拆掉、换掉回放项目的真实训练曲线:去残差、去 FFN、Post-LN、LayerNorm、GELU

主线定位

🧭

上游: C1 把文字变成 [T × 5120] 的向量,C2 让模型知道顺序,C3 让每个 token 从别的 token 那里取回信息。
本章: 注意力之外的全部零件——FFN 负责每个 token 内部的加工,残差让几十层叠得起来,归一化把每层输入的尺度拉回来,门控让模型自己决定放行多少。
下游: C5 问「块搭好了,参数怎么更新得又快又稳」;C8 兑现本章留下的疑问「Post-LN 为什么离不开 warmup」;C11 揭晓 48 层 Gated DeltaNet 的输出门,并把 FFN 拆成 MoE 专家;C15 讲 SiTU-GLU 的软封顶、mHC 与 AttnRes 这些对残差的新改法。

累计数据流 · 从文字到下一个 token 的概率 已学本课新增还没学
解码层

点任一已学环节,看这一步的输入 → 输出形状、参数量,以及它解决了什么问题(数字取自 Qwen/Qwen3.8-27B 真实 config)。

下面是本章要拆开的那个块。先整体走一遍,后面每一课只放大其中一步。

Qwen3.8 的一个解码层,一步一步走
残差流(主路):只做加法 xₗ[T × d] RMSNorm 注意力或 Gated DeltaNet Δ₁ [T × d] + RMSNorm FFNSwiGLU Δ₂ [T × d] + xₗ₊₁[T × d]

    🎯

    试三件事:① 走到第 4 步,注意主路上只有加法——子层的输出必须和输入一样是 [T × 5120],L2 会解释为什么这件事这么重要;② 走到第 6 步,FFN 在每个 token 上单独计算,token 之间在这里不交换信息;③ 走完最后一步,数一数 64 层一共往残差流里写了几次——这个数在 L2 会变成一个真实的方差问题。

    演进图:每个零件都是被逼出来的

    下面这张表是本章的地图:四行是四个零件(每行一课),五列是五代做法。每一格写这一代的做法,带「卡在」的格子写逼出下一代的那个真数字。每一课都会再出现这张表,并高亮那一课的那一行。

    一个 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。

    项目推进:把零件组装成块

    对应 04-实践项目/MILESTONES.md 的 C4。代码已经在 minillm/model.py 里(Block、RMSNorm / make_norm、MLP),形状测试覆盖 768 种配置组合;你要做的是拍板和验收。

    要拍板的决策依据什么在哪一课学到
    归一化放在残差支路里面(Pre-Norm)还是外面(Post-LN)?首 100 步 grad_norm 的最大值;不带 warmup、学习率 1e-2 时会不会发散。为什么会这样,到 C8 再讲L3 → C8
    LayerNorm 还是 RMSNorm?val loss 的差是否超出噪声底线(以 C14 为准:同配置 5 次重跑极差 0.016,差距小于 3σ ≈ 0.020 不下结论);tokens/s 差多少(单独运行 3 次取中位)L3
    GELU 还是 SwiGLU?参数必须对齐:SwiGLU 中间维 688 对 GELU 1024,单层 528,384 对 524,288L4
    残差输出投影的初始化要不要缩小?说清 model.py 里 is_residual_out 为什么按 1/√(2·n_layer) 缩小(4 层时 std = 0.02/√8 = 0.00707)L2

    验收标准

    坑与带走

    坑症状 → 后果怎么办
    残差连接漏了(旧 M2 坑表)层数一深,训练 loss 降得比浅模型还慢;纯注意力堆叠时各位置的输出越来越像 → 深度白加每个子层都写成 x = x + sublayer(norm(x)),形状保持 [T × d]
    以为残差只是「防梯度消失」看到梯度范数正常就以为不需要残差 → 深网仍然退化(He 2015 的 plain 网梯度正常,34 层仍比 18 层差)记住它的作用是让「什么都不做」成为默认,每层只学增量
    语言模型里用 BatchNorm同一句话的输出随批里别的句子变化;生成时单个 token 的批内方差为 0 → 训练与推理不一致用逐 token 统计的 LayerNorm / RMSNorm
    Post-LN 不带 warmup 就上大学习率文献里的表现是训练开头 grad_norm 飙升甚至发散;我们的小模型上表现为停滞而不是发散:学习率 ≥ 3e-3 时有没有 warmup 都停在字频基线 6.63;降到 1e-3,不 warmup 仍停在 6.63,加 100 步 warmup 就训到 4.87用 Pre-Norm + 最终 norm;原因见 C8 第 2 课
    SwiGLU 与 GELU 对比时没对齐参数中间维都取 4d,SwiGLU 多 50% 参数 → 结论不可信SwiGLU 中间维取 8d/3(向上取整到 16 或 256 的倍数)
    RMSNorm 增益用标准写法,又对它做 weight decayweight decay 把增益往 0 拉,等于慢慢把这一层的输出压没用 zero-centered 写法(乘 1 + w,w 初始化 0),或把 norm 权重排除在 weight decay 之外
    把 SwiGLU 的门当成 0 到 1 的开关误读激活:SiLU 的门可以是负数、也可以远大于 1只有 sigmoid 门(GLU、Qwen 的注意力输出门)在 (0, 1) 里

    这一章带走的五句话

    深入这一章

    下面每条都在 2026-09-26 实际打开核对过,并写清它讲清了什么、对应哪一课。建议顺序:先玩 bbycroft 的三维模型,再看 3Blue1Brown 第 7 章,想把残差和 LayerNorm 亲手写一遍就看 Karpathy 那 13 分钟。

    🎮 想更直观 10–30 分钟

    一个就够:它能让你看到本章每个零件在真实网络里的位置
    20–30 min英文(文字量小)需联网
    把一个完整 GPT 拆成三维结构,token 逐层穿过 LayerNorm → 自注意力 → MLP,能看到残差相加落在哪、MLP 为什么比注意力「胖」。
    对应 L1–L3 与 B1:左侧目录里的 Layer Norm、MLP 和残差相加正好是本章的零件清单;切到 GPT-2 / GPT-3 看 MLP 的体积,就是 B1「参数花在哪」的直观版。

    🔬 想吃透原理 1–3 小时

    3Blue1Brown 第 7 章是唯一必看;其余按缺哪块补哪块
    22 minB 站官方双语需联网
    把 MLP 讲成「W_up 的每一行在问一个问题、激活函数决定答不答、W_down 的每一列把答案写回残差流」,6:07 起是 MLP 内部,15:38 数参数,17:04 讲 superposition。
    对应 L1、B1:它和 L1 的「读方向 + 门槛 + 写方向」是同一种拆法,还顺带解释了为什么一个神经元往往不等于一个概念。3Blue1Brown 官方中文号,不是搬运。
    看 1:24:25–1:37:49,约 13 min中英字幕需联网
    在同一份代码里依次加上 feedforward、残差连接、LayerNorm,每加一样都看 val loss 怎么变,并讲清 LayerNorm 与 BatchNorm 只差在按哪个维度统计。
    对应 L1–L3:「先写出会坏的版本,再加零件」和本章的朴素方案 → 失败 → 改进完全同构;他用的也是 Pre-Norm 写法。
    87 min中文需联网
    逐段读 Attention Is All You Need,其中专门讲了 LayerNorm 与 BatchNorm 的区别,以及为什么变长序列不适合按批统计。
    对应 L3:它讲的是原版的 Post-LN;读完再回来看 L3 的 BlockFlow 切换,就能看清 Pre-Norm 改了哪一处。
    1–2 h(读「The Residual Stream」一节即可)英文需联网
    把残差流看成各层共用的通道:每个子层从里面读、再把结果加回去,整个网络的输出是所有子层写入之和。
    对应 L2:L2 的展开式 x_L = x_0 + Σ F_l 在这里被当成理解整个模型的出发点;想知道「每层只学增量」能推出什么,读它。

    📄 想读原文 按需

    Vaswani 2017 与 GPT-2 报告已在本地论文库,其余是 arXiv 页面
    残差出处需联网 He 等 · 2015-12
    对应 L2。先读 §1 与 Table 2:34 层 plain 网比 18 层更差,而且作者确认梯度范数正常;再看残差怎么把「恒等」变成默认。
    RMSNorm 出处需联网 Zhang & Sennrich · 2019-10
    对应 L3。假设「减均值」可以不要,只保留按均方根缩放;报告效果与 LayerNorm 相当、运行时间少 7%–64%(在他们的模型与实现上)。
    SwiGLU 出处需联网 Shazeer · 2020-02
    对应 L4。只有 4 页:式 (6) 定义 SwiGLU,§3.1 说明为什么中间维要乘 2/3,Table 1 是参数对齐后的对比;结论一节坦白没有解释它为什么有效。
    注意力输出门出处需联网 Qiu 等(Qwen)· 2025-05
    对应 L4。比较了 30 多种门的位置,SDPA 输出后逐头的 sigmoid 门最好;Table 4 给出首 token 注意力占比 46.7% → 4.8% 的数字。Qwen3.8 的 q_proj 多输出一倍就是它。
    🚫

    我替你排除了什么: 苏剑林《为什么 Pre Norm 的效果不如 Post Norm?》——本机访问 spaces.ac.cn 与 kexue.fm 都返回 403,无法核对内容,暂不收录,留到 C8 再核;Sebastian Raschka 的 LLM Architecture Gallery——访问返回 406,未能核对;Transformer Explainer——它不展开残差与 LayerNorm,C3 已经用过;「几分钟看懂残差 / 归一化」类速通视频与只贴公式不讲动机的转载——只能得到结论,得不到本章每一课要的「朴素方案为什么失败」。