优化:参数怎么更新得又快又稳
主线定位
「小明把苹果给了小王」经过前五章搭好的网络,在每个位置输出下一个 token 的概率。随机初始化时每个位置的 loss ≈ ln 248,320 = 12.42;反向传播给 Qwen3.8-27B 的 27,781,427,952 个参数各算出一个梯度。前向的每一环都已经学过:
点任一已学环节,看这一步的输入 → 输出形状、参数量,以及它解决了什么问题(数字取自 Qwen/Qwen3.8-27B 真实 config)。
上游 · C4 一个 Transformer 块:一层才算完整,整个前向从文字算到概率;loss 与梯度的来历在 D 章地基和 C0 第 5 课。
本章(C5):拿到梯度之后,参数到底怎么动——每一步怎么走、学习率怎么随时间变、异常怎么挡。
下游 · C6 规模:优化器定了,该训多大的模型、喂多少数据?
这一章看的是训练循环。下面每一格点开,都有它在锚点模型上的真实大小(由 config 现算):
这一章为什么换例子。「参数怎么动」在例句上看不见:27,781,427,952 维的 loss 面画不出来,也没法手算。所以本章用三个替身,每课开头都会说清换例子的原因:① 峡谷 f = ½(x² + 100y²),一个画得出、能手算的 loss 面,所有机制先在这里浏览器实时算;② 浏览器里的微型模型,《三國志演義》24,000 字、24,896 个参数的字级语言模型,每改一次超参就真训练一遍;③ 我们项目的 3.7M 参数 Transformer 的真实日志。结论最后都回到大模型的公开配置上。锚点 Qwen3.8-27B 的优化器与超参没有公开,本章不替它编。
本章问题链
每一课只回答一个问题;上一课的失败,逼出下一课的问题。
| 课 | 这一课的问题 | 朴素方案在哪个真数字上失败 | 一句话 |
|---|---|---|---|
| 第 1 课 | 为什么梯度下降在峡谷里要走两百多步? | 全量梯度:DeepSeek-V3 的 14.8T token 一步就用完;单一 η:条件数 100 的峡谷里 η < 0.02,缓方向第 241 步才进 1% 圈 | 学习率被最陡的方向卡住;动量让一致的方向越走越快、来回的方向互相抵消 |
| 第 2 课 | 一个学习率管得了所有参数吗? | 同一批里梯度的 99% 分位是 1% 分位的约 900 倍,12.7% 的参数梯度恰为 0 | Adam 给每个参数配一把自己的尺子:步长约等于 η,与梯度大小无关 |
| 第 3 课 | Adam 里的 weight decay 为什么失效了? | L2 进梯度被 √v̂ 除:同一个 λ,衰减力度差 1,000 倍;真训练里 λ 从 0.01 换到 0.3,行范数几乎不变 | 衰减应该作用在参数上,不应该混进梯度再被 √v̂ 除掉 |
| 第 4 课 | 为什么一开局就用峰值学习率会炸? | η = 0.3 直接开局:第 1 步每个权重都走满 η,第 2 步 loss 从 5.55 跳到 35.4 | Adam 第一步每个参数都走满 η;warmup 让「第一步」先小下来 |
| 第 5 课 | 不知道要训多久,学习率该怎么降? | 常数学习率在谷底来回跳(600 步:3.815 对 cosine 3.677);cosine 又必须先定总长 | 大学习率负责走远,小学习率负责落底;WSD 把「何时落底」推迟到决定停下的时候 |
| 岔路 | batch 能无限加大吗? | 刚初始化时梯度噪声尺度只有约 26 个样本,开局用大 batch 是浪费 | batch 加大只减少噪声;噪声已经不是瓶颈时,再加 batch 就是浪费 token |
| 第 6 课 | 一个超大的梯度会把模型甩飞吗? | 注入一次异常 batch:‖g‖ 是平时的 40 倍,验证 loss 从 3.70 跳到 4.52 | 裁剪只缩长度、不改方向:把一次离谱的梯度变成一次普通的步子 |
| 第 7 课 | Adam 把一个权重矩阵当成一堆独立的数,浪费了什么? | 隐层矩阵的一阶矩,最大的一个奇异方向独占 57% 的能量 | Adam 看坐标,Muon 看矩阵的奇异方向:把更新的奇异值都拉到 1 附近 |
| 实验课 | 同样的模型和数据,换优化器、换调度,真实曲线差多少? | — | 每个优化器在自己的最佳点比,差距要大于种子间的波动才算数 |
表里标「浏览器实训」性质的数字(梯度分位数、loss、奇异值占比)都来自本章组件的默认设置、种子 1,在 Chrome 中实测;换浏览器末位可能略有不同,换种子会有波动,各课都给了第二个种子的结果。
演进地图
- 解决了随机一小批的梯度是全量梯度的无偏估计;按 DeepSeek-V3 的满 batch(15,360 条 × 4K),同样的数据能走约 235,240 步。卡在条件数 100 的峡谷里 η 必须小于 0.02,缓方向每步只缩到 0.981 倍:241 步才进 1% 圈(第 1 课浏览器实算)。
- 解决了一致的方向累积、来回的方向抵消;β = 0.9 时同一峡谷 87 步,稳定上限放宽到 2(1+β)/λ。卡在全体参数共用一个 η:微型模型同一批里,非零梯度的 99% 分位是 1% 分位的约 900 倍(第 2 课浏览器实训)。
- 解决了每个参数除以自己梯度的均方根;第 1 步每个参数的位移恰好是 ±η。峡谷上 η = 0.3 时 83 步。卡在weight decay 以 L2 形式混进梯度,也被 √v̂ 除:同一个 λ,梯度均方根 10 与 0.01 的两个参数,衰减力度差1,000 倍(R4 算例)。
- 解决了衰减放在 Adam 之外:每个矩阵参数每步乘 (1 − ηλ),与梯度大小无关。卡在开局第 1 步,每个参数都走满一个 η:微型模型峰值 η = 0.3 时,第 2 步的 loss 从 5.55 跳到35.4(第 4 课浏览器实训)。
- 解决了前 W 步让 η 线性爬升;同样的峰值与种子,warmup 30 步时第 2 步 loss 为 5.35。卡在warmup 只管开局:之后若一直用常数学习率,Adam 在谷底附近来回跳;cosine 又必须事先定好总步数。
- 解决了WSD 用一条常数主线分叉出任意长度:三个长度的模型共训练 732 步,cosine 各跑一遍要 1,260 步。卡在一次异常 batch:动量 SGD 注入 ×30 输入后,验证 loss 从 3.70 跳到4.52(第 6 课浏览器实训)。
- 解决了梯度噪声尺度随 loss 下降而变大:微型模型从初始化时的约 25 个样本涨到 100 步后的 1,000 以上,所以 batch 可以后期再加大。卡在有分歧:Qwen3.8-Flash-Next 报告称一开始就用 25.2M token 的 batch,比从 6.3M 渐增更好,还省 18.8% 步数(官方自报)。
- 解决了‖g‖ 超过 c 就整体乘 c/‖g‖,方向不变;GPT-3、DeepSeek-V3 都取 c = 1.0。卡在Adam 把一个权重矩阵当成一堆独立标量:微型模型隐层矩阵的一阶矩,最大的一个奇异方向占57%的能量(第 7 课浏览器实训)。
- 解决了Newton-Schulz 把更新的奇异值拉进 [0.68, 1.13];Moonlight 报告在算力最优训练下约为 AdamW 的2 倍算力效率。卡在Kimi K2 在中等规模 run 中用原版 Muon,注意力 max logit 很快超过1,000——稳定性问题留到 C8 兑现。
三层交互
| 层 | 本章里的组件 | 在哪一课 |
|---|---|---|
| 概念动画 | 一维抛物线上的跳点与 ηλ 数轴(先预测再揭示);2×2 矩阵把单位圆变成椭圆、Newton-Schulz 把椭圆变圆 | 第 1 课、第 7 课 |
| 算法模拟器(真算法、小数据) | 峡谷上的 GD / 动量 / Adam 真实轨迹与按方向分解;(η, β) 收敛速度热力图;旋转峡谷;Adam 逐坐标账本;L2 与解耦衰减的计算器;五条公开学习率调度的公式曲线;Newton-Schulz 标量与矩阵两种迭代的对照 | 第 1、2、3、5、7 课 |
| 真模型 / 真数据 | 浏览器内真训练的字级语言模型(梯度尺度、衰减、warmup、调度与 WSD 分叉、注入异常 batch、真实动量矩阵的奇异值、梯度噪声尺度、9 个 run 的优化器比较);我们项目 3.7M 参数模型的真实日志回放;DeepSeek-V3/V4、Kimi K3、GPT-3 报告原文里的配置 | 第 2–7 课、岔路、实验课 |
项目推进:给我们的模型选优化器与学习率计划
对应 04-实践项目/MILESTONES.md 的「C7 优化与稳定」(项目沿用旧章号,就是本章)。分工照旧:代码 AI 来写,决策你来做。minillm/optim.py 已经实现 SGD(动量、Nesterov)、AdamW、Muon(隐藏层矩阵用 Muon,embedding / 输出头 / 归一化 / MoE router 用 AdamW),constant / cosine / WSD 三种调度(都带线性 warmup)和梯度裁剪;minillm/train.py 有注入异常 batch 的 inject_step、inject_kind、inject_loss_scale。
你要拍板的三件事
- 用哪个优化器?每个优化器都要在各自的最佳学习率下比较(至少 3 点扫描:SGD 0.1 / 0.3 / 1.0,AdamW 10−3 / 3×10−3 / 10−2,Muon 0.01 / 0.02 / 0.05),不许共用一个学习率(第 2 课、实验课)。
- 调度用 cosine 还是 WSD?WSD「能从稳定段任意一点接着训」的好处,值不值它在同一长度上可能略高的 loss(第 5 课)?
- warmup 多少步?梯度裁剪阈值多少?阈值要引用基线日志里
grad_norm的典型值,取在它之上(第 4、6 课)。
验收标准(全部可测):
- 三种优化器各一张 3 点学习率扫描表,标出最佳点;两个 run 相差小于噪声底线时不下结论,要换 3 个种子取均值。噪声底线以 C14 为准:同配置 5 次重跑,标准差 0.0066、极差 0.016,差距小于 3σ ≈ 0.020 不下结论(同种子在 MPS 上重跑两次差 0.003,只是其中一对)。
- WSD 衰减段开始之后验证 loss 的下降量(数值,不是「明显下降」)。
- 注入异常 batch 的那一步,日志里的
grad_norm_max,以及开 / 关裁剪两条验证曲线在注入后的最大值与最终值。 - 能用本章的数字解释自己的选择:例如「AdamW 的最佳学习率在 3×10−3,比 10−2 低 x」「基线典型 grad_norm 是 y,所以阈值取 z」。
- 已知现状:基线(AdamW 3×10−3、cosine、warmup 100 步、裁剪 1.0)400 步后验证 loss 4.6263;MILESTONES 记录的「warmup 100 → 40 步时 4.63 → 4.74」日志未保留,重跑确认后才能当依据。
坑与带走
| 坑 | 症状 | 后果 | 怎么办 |
|---|---|---|---|
| 学习率太大 | loss 震荡后飙升,甚至 NaN | 训练报废 | 上限由最陡的方向决定;先小数据扫几个学习率(第 1 课、实验课) |
| 学习率太小 | 几百步 loss 几乎不动 | 烧算力 | 同上,往大扫;看各组扫描表而不是一条曲线 |
| 没加 warmup、峰值又大 | 开头几步 loss 突然跳到几十 | 一步把输出层推飞,之后要花很多步爬回来 | Adam 第 1 步每个参数都走满 η;前 W 步线性升温(第 4 课) |
用 torch.optim.Adam(weight_decay=…) 以为是 AdamW | weight decay 调了几档,结果差不多 | 正则化没按设计起作用 | 用 AdamW,或新版 Adam 的 decoupled_weight_decay=True(第 3 课) |
| 常数学习率一直训到底 | loss 后段停在一个平台附近抖 | 白白少降一截 | 末段衰减:cosine 或 WSD(第 5 课) |
| 用同一组超参比两种调度 / 两种优化器 | 得出「谁更好」的结论 | 结论只是超参偏向的结果 | 各自调优再比(K3 的做法;实验课规则 2) |
| 裁剪阈值低于典型梯度范数 | 日志里几乎每步都在裁 | 裁剪变成了另一种优化器,不再只拦异常 | 先看 grad_norm 的典型值,阈值取在它之上(第 6 课) |
| 以为开了裁剪就不会 spike | 大模型训练中仍出现 loss spike | 措手不及 | 裁剪只管单步梯度长度;监控 grad_norm,spike 的其他来源见 C8 |
| 把 Muon 用到 embedding / 输出头 / router 上 | 效果变差或早期不稳定 | 白换优化器 | Muon 只管隐藏层二维矩阵,其余用 AdamW(第 7 课) |
| 把小模型上的优化器结论搬到大模型 | 结论与报告相反 | 错误决策 | 带上规模、调度、衰减、种子一起报告;同一个小模型上结论都会翻转(第 7 课、实验课) |
这一章你应该带走的(各课「一句话」):
- 学习率被最陡的方向卡住;动量让一致的方向越走越快、来回的方向互相抵消。
- Adam 给每个参数配一把自己的尺子:步长约等于 η,与梯度大小无关。
- 衰减应该作用在参数上,不应该混进梯度再被 √v̂ 除掉。
- Adam 第一步每个参数都走满 η;warmup 让「第一步」先小下来。
- 大学习率负责走远,小学习率负责落底;WSD 把「何时落底」推迟到决定停下的时候。
- batch 加大只减少噪声;噪声已经不是瓶颈时,再加 batch 就是浪费 token。
- 裁剪只缩长度、不改方向:把一次离谱的梯度变成一次普通的步子。
- Adam 看坐标,Muon 看矩阵的奇异方向:把更新的奇异值都拉到 1 附近。
- 每个优化器在自己的最佳点比,差距要大于种子间的波动才算数。
提问记录:这一章还没有真实提问。学习中有疑问,记到 05-问答记录/C5-L<课号>.md(岔路课是 C5-B1.md);有价值的问题会展开成新的岔路课。
深入这一章
每条都在 2026-09-26 实际打开核对过:写清它讲清了什么、对应哪一课、为什么推荐。
🎮 想更直观 10–30 分钟
🔬 想吃透原理 1–3 小时
📄 想读原文 按需
排除了什么(也是筛选标准):① 各类「十分钟看懂优化器」速通视频与 CSDN 同质化转载——只给结论不给推导,数字多无出处;② Sebastian Ruder《An overview of gradient descent optimization algorithms》(2016)——经典,但动图不能交互,AdamW 只一句带过,已被本章组件与 Distill 覆盖;③ losslandscape.com 一类 3D「损失地形」艺术图——沿两个随机方向切出的截面,容易让人以为真实高维地形就长那样(见第 1 课 Dig);④ gradient_descent_viz 桌面程序——要下载安装,只借了它「正方形边长 = √v」的画法(第 2 课 AdamLedger);⑤ 本平台旧版的 LossChart「loss 诊断器」——用正弦噪声模拟 loss、开关 warmup 结果相同,不是真实训练动力学,本章用浏览器内真训练替代;⑥ 旧版 M3 地形实验的说明文字——「η 拖过 0.125,SGD 和动量当场发散」是错的(动量上限是 0.2375),第 1 课已纠正。