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

残差流越加越大,怎么让每一层看到尺度稳定的输入?

归一化:把每层的输入拉回同一个尺度
BatchNorm 按批统计,在语言模型里会让同一个 token 的结果随别的句子变化,生成时甚至归零;LayerNorm 改成每个 token 自己统计,RMSNorm 再去掉减均值。Qwen3.8 用的是 zero-centered RMSNorm,放在每个子层的入口。

本课一问:残差流越加越大,怎么让每一层看到尺度稳定的输入?

这一课先不碰:Post-LN 为什么离不开 warmup、Pre-Norm 为什么更稳(C8 第 2 课兑现);QK-Norm 这类「按住注意力 logit」的归一化(C3 提过,C8 展开)。

② 从已有出发

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

拿贯穿例句「小明把苹果给了小王」里的「苹果」来说:它在残差流里是一行 5,120 维的向量,每往上走一层就被加上两次增量。上一课的结论:残差流 x_L = x₀ + Σ F_l 是所有子层写入之和。128 次写入如果各自方差为 1,最后的标准差是 11.36;在随机初始化的 32 层网络里,只开残差时残差流 RMS 从 1 涨到 6.3。越往上的子层,读到的数越大。我们要一个零件:每个子层读残差流之前,先把它的尺度拉回同一个范围。

③ 先自己设计:用 CV 里现成的 BatchNorm 行不行?

ResNet 用的就是 BatchNorm:对每一个特征,在一批样本上算均值和方差,减均值、除标准差。它在图像上效果很好,拿过来最省事。下面就用贯穿例句试:把「小明把苹果给了小王」和另一句话拼成一批,看「苹果」被归一化成什么。

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案BatchNorm:对每个特征,在批里所有 token 上统计均值和方差

4 维向量是手工构造的教学数据;要看的只是「统计量从哪来」。

4 维向量是手工构造的教学数据,只用来看清「统计量从哪来」;真实隐藏向量有 5,120 维。

拼进同一批的另一句:
情形批里有几个 tokenBatchNorm 后的「苹果」

失败的原因不需要实验:BatchNorm 的均值和方差是对批求的,所以输出必然依赖批里其他样本 。语言模型偏偏最常见「批里的东西不齐」:句子长短不一要补齐,推理时一次只生成一个 token。

④ 改进到现代做法

第 1 步:LayerNorm——每个 token 自己统计

LayerNorm(Ba 等 2016)对每个 token 的 d 个数求均值 μ 和标准差 σ,算 (x − μ) / σ,再乘可学习的 γ、加可学习的 β。对 x = [1, 2, 3, 6]:μ = 3,方差 = (4 + 1 + 0 + 9) / 4 = 3.5,σ = 1.871,输出 [−1.069, −0.535, 0, 1.604],均值恰为 0。

第 2 步:RMSNorm——连均值都不减

RMSNorm(Zhang & Sennrich 2019)只除以均方根:RMS = √((1 + 4 + 9 + 36) / 4) = √12.5 = 3.536,输出 [0.283, 0.566, 0.849, 1.697],没有 β。它提出的理由是一个假设:LayerNorm 里真正起作用的是「缩放不变」,「平移不变」可以不要 ;实验上效果与 LayerNorm 相当,在他们测的几个模型上运行时间少 7%–64% 。

两者的差别,在下面的组件里点两下就能看见:

改输入向量的任意一维,或者点下面的操作,每次结果都会记进操作记录。

#输入 xLayerNorm(x)RMSNorm(x)

🎯

试三件事:① 点「整体 ×3」:两列都「没变」——这就是缩放不变性,残差流整体变大 11 倍,子层读到的也还是同一个东西;② 点「整体 +2」:LayerNorm 一列没变,RMSNorm 一列变了——RMSNorm 放弃了平移不变,换来少算一个均值;③ 随便改一维,看 RMSNorm 的输出长度:永远是 √4 = 2。d = 5120 时它是 √5120 = 71.55——RMSNorm 把每个 token 都放到同一个半径的球面上,只保留方向。

极小真数字:平移 +2。 x’ = [3, 4, 5, 8]:LayerNorm 的均值变成 5、方差仍是 3.5,输出不变;RMSNorm 的 RMS = √((9 + 16 + 25 + 64) / 4) = √28.5 = 5.339,输出 [0.562, 0.749, 0.937, 1.499],变了。放大 ×3:x” = [3, 6, 9, 18],两者的输出都和原来一模一样(RMS 变成 3 × 3.536 = 10.607,正好约掉)。

改进方案:

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

上面是上一课的模拟器,这次默认勾着「子层输入先 RMSNorm」。操作:保持 N = 32、gain = 1,先取消这个勾,读摘要里的两个数(底层与顶层的梯度之比 20.4、残差流 RMS 6.3);再勾上,变成 3.5 和 4.3;最后再勾上「写回乘 1/√N」,两者分别回到 0.97 和 1.1。归一化管的是每个子层读到的输入,残差流本身的增长还要靠写回缩放来控制。

第 3 步:Qwen3.8 的写法——zero-centered RMSNorm

Qwen3.8 的 RMSNorm(zero-centered,eps = 1e-6)
= / + ·
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
Qwen3.8 每层 2 个、最后 1 个:64 × 2 × 5120 + 5120 = 660,480 个参数,占全模型 0.0024%——几乎不花参数。

这张图只看 weight decay 单独往哪边拉(η、λ 取项目的 lr 3e-3 与 weight_decay 0.1),真实训练里梯度会和它拉锯;zero-centered 的理由是「默认值」换成了更安全的那个 。

第 4 步:放在哪——Pre-Norm 与 Post-LN

同一个归一化,可以放在残差加法之后、主路之上(2017 年原版的 Post-LN),也可以放在每个子层的入口、主路之外(GPT-2 起的 Pre-Norm,Qwen3.8 也是):

同一个块,归一化放在两个位置
主路:加法之后还要过 LayerNorm xₗ[T × d] 注意力直接读主路 Δ₁ + LayerNorm FFNReLU(原版) Δ₂ + LayerNorm xₗ₊₁[T × d]

    两种摆法的结构差别可以直接从公式读出来:

    ⏳

    为什么 Post-LN 需要 warmup、Pre-Norm 可以不要? 这件事需要先讲初始化与学习率预热(C5、C8)。Xiong 等 2020 证明了:初始化时 Post-LN 靠近输出层的参数梯度期望很大,大学习率下不稳 。这个疑问将在 C8 第 2 课兑现;这里只要求你认得两种摆法,并在下面的项目实测里看见差别。

    第 5 步:同一个 RMSNorm,还被放在别处

    位置在 Qwen3.8 里作用细节在哪
    每个子层入口(Pre-Norm)每层 2 个,形状 [5120]让子层读到尺度稳定的输入本课
    最后一层之后1 个 [5120]Pre-Norm 的残差流没被归一化,输出前补一次本课
    每个注意力头的 q、k(QK-Norm)q_norm、k_norm 各 [256]按住注意力打分的尺度C3、C8
    Gated DeltaNet 内部[128]线性注意力输出的归一化C11

    还有别的放法:OLMo 2 把归一化挪到子层输出端(h = x + Norm(F(x)));DeepSeek-V4 对 Q 和 KV 都做 RMSNorm,官方说因此不再需要 Kimi K2 的 QK-Clip——这些都是 C8「稳定性」的话题。

    演进图:本课在第几代

    从 BatchNorm 到 LayerNorm(2016),从 Post-LN(2017)到 Pre-LN(2018 年 Tensor2Tensor 已有,GPT-2 2019 年推广),再到 RMSNorm 与 Qwen3.8 的 zero-centered 写法,都在这张表的第三行。

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

    ⑤ 项目实测

    真训练日志 · 我们自己的模型:放哪、用哪种、算得多快

    对照组各只改一项:c4_postln 把归一化挪到加法之后;c4_postln_nowarmup_lr1e-2 与 c4_preln_nowarmup_lr1e-2 是同一对放法在「不 warmup、学习率 1e-2」下的对比;c4_layernorm 把 RMSNorm 换成 LayerNorm。把指标切到 grad_norm,看 Post-LN 开头那一段。

    项目实测 · Pre / Post 与 LayerNorm / RMSNorm

    项目实测 · Pre-Norm 与 Post-LN 的残差流 RMS

    项目实测 · 归一化算子有多快

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

    ⑥ ⚠ 常见误解

    ⚠️

    「归一化之后每个向量一样长,信息丢了」✗ —— RMSNorm 对一个 token 只拿掉 1 个数(整体尺度),方向(5,119 个自由度)全部保留;可学习的逐维增益 (1 + w) 还能把需要的维度重新放大。

    「LayerNorm 在 batch 上统计」✗ —— 那是 BatchNorm。LayerNorm 和 RMSNorm 只用一个 token 自己的 d 个数。

    「RMSNorm 是 LayerNorm 打了折扣的近似」✗ —— 原文报告两者效果相当;这是实验结论,不是「必然一样好」的证明,但也谈不上打折。

    「RMSNorm 一定比 LayerNorm 快」✗ —— 少算一个均值是真的;快不快还要看实现。融合成一个内核的 LayerNorm 可能比几个小算子拼起来的 RMSNorm 更快,上面的项目微基准就是为此放的。

    ⑦ 证据三问

    ⑧ 一句话

    归一化只拿掉每个 token 的整体尺度:残差流再大,子层读到的输入都回到同一个尺度。

    ⑨ 下一个缺口

    到这里,一个块的骨架齐了:RMSNorm → 注意力 → 加回,RMSNorm → FFN → 加回。可打开 Qwen3.8 的权重,还有两处对不上 L1 的 FFN 和 C3 的注意力:FFN 有三个矩阵(gate_proj、up_proj、down_proj),注意力的 q_proj 输出 12,288 维,是 24 × 256 = 6,144 的两倍。多出来的是什么?下一课:门控。

    ⑩ 你可能会问