从0训练LLM · 现代大模型体系 阅读 + 实验 ≈ 40 分钟 页内实验 ×4
C5 优化:参数怎么更新得又快又稳 第 5 课

不知道要训多久,学习率该怎么降?

学习率调度:cosine 与 WSD
学习率一直停在峰值,模型会在谷底附近来回跳:浏览器里的小模型 600 步后,常数学习率的验证 loss 是 3.815,cosine 降到 3.677。可 cosine 的形状取决于总步数,中途想多训只能重跑。WSD 让学习率先保持、最后一段再降:一条主线分叉出三个长度的模型只要 732 步,cosine 要 1,260 步——代价是每个长度上 loss 略高。两者谁更好,至今有分歧。
❓

本课一问:warmup 之后,学习率要一直停在峰值吗?事先不知道要训多久,又该怎么降?

这一课先不碰:衰减那一段该喂什么数据(C7 兑现);总共该训多少 token(C6)。batch 要不要也随训练变,见岔路课。

从已有出发

训练往后走,模型对「小明把苹果给了小王」每个位置的预测一步步变准,loss 越降越慢。第 4 课管住了开局;这一课看后半程:loss 快降不动的时候,步子还该那么大吗?

从已有出发 · 累计数据流 点已学环节看形状与参数
解码层
从已有出发 · 训练的一步(点任一环节看它在 Qwen/Qwen3.8-27B 上有多大) 本课本章后面的课

继续用两个替身:峡谷(看机制)和浏览器里的小模型(看真实的 loss 曲线);大模型的真实调度都来自公开报告。

先自己设计:常数学习率一直训到底

最省事的:warmup 之后就停在峰值,一直训到结束。先回到峡谷看它在谷底附近会怎样——把 Adam 的 β₂ 切到大模型常用的 0.95、η 拖到 0.5:

算法模拟器 · 常数学习率的 Adam 在谷底附近(浏览器实时算)

轨迹冲到谷底附近后停不下来:400 步内始终进不了 1% 圈。步子是「每步约 η」,离谷底只剩不到 η 的距离时,每一步都会迈过头。再看真训练的 loss:

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案常数学习率,warmup 之后一直不降
常数 / cosine / WSD:学习率怎么降 字级 MLP · 24,896 参数 · 《三國志演義》24,000 字 · 浏览器内真训练
等待开始(滚动到这里会自动开始)

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

🎯

试三件事(在「朴素方案」里):① 默认设置读表:常数 3.815、cosine 3.677、WSD 3.729。② 把种子切到 2 再跑:3.748 / 3.666 / 3.694,顺序不变。③ 把峰值切到 3×10−3:三条线挤得很近——学习率本来就小时,降不降差别不大;衰减的收益来自「峰值时的步子相对谷底太大」。

为什么降学习率,loss 会在衰减段明显下降?峡谷给了一个可以看见的解释:大步长让参数一直在谷壁之间来回,平均位置离谷底有一段距离;步长变小,它才能落到更低的地方 。所以衰减段的下降来自「步子变小」,不是模型在这几百步里突然学会了新知识。

cosine 衰减(p 是衰减段走过的比例)
= + ½( − ηmin)(1 + )
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
代入 DeepSeek-V3 的衰减段(10T → 14.3T token,2.2×10⁻⁴ → 2.2×10⁻⁵),中点 12.15T 处 η = 2.2×10⁻⁵ + ½ × 1.98×10⁻⁴ × (1 + 0) = 1.21e-4。

改进:WSD——把「什么时候降」推迟到决定停下的时候

cosine 的 p 是「衰减段走过的比例」,分母就是预定的总长。决定多训一半怎么办?下面是公开报告里写明的五条真实调度,底部的 what-if 面板专门回答这个问题:

真实配置 · 五条公开的学习率调度(公式现算,hover 读数)
what-if:训到一半想多训一段,cosine 和 WSD 各要怎么办?
🎯

试三件事:① 纵轴选「η ÷ 峰值」比形状:DeepSeek-V3 在前 10T / 14.8T ≈ 68% 的 token 里完全是平的,之后才用 cosine 降——它不是纯 cosine,是「先保持、再衰减」的混合调度。② 在图上移动鼠标到 DeepSeek-V3 的 cosine 段中间,和下方公式面板的 1.21×10−4 对一下。③ 展开 what-if,把「训到原计划的几倍」拖到 1.5:原计划的 cosine 在 T 处已经降到 10%;要是一开始就知道训 1.5T,它在 T 处应该还在 32.5%——两条线从第一步起就不一样,中途改主意只能重跑。

WSD(Warmup-Stable-Decay)是 MiniCPM 给这种「先保持、最后一段再降」的调度起的名字:warmup 之后学习率保持常数,只在最后约 10% 的 token 里衰减 。它最大的好处是稳定段可以随时分叉:想要哪个长度的模型,就从稳定段的对应位置接一段衰减。Hägele et al. 2024 进一步报告,「常数 + cooldown」的 scaling 行为和 cosine 一样可预测,而且一条训练就能复用出多个长度的点 。

在小模型上把「分叉」真做一遍:

浏览器内真训练 · WSD 分叉 vs 每个长度各跑一遍 cosine
常数 / cosine / WSD:学习率怎么降 字级 MLP · 24,896 参数 · 《三國志演義》24,000 字 · 浏览器内真训练
等待开始(滚动到这里会自动开始)

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

🎯

试三件事:① 把「实验」切到「WSD 分叉 vs 重跑 cosine」:拿到 240 / 420 / 600 步三个模型,WSD 一共训练 732 步,cosine 要 1,260 步。② 读表格:每个长度上 cosine 都更低(3.757 / 3.726 / 3.677 对 3.831 / 3.766 / 3.729)——省下的算力是有代价的。③ 把「WSD 衰减段占比」从 20% 改成 30% 再跑(回到「三种调度同跑」):WSD 的最终 loss 从 3.729 降到约 3.71,和 cosine 的差距缩小但没有消失。

现代做法:有分歧

项目里同样会跑三种调度的对照(cosine 即基线、常数、WSD),并标出 WSD 开始衰减的那一步:

项目真实日志 · 三种调度

⚠ 常见误解

⚠

「cosine 就是降到 0。」公式允许,实际很少这么做:GPT-3 停在峰值的 10%,之后以 10% 继续训;DeepSeek-V4.1-Flash 最后 5T token 停在 10%;我们的项目基线也是 10%。LrScheduleDemo2 的表里有每条调度「最后 ÷ 峰值」的比例。

⚠

「衰减段 loss 突然下降,说明模型在这段时间学会了新东西。」同样的数据、同样的模型,只把步子变小,loss 就会下降——小模型的 WSD 曲线里,第 480 步前和常数完全重合,开始衰减后才分开。下降主要来自优化本身;至于衰减段喂高质量数据能不能额外获益,是 C7 的问题。

⚠

「用同一组超参比一下 cosine 和 WSD,就知道哪个好。」两者的最优峰值与 batch 不同;共用一组超参,等于替其中一个选了超参(Kimi K3 的提醒)。本课小模型的对比就是这种「不公平比法」,所以只能说明现象,不能下「谁更好」的结论。

在演进地图上的位置

优化的演进:每一代都被上一代的某个真数字逼出来(点名字去那一课;完整版在章首页)
  1. 全量梯度下降
  2. 小批量随机梯度下降 SGD
  3. 动量(重球法)
  4. Adam:每个参数一把尺子
  5. AdamW:解耦权重衰减
  6. 学习率 warmup
  7. 学习率衰减:cosine → WSD
  8. batch 大小:渐增,还是一开始就大
  9. 全局梯度范数裁剪
  10. Muon:按矩阵的奇异方向更新
时间维 ②学习率衰减:cosine → WSDSGDR 2016 → MiniCPM 2024你在这里
解决了WSD 用一条常数主线分叉出任意长度:三个长度的模型共训练 732 步,cosine 各跑一遍要 1,260 步。
卡在一次异常 batch:动量 SGD 注入 ×30 输入后,验证 loss 从 3.70 跳到4.52(第 6 课浏览器实训)。
数字口径:峡谷步数、Newton-Schulz 范围、DeepSeek-V3 与 WSD 的步数账在构建时现算;标「浏览器实训」的来自本章 MicroTrainer 的默认设置(种子 1,Chrome 实测);DeepSeek-V3 数字出自其报告 §4.2(本地 PDF);Moonlight「约 2 倍」出自 arXiv 2502.16982 摘要;Kimi K2「max logit > 1000」出自 arXiv 2507.20534;Qwen3.8-Flash-Next 为官方技术报告自报。

证据三问

问本课的回答
① 它解决什么问题?常数学习率在谷底附近停不稳;cosine 必须预先定总步数,中途改主意要重跑
② 数学上为什么可能有效?二次近似下,步长决定参数围绕谷底波动的幅度,步长变小才能落得更低 ;cosine、WSD 的具体形状没有最优性推导
③ 实验上为什么最后选了它?靠实验,而且有分歧:MiniCPM、Hägele 支持 WSD 的可复用性;Kimi K3 在独立调优后选 cosine;DeepSeek 用两者的混合

一句话

📌

大学习率负责走远,小学习率负责落底;WSD 把「何时落底」推迟到决定停下的时候。

🚦

先停在这里:记住「常数停不稳、cosine 要先定长度、WSD 可分叉但未必最低」,进下一课。
再深一层:读 Kimi K3 报告 §3.2 那一段(本地 PDF 第 10 页),再读 Hägele 的摘要,想想它们各自在回答什么问题。

下一个缺口

学习率的时间表定了。可训练中偶尔会遇到一批「异常」数据,梯度一下子大了几十倍——一次超大的梯度,会不会把模型甩飞? → 第 6 课:一个超大的梯度会把模型甩飞吗?

回扣先记下:WSD 的衰减段 loss 下降得最快,「这段时间喂什么数据最划算」会在 C7(数据)兑现。

你可能会问

这一课还没有收到真实提问;你的提问会记到 05-问答记录/C5-L5.md。

🔍 为什么多数配置停在峰值的 10%,而不是降到 0?

公开报告里只写了做法,没有给出选择 10% 的推导;GPT-3 的写法是「降到 10% 之后以 10% 继续训练」。这属于经验选择。我们的小模型也用 10%(与项目 optim.py 的 min_lr_ratio = 0.1 一致),你可以在项目里改 train.min_lr_ratio 自己试。