
聊大模型训练大家一上来通常盯着模型结构、数据配比、并行策略这些大方向。我自己几轮大模型项目跑下来感受最深的却是另一个东西真正决定一个实验能不能收敛、收得稳不稳、显存够不够用的往往是配置文件里那一行optimizer。这个系列上一篇聊了数据侧的准备工作这篇把优化器这件事单独拎出来仔细说一说因为它的影响范围比大多数人以为的要大得多。这篇会从SGD到Adam再到最近社区里讨论很多的Muon把优化器在大模型训练里的角色、原理、参数含义、显存开销和实际坑都过一遍。不管是刚接触大模型训练、准备跑第一个预训练任务的新手还是已经在微调多个模型、想换优化器省显存的老手都能从中找到可以直接抄的配置和必须避开的坑。1. 优化器在大模型训练里的真实地位1.1 它到底在做什么优化器的任务一句话就能说清根据梯度更新模型参数。但大模型训练里这件事的难度完全被这句话掩盖了。模型动辄几十亿上百亿参数Loss面超乎想象地崎岖不平梯度本身包含大量噪声显存又极度紧张。优化器不仅要让参数朝着Loss下降的方向走还要在噪声中保持稳定、在平坦区域能加速、在陡峭区域不震荡同时尽量少占显存。打个比方普通小模型的训练像是开车去几公里外的小店路况简单方向对就行。大模型预训练更像是开着卡车穿越没有路标的荒漠既要看远方的方向又要防着脚下的坑还要求油耗尽可能低。优化器就是这辆车的悬挂系统和方向盘悬挂太硬稍微一点路面起伏就颠得七荤八素方向盘太迟钝等发现偏了已经绕了大半个圈子。在大模型训练语境下优化器选型和参数配置直接影响三个关键指标是否能收敛、收敛速度和稳定性、可支撑的最大模型规模。这三个指标每一项背后都是真金白银的GPU成本。1.2 从SGD到Adam为什么一代代在换理解优化器演进最好从SGD的缺陷开始。Vanilla SGD就是沿梯度反方向走固定步长在小模型上可行但在大模型尺度上问题很明显不同参数维度上的梯度尺度差异巨大同一个学习率对有些参数合适对另一些参数不是太大就是太小收敛极其缓慢。于是有了Momentum用梯度的指数滑动平均来平滑更新方向相当于给运动中的小球加了惯性能冲出小的局部坑。再后来RMSProp、AdaGrad对每个参数维度单独缩放学习率解决不同尺度参数的适配问题。Adam把这两件事合在一起既用一阶矩估计动量作为更新方向又用二阶矩估计梯度平方的滑动平均对每个参数做归一化。Adam出现后几乎迅速统治了深度学习优化器领域大模型训练更是如此。它好用、稳定、超参数直观不用像SGD那样费心调整学习率。但Adam在真正的超大规模场景里也暴露出一个问题显存开销太大。每个参数要额外维护两个状态量这成了后来一系列优化器变体最想解决的痛点。2. Adam系优化器深度拆解2.1 Adam的核心机制和公式直觉Adam的更新过程我在实际代码里看就是维护两个额外的张量一阶动量m和二阶动量v。每步更新时用当前梯度g更新这两个量再做偏差校正然后用m除以sqrt(v)加一个极小值eps得到最终更新方向乘以学习率之后作用到参数上。m_t beta1 * m_{t-1} (1 - beta1) * g_t v_t beta2 * v_{t-1} (1 - beta2) * g_t^2 m_hat m_t / (1 - beta1^t) v_hat v_t / (1 - beta2^t) theta_t theta_{t-1} - lr * m_hat / (sqrt(v_hat) eps)这里每一步都有直观含义。m是对梯度方向的平滑让更新方向稳定v是对梯度幅度的估计让每个参数的更新步长按历史梯度大小归一化。一个参数如果梯度一直很大v就大实际更新步长被压缩反之梯度一直很小v就小更新步长被放大。这就是Adam能适应不同参数尺度差异的关键。实操中我发现最容易让新手困惑的一点Adam的“自适应学习率”并不是让每个参数自动获得合适的绝对学习率而是让每个参数在自身历史梯度尺度下获得相对的稳定性。模型结构设计不合理导致的某些层梯度过小Adam能缓解但不能根治。2.2 beta1、beta2、eps到底应该设多少很多代码模板直接写AdamW(lr3e-5)其他参数全默认这是我最反对的做法。beta1、beta2、eps三个参数在大模型场景里对收敛行为的影响非常显著。beta1是一阶动量的指数衰减系数默认0.9。它控制更新方向对历史梯度的依赖程度beta1越大方向越平滑但反应越迟钝。大模型预训练我基本保持0.9不动但短训练周期比如SFT几十个step可以把beta1调到0.8让方向更新更灵活我记得一些短训练基准里beta10.8比0.9效果更好。beta2是二阶矩的衰减系数默认0.999。它的含义是v大约看重过去1/(1-beta2)步的梯度平方信息。beta20.999时窗口约1000步适合几万步以上的长训练beta20.95时窗口只有约20步适合短训练或者数据分布变化快的场景。我踩过最大的坑就是在短训练任务里保留beta20.999结果训练到后半段二阶矩过于平滑实际更新量严重缩水表现为loss在某个平台上长期不动。eps默认是1e-8但这个值在混合精度训练里经常不够。大模型领域比较常见的设置是eps1e-6甚至1e-5因为bf16的精度极限加上Adam的更新量eps太小会导致数值噪声放大。如果loss曲线在正常范围内出现细碎抖动先检查eps别急着调学习率。2.3 为什么大模型训练几乎必选AdamWAdamW和Adam的区别一句话说就是把weight decay从梯度里拿出来不参与二阶矩的归一化直接在参数更新后做权重衰减theta theta - lr * wd * thetaAdam里如果直接加L2正则权重衰减会被v归一化掉结果是不同参数实际衰减的力度非常不均匀。对于参数量庞大、参数尺度差异明显的Transformer结构这个问题会被放大。AdamW的权重衰减是解耦的每个参数按自身的绝对尺度等比缩小语义清晰行为可控。在实际大模型预训练和微调中AdamW几乎是默认选择weight decay通常在0.01到0.1之间。0.01适合大部分场景0.1在长预训练里能明显提升泛化但在小数据微调时风险更高。我在代码里经常顺手把weight decay写进优化器参数注意与梯度裁剪的顺序并不冲突后面会细说。3. Muon优化器为什么最近大家都在聊3.1 Muon的核心思路把更新方向做正交化Muon是前阵子开源社区讨论度非常高的一个优化器属于“大模型原生优化器”的方向。它核心做法的一个直观理解是对更新方向做了正交化处理。具体的算法逻辑大致是对维度大于等于2的参数张量把它视为一个矩阵用于更新的动量或者说速度在每一轮先经过Newton-Schulz迭代做正交化然后再乘以一个缩放系数作为真正的更新量。一维参数和embedding、head等结构则使用类Adam的规则处理。Newton-Schulz迭代通常做4到5次迭代次数本身是一个可调超参数。为什么正交化有效神经网络里的权重矩阵通常具有各异向性的特征不同奇异值方向上的更新量差异可能非常大导致训练不稳定。正交化把动量矩阵的特征方向统一到单位尺度等价于给更新方向做了一次“白化”。这比Adam在逐元素上做的归一化更符合矩阵结构本身的性质。有人实验后指出Muon的许多收益其实来自它的强梯度裁剪效应这个观察我认为非常有价值。正交化本身天然限制了更新向量的模长这让训练在初始化偏差较大或数据含噪时更不容易跑飞。3.2 Muon的配置实践与我的实测感受Muon的具体使用方式公开实现里一般是这样二维及以上的参数矩阵走Muon分支一维参数比如LayerNorm的gamma、beta和embedding、LM head走类Adam分支。我自己的实验里学习率设置上Muon分支可以比AdamW大不少类Adam分支则用AdamW的相对小学习率两者解耦。我拿一个大概1B规模的模型做过几组对比在相同数据、相同步数的条件下Muon的训练Loss下降曲线在中期比AdamW略平滑最终验证困惑度略好。但说实话优势没有夸张到悬殊更大的感受是在大学习率区间下Muon不容易炸训练稳定性确实比AdamW容错更强。不过Muon目前不是所有场景都优于AdamW。在小规模任务、短微调、以及模型分支包含大量不规则张量结构时Muon的收敛优势不明显而且它的部分实现没有完全适配所有分布式框架需要额外检查梯度同步后的张量形态。我的建议是如果你在跑中等规模以上的新训练可以拿Muon作为候选方案做对比实验如果实验体系已经成熟稳定没必要为了追新把收益不明确的东西引入生产流程。3.3 对Muon的理性看待围绕Muon的讨论里不乏将其吹成“下一代Adam替代方案”的说法我持保留态度。优化器领域很少出现一个在所有尺度、所有任务上都全面碾压的方案Muon展现出的优势更多集中在超大模型、长训练轨迹、对稳定性和更新量控制要求高的场景。关键是保持可复现性换优化器相当于换了整个训练动力学需要重新调整学习率、warmup、甚至数据策略。同一套超参数在Muon和AdamW下可能表现差异巨大不能只改一个optimizer字段就期望一切顺利。4. 大模型训练的优化器工程实践4.1 显存开销AdamW的隐性成本大模型训练显存不够优化器往往才是真正的“大户”。AdamW每个参与优化的参数要额外保存一阶动量m和二阶动量v两个float32张量也就是每个参数多8字节。一个7B模型光是AdamW的状态量就是56GB远超过模型权重本身。这在单卡上是不可想象的所以现代大模型训练全部采用混合精度加分布式分片。混合精度下模型权重通常保存一份bf16精度用于前向反向另有一份fp32主权重用于更新优化器状态m和v保持fp32。显存占用按照“权重、梯度、优化器状态、激活”四个维度去估算优化器状态永远是最大的单一开销项。要省显存方向无非几个用Adafactor这类不维护完整二阶矩的优化器用8-bit量化优化器bitsandbytes一类工具或者用FSDP/ZeRO把优化器状态分片到多卡每个rank只保存自己负责的那片参数对应的优化器状态通信成本换显存。我在实践里通常组合使用FSDP分片复用显存 剪裁不必要的优化器参数冻结不训练的层 如果还紧张再考虑8-bit。需要提醒的是8-bit优化器会带来轻微的精度损失在梯度幅度很不均衡的场景下更明显预训练大模型要谨慎。4.2 学习率、warmup和调度策略是优化器的一部分优化器算法本身只决定“更新方向”但更新多少还要看学习率调度。大模型训练几乎标配warmup加cosine decay训练初期用很小的学习率在几百到几千步内线性上升到峰值之后按cosine曲线衰减终点通常是峰值的3%到10%。warmup的必要性我的理解是模型刚初始化时梯度统计噪声极大Adam的二阶矩估计还不准此时大学习率容易把参数推到奇怪的区域后续很难拉回来。峰值学习率的选择跟模型规模、batch size强相关7B模型常见峰值在1e-3到2e-4之间具体需要根据loss曲线判断。如果loss前几千步降得很慢先看warmup是否太长、峰值是否太低如果一开始就震荡甚至上翻大概率是峰值太高或者warmup太短。调度器在代码里通常是独立于优化器的对象但它的作用完全是优化器行为的一部分。我见过不止一次有人保存了优化器state_dict却忘了保存scheduler的step恢复训练后学习率调度从头开始预训练直接废掉。这两个状态必须绑定保存。4.3 梯度裁剪与混合精度梯度裁剪是优化器旁边最重要的“安全带”。大模型训练里我几乎总会配置max_grad_norm为1.0对全局梯度范数做裁剪。它防止少数异常batch产生超大梯度把模型参数推离正常区域表现为loss出现突刺或者直接NaN。Muon这类正交化优化器对它的依赖较低但AdamW下建议不要省略。混合精度训练里需要特别注意的是bf16和fp16的数值范围差异很大bf16的指数范围更宽所以正常场景下不容易溢出但精度只有8位有效数字。优化器更新时主权重和高精度状态必须保持在fp32中绝不能在bf16梯度上直接更新否则更新量的低精度累计误差会毁掉训练。梯度累积和梯度裁剪的顺序也有讲究先累积梯度再把累积后的梯度除以有效batch size或等效累积步数然后再做裁剪。先裁剪再累积会导致裁剪的尺度无法与整体梯度的真实范数对应裁剪效果打折。4.4 FSDP/ZeRO里优化器状态的切分现代大模型跑多卡训练绕不开FSDP/ZeRO。我的理解里它的核心思想就是把模型参数、梯度、优化器状态切分成多个分片每个GPU只持有其中一部分用通信来换取显存。优化器状态通常是分片的第一个优先级别把56GB的Adam状态分摊到8张卡上每张卡只占7GB显存压力骤降。但分片也意味着优化器更新逻辑发生改变每个rank只对自己拥有的参数分片计算更新在参数更新前需要通过all-gather把完整参数广播出去。这个过程的通信量很大跨节点时会显著影响训练吞吐。实践中可以把通信与计算重叠优化分片粒度或者适当增大batch size减少通信频率。优化器选型在这个阶段会和通信开销产生直接勾连一个更省状态的优化器比如Adafactor或者量化Adam在极大规模场景下能有比它算法优势更可观的工程价值。5. 常见问题排查与调参实录5.1 Loss不降或者下降极慢这个现象最让人头疼。先看几个最容易确认的地方。第一学习率数量级是否差太远AdamW在大模型里如果lr设为3e-3以上且没配套warmup很容易直接不收敛反过来如果lr1e-5损失可能也走不动表现为下降极慢但不发散。第二检查数据流是否正常尤其是attention mask、标签对齐我遇到过loss在1e-1附近死活下不去最后发现是padding位置参与了loss计算。第三用一个小batch、固定随机种子、小模型跑几步如果loss在极短步数里能降说明整体设置没问题问题出在scale up上。还有一个我排查过多次的原因不同层的学习率没有区分像embedding和head这类参数更新频率高、梯度尺度大主干transformer层往往梯度更小。直接在优化器里对不同参数组设置不同的lr比统一调大全局lr更有效。5.2 Loss突然出现尖刺甚至NaN大模型训练里出现loss尖峰优先级最高的排查点是数据。一个损坏的样本、一条极端异常的序列、某个超出正常范围的位置编码都可能让梯度范数瞬间暴涨。此时检查梯度裁剪是否开启、max_norm是否合理以及fp16情况下是否有溢出。如果尖峰能恢复大多数情况下继续训练即可如果尖峰后loss无法回到正常水平建议回滚到尖峰前的checkpoint处理。预训练脚本里额外记录每个step的loss和梯度范数是事后分析的关键线索。我处理过一次反复在同一个数据阶段出现尖峰的问题最终定位到tokenizer对某些特殊字符的编码结果异常导致一个batch的输入分布完全失真。这类问题优化器本身无法解决但日志里的梯度范数记录能极大加速定位。5.3 训练中显存OOM显存不足优化的优先级顺序是先开activation checkpointing减少中间激活再调整batch size然后才考虑换省显存的优化器或加并行卡数。因为在多数场景下激活内存也会非常大换优化器带来的节省可能不如这些措施直接。如果优化器状态是瓶颈我建议的顺序是先确认是否冻结了不必要的层比如只训练LoRA时主干全部冻结再用FSDP的sharding策略把优化器状态分片再不行换Adafactor或8-bit版本。Adafactor在大模型上确实能省一半优化器显存但它对weight decay的处理方式不太一样需要额外留意不能直接把AdamW的配置原样套用。5.4 checkpoint恢复后Loss对不上这个问题的原因绝大多数是恢复不完整。优化器state_dict没加载、scheduler的step没恢复、数据加载器没恢复到正确的shuffle位置、RNG状态不一样任何一种情况都会让恢复后的loss与继续训练的轨迹不一致。有个细节容易被忽略即使加载了优化器state_dict如果模型结构和参数顺序发生变化比如改了某个模块state_dict严格匹配就会失败或者静默地错误匹配。我的做法是恢复训练前固定写一个assert比较optimizer state的键集合与当前模型参数名集合不匹配直接报错宁可让训练停下来也不要带病运行。优化器配置速查内容比较多整理一个我自己的常用初始配置单位统一seed固定为0的前提下训练场景优化器lr峰值beta1beta2weight decaymax grad normwarmup7B级预训练AdamW1e-3到2e-40.90.95到0.9990.01到0.11.0500到2000步SFT微调AdamW1e-5到3e-50.90.95到0.9990.011.030到200步短任务微调AdamW3e-5到1e-40.8到0.90.9到0.950到0.011.00到50步探索性预训练Muon相对更大0.95Muon动量类Adam分支单独设0到0.011.0同上这个表格是起点不是终点。所有参数都应该在你的具体模型和数据集上做小规模消融不要直接照搬到生产训练。关于优化器还有一个容易被忽略的点理论上的最优算法如果实现里没有和你的并行策略、数据加载、精度管理完全配合实际收益会大打折扣。我自己用过几个第三方实现的“优化器库”代码能跑但一个隐藏的数值bug就能让训练进程悄悄劣化所以我对引入非常新颖的优化器实现始终保持怀疑态度会先小规模验证数值一致性再大规模使用。我个人这几年跑大模型训练下来形成了一个固定的习惯训练前的最后一个检查动作一定看优化器配置本身而不是模型结构。我不止一次因为把某个模型的原本配置从AdamW顺手改成其他优化器而损失了整整几天的训练进度。如果你也准备开始新一轮大模型训练我真心建议先拿AdamW把流程完全跑通确认数据、并行、恢复的逻辑都可靠之后再考虑Muon这类新方案。优化器这个看似细枝末节的选择在大模型训练的全局里拥有超乎想象的话语权。