Adam 里的 weight decay 为什么失效了?
本课一问:训练 LLM 几乎都要加 weight decay,每一步把参数往 0 轻轻拉一点。把它照 SGD 的老办法加进 Adam 的梯度里,它还是原来那个 weight decay 吗?
这一课先不碰:学习率怎么随步数变(第 4、5 课);weight decay 为什么能帮助泛化——那是正则化的话题,本课只看「它有没有按设计的力度起作用」。
从已有出发
「小明」「小王」这类常见 token 的 embedding 行几乎每一批都收到梯度;语料里只出现过几次的字,那一行很少收到(第 2 课的零梯度)。weight decay 的本意是:不管梯度大小,每个参数每步都按同一个比例往 0 缩一点,防止某些权重无限长大。
这一课的「更新」那一格多了一件事。它有两种写法,在 SGD 里完全等价:
- weight decay:更新完再乘一个略小于 1 的数,θ ← (1 − ηλ)θ − η·g;
- L2 正则:在 loss 里加 ½λ‖θ‖²,它的梯度是 λθ,于是 θ ← θ − η(g + λθ)——展开就是上一行 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条SGD 下两者只差 λ 的重新标定;论文摘要原话:「equivalent for standard SGD」 出处Loshchilov & Hutter 2017 §2 ↗ 。
所以很多实现(包括 PyTorch 的 torch.optim.Adam)就用 L2 的写法:把 λθ 加进梯度,交给优化器。用两个例子看它在 Adam 里的后果:先用公式算一步的账(输入是你设定的,不是测量值),再在浏览器里的小模型上真训练 400 步。
先自己设计:照搬 L2,把 λθ 加进 Adam 的梯度
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
AdamW 的改动只有一处:把 λθ 从梯度里拿出来,放到 Adam 的自适应分母之外,直接乘到参数上:θ ← θ − η·(m̂ ⁄ (√v̂ + ε) + λθ)。于是左边两个参数都按同一个 η·λ·w = 10−4 往回拉(左表最后一列),与梯度大小无关。下面是这行公式的逐项解释,再往下用真训练看两种写法的区别。
试三件事(在「朴素方案」的计算器里):① 默认值读最后一行:L2 的衰减比是 1,000 倍,AdamW 是 1 倍。② 把两个参数的梯度均方根拖成一样:L2 的差距消失——问题只在「不同参数梯度尺度不同」时出现,而第 2 课说过,这正是常态。③ 把 λ 拖大拖小:L2 那两列同比例变,两行的比值始终等于梯度均方根之比。
改进:AdamW
🔍 AdamW 的「W」是什么?
AdamW 的 W 是解耦的权重衰减(decoupled weight decay)。论文标题里根本没有「AdamW」——它叫《Decoupled Weight Decay Regularization》。它的贡献不是发明一个新优化器,而是指出一个大家都没注意到的实现问题:在 Adam 里,以 L2 形式塞进梯度的衰减会被自适应分母除掉,于是「参数或梯度的历史幅度大的权重,被约束得更少」(论文 §1 原话的意思)。解耦后衰减直接作用在参数上。
真训练一遍:同一个 λ,衰减到了哪里
三个 run 共用同一个初始化和取批顺序,学习率 3×10−3(常数,前 20 步 warmup)、400 步,只改衰减的写法:
数字来源:浏览器实时训练(纯 JS,Float64)。模型、语料、种子写在上方;同一组对照里所有 run 共用初始化和取批顺序,只改被比较的那一项。换种子重跑可以看到噪声有多大——差异小于种子间波动时不下结论。
试三件事:① 默认 λ = 0.1:embedding 行范数从 3.99 出发,Adam+L2 压到约 1.19,AdamW 约 3.57,验证 loss 约 3.81 对 3.66。② 切到 λ = 0.01 再训练:AdamW 的行范数回到约 3.95(衰减变弱了),Adam+L2 仍是约 1.2。③ 再切到 λ = 0.3:AdamW 降到约 2.85,Adam+L2 还是约 1.19——下方累积表把你跑过的 λ 排在一起,Adam+L2 那一列几乎不动。
为什么 Adam+L2 的结果几乎与 λ 无关?这个小模型里 embedding 的梯度均方根只有 10−4–10−3 量级(第 2 课:初始化时约 2×10−4,训练 100 步后约 1.5×10−3),而 λw 在 0.1 量级。送进 Adam 的「梯度」g + λw 几乎全是 λw,于是 m̂ ÷ √v̂ ≈ λw ÷ |λw| = w 的符号:每步把每个参数往 0 推约一个 η,λ 被分子分母约掉了。这就是计算器里那个比例关系的另一个极端:梯度越小的参数,L2 在 Adam 里越接近「按符号推满 η」。
两种失效其实是同一件事:衰减一旦混进梯度,它的力度就由 √v̂ 决定,而不是由 λ 决定。
这里的 loss 对比只说明「写法不同,结果不同」,不说明 AdamW 在所有任务上都更好。这个 2.5 万参数的小模型 400 步就过了一遍多数据,正则化的作用有限;论文的泛化收益来自图像分类上的实验。本课要带走的是可证明的那一半:两种写法不等价,Adam+L2 的实际衰减力度不受 λ 控制。
现代做法:AdamW 成为默认,剩下的是「哪些参数衰减」
- GPT-3:weight decay 0.1;DeepSeek-V3:AdamW,β₂ = 0.95,weight decay 0.1;Kimi K3:weight decay 0.1 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条三份报告原文;0.1 是惯例,不是推导出来的最优值 出处DeepSeek-V3 §4.2;GPT-3 附录 B;Kimi K3 §3 ↗ 。
- 哪些参数不衰减:我们项目只对二维以上的参数(矩阵)做衰减,归一化增益和偏置不做(
04-实践项目/minillm/optim.py);OLMo 2 连 embedding 也不做衰减(R4 从原文摘录)。 - 换成 Muon 的模型也保留解耦衰减:DeepSeek-V4 的 Muon 与 AdamW 部分都用 weight decay 0.1,写法就是 W ← W·(1 − ηλ) − η·O 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条Moonlight 把「给 Muon 加 weight decay」列为 Muon 能扩展到大模型的两个关键之一(第 7 课) 出处DeepSeek-V4 算法 1 与 §4.2.2 ↗ 。
⚠ 常见误解
「weight decay 就是 L2 正则。」只在 SGD 下成立。在 Adam 这类自适应优化器里,两者不等价:L2 的梯度 λθ 会被 √v̂ 除掉。
「torch.optim.Adam(weight_decay=0.1) 就是 AdamW。」不是。PyTorch 源码里这个参数的写法是 grad = grad.add(param, alpha=weight_decay),文档写着「weight decay (L2 penalty)」——实现的正是本课的朴素方案。要解耦衰减,用 torch.optim.AdamW,或者在新版 Adam 里另开 decoupled_weight_decay=True。
「λ 调大,约束就变强。」在 AdamW 里是;在 Adam+L2 里不一定——上面的实验里 λ 从 0.01 到 0.3 翻了 30 倍,embedding 行范数只从 1.22 变到 1.19。
在演进地图上的位置
- 全量梯度下降
- 小批量随机梯度下降 SGD
- 动量(重球法)
- Adam:每个参数一把尺子
- AdamW:解耦权重衰减
- 学习率 warmup
- 学习率衰减:cosine → WSD
- batch 大小:渐增,还是一开始就大
- 全局梯度范数裁剪
- Muon:按矩阵的奇异方向更新
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | 让 weight decay 真的按 λ 设计的力度作用在每个参数上,与梯度尺度无关 |
| ② 数学上为什么可能有效? | 「SGD 下 L2 与 weight decay 等价、Adam 下不等价」是代数事实 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条直接展开更新式即可;Adam+L2 的衰减项是 ηλθ/(√v̂+ε) 出处Loshchilov & Hutter 2017 命题 1、2(证明在附录 A) ↗ ;weight decay 为什么帮助泛化,是正则化的理论动机,不是定理 证据等级 · 有理论动机 有数学或机制上的理由说明它可能有效,但没有证明它一定更好。最后是否采用,仍然要看实验。 本条控制权重尺度 → 限制模型复杂度,这是动机层面的解释 出处待补 |
| ③ 实验上为什么最后选了它? | 论文在图像分类上报告 AdamW 明显改善了 Adam 的泛化、并让学习率与衰减系数两个超参更好分开调 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条CIFAR-10 与 ImageNet32x32 上的 ResNet 实验;LLM 上的普及是后来的工程选择 出处Loshchilov & Hutter 2017 摘要与图 2、图 4 ↗ ;之后 GPT-3、DeepSeek-V3、Kimi K3 等都用解耦衰减 |
一句话
衰减应该作用在参数上,不应该混进梯度再被 √v̂ 除掉。
先停在这里:记住「L2 进 Adam 会被 √v̂ 除,AdamW 把衰减拿出来」,进下一课。
再深一层:打开本地论文 S3-05,看算法 2 里 Adam+L2 与 AdamW 只差的那一行,再读命题 2(自适应优化器下两者不等价)。
下一个缺口
优化器本身(AdamW)准备好了。可是第 2 课埋下的那件事还没解决:第 1 步,每个参数都走满一个 η。如果一开局就用峰值学习率,会怎样? → 第 4 课:为什么一开局就用峰值学习率会炸?
你可能会问
这一课还没有收到真实提问;你的提问会记到 05-问答记录/C5-L3.md。
🔍 为什么归一化层的增益、偏置通常不做衰减?
它们是一维参数,作用是调节尺度或平移,本来就不多;把归一化增益往 0 拉,相当于逼着整层输出变小,和它「恢复尺度」的作用对着干。常见实现(包括我们项目的 optim.py)只对矩阵做衰减。这是工程惯例,各家不完全一样:OLMo 2 连 embedding 也不衰减,DeepSeek-V4 把 RMSNorm 权重交给 AdamW 并用 weight decay 0.1(报告 §4.2.2 的写法是对 AdamW 部分整体设 0.1,是否排除一维参数没有单独说明)。