从0训练LLM · 现代大模型体系 实验 实验 ≈ 40 分钟(浏览器里跑 9 个 run 约 20–40 秒) 页内实验 ×2
C5 优化:参数怎么更新得又快又稳 实验课

同样的模型和数据,换优化器、换调度,真实曲线差多少?

实验课:在同一个模型上比优化器
本章第三层交互:在浏览器里把动量 SGD、AdamW、Muon 各扫 3 个学习率真训练一遍,在各自的最佳点比较;再回放我们项目 3.7M 参数模型的真实日志。规则只有三条:只改一项、各在最佳学习率下比、差距要大于种子间的波动。
🧪

这节实验课做什么:把前七课的优化器放到同一个模型、同一份数据上真训练,看曲线到底差多少;然后回放我们自己项目的真实训练日志。页面上的每一条曲线,要么是你的浏览器刚刚算出来的,要么来自项目的日志文件——没有一条是画出来的。

比较的三条规则

这三条来自 04-实践项目/MILESTONES.md 的全局约定,也是本章第 5 课(K3 的公平比较)和第 7 课(结论随设置翻转)的教训:

  1. 只改一项。同一初始化、同一取批顺序、同一步数;对照组之间只差被比较的那一项。
  2. 各在自己的最佳学习率下比。不同优化器的学习率单位都不同(第 2 课),共用一个学习率等于替其中一个选了超参。每个优化器至少扫 3 个学习率。
  3. 差距要大于噪声。同一配置换个种子,结果也会变;两个 run 的差距小于种子间的波动,就不下结论。我们项目里,同种子在 MPS 上重跑两次差 0.003(4.6291 对 4.6263);C14 用 5 次重跑定的噪声底线是标准差 0.0066、极差 0.016,差距小于 3σ ≈ 0.020 不下结论,比较以 C14 实验课 为准。

第一部分:浏览器里的 9 个 run

字级小模型(《三國志演義》24,000 字,24,896 个参数),cosine 调度(前 20 步 warmup,降到峰值的 10%)、weight decay 0.1、400 步。三种优化器各扫 3 个学习率:动量 SGD 0.01 / 0.03 / 0.1;AdamW 10−3 / 3×10−3 / 10−2;Muon 的矩阵学习率 0.01 / 0.02 / 0.05(其余参数用 AdamW 3×10−3)。

浏览器内真训练 · 三种优化器 × 三个学习率(点「重新训练」开始)
三种优化器,各在自己最好的学习率下比 字级 MLP · 24,896 参数 · 《三國志演義》24,000 字 · 浏览器内真训练
等待开始(滚动到这里会自动开始)

数字来源:浏览器实时训练(纯 JS,Float64)。模型、语料、种子写在上方;同一组对照里所有 run 共用初始化和取批顺序,只改被比较的那一项。换种子重跑可以看到噪声有多大——差异小于种子间波动时不下结论。

🎯

试三件事:① 点「重新训练」跑完 9 个 run(约 20–40 秒),读表:种子 1 下动量 SGD 最好 3.800(η = 0.1)、AdamW 3.651(3×10−3)、Muon 3.623(0.02)。② 把种子切到 2 再跑:3.804 / 3.653 / 3.629。两个种子的顺序一致,Muon 比 AdamW 低约 0.025,大于同一优化器在两个种子间 0.002–0.006 的波动——在这个模型、这组设置下可以说 Muon 略好。③ 看 Muon 的三个学习率:3.628 / 3.623 / 3.628 几乎一样,而 AdamW 的三个是 3.771 / 3.651 / 3.673——在这个小模型上,Muon 对学习率不敏感得多。这一条只是本实验的观察,不能外推到大模型。

把第 7 课的结果放在一起看:同一个模型,常数学习率、不加衰减时 Muon 反而比 AdamW 差(3.704 对 3.677);cosine + weight decay 时反过来。「哪个优化器更好」这个问题,离开调度、衰减、学习率扫描和种子,是没有答案的 。

从曲线读训练状态:三种常见形态

旧版课程用一个「loss 诊断器」演示过三种形态,那个组件用的是正弦噪声模拟,不是真训练。现在三种形态都能在本章的真训练里找到:

形态在哪里看到读法
太慢上面动量 SGD η = 0.01、AdamW η = 10−3 两条细虚线:400 步后仍明显高于同类的最佳线学习率太小,还没走到;先往大扫
健康各优化器的粗线:开头快速下降、后段平缓、cosine 收尾时再往下压一点保持;看能否在更长训练里继续下降
发散 / 尖峰第 4 课峰值 η = 0.3 不 warmup:第 2 步 loss 从 5.55 跳到 35.4;第 6 课注入异常 batch 不裁剪:验证 loss 从 3.70 跳到 4.52开局炸看 warmup,训练中途炸看梯度范数和裁剪

第二部分:我们项目的真实日志

项目的基线已经真实跑过一次(04-实践项目/README.md):3,688,704 个参数、AdamW(学习率 3×10−3、weight decay 0.1)、cosine(warmup 100 步)、梯度裁剪 1.0,400 步后验证 loss 从 7.6852 降到 4.6263(均匀猜测是 ln 2048 = 7.62),训练 29.1 秒。下面的回放组件读的是本章的实验数据文件;它还没生成时,组件会写明要跑哪条命令,不画替代曲线。

项目真实日志 · 基线的 loss、梯度范数与学习率
项目真实日志 · SGD / AdamW / Muon 各 3 个学习率
项目真实日志 · 三种调度
项目真实日志 · 注入异常 batch 时有无裁剪

你来拍板

对应 MILESTONES「C7 优化与稳定」(项目沿用旧章号,就是本章)。代码已经写好:minillm/optim.py 里有 SGD、AdamW、Muon,constant / cosine / WSD 三种调度和梯度裁剪;minillm/train.py 有注入异常 batch 的三个配置项。你要做的是决策:

决策依据什么用哪个实验
用哪个优化器?每个优化器 3 点学习率扫描后的最佳点;差距是否大于噪声底线python3.12 experiments/c7_optimizers.py
cosine 还是 WSD?WSD 衰减段开始后验证 loss 降了多少;「能从稳定段接着训」对你值不值python3.12 experiments/c7_schedules.py
warmup 多少步?裁剪阈值多少?基线日志里 grad_norm 的典型值(阈值取在它之上);注入异常 batch 那一步的 grad_norm_maxpython3.12 experiments/c7_warmup_clip.py

脚本约定:只调用 minillm.train.train(),结束时导出到 exports/,run 名以 c7_ 开头;实验 agent 会把结果整理成本章组件读取的 c05-optimization.json(格式见本章设计卡)。

🔍 为什么小模型上的结论不能直接搬到大模型?

三个原因。① 规模:我们的浏览器模型 2.5 万参数、项目模型 370 万,Moonlight 是 30 亿激活 / 160 亿总参数,中间差了几个数量级,优化器的相对优劣可能翻转(第 7 课在同一个小模型上就见过翻转)。② 训练长度:400 步只是训练的开头,很多差别(比如调度的收尾)要在长训练里才显现。③ 超参:大模型的学习率、batch 都是按规模重新拟合的(C6)。所以本章每个实验结论都带着「在这个设置下」。

下一步

优化器定了。下一章换一个尺度的问题:该训多大的模型、喂多少数据? → C6 规模