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

参数到底花在哪?

参数到底花在哪
按经典公式 12·L·d² 估 Qwen3.8,会少算 23%。逐个零件对着 safetensors 数一遍:FFN 占 61.6%,归一化几乎不花参数;可到了长上下文,算得最多的却是注意力。

本课一问:参数到底花在哪?

这一课先不碰:MoE 怎么让「总参数」和「每个 token 用到的参数」分家(C11);一个参数训练时要占几个字节(C9)。

② 从已有出发

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

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

贯穿例句「小明把苹果给了小王」的 5 个 token,从 embedding 一路走到 lm_head,用的是同一套参数。点上面每个环节都能看到它的参数量(由 config 现算,✓ 表示与 safetensors 实测一致)。这一课把它们放到一张账本上。

③ 先自己设计:用经典公式估一下

很多教程给的估法是:每层注意力 4d²(Q、K、V、O 四个 d × d)、FFN 8d²(两个 d × 4d),一层 12d²,再加 embedding V·d。对 Qwen3.8(L = 64,d = 5,120,V = 248,320):

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案经典公式 12·L·d² + V·d

12 × 64 × 5,120² + 248,320 × 5,120 = 21,404,057,600

HF 上 safetensors 的实际总数:27,781,427,952。

差在哪里,逐条对得上:FFN 是 3.4d 宽的三矩阵 SwiGLU(每层 10.2d²,不是 8d²);注意力的 Q 头拼起来是 6,144 维,还多一份输出门,K/V 却因为 GQA 只有 1,024 维;48 层是 Gated DeltaNet,不是标准注意力;lm_head 不和 embedding 共享(再多 1,271,398,400);另外还有视觉塔和 MTP 模块。

④ 改进到现代做法:三张账

在上面的组件里切换视图,或者用下面这个实例直接看后两张:

🎯

试三件事:① 在「Qwen3.8 总账」里切换分母:FFN 是 61.6% 还是 63.6%,只取决于算不算视觉塔和 MTP——PipelineTracker 里写的是后者;② 看「一层里」:单层 GDN 比单层全注意力还多约 1,100 万个参数,线性注意力省的是 KV cache 和长序列计算,不是参数;③ 看「三个模型并排」:GPT-2 的 embedding 占三成,Qwen3.8 只占一成不到——模型越大,词表那一份越不显眼,FFN 越显眼。

演进图里,FFN 这一行从「4d 宽的两矩阵」一路变成「3.4d 宽的三矩阵」,这张账本就是这一行在 Qwen3.8 那一格的结果:

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

参数 ≠ 计算

参数占比说的是「存了多少」,不是「每生成一个 token 算了多少」。解码第 T 个 token 时,FFN 每层的矩阵乘法与 T 无关;全注意力层的 q·k 打分与加权求和却要对 T 个历史 token 各做一次:

交叉点 T = 87,040 这个数完全由 config 算出:FFN 每 token 2 × 3 × 5,120 × 17,408 × 64 FLOPs,全注意力每多一个历史 token 多 16 层 × 2 步 × 2 × 24 × 256 FLOPs,两者相除 。Qwen3.8 原生支持 262,144 的上下文,在那里这一部分注意力计算是 FFN 的 3 倍——这也是为什么它只让 16 层用全注意力,另外 48 层换成与 T 无关的线性注意力(C11)。

⑤ 极小真数字算一次:我们自己的模型

项目基线(d = 256,4 层,8 头 MHA,SwiGLU 中间维 688,词表 2,048,输入输出共享 embedding)可以完全手算:

合计 524,288 + 4 × (262,144 + 528,384 + 512) + 256 = 3,688,704,与 README 里训练脚本打印的数一致 。FFN 占 4 × 528,384 / 3,688,704 = 57.3%。

🔍 旧版课程在这里算错过 旧版 M2 推荐「6 层 · 384 维 · 6 头 + 8K 词表」,说是约 50M 参数的小模型。用同一个经典公式:12 × 6 × 384² + 8,192 × 384 = 10,616,832 + 3,145,728 = 13,762,560,约 14M(共享 embedding 时),差了三倍多。参数量永远自己算一遍再写进计划(R5 Q-E5)。

⑥ ⚠ 常见误解

⚠️

「参数最多的部件,计算也最多」✗ —— 参数与 T 无关,注意力的打分计算与 T 成正比;Qwen3.8 在 T 超过 87,040 之后,全注意力这部分的计算就超过了 FFN。

「FFN 占比是个固定常数(常说的三分之二)」✗ —— 三分之二只对「4d 宽的两矩阵 FFN + 4 个 d × d 注意力矩阵」成立;GPT-2 算上 embedding 只有 45.5%,Qwen3.8 是 61.6%(文本主干 63.6%),MoE 的 Qwen3.8-2.4T 按 config 现算约 97%。先问分母是什么。

「HF 页面上的参数总数就是参数量」✗ —— GPT-2 显示 137,022,720,其中 12,582,912 是 12 张 1,024 × 1,024 的因果掩码缓冲区,不是可学习参数;按 config 逐项现算是 124,439,808

🔍 论文里又写的是 117M GPT-2 报告 Table 2 把最小的模型写成 117M;按官方 config 逐项现算(含全部偏置与 LayerNorm)是 124,439,808,报告没有说明差在哪里。三个数各有口径:论文表格、逐项现算、safetensors 文件里的张量总数。
。

⑦ 证据三问

⑧ 一句话

参数大头在 FFN,长上下文的计算大头却在注意力:数参数之前先问分母,数计算之前先问 T。

⑨ 回到主线

零件和账本都齐了。下一章不再问「块里有什么」,而是问「这 27,781,427,952 个参数怎么学出来」:C5 优化:参数怎么更新得又快又稳。

⑩ 你可能会问