同样的模型和数据,换优化器、换调度,真实曲线差多少?
这节实验课做什么:把前七课的优化器放到同一个模型、同一份数据上真训练,看曲线到底差多少;然后回放我们自己项目的真实训练日志。页面上的每一条曲线,要么是你的浏览器刚刚算出来的,要么来自项目的日志文件——没有一条是画出来的。
比较的三条规则
这三条来自 04-实践项目/MILESTONES.md 的全局约定,也是本章第 5 课(K3 的公平比较)和第 7 课(结论随设置翻转)的教训:
- 只改一项。同一初始化、同一取批顺序、同一步数;对照组之间只差被比较的那一项。
- 各在自己的最佳学习率下比。不同优化器的学习率单位都不同(第 2 课),共用一个学习率等于替其中一个选了超参。每个优化器至少扫 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)。
数字来源:浏览器实时训练(纯 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 时反过来。「哪个优化器更好」这个问题,离开调度、衰减、学习率扫描和种子,是没有答案的 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条本页与第 7 课的浏览器实训;结论只在 2.5 万参数、400 步的设置下成立 出处待补 。
从曲线读训练状态:三种常见形态
旧版课程用一个「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 秒。下面的回放组件读的是本章的实验数据文件;它还没生成时,组件会写明要跑哪条命令,不画替代曲线。
你来拍板
对应 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_max | python3.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 规模