从0训练LLM · 现代大模型体系 阅读 + 实验 ≈ 35 分钟 页内实验 ×4
C8 稳定性:放大后为什么会炸 第 1 课

深网为什么一初始化就炸?

初始化:第 0 步的数有多大
每个权重都从 N(0,1) 抽:4,096 维时一次点积的标准差就是 64,逐层累乘,第 22 层超过 FP32 的上限。按 fan-in 缩放方差(Xavier、He)能止住累乘;现代 LLM 却用固定的 0.02,因为 Pre-Norm 让每个子层读到的输入都回到 RMS 1,初始化只剩两件事要管:残差写回多大、开局 logits 多大。
❓

本课一问:一个刚建好、一步都没训练的深网,第 0 步里的每个数有多大?为什么「深网一初始化就炸」?

这一课先不碰:归一化放在残差的哪一边(第 2 课)、训练中途 logit 一步步长大(第 3、4 课)、模型变宽时学习率怎么跟着变(第 5 课)。

从已有出发

「小明把苹果给了小王」进模型之后的每一步,前面几章都讲过了:切成 5 个 token,查 embedding,经过 64 层的注意力与 FFN,最后由 lm_head 打分。点亮的两处是本课要追问的地方——embedding 表和每层矩阵里的初始值从哪来,开局的 logits 因此有多大。

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

两条旧线索:C0 第 5 课的开局自检说,还没训练的模型 loss 应该约等于 ln V(我们的项目开局是 7.6852,ln 2048 = 7.6246);C4 第 2 课说,残差支路的输出投影要按 1/√(2L) 缩小初始化。两件事背后是同一个问题:参数的初始值多大,第 0 步的每个数就多大——层数一多,这个「多大」会被一层层乘上去。

先自己设计:权重就用标准正态随机数

最省事的做法:所有权重都从 N(0,1) 里抽。先只看一层里的一个数:它是 d 个「权重 × 输入」相加。

一次点积有多大:d 个 N(0,1) × N(0, σ²) 相加

计算中……(纯浏览器真算,种子固定)

标准差不是 1,是 √d。这不是巧合:d 个独立、方差为 1 的项相加,方差就是 d。一层是这样,每一层都是这样——那就逐层相乘。下面让 5 个 token 的向量(代表例句里的 5 个位置,用随机数代替,输入 RMS ≈ 1)穿过 L 层,每层 x ← W·x:

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案所有权重都从 N(0, 1) 抽
5 个 token 穿过 L 层:直接堆叠

计算中……(纯浏览器真算,种子固定)

🎯

试三件事:① 在朴素一侧把层数拖到 64:表里「FP32 / BF16 变 inf」那一格是第几层?(组件按每层最大的那个元素判断;开发时另用 Float32Array 直接跑过一遍,变 inf 的层数一致)② 切到改进一侧,激活选「线性」:Xavier 那条线平了,He 那条线往上走——He 多给的那个 2 是给 ReLU 准备的;③ 激活切到 ReLU:这回 Xavier 往下掉,64 层后只剩 10⁻¹⁰ 量级,He 平了。太大会炸,太小会消失,只有「刚好」才能叠深。

改进一:按 fan-in 缩放方差

把上面那个「为什么是 √d」写成公式,逐项看:

一层输出的方差 · Glorot & Bengio 2010、He et al. 2015 的出发点
= · ·
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
推导的前提:w 与 x 独立、各项独立、均值为 0。这是初始化那一刻才大致成立的假设,训练开始后就不再成立。

这个等式本身是可以证明的 ;「让每层方差保持不变就好训」是理论动机,Glorot 与 He 都用实验验证了它在各自网络上的效果 。

改进二:现代 LLM 反而用固定的 0.02——为什么不炸

翻开真实的配置,看到的却不是 1/√d:GPT-2 所有权重用 std 0.02;OLMo 2 写明「所有参数从均值 0、标准差 0.02 的截断正态分布初始化」;锚点 Qwen3.8 的 config 里 initializer_range 也是 0.02(这是 Hugging Face 实现随机初始化时用的值,官方没有公开预训练真正用的初始化);DeepSeek-V3 用 0.006 。

按上面的公式,0.02 在 d = 5,120 时每层放大 0.02 × √5,120 = 1.431 倍,平铺 64 层会放大到 1.43164 ≈ 9.2e+9 倍。它不炸,是因为真实模型根本不是「平铺」:C4 讲过的 Pre-Norm 块里,每个子层读的是 RMSNorm(x),不管残差流多大,读到的都是 RMS 为 1 的向量;子层写回的量再加到残差流上。逐层相乘这件事,被归一化切断了。

5 个 token 穿过 L 层:Pre-Norm 残差结构

计算中……(纯浏览器真算,种子固定)

🎯

试两件事:① 看 std 0.02 那条线:64 层之后残差流的 RMS 几乎没动——每层写回的量很小(还乘了 1/√L);② 把「写回乘 1/√L」取消勾选,再看 N(0,1) 那条线涨了多少——它没有溢出,但比勾选时大得多。这就是 GPT-2 为什么要按残差层数缩放写回(C4 第 2 课)。

所以在现代 LLM 里,初始化的职责缩成两件事:每个子层往残差流里写多大(0.02,再乘 1/√(2L)),以及开局的 logits 多大。OLMo 2 还做过一个反直觉的对照:把早先版本里「越往上的层初始化越小」的缩放初始化,换回所有参数一律 0.02,梯度范数的 spike score(偏离近 1,000 步滚动均值 7 个标准差以上的点所占比例)从 0.40 降到 0.03 。

改进三:开局 logits——loss 从 ln V 开始

我们的项目共享 embedding:lm_head 就是 embedding 表 E 本身。最终 RMSNorm 的输出 h 长度被拉到 √d,再和 E 的每一行做点积,得到每个候选词的 logit:

项目的输出层:logits = h · Eᵀ(共享 embedding;T = 5 个位置)
logits = h · ET
参数(可学习)输出中间结果 渐变 = 分数(连续值)
边长按对数比例缩放,块边上的数字是真实值;同一维度符号的边在全图等长。
维度含义 真实规模 小规模
T 位置数(例句 5 个 token) 5 5
d 项目的 d_model 256 4
V 项目的词表大小 2048 8
形状取自 04-实践项目/configs/baseline.json;锚点 Qwen3.8 不共享 embedding(tie_word_embeddings = false),但 lm_head 的初始化同样决定开局 logits。

logit 标准差 = σ·√d(乘上 h 的长度因子)。项目用 σ = 0.02、d = 256,预测 0.32;C0 在同一个未训练的模型上实测是 0.3242。把 σ 换成 1,预测变成 16——开局 loss 会是多少?先猜,再看:

开局 logits 有多大,开局 loss 是多少

计算中……(纯浏览器真算,种子固定)

真训练日志 · 我们的模型只改初始化标准差
项目实测 · 初始化标准差四档

σ = 1 时,开局 val loss 是 55.33,比「均匀乱猜」的 ln V 高出 47.71:模型一上来就自信地猜错,前几百步都在把 logits 缩回来(Karpathy 管这种形状叫 hockey stick)。PaLM 的 embedding 就是 N(0,1)(embedding 后面没有归一化),所以它把输出 logits 乘 1/√n(n 为 embedding 维数)——等于把上表的 s 除以 √d,拉回到 1 附近 。

⚠

常见误解一:「初始化越小越安全。」平铺的 ReLU 网络用 1/n 的方差,64 层后信号只剩 10⁻¹⁰ 量级,梯度同样传不回来;我们的项目里 σ = 0.002 的开局 loss 恰好等于 ln V,最终 val 却是 5.352,比 0.02 的 4.641 差(σ = 0.02 这一档就是参照 run ref_baseline;它与 runs/baseline 的 4.626 同配置同种子,差的 0.014 是重跑波动,见 C14)。太大会炸,太小学不动。

常见误解二:「现代 LLM 都用 Xavier / He。」GPT-2、OLMo 2 用固定的 0.02(配合 Pre-Norm 与残差写回缩放),DeepSeek-V3 用 0.006。平铺深网里「保持每层方差」的任务,已经交给了归一化。

常见误解三:「开局 loss 高一点无所谓,反正会降。」开局 logits 大,意味着开局梯度大、前几百步都在做无用功;我们的 σ = 1 那一组 400 步后最终 val 仍是 6.552。开局自检 loss ≈ ln V,是训练第一个应该看的仪表。

🔍 ReLU 为什么正好「砍掉一半」?

输入 z 关于 0 对称时,ReLU(z) = max(z, 0) 把负的一半变成 0,正的一半原样保留。于是 E[ReLU(z)²] = ½·E[z²]:二阶矩恰好减半。下一层做 Σ w·x 时,E[x²] 只有原来的一半,权重方差就要乘 2 才能补回来——He 初始化的 2/n 就是这么来的。组件里切到 ReLU,He 那一行的「实测每层倍数」约为 1.00,而 Xavier 约为 0.707 = 1/√2。GELU、SiLU 不是恰好砍一半,对应的系数也不再是 2;这也是现代 LLM 不再逐层精算、而把尺度交给归一化的原因之一。

证据三问

问本课的回答
① 它解决什么问题?让第 0 步的每个数处在能计算、能传梯度的量级:不溢出成 inf,也不消失成 0;开局 loss 从 ln V 附近开始
② 数学上为什么可能有效?Var(Σwx) = n·Var(w)·E[x²] 在独立假设下可证 ;「保持逐层方差」是 Xavier / He 的理论动机 ;有了归一化之后,初始化只需管写回与开局 logits,这一点由块结构直接推出
③ 实验上为什么最后选了它?He 在 30 层 ReLU 网络上验证 2/n;OLMo 2 实测固定 0.02 比按层缩放更稳;我们的项目里 0.02 在四档里最终 val 最低(见上面的项目实测)

一句话

📌
方差会逐层累乘:N(0,1) 每层放大 √d;按 fan-in 缩放能止住累乘,而现代 LLM 把这件事交给归一化,初始化只管两件事——残差写回有多大,开局 logits 有多大(loss ≈ ln V)。
🚦

先停在这里:记住「√d 逐层累乘」「0.02 能用是因为有 Pre-Norm」「开局 loss ≈ ln V」三句。
再深一层:读侧栏苏剑林那篇,再展开上面 ReLU 的 Dig。

下一个缺口

初始化算好了,原版 Transformer 为什么还离不开 warmup? → 第 2 课:原版 Transformer 为什么离不开 warmup?

回扣:C5 第 4 课讲了 warmup 管的是开局——Adam 第 1 步每个参数都走满 ±η;那一课留下的悬念「Post-LN 为什么格外离不开 warmup」,下一课兑现。

你可能会问