学习率调度的艺术:Warmup、Cosine Decay 与 WSD 背后的原理
学习率调度的艺术:Warmup、Cosine Decay 与 WSD 背后的原理
学习率是深度学习中最重要的单个超参数。选错了,再好的架构也是废铁。但比选一个固定值更重要的是——如何在训练过程中动态调整它。
故事从一个灾难开始
想象你刚买了一辆超级跑车,引擎冷得冰凉,你一脚油门踩到底。会发生什么?引擎可能直接熄火,变速箱可能打滑,甚至可能把传动轴扭断。
训练一个大语言模型的前几百步,面临的就是完全相同的困境。
模型的所有参数都是随机初始化的——就像一个从未见过世界的婴儿,对一切一无所知。在这个状态下,梯度信号是嘈杂的、方向是混乱的、损失景观是崎岖的。如果你此时就用一个很大的学习率去更新参数,模型会做出巨大的、方向错误的跳跃,可能直接”飞”到损失景观中无法恢复的区域——loss 爆炸、NaN、训练彻底崩溃。
但另一方面,如果你一直用一个很小的学习率,训练会慢得令人绝望。100 万步都走不到一个好的最小值,而你的 GPU 集群每小时烧掉几万美元。
这就是学习率调度(Learning Rate Schedule)要解决的核心问题:如何在训练的不同阶段,用不同的”油门力度”,既不翻车、又不龟速?
现代 LLM 训练中,几乎所有成功的模型——GPT-3、LLaMA、Chinchilla、Mistral——都使用某种形式的学习率调度。最经典的模式是三个字:先升后降。先小心地加速(Warmup),达到巡航速度后,再缓慢减速直到训练结束(Decay)。
但为什么是这个模式?为什么不能一直用固定学习率?”先升后降”的每个阶段到底在做什么?让我们从物理直觉开始,一步步拆解。
第一幕:Warmup——为什么要先慢后快
问题:训练开始时到底出了什么事?
训练的前几步有三个同时存在的麻烦:
麻烦一:Adam 优化器的”冷启动”问题。 Adam 维护两个滑动平均:梯度的均值 $m_t$ 和梯度平方的均值 $v_t$。它们都初始化为零。虽然 Adam 有偏差修正(bias correction),但在第一步,修正因子是 $1/(1-\beta_2^1) = 1/(1-0.999) = 1000$。这意味着 Adam 在第一步的行为完全由一个单独的梯度样本决定——这就像看了一分钟新闻就对世界政治做出全面判断。
麻烦二:梯度方向极度不稳定。 随机初始化的模型还没学到任何结构。不同 batch 计算出的梯度可能指向几乎相反的方向。这时大步更新,有一半概率是在往错误方向狂奔。
麻烦三:损失景观极度崎岖。 随机初始化点附近的损失景观曲率(sharpness)通常很高。高曲率意味着”悬崖”多——大步一跨就可能跌落,触发 loss 爆炸。
直觉:Warmup 在做什么?
2024 年 NeurIPS 的一篇论文(Kalra & Barkeshli, “Why Warmup the Learning Rate?”)给出了一个优雅的解释:
Warmup 的核心作用不是”让模型慢慢学”,而是把模型推到损失景观中更平坦、条件数更好的区域。
这是什么意思?想象损失景观像一个山脉。随机初始化可能把你放在一个尖锐的山脊上——稍微偏一点就会跌落。Warmup 的作用是:在学习率还很小的时候,模型做的微小更新帮助它从尖锐的山脊滑到相对平坦的山谷。一旦到了平坦区域,损失对参数变化不那么敏感了,这时再加大学习率就安全了。
用物理学的语言说:warmup 降低了损失函数的 sharpness(Hessian 最大特征值),使得系统能够容忍更大的学习率而不失稳。
论文还发现了两个有趣的机制:
-
渐进锐化(Progressive Sharpening):在某些初始化下,模型的 sharpness 会自然增长。此时 warmup 和自然锐化是”竞争关系”——warmup 试图让模型找到平坦区域,而自然演化在增加曲率。这解释了为什么 warmup 步数太短可能不够。
-
损失弹射(Loss Catapult):当学习率超过当前 sharpness 所能承受的阈值时,loss 会突然跳高——但这次跳高反而迫使模型跳到 sharpness 更低的区域。这像是”以毒攻毒”——短期的不稳定换来了长期的稳定。
实践:Warmup 怎么做?
最常见的实现是 线性 Warmup:
\[\eta_t = \eta_{\text{target}} \cdot \frac{t}{T_{\text{warmup}}}\]从接近零线性增长到目标学习率。典型的 warmup 步数:
| 模型 | 总训练步数 | Warmup 步数 | 占比 |
|---|---|---|---|
| GPT-3 175B | 300K | 375 | 0.1% |
| LLaMA-2 70B | ~500K | 2000 | 0.4% |
| Chinchilla 70B | 1.4M | 2000 | 0.14% |
| 小模型微调 | 10K | 100-500 | 1-5% |
一个经验法则:预训练通常 warmup 1000-2000 步(占总步数不到 1%),微调可能 5-10%。关键不是步数本身,而是给 Adam 的二阶矩估计足够的时间来收集可靠的统计信息。
第二幕:Cosine Decay——为什么学习率要像日落
问题:为什么不能一直用峰值学习率?
直觉上,既然大学习率训练得快,为什么不一直保持最大值?
答案涉及 收敛精度 和 泛化 两个维度:
收敛精度:大学习率意味着大步长。在训练后期,模型已经接近一个好的最小值,此时需要小步精确调整。继续用大步长会让模型在最小值附近来回振荡,永远无法真正”坐进”最低点。就像你开车到了目的地附近,继续踩油门只会让你反复冲过停车位。
泛化(找到平坦最小值):大学习率本身有一个好处——它自带隐式正则化,因为大步长的”噪声”效果会让模型跳出尖锐的最小值(尖锐最小值虽然训练 loss 低,但泛化差)。然而在训练末期,我们希望模型安定在一个平坦且低的最小值中。这需要逐渐降低”温度”——减小学习率。
这和物理中的 模拟退火(simulated annealing) 是同一个道理:先在高温下充分探索(大学习率),再慢慢降温让系统凝固在能量最低的状态(小学习率)。
核心直觉:为什么是 Cosine 而不是线性?
2017 年,Loshchilov 和 Hutter 在 SGDR 论文中提出了余弦退火(Cosine Annealing),随后被整个 LLM 社区采用。公式很简单:
\[\eta_t = \eta_{\min} + \frac{1}{2}(\eta_{\max} - \eta_{\min})\left(1 + \cos\left(\frac{\pi \cdot t}{T}\right)\right)\]但为什么是余弦形状而不是简单的线性衰减?
Cosine 形状的关键优势在于它的”两头慢、中间快”:
-
前期下降慢:刚从峰值开始,学习率几乎不变。模型可以在大学习率下充分探索,不会因为过早衰减而困在局部最小值。
-
中期下降快:训练中期,大方向已经确定,模型需要从探索模式切换到利用模式。Cosine 的中段斜率最大,快速完成这个过渡。
-
后期下降慢:接近最小学习率时,变化趋于平缓。模型可以在低学习率下做精细调整,慢慢”沉淀”到最小值底部。
相比之下,线性衰减在每个阶段的下降速度相同——前期降得太快(浪费了探索时间),后期又不够平稳。
一个更深的视角:累积学习率与幂律
2025 年的研究发现了一个惊人的联系:如果你计算”到第 $t$ 步为止所有学习率的累积和” $\sum_{i=1}^{t} \eta_i$,loss 的下降与这个累积学习率之间存在幂律关系。Cosine 调度恰好使得这个累积值的增长曲线在训练全程都保持”最优效率”——在每个时刻都恰好平衡了”探索”与”收敛”的需要。
翻译回人话:Cosine 衰减不是随便选的一条漂亮曲线,而是在”总共能走多远”和”每一步走多稳”之间找到了接近最优的平衡。
第三幕:WSD——打破”必须预知终点”的枷锁
问题:Cosine 的致命缺陷
Cosine 调度有一个巨大的实际问题:你必须在训练开始前就决定总步数 $T$。
为什么这是问题?因为大模型训练往往持续数周甚至数月,期间你可能发现:
- 模型还在快速进步,想继续训练更久
- 数据加载遇到问题,需要延长
- 计算资源临时增加了,想多训一些
- 需要在中途接上新数据做持续训练
但一旦 Cosine 调度开始运行,学习率的整条曲线就被锁死了。如果你决定训练更长,要么从头重新训(代价巨大),要么在学习率已经接近零的时候继续(几乎没有学习能力)。
这就像你给汽车设定了”到北京自动停车”,但开到半路发现其实应该去上海。
核心直觉:WSD 的三段式智慧
2024 年,MiniCPM 团队提出了 Warmup-Stable-Decay(WSD)调度,优雅地解决了这个问题:
学习率
↑
| /‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\
| / \
| / \
| / Warmup → Stable → Decay \
| / \__
+————————————————————————————————————————→ 训练步数
三个阶段:
- Warmup:和以前一样,线性升到峰值
- Stable:保持峰值学习率不变,想训多久训多久
- Decay:需要结束时,执行一个快速衰减(通常用 $1 - \sqrt{t/T_{\text{decay}}}$ 或指数衰减)
WSD 的革命性在于:Stable 阶段可以无限延长。你可以在任何想停的地方”分叉”一个 Decay 分支,得到一个高质量的最终模型——而主干继续以恒定学习率训练。
为什么 Stable 阶段有效?
一个合理的疑问:恒定学习率难道不会导致模型在最小值附近振荡,无法收敛?
答案是:确实如此——但这不一定是坏事。
ICLR 2025 的论文(”Understanding WSD: A River Valley Loss Landscape”)提出了一个漂亮的类比。把损失景观想象成一个河谷:
- 河谷有一个沿着河流方向的缓坡(代表真正有意义的 loss 下降方向)
- 河谷横截面是 U 形的(代表”振荡”方向)
在 Stable 阶段,恒定学习率让模型沿着河谷方向持续前进(loss 稳步下降),同时在横截面方向来回振荡。这个振荡并不浪费——它实际上在帮模型探索河谷中不同的路径。
当 Decay 阶段开始时,振荡被压制,模型安定在河谷底部的最低轨迹上——这最后的 loss 下降就是 decay 带来的额外收益。
WSD vs Cosine:实际对比
MiniCPM 的实验表明,WSD 在最终性能上与精心调参的 Cosine 调度基本持平,但在灵活性上碾压:
| 特性 | Cosine | WSD |
|---|---|---|
| 需要预知总步数 | ✅ 必须 | ❌ 不需要 |
| 支持持续训练 | ❌ 困难 | ✅ 天然支持 |
| 中途取模型 | ❌ 只有最后一个好 | ✅ 任何时候 branch decay |
| 调参难度 | 较高($T$ 很敏感) | 较低(stable 阶段自由) |
| 最终 loss | 基准 | ≈ 基准(±0.1%) |
JetMoE、DeepSeek、Qwen 等新一代模型也在采用 WSD 或其变体。
第四幕:不衰减行不行?——一个反直觉的发现
2026 年 3 月的一篇新论文提出了一个有趣的观点:如果你的目标不是预训练 loss 最低,而是下游微调效果最好——可能根本不需要学习率衰减。
他们的发现是:学习率衰减确实降低了预训练 loss,但这个”好处”来自模型过度拟合到当前训练数据分布。当你随后做 SFT(监督微调)时,衰减模型反而不如恒定学习率训练的模型灵活——后者更容易适应新任务。
这就像一个运动员:严格按固定计划训练到极致(cosine decay 到 loss 最低),可能反而不如一个保持”通用体能”的运动员在面对新项目时适应得快。
当然,这是一个很新的结论,需要更多验证。目前的主流实践仍然是使用衰减。
数学附录:三种调度的精确公式
对于想在代码中实现这些调度的读者,这里是精确公式。
Linear Warmup
\[\eta_t = \eta_{\text{target}} \cdot \min\left(1, \frac{t}{T_w}\right)\]$T_w$ 是 warmup 步数。简单,鲁棒,几乎没有需要调的参数。
Cosine Decay(含 Warmup)
\[\eta_t = \begin{cases} \eta_{\text{max}} \cdot \frac{t}{T_w} & \text{if } t \leq T_w \\[6pt] \eta_{\min} + \frac{1}{2}(\eta_{\max} - \eta_{\min})\left(1 + \cos\left(\frac{\pi(t - T_w)}{T - T_w}\right)\right) & \text{if } t > T_w \end{cases}\]典型参数:$\eta_{\min} = 0.1 \cdot \eta_{\max}$(GPT-3 用的是 $\eta_{\min} = 0$)。
WSD
\[\eta_t = \begin{cases} \eta_{\max} \cdot \frac{t}{T_w} & \text{Warmup: } t \leq T_w \\[4pt] \eta_{\max} & \text{Stable: } T_w < t \leq T_s \\[4pt] \eta_{\max} \cdot f\left(\frac{t - T_s}{T_d}\right) & \text{Decay: } t > T_s \end{cases}\]其中衰减函数 $f$ 常用 $f(x) = 1 - \sqrt{x}$(MiniCPM 用的)或 $f(x) = \frac{1}{2}(1 + \cos(\pi x))$(cosine 衰减段)。衰减段通常占总训练的 10-20%。
这意味着什么
学习率调度不是一个随便选择的超参数——它编码了我们对训练动态的深层理解:
- 训练开始时模型脆弱(warmup 保护它)
- 训练中期需要大胆探索(峰值学习率维持住)
- 训练末期需要精确收敛(decay 把模型推入最小值)
- 灵活性比最优性更重要(WSD 牺牲微小的最优性换来巨大的实用价值)
如果你正在训练自己的模型,一个安全的默认选择是:
- 微调:Linear warmup 5-10% + Cosine decay(标准且经过验证)
- 预训练:WSD(灵活、适合不确定训练时长)
- Warmup 步数:1000-2000 步或总步数的 1%,取较大者
学习率调度是训练稳定性的”看不见的手”——做对了你不会注意到它,做错了训练直接崩溃。理解它背后的原理,是成为一个合格的 LLM 训练者的必修课。
下一篇预告
我们讲了学习率”怎么调”,但还没回答一个更根本的问题:学习率到底该设多大? 当你把模型从 7B 放大到 70B,学习率应该怎么变?当你把 batch size 翻倍,学习率要跟着翻倍吗?这涉及到一个精妙的理论——µP(Maximal Update Parameterization),它让超参数可以从小模型直接迁移到大模型。下次我们来拆解这个魔法。