从0训练LLM · 现代大模型体系 实验 读图 + 拍板 ≈ 30 分钟 页内实验 ×5
C4 一个 Transformer 块:FFN、残差、归一化、门控 实验课

在我们自己的模型上,把块里的零件一个个拆掉、换掉,会怎样?

实验课:把块里的零件一个个拆掉、换掉
回放 04-实践项目里真实跑出来的对照实验:去残差、去 FFN、Post-LN、LayerNorm、GELU。每一组只改一个配置,差异小于噪声底线的不下结论。

本课一问:在我们自己的模型上,把块里的零件一个个拆掉、换掉,会怎样?

这一课先不碰:为什么 Post-LN 离不开 warmup(在我们这里表现为卡在字频,C8 解释);更大的模型上结论是否不变(C6 规模、C14 评测)。

② 从已有出发

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

前四课每个零件都有一个「朴素方案 → 失败 → 改进」,但那些失败大多是论文里的数字或随机初始化网络上的现象。这一课换成我们自己从 0 训练的模型:4 层、d = 256、8 头、词表 2,048、上下文 256,在《三國志演義》上训练 400 步,参数 3,688,704;基线 val loss 从 7.6852 降到 4.6263(README 记录的真实日志)。

这一课的探针不用贯穿例句,原因是:「小明把苹果给了小王」是简体现代汉语,项目语料是繁体古白话,它在项目分词器下切成 13 个 token,「苹」「给」两个字不在词表里、被拆成字节(README 的已知局限)。拿它去看神经元,看到的主要是「生僻字节」而不是模型学到的东西,所以探针用验证集里的真实原文。

我们的基线是演进图里「Llama 式」那一代(紫框那一列):Pre-RMSNorm、SwiGLU、普通残差;对照组把某一个零件退回更早的一代,或者干脆拿掉。

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

③ 先自己设计:怎么比才算数?

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案每个配置各跑一次,最终 val loss 低的就更好
配置最终 val loss(跑一次)
基线4.6263
某个改动比基线低 0.005 → 「更好」?

同一台机器上吞吐受其他进程影响:同一个模型 400 步,训练时间 22 / 29 / 38 秒。

④ 回放真实实验

真训练日志 · 五组对照,共享坐标

每一张小图都画「基线 + 这一组的改动」,所有小图共用同一横纵坐标;表里 Δ 的绝对值小于 0.02(C14 的 3σ)会标「在噪声内」。把指标切到 grad_norm,再看 Post-LN 那一组的开头。

项目实测 · C4 全部对照组

真模型探针 · 从 checkpoint 里读出来的中间量
初始化时与训练后,每一块收到的梯度

残差流 RMS 随写入次数的变化

基线模型第 2 层的真实 FFN 神经元

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

⑤ 读图:把数字变成决策

读完上面的图表,把下面这张表填完——每一格都要写数字,而且要写这个数字来自哪张图、是否超出噪声底线:

要拍板的决策读哪里你要写下的数字本章哪一课讲原理
要不要残差「残差有 / 无」组;逐块梯度图4 层与 12 层各自的 Δ val loss;初始化时底层块与顶层块的梯度之比L2
FFN 能不能省「FFN 有 / 无」组同深度去 FFN 的 Δ;参数对齐的 12 层纯注意力的 ΔL1
Pre 还是 Post「归一化放在哪」组,指标切 grad_norm首 100 步 grad_norm 最大值;不 warmup、lr 1e-2 时谁发散、在第几步L3 → C8
LayerNorm 还是 RMSNorm「LayerNorm / RMSNorm」组;算子微基准Δ 是否在噪声内;tokens/s 中位数之差;算子耗时L3
GELU 还是 SwiGLU「GELU / SwiGLU」组参数差(应 < 1%)与 ΔL4
🔍 填完再对答案(2026-09-26 导出的数据) 残差:去掉后 4 层 6.627(+2.000),12 层 6.627(对照 12 层有残差 4.732,+1.895),都贴着「只按字频猜」的 6.625;训练结束时 4 层无残差模型底层块梯度只有顶层的 3.7 × 10⁻⁷。FFN:同深度去掉 5.205(+0.578),参数对齐的 12 层纯注意力 5.330(+0.704)。Pre / Post:Post-LN 首 100 步 grad_norm 最大 12.8(基线 3.2),停在 6.627;不 warmup、lr 1e-2 时 Post-LN 6.626、Pre-Norm 5.680,两者都没有 NaN。LayerNorm 对 RMSNorm:+0.0003,在噪声内;单独运行 3 次的吞吐中位数 198,300 对 200,900 tokens/s,算子微基准 LayerNorm 0.667 ms、RMSNorm 0.792 ms。GELU 对 SwiGLU:单种子 +0.015,在重跑噪声之内(C14:5 次重跑极差 0.016);补到 5 个种子后 +0.044(4.6657 ± 0.0206 对 4.6218 ± 0.0060),超过 3σ 阈值 0.020,可以下结论。数据更新后以上面组件显示的为准。
🎯

试三件事:① 先找出所有标着「在噪声内」的对照——这些不能写成「A 比 B 好」;② 看「FFN 有 / 无」组:同深度去掉 FFN,参数少了一半多,变差是意料之中;真正回答 L1 问题的是参数对齐的那一条;③ 在「归一化放在哪」组把指标切到 grad_norm,只看前 100 步,再对照 C8 的解释。

⑥ ⚠ 常见误解

⚠️

「val loss 低 0.005,所以 A 更好」✗ —— 同配置同种子重跑 5 次,极差就有 0.016(C14 实验课);差值小于 3σ ≈ 0.020 时只能说「这个规模下看不出区别」。

「tokens/s 跑一次就能比」✗ —— 同一个模型三次训练时间 22 / 29 / 38 秒。要比吞吐,单独运行、多次取中位数,最好再看算子级的微基准。

「在 3.7M 参数的模型上成立,在 27B 上也成立」✗ —— 这些实验只能提供「实验验证」这一层证据,而且只在「3.7M 参数 + 0.5M token 语料」这个规模上成立;Pre-LN 的优势随深度变大才更明显,残差、归一化的差距在 4 层上可能很小。

⑦ 证据三问

⑧ 一句话

只改一项、对齐参数、看清噪声底线,再下结论——这三条比任何一个零件都重要。

⑨ 下一个缺口

块的结构拍板了。可开着同样的块,学习率取 3e-3 还是 1e-2、要不要 warmup、用 AdamW 还是 Muon,结果会差很多——Post-LN 不带 warmup 的那一组已经预告了这一点。下一章:C5 优化;Post-LN 为什么需要 warmup,C8 兑现。

⑩ 你可能会问