大模型训练跑到一半 loss 突然发散、把优化器从 Adam 换成 SGD 后收敛反而更慢、同一个模型在不同框架里效果对不上——这些坑我都在实际训练中踩过。排查到最后十有八九不是网络结构写错了而是对优化器的理解差了一层。优化器看着就是一行 API 调用但它内部的状态变量、更新规则、数值稳定性处理直接决定了训练的收敛速度、最终精度以及你能不能顺利跑完一个 7B 甚至 70B 级别的模型。这篇文章把大模型训练里最常用的几个优化器——SGD、EMA、Momentum、RMSProp、Adam/AdamW——从头到尾掰开讲一遍重点放在为什么这么做和实际训练里怎么配上适合正在做大模型预训练或微调、想彻底搞懂训练配置的开发者。1. 优化器到底在解决什么问题别把梯度下降想得太简单1.1 优化器的本质是怎么走下一步很多人第一次接触深度学习时记住的第一句话是梯度下降就是沿着梯度的反方向更新参数。这句话没错但它默认了一个前提每一步的梯度是可靠的、各方向等价的。真实训练里这个前提几乎不成立。模型的 loss 表面极其崎岖有陡峭的峡谷、平坦的高原、尖锐的局部极小值。梯度本身携带的是当前位置最陡的方向但这个方向只在小范围内有效跨过一步之后可能就完全变了。再加上我们用的是小批量mini-batch梯度每个 batch 的梯度都有噪声直接用它更新参数就像蒙着眼睛在山里走路每走一步都有人在你耳边报一个可能出错的方向。优化器要解决的就是三件事方向怎么修正、步长怎么定、历史信息怎么用。SGD 是最朴素的听导航、走固定步长Momentum 加上了惯性RMSProp 学会了根据地形调整步长Adam 把两者结合AdamW 则修正了正则化在大模型场景下的副作用。理解了这条演进线你再看任何新优化器都不会觉得陌生。1.2 大模型场景让优化器问题被放大了小模型时代优化器的差异往往就几个点的准确率。到了大模型时代问题完全变了维度。首先7B 模型的参数量意味着优化器的每个状态变量都要乘以 70 亿。Adam 有两个状态变量一阶矩和二阶矩在 fp32 下每个变量占 4 字节光优化器状态就是 2×4×7B 56GB。这还没算参数本身、梯度和主权重副本。所以在大模型训练里选优化器不只是收敛问题是显存预算问题。这也是为什么大模型普遍用 AdamW 而不是更花哨的优化器——在满足收敛需求的前提下它的状态开销是标准答案级别。其次大模型训练动辄几万步学习率的衰减策略、梯度裁剪的时机、混合精度下的数值范围这些全都和优化器的内部状态耦合在一起。一个很小的 epsilon 设置失误可能在几千步后才显现为 loss 异常。理解优化器内部怎么算的不是为了面试背公式而是为了在训练出问题时能快速定位。2. SGD 与 EMA最朴素的起点和最被低估的技巧2.1 SGD 不只是笨办法它是理解一切的坐标系SGD随机梯度下降的更新规则简单到可以用一行写完w w - lr * g其中 g 是当前 batch 的梯度。严格说深度学习里说的 SGD 通常是 mini-batch 梯度下降——每次用一个 batch 的数据计算梯度而不是单个样本也不是整个训练集。batch 大小不同梯度噪声的性质就不同这也直接影响了后面所有优化器的表现。我见过不少同学觉得 SGD 太低效直接跳过不学。但实际做实验时你会发现SGD 的行为是最容易预测的它适合当作对照组。如果你想验证一个新加的正则项是否有效先用 SGD 跑通再用 AdamW 跑能明显区分是优化器带来的收益还是方法本身带来的收益。SGD 的关键超参数只有一个学习率。但学习率怎么取取决于你用的 batch size。一个在实践里反复验证的经验是batch size 翻倍学习率也应该大致线性放大但要设置梯度裁剪兜底。这背后的直觉是更大的 batch 提供了更稳定的梯度估计噪声更小因此可以走更大的步子。但步子太大会踩空所以裁剪阈值得跟着调整。这个learning rate scaling的规则在后来的大模型训练里被进一步形式化了但最朴素的源头就在 SGD 这里。SGD 在 Transformer 大模型上几乎不会被直接用因为它的收敛速度确实太慢。但它的稳定性很有参考价值——如果一个优化器在大模型上表现还不如 SGD那基本说明超参数配置出了问题而不是优化器本身的问题。2.2 EMA 权重平均训练时额外的影子变量EMAExponential Moving Average指数移动平均严格来说不是一个优化器而是一种模型参数的后处理技巧。它在大模型训练里极其常用但经常被忽视。做法是训练过程中维护一份参数的指数移动平均ema_w alpha * ema_w (1 - alpha) * w这里 alpha 通常取 0.9 到 0.9999 之间。每个 step 结束后用当前权重 w 去更新 ema_w 这份影子权重但这个影子权重不参与训练只在评估和推理时使用。为什么要这样做因为训练过程中权重在剧烈波动尤其是使用大学习率或 Adam 这类自适应优化器时最后收敛到一个中心点附近的权重往往比某个特定 step 的权重更稳健。EMA 相当于对权重轨迹做了低通滤波平滑掉了振荡让最终模型对超参数不那么敏感泛化能力通常更好。我在实际训练中遇到的一个细节是EMA 什么时候开始更新、要不要把 BN 的统计量如果模型里有的话同步进去这些都要想清楚。更关键的是如果你在训练过程中保存了 EMA 权重作为最终 checkpoint但继续训练时加载的却是普通权重两边就错位了。我见过一次因为 checkpoint 里混了 EMA 权重和优化器状态不匹配导致恢复训练后 loss 突然跳高的问题。EMA 还有一个用途是辅助判断模型的训练状态。对比当前权重和 EMA 权重在验证集上的表现差能大致判断模型是否还在剧烈振荡。如果两者差距持续很大说明学习率可能偏高如果差距快速缩小说明模型正在收敛。3. Momentum 与 RMSProp两大改进方向的分水岭3.1 Momentum给梯度加上惯性SGD 每个 step 都是独立决策完全不管之前朝哪个方向走。这在陡峭峡谷和噪声梯度下会来回震荡。Momentum 的核心想法是让每一步的更新方向不仅依赖当前梯度还要结合历史的运动趋势。它的更新规则常见有两种写法但内在等价v mu * v g w w - lr * v这里 mu 是动量系数一般取 0.9 或者 0.99。v 是累积的速度向量。直觉上如果当前的梯度方向和历史累积方向一致v 会越来越大参数就沿着这个方向快速前进如果当前梯度噪声很大、方向乱跳v 会把噪声平滑掉让更新方向保持稳定。另一个等价的写法是v mu * v (1 - mu) * g w w - lr * v两种写法里 v 的尺度不同但最终效果类似。在实际代码里用的通常是第二种形式因为它和 Adam 的一阶矩更新方式统一方便共享实现。注意这里决定了学习率的有效尺度不同所以你从某个框架源码抄 Momentum 实现去对比另一个框架时如果公式不一样学习率可能需要按 (1 - mu) 的因子换算。Momentum 在大模型训练里的直接价值主要体现在预热的初期。刚起步时梯度方向不稳定Momentum 能帮助模型在 loss 面比较平缓的高原区域更平滑地起步。不过一旦引入自适应学习率RMSProp/AdamMomentum 的作用就被一阶矩项吸收了很少单独出现。3.2 RMSProp给每个参数配一把可变的尺子SGD 和 Momentum 有一个共同问题所有参数共享同一个学习率。但模型里不同参数的梯度尺度差异极大。某些层比如 embedding 层的梯度天然就大某些层比如深层网络的末端的梯度天然就小。统一学习率意味着梯度大的参数容易震荡梯度小的参数又更新太慢。RMSProp 提出的解法是为每个参数维护一个梯度平方的指数移动平均s rho * s (1 - rho) * g^2 w w - lr * g / sqrt(s eps)s 相当于对每个参数的历史梯度大小做了个估算。更新时用当前梯度除以 sqrt(s) 做归一化梯度大的参数有效学习率被压低梯度小的参数有效学习率被放大。rho 通常取 0.9 到 0.99eps 是为了防止除以零的微小常数一般取 1e-6 到 1e-8。这个按参数自适应缩放的思想非常关键它让模型在不同尺度的层之间都能以合适的步长前进。但 RMSProp 也有代价它只看梯度的大小不看梯度的方向历史所以在噪声大、方向反复的场景下还是不够稳。另外s 是梯度平方的移动平均它对异常大的梯度反应很快如果某个 step 出现极端梯度比如 loss spikes 会瞬间变大接下来好几个 step 的有效学习率都会被压低恢复较慢。实际调 RMSProp 时我比较注意 rho 的选择。rho 越大s 的估计越长远对瞬时大梯度越不敏感但也容易在梯度分布快速变化的训练阶段反应迟钝。在 Transformer 上很多人用 0.99 起步如果训练后期出现 loss 振荡可以降到 0.95 试试。3.3 为什么说 Momentum 和 RMSProp 是两条腿Momentum 解决的是方向问题——利用历史梯度方向稳定更新轨迹RMSProp 解决的是步长问题——根据历史梯度尺度为每个参数分配合理的学习率。这两个问题恰好正交方向归方向步长归步长。Adam 的出现不是创新一个概念而是把这两个互补的思路拼在一起。理解这一点你就明白为什么 Adam 的公式里有两个独立的状态变量、两个独立的超参数 beta1 和 beta2。它们分别对应 Momentum 的 mu 和 RMSProp 的 rho职责完全不同不要混为一谈。有些人调 Adam 时只把学习率拐来拐去却不动 beta1/beta2这其实浪费了 Adam 一半的调节空间。当训练不稳定时先调 beta2 看二阶矩的响应当收敛太慢或方向犹豫时再考虑 beta1。这是我在实践中比较有效的排查顺序。4. Adam 的核心机制与实用配置一个公式里藏着三件事4.1 Adam 的完整更新流程拆解Adam 把 Momentum 和 RMSProp 合在了一起还多做了一个偏差校正bias correction。完整流程如下m beta1 * m (1 - beta1) * g s beta2 * s (1 - beta2) * g^2 m_hat m / (1 - beta1^t) s_hat s / (1 - beta2^t) w w - lr * m_hat / (sqrt(s_hat) eps)m 是一阶矩梯度方向的移动平均s 是二阶矩梯度平方的移动平均t 是当前 step 数。m_hat 和 s_hat 是偏差校正后的估计。偏差校正解决的是一开始的问题t1 时m 只是 g 的 (1-beta1) 倍远小于真实均值s 同理。如果不校正训练初期有效步长会被严重低估相当于白白浪费最初几千步。在长训练里这点影响会被时间摊薄但在几百步就结束的小任务里偏差校正非常关键。这里有一个容易忽略的细节m_hat 是 m 除以 (1-beta1^t)s_hat 是 s 除以 (1-beta2^t)。因为 beta2 通常接近 1例如 0.999(1-beta2^t) 在一开始的衰减比 (1-beta1^t) 慢得多所以 s_hat 在早期要比 s 大挺多对应的有效学习率更小。这就是 Adam 在训练早期天然带一点 warmup效果的来源。4.2 大模型训练里 Adam 超参数的常见配置大模型训练里 Adam 的默认配置如下超参数常规深度学习默认值大模型预训练常用值说明lr1e-33e-4 ~ 1e-4需要配合 schedule 和 batch sizebeta10.90.9方向平滑一般不动beta20.9990.95 ~ 0.999越大越平滑但后期易震荡eps1e-81e-6 ~ 1e-8fp16/bf16 下可能需要调大weight_decay00.01 ~ 0.1AdamW 下独立设置为什么大模型预训练经常把 beta2 调到 0.95 而不是默认的 0.999原因在于大模型的 loss 面有大量持续时间较长的梯度方向变化beta2 太大时二阶矩的响应太慢对梯度尺度的快速变化不敏感容易在某个区间积累出不合理的有效学习率。多个团队的实验都表明 0.95 在预训练中让 loss 更稳定收敛更快。不过这个数值不是绝对的如果你的训练数据分布非常稳定0.99 也可以一旦出现反复的 loss spike优先怀疑 beta2 是否偏大。eps 这个参数在混合精度训练里要单独提一下。fp16 的最小正常数大约是 6e-5 量级bf16 的精度更低。如果 eps 取 1e-8在计算 sqrt(s)eps 时eps 几乎不起作用数值上很容易因为 s 极小导致除法结果溢出。所以很多大模型训练框架在混合精度下建议把 eps 提高到 1e-6。这个改动不大但对避免 NaN 很有效。4.3 显存开销是 Adam 的另一个隐形成本Adam 在每个参数上需要维护两个 fp32 状态变量 m 和 s。加上 fp32 的主权重副本master weight训练一个模型时每个参数的内存占用大约是参数本身fp16/bf162 字节主权重fp324 字节一阶矩 mfp324 字节二阶矩 sfp324 字节梯度fp16/bf162 字节部分框架会释放合计约每参数 16 字节。以 7B 模型为例光训练状态就是约 112GB。这还不算激活值activation和通信开销。这就是为什么大模型训练必须要各种显存优化技术——activation checkpointing、梯度累积、ZeRO 分片——Adam 的 8 字节状态变量是压不掉的大头。理解了这一点你也能理解为什么很多新优化器比如 Adafactor、狮身人面像那类会尝试减少二阶矩的存储。但至少在目前的大模型生态里AdamW 仍然是显存开销和训练稳定性之间最平衡的选择其他优化器想替代它要么收敛不稳要么在长训练里效果没优势。5. AdamW 与大模型训练关键不在 Adam在于解耦5.1 L2 正则与 Weight Decay 在 Adam 里为什么不等价很多框架的 API 里weight_decay 和 L2 正则化经常被混为一谈。传统 SGD 里L2 正则等价于在每次更新时把权重往零方向推一点w w - lr * (g lambda * w)也可以写成w w * (1 - lr * lambda) - lr * g这里的 (1 - lr * lambda) 就是 weight decay 的效果衰减比例随学习率变化。但在 Adam 里更新公式变成w w - lr * m_hat / (sqrt(s_hat) eps)如果把 L2 正则塞进梯度里正则项的梯度 lambda*w 也会被除以 sqrt(s_hat)。问题来了某一维参数如果历史梯度大s_hat 大它对应的正则项会被除以一个大的数正则效果被削弱历史梯度小的参数正则效果反而被放大。这导致正则化对不同参数产生了不一致的、不可控的强度削弱了正则本身把所有参数往零拉的本意。5.2 AdamW 的更新规则与实现形式AdamW 的解法很直接把 weight decay 从梯度里拿出来不参与 Adam 的自适应缩放直接在更新后单独操作m beta1 * m (1 - beta1) * g s beta2 * s (1 - beta2) * g^2 m_hat m / (1 - beta1^t) s_hat s / (1 - beta2^t) w w - lr * m_hat / (sqrt(s_hat) eps) w w - lr * wd * w最后一行就是解耦的 weight decay它对每个参数施加固定的衰减比例不因梯度的历史大小而改变。这个改动看起来微不足道但实验效果差异明显在同样的训练设置下AdamW 的泛化性能通常稳定优于 AdamL2尤其是在 Transformer 和大规模预训练上。另一个实现细节是 weight decay 和学习率的耦合。上面的写法里衰减幅度是 lr * wd * w意味着学习率变化时衰减的绝对量也会变。长训练里学习率会按 cosine schedule 降到接近 0此时 weight decay 的有效强度也随之降到接近 0相当于后期隐式关闭了正则化。有些框架会把 weight decay 写成固定的 w w * (1 - wd) 形式即不乘 lr。两种形式在代码里都存在切换框架时要注意确认否则换框架之后同样的 wd 值效果完全不同。大模型实践里通常用的是乘 lr 的形式配合 cosine schedule前期正则强、后期正则弱实际效果在很多任务上更好。5.3 为什么大模型训练几乎都用 AdamW从 GPT 系列到各类开源大模型预训练配置里基本清一色 AdamW。原因可以归纳为三点第一稳定性。大模型训练成本极高一次跑崩损失可能是一个月的算力。Adam 家族的自适应学习率天然比 SGD 更不容易发散而 AdamW 又修复了正则化不一致的问题属于既有稳定性又有更好泛化的选择。第二超参数习惯成熟。预训练社区的实践基本收敛到一套配置lr3e-4配合 warmup 和 cosine、beta10.9、beta20.95、wd0.1、grad clip1.0。新任务直接抄这套配置大概率能跑通不需要太多额外探索。第三配合混合精度和分布式训练实现成熟。主流框架对 AdamW 的 fp32 主权重、bf16 计算、ZeRO 分片都有深度优化这个生态优势是任何新兴优化器短期内难以追赶的。我见过有人尝试用 SGD 做全量参数微调来省钱结果收敛曲线晃晃悠悠最后效果比 AdamW 差了一截还搭进去不少调参时间。对于大模型AdamW 是稳字当头的现实选择。6. 优化器对比与选择思路一张表和一个决策流程6.1 六种优化器的核心差异对照优化器方向修正步长修正状态变量数典型场景主要局限SGD无无0基线实验、小模型收敛慢对学习率敏感EMA参数后处理无1提升模型稳健性不是独立优化器Momentum有一阶惯性无1中等规模任务各参数步长仍统一RMSProp无有二阶归一化1非平稳目标方向噪声大时稳定性差Adam有有2大多数深度学习任务正则化不一致、显存多AdamW有有2大模型预训练/微调显存最多依赖 decay 配这张表的核心信息是优化器的演进不是新的取代旧的而是每个新方案解决前一个方案的一个具体缺陷。选择时先问自己三个问题——我的训练里梯度噪声大吗不同层梯度尺度差异大吗我最后想要的是收敛速度还是最终泛化精度——答案自然指向对应的优化器。6.2 不同训练阶段的组合策略大模型训练里优化器和学习率 schedule 是绑定使用的很少单独调。一套比较成熟的组合是warmup 阶段前 1% ~ 2% 的 step学习率从 0 线性升到峰值Adam 的偏差校正也在这个阶段发挥作用两者叠加让训练平稳起步。稳定训练阶段学习率保持在峰值附近此时 Adam 的 beta2 对梯度尺度的动态适应是主要平衡手段。衰减阶段cosine 或线性衰减到峰值学习率的 10% 左右配合 AdamW 的 weight decay让模型在后期充分精细收敛。不同训练阶段出现问题时排查顺序也可以跟着这个节奏走起步期发散看 warmup 和 eps中期振荡看 beta2 和学习率峰值后期 loss 不降看 weight decay 和学习率衰减曲线。6.3 用 EMA 提升最终模型效果EMA 在微调SFT/RLHF 前的最后阶段中尤其值得用。微调数据量小模型很容易在某些 step 过拟合到某个 batch 上EMA 能有效平滑这种波动。我的习惯是 alpha 取 0.999评估时对比 EMA 权重和当前权重的指标选更好的那份作为最终产物。需要注意EMA 只对权重做平均不涉及 Gradient 的状态。如果你把 EMA 权重保存后还想接着训练必须从普通权重继续不能从 EMA 权重继续。最好在训练脚本里把训练用权重和评估用权重分开管理这个习惯能避免很多恢复训练的脏问题。7. 实战中的高频坑与排查记录7.1 训练发散时的排查顺序大模型训练 loss 突然变成 NaN 或者直接飞升第一步做的不是去改模型结构而是按这个顺序排查优化器相关设置查梯度是否异常打印每个 rank 的梯度范数如果出现个别参数梯度超过正常量级几个数量级优先怀疑数据问题或 loss 计算问题优化器只是背锅。查 eps 是否过小混合精度下 eps1e-8 很容易让二阶矩开根号后出现极端值先调到 1e-6 跑一个短实验。查 weight decay 是否过大wd0.1 是常见起点但如果你用的是没有解耦的 Adam 且框架把 wd 当 L2 处理这个值等于把正则项放进了自适应缩放效果完全不可同日而语。查梯度裁剪是否生效大模型训练基本都会开 grad clip1.0裁剪阈值过大等于没裁但裁剪太紧也可能导致有效梯度被压缩过度、训练变慢。我排查发散问题时习惯写一个最小复现脚本只保留 1000 个 batch快速迭代超参数组合。这样能把排查周期从小时级压缩到分钟级。7.2 恢复训练时优化器状态必须完整大模型训练中断后续训最经典的错误是只加载模型权重没有加载优化器的 m、s 状态和学习率调度器的 step 计数。这会导致两个问题其一Adam 的二阶矩 s 从零开始重新积累初期有效学习率被高估训练曲线会突然跳一下其二学习率 schedule 的位置从 0 重新算相当于训练全程的衰减曲线整体右移和预期的学习率节奏完全错位。如果你用框架自带的 checkpoint 功能保存整个 trainer state通常不会出问题。但如果你手写了 checkpoint 逻辑一定要把 optimizer state dict、scheduler state、EMA 影子权重、当前 step、随机数生成器状态一并保存。这五个缺一不可。7.3 混合精度下 Adam 的数值稳定性细节bf16 训练是目前大模型的主流。bf16 的指数范围跟 fp32 一致但尾数精度低梯度小的时候容易被丢掉。Adam 的 m 和 s 必须用 fp32 保存和更新这是标准做法。还有一个容易被忽略的点如果模型里某些层比如 layernorm 的 gamma的梯度长期为 0 或接近 0Adam 的 s 会非常小此时有效学习率 lr / (sqrt(s)eps) 会非常大导致这一维权重被疯狂更新。虽然 eps 有兜底但如果 eps 太小依然可能造成个别参数突然变大进而影响整个网络的数值稳定性。大模型里对这类参数做单独的梯度裁剪或者给 eps 设置下限都是实践中用过的手段。7.4 不同框架之间优化器实现的差异同一个AdamW在几个主流框架里实现细节并不完全一致。我实际对比过有些框架在初始化时就把 m 和 s 置零有些框架的 weight decay 写在 Adam 更新内部、有些写在更新外部有的框架对 bias、LayerNorm 参数做 weight decay 豁免有的不做。这些差异在单卡小模型上可能完全无感但在多机多卡上会表现为收敛曲线的细微不同。如果把自己在一个框架里的训练经验直接搬到另一个框架建议做一次 500 步左右的对齐实验确认 loss 曲线和梯度范数量级一致再投入长训练。这比训练到一半再发现对不上要划算得多。我在实际训练里的体会是优化器这块知识最值钱的部分不在公式本身而在于当你看到一条异常的 loss 曲线时能快速判断是哪一环出了问题。是学习率太快、beta2 太钝、eps 太小、还是 weight decay 没生效这需要你真正理解每个超参数控制的是方向、步长、数值稳定性、还是正则强度。四个问题对应四个旋钮往正确的方向拧比盲目搜一百组参数都管用。最后再分享一个小技巧训练脚本里把优化器状态每隔固定步数 dump 一份单独存。训练结束后你可以用这些快照回放 loss 曲线某个区间做更细粒度的归因分析。这个习惯帮我定位过好几个看起来像数据问题、实际上是优化器状态问题的疑难案例。