资讯动态

MindSpore大模型训练优化器参数调优实战:从学习率到LAMB全解析

发布时间:2026/9/19 6:07:52 来源:尧图企业网站定制
训练大模型最痛苦的坑一半都出在优化器上。模型结构抄过来很容易数据管线照搬也简单唯独优化器参数抄错了不收敛抄对了也可能显存爆炸。我在昇思 MindSpore 上跑过不少从亿级到百亿级参数规模的模型踩过很多次优化器相关的坑。这篇就专门讲 MindSpore 大模型训练里优化器参数的来龙去脉包括每个参数到底影响什么、大模型场景下怎么调、以及实际踩坑后的修复方案。这篇内容适合已经在用 MindSpore 做模型训练、或者正准备把业务模型从小规模扩展到大规模的同学。不会讲太多教科书式定义更多是从“这个参数不调会怎样”的角度切入。你如果还停留在调 API 的阶段看完这篇应该能理解优化器底层在干什么如果你已经在大模型训练上撞过几次墙这里面的排查思路和参数组合可以直接拿回去试。1. 为什么大模型训练对优化器参数如此敏感1.1 优化器承担的不只是梯度更新很多人把优化器理解成“根据梯度更新权重”的黑盒这没有错但在大模型场景下优化器承担的职责比这个复杂得多。先看一个最简单的 SGD 更新公式[ \theta_{t1} \theta_t - \eta \cdot \nabla L(\theta_t) ]这里 (\eta) 是学习率(\nabla L(\theta_t)) 是当前步的梯度。小模型任务里你随便设个学习率比如 0.01模型大概率能收敛到差不多的结果。但到了大模型事情就变了参数量动辄几十亿上百亿每一层的梯度尺度差异可以差好几个数量级如果所有参数共用一个固定的学习率浅层参数要么学得太慢要么深层参数直接振荡不收敛。优化器在大模型训练里要做的事情远不止“沿梯度反方向走一步”。它还要负责平衡不同参数尺度的更新幅度在超大 batch size 下稳定训练过程控制权重衰减对模型泛化能力的影响考虑显存和内存的占用因为优化器状态本身就可能占据比模型权重更大的存储空间。MindSpore 的优化器模块恰好围绕这几个点做了大量设计而这些设计的落点就是暴露给用户的那些参数。1.2 大模型场景的三大挑战如果只用一句话总结大模型训练对优化器的要求那就是它必须在极端条件下维持稳定且高效的学习过程。具体拆开看有三个绕不开的挑战。第一是梯度尺度的极不均衡。大模型往往有 embedding 层、Transformer 层、输出层不同模块的梯度范数可能相差很大。比如 embedding 层在训练初期可能收到很大梯度而深层 Transformer 块的梯度则相对较小。如果用同一个学习率去更新所有参数那某些层就会训练不充分。第二是学习率与 batch size 的强耦合。大模型训练几乎都是大 batch 并行batch size 从 1024 到上万都很常见。过去小模型时代有所谓线性缩放法则即 batch size 翻倍、学习率也翻倍但这个法则在超大 batch 下会失效需要更精细的优化器设计来适配比如 LAMB 这类专门为大 batch 训练的优化器。第三是显存与内存压力。Adam 优化器需要为每个参数保存一阶动量moment和二阶动量variance加上梯度本身训练时显存占用大约是模型参数量的 12 到 16 倍。百亿参数模型光优化器状态就可能吃掉上百 GB 显存。这也是为什么大模型训练里优化器参数不仅是超参数问题还是存储规划问题。MindSpore 通过提供不同优化器、参数分组、混合精度适配、状态压缩等手段来应对这些挑战。下面逐个说透。2. MindSpore 优化器核心参数逐个拆解2.1 学习率最核心也最容易犯错学习率是优化器参数里当之无愧的头号核心。它不仅决定收敛速度还直接决定训练是否稳定。在 MindSpore 里学习率的设置有两种方式。第一种是直接写死from mindspore.nn import AdamWeightDecay optimizer AdamWeightDecay( paramsmodel.trainable_params(), learning_rate1e-4, )第二种是配合学习率调度器learning rate schedule在训练过程中动态调整。大模型几乎不会用固定学习率跑全程因为前期需要较大的学习率快速下降后期需要较小的学习率精细收敛。MindSpore 常用的调度器有PiecewiseConstantLR、CosineDecayLR、WarmUpLR等。我在实际项目中用得最多的是 warmup 加 cosine 衰减的组合from mindspore.nn.learning_rate_schedule import WarmUpLR, CosineDecayLR warmup_epochs 2000 total_steps 50000 lr_schedule WarmUpLR( learning_rateCosineDecayLR(min_lr1e-6, max_lr3e-4, decay_stepstotal_steps), warmup_stepswarmup_epochs, )这个组合的逻辑是训练开始时模型对数据分布一无所知如果直接上大学习率容易在早期就对参数造成不可逆的破坏。所以先用 warmup 阶段把学习率从 0 或很小的值逐渐升到目标值让模型平稳起步。之后再通过 cosine 衰减让学习率平滑地降下来有利于收敛到更平坦的极小值区域。具体学习率的量级怎么选这取决于优化器的类型、模型深度、batch size 和数据复杂度。以我经验来看MindSpore 上 AdamW 系优化器在大部分大模型任务里的合理初始学习率在 1e-4 到 3e-4 之间。SGD 带动量则需要更高通常在 0.01 到 0.1 之间。如果使用 LAMB 这类专门为大 batch 设计的优化器学习率可以相对激进一些在 1e-3 量级。2.2 beta1 与 beta2控制动量的记忆长度Adam 及其变体中有两个关键超参数 beta1 和 beta2。很多人在用 Adam 时直接取 PyTorch 或 MindSpore 的默认值beta10.9beta20.999很少深究它们的作用。在大模型场景下这两个参数值得仔细审视。beta1 控制一阶动量梯度均值的衰减速度可以理解成对历史梯度的“记忆长度”。beta1 越大优化器参考的历史梯度时间窗口越长更新方向越平滑。beta1 越小越关注最近的梯度更新方向越容易波动。默认 0.9 意味着优化器大约参考最近 10 步的梯度信息。beta2 控制二阶动量梯度平方均值的衰减速度用于自适应调整每个参数的学习率。beta2 越大对梯度方差的估计越平稳每个参数的学习率调整越平滑。但 beta2 太大会导致早期训练阶段二阶动量估计不足从而让初始几步的学习率偏大。在 MindSpore 的AdamWeightDecay中默认值通常是beta10.9, beta20.999。这个组合在大多数任务上表现良好。但在一些大规模预训练任务里我见过把 beta2 调到 0.98 的情况目的是让学习率自适应更快响应梯度变化但代价是训练稳定性下降。如果你观察到训练 loss 在波动幅度过大可以尝试适当调大 beta1比如从 0.9 调到 0.95让更新方向更平滑。如果 loss 下降太慢可以适当调小 beta1让优化器更能及时响应新梯度。2.3 weight_decay大模型泛化的隐形调节器权重衰减weight decay是在损失函数之外对参数大小进行约束的正则化手段。它的作用是将参数向零的方向适当收缩防止过拟合。好多人在 MindSpore 里这样用optimizer AdamWeightDecay( paramsmodel.trainable_params(), learning_rate1e-4, weight_decay1e-2, )在大模型训练中weight_decay 的典型值在 0.01 到 0.1 之间。但这里有个容易被忽略的坑weight_decay 是否作用于所有参数大模型里有两类参数不适合做权重衰减。第一类是 LayerNorm 或 BatchNorm 中的 gamma 和 beta这些参数控制特征的尺度和平移如果强行做权重衰减会破坏归一化层的表达能力。第二类是 bias 项bias 本身就是偏置不需要额外正则化。MindSpore 支持通过参数分组来精确控制哪些参数应用 weight_decay。我通常把参数分成两组from mindspore import nn def group_params(model): decay_params [] no_decay_params [] for param in model.trainable_params(): if len(param.shape) 1 or param.name.endswith(.bias): no_decay_params.append(param) else: decay_params.append(param) return [ {params: decay_params, weight_decay: 0.01}, {params: no_decay_params, weight_decay: 0.0}, ] params group_params(model) optimizer AdamWeightDecay(paramsparams, learning_rate1e-4)判断规则是一维参数通常是偏置和归一化层的缩放因子这些不做 weight_decay其余参数正常做。这个经验在好几个百亿级模型训练中验证过loss 曲线比全部参数一刀切更平稳。2.4 eps一个常常被忽略但能救命的数值eps 参数在优化器中是一个极小的常数用于防止分母为零。Adam 系列优化器的参数更新公式为[ \theta_{t1} \theta_t - \frac{\eta \cdot m_t}{\sqrt{v_t} \epsilon} ]这里的 eps 既防止了梯度平方项为零导致的除零错误也起到了数值稳定作用。但 eps 的大小其实会影响训练的稳定性。MindSpore 里默认的 eps 通常是 1e-6 或 1e-8。如果训练中遇到 loss 突然变成 NaN 或者出现异常振荡检查一下 eps 是否太小往往能发现问题。尤其在混合精度训练中由于计算精度从 FP32 降到了 FP16 或 BF16梯度平方值很小更小的 eps 可能导致数值溢出。我的经验是在混合精度场景下把 eps 适当调大一些比如 1e-6 甚至 1e-5能够显著减少数值不稳定的问题。代价是参数更新的精度会有轻微损失但在大模型任务中这个损失几乎可以忽略。2.5 梯度累积相关设置梯度累积虽然不是优化器参数但和优化器配合紧密。大模型往往由于显存限制无法使用大 batch而梯度累积可以在不大幅增加显存开销的情况下等效增加 batch size。MindSpore 中梯度累积通常通过model.set_grad_accumulation_steps或自定义训练循环来实现。优化器本身需要配合调整的是学习率的问题。举个例子原始设定batch size 32学习率 1e-4梯度累积每 8 步累积一次梯度等效 batch size 256在这种情况下学习率不能直接保持 1e-4 不变。我一般按照平方根缩放法则来调整学习率缩放为 (1e-4 \times \sqrt{256/32} 1e-4 \times 2.83 \approx 2.8e-4)。这是一种比较保守的调整方式比线性缩放稳定。3. 大模型场景下的优化器选型与参数实践3.1 为什么大模型首选 AdamWeightDecay在 MindSpore 里内置了多种优化器新手容易在选型上犯迷糊。直接给结论大模型预训练和微调优先选AdamWeightDecay分布式大 batch 场景考虑LAMB对收敛速度有极致追求且显存充裕可以尝试Lion。为什么不是普通 Adam普通 Adam 的权重衰减实现方式是 L2 正则化作者在其后续论文中澄清这并不等价于真正的 weight decay。AdamWeightDecay 是在更新权重之后再额外执行一步衰减[ \theta_{t1} \theta_t - \eta \cdot \left( \text{update}_t \lambda \cdot \theta_t \right) ]大模型有巨大的参数量任何微小的实现差异经过几十万步训练都会被放大。使用正确的权重衰减方式能获得更平滑的收敛曲线和更好的泛化表现。MindSpore 中还有个容易被忽略的选项AdamOffload优化器。它是为了应对大模型训练中的显存压力而设计的把优化器状态比如一阶和二阶动量放到 CPU 内存或 Host 内存中GPU 只保留模型权重和梯度。这样做会牺牲一些训练速度但能显著降低显存占用。在 64 GB 显存下可以用 AdamOffload 训练原本需要 80 GB 显存的模型。3.2 LAMB 优化器与超大 batch 训练当 batch size 超过 2048 时普通的 AdamW 很容易出现训练不稳定的问题。原因是大 batch 的梯度更接近真实梯度噪声较小而自适应优化器假设的梯度分布与实际分布有偏差导致更新步长不够合理。LAMBLayer-wise Adaptive Moments optimizer for Batch training在 Adam 框架基础上引入了逐层自适应学习率。它对每一层单独计算学习率缩放因子[ \text{ratio} \frac{|\theta_t|}{|\text{update}_t|} ]如果某一层的参数范数相对其更新范数较大就放大学习率反之缩小。这种设计帮助模型在超大 batch size 下仍然保持稳定更新。MindSpore 中 LAMB 的关键参数和 Adam 类似from mindspore.nn import LAMB optimizer LAMB( paramsgroup_params(model), learning_rate1e-3, beta10.9, beta20.999, eps1e-6, weight_decay0.01, )这里的学习率设置比 AdamW 要更大一些通常可以到 1e-3 量级。如果你从 AdamW 切换到 LAMB务必同步调整学习率否则等于没有发挥出 LAMB 的优势。3.3 优化器参数与混合精度训练的适配大模型训练基本都会开启混合精度MindSpore 里就是model.fit时传入MixedPrecision配置或使用amp模块。混合精度会直接影响优化器的梯度精度。FP16 的表示范围约在 1e-5 到 65504 之间而 BF16 的表示范围和 FP32 相同但精度更低。在此前提下如果优化器的 eps 设置太小Adam 更新量计算时可能出现梯度平方下溢为 0 的情况导致该参数永远不更新。我在 MindSpore 中使用混合精度时会将eps从默认的 1e-8 调整为 1e-6。同时如果使用 FP16还会额外开启 loss scale并且让 loss scale 的初始值设置合理。loss scale 的目的是将梯度放大到 FP16 可表示的范围内优化器实际收到的梯度是放大后的梯度如果优化器内部计算时没有正确处理 loss scale 的还原参数更新就会异常。MindSpore 内部对混合精度与优化器的配合做了适配但在自定义优化器时要注意梯度必须先除以 loss scale 再更新参数或者统一在优化器内部处理。否则会出现训练初期 loss 下降正常、后期突然发散的情况。3.4 参数分组的实际调参案例直接分享一个我在某中文大模型预训练任务中的参数配置。模型约 13 亿参数训练数据约 200GBbatch size 为 512。参数分组前我先确定了两组参数策略第一组embedding 和输出层学习率衰减系数 0.5weight_decay 0.02第二组Transformer 内部参数学习率保持基准值weight_decay 0.01。这样做的理由很直接embedding 和输出层的参数量巨大而且梯度稀疏如果和 Transformer 内部参数使用相同学习率步长容易过大导致训练不稳。降低这些层的学习率之后loss 曲线明显平滑。MindSpore 支持这种精细分组optimizer AdamWeightDecay( params[ {params: embedding_params, lr: 5e-5, weight_decay: 0.02}, {params: transformer_params, lr: 1e-4, weight_decay: 0.01}, ] )注意在 MindSpore 的优化器参数分组里每个字典中指定的lr会覆盖优化器级别的learning_rate设置。如果某个分组没有指定lr则使用优化器级别的学习率。这个机制在需要精细化调参时很实用。3.5 优化器状态的内存优化技巧大模型训练里显存经常比算力更值钱。优化器状态占用显存的比例非常大尤其 Adam 系列。以 FP16 训练为例模型权重是 2 字节梯度是 2 字节一阶动量是 4 字节保持 FP32二阶动量也是 4 字节总共 12 字节。如果模型有 70 亿参数光优化器状态就占 56 GB 内存。MindSpore 在显存优化上做了很多工作。第一是AdamOffload优化器把优化器状态放到 CPU 上这在单机多卡场景效果明显代价是额外的 CPU 与 GPU 数据通信。第二是优化器状态压缩比如将一阶动量用 BF16 存储来减半内存占用这在 MindSpore 中可以通过相关配置打开。第三是model._set_offload或类似 API 配合分布式并行把优化器状态分片到不同卡上这就是 ZeRO 的思想。我在实际项目中用过这几种策略的组合启动AdamOffload把二阶动量放到 CPU模型并行时对优化器状态做分片每个 GPU 只保存自己负责的参数子集的优化器状态训练时监控显存把优化器状态占用量控制在总显存 50% 以内。如果你在 MindSpore 里训练时提示显存不足第一反应不应该是换更小的 batch size而是先看一眼优化器的状态占了多大。把优化器状态从 GPU 搬到 CPU 往往比缩小 batch 对训练效率的损失更小。4. 实操MindSpore 大模型优化器配置完整示例4.1 从零搭建一个可复现的训练配置下面给出一套实测可行的 MindSpore 大模型训练优化器配置。这套配置在单机 8 卡、10 亿参数规模模型上验证过。直接复制可能会因为环境差异导致效果不同但配置思路可以复用。import mindspore from mindspore import nn from mindspore.nn import AdamWeightDecay from mindspore.nn.learning_rate_schedule import WarmUpLR, CosineDecayLR # 模型与数据构造略 # model MyLargeModel() def create_optimizer(model, base_lr3e-4, total_steps100000, warmup_steps5000): # 参数分组 decay_params [] no_decay_params [] embedding_params [] for param in model.trainable_params(): name param.name if embedding in name: embedding_params.append(param) elif len(param.shape) 1 or name.endswith(.bias): no_decay_params.append(param) else: decay_params.append(param) lr_schedule WarmUpLR( learning_rateCosineDecayLR( min_lr1e-6, max_lrbase_lr, decay_stepstotal_steps - warmup_steps, ), warmup_stepswarmup_steps, ) grouped_params [ {params: embedding_params, lr: base_lr * 0.5, weight_decay: 0.02}, {params: no_decay_params, weight_decay: 0.0}, {params: decay_params, weight_decay: 0.01}, ] optimizer AdamWeightDecay( paramsgrouped_params, learning_ratelr_schedule, beta10.9, beta20.995, eps1e-6, ) return optimizer这段配置里有两个关键点lr以调度器对象传入时MindSpore 会在每一步训练时动态计算当前学习率embedding_params单独降学习率因为 embedding 通常在训练阻尼中占据主导降一点学习率能显著提升稳定性。4.2 分布式训练时的优化器配置注意事项多卡训练时MindSpore 会自动将梯度做 AllReduce 聚合优化器仍然按单卡的逻辑执行梯度更新。但有一个隐藏问题所有卡的梯度平均值会小于单卡的梯度值这相当于学习率被隐式放大了。这个问题的根源在于并行训练改变了梯度统计的分布。解决方式有两种跨卡保持总 batch size 不变那么梯度均值和单卡场景等效学习率无需调整如果总 batch size 增大了则将学习率按 batch size 的平方根比例缩放。MindSpore 分布式训练时还有一个针对性优化部分通信算子可以和优化器更新重叠。这是 MindSpore 框架层面自动完成的不需要用户配置。但在使用AdamOffload这类涉及 CPU 的优化器时重叠效果会降低训练吞吐会下降你需要评估收益是否划算。4.3 用model.fit还是自定义训练循环MindSpore 同时支持高层 APImodel.fit和自定义训练循环。在大模型场景下我推荐用自定义训练循环因为自由度更高方便在优化器更新前检查梯度范数、做梯度裁剪。# 伪代码示例 for epoch in range(epochs): for step, batch in enumerate(dataset): def forward_fn(inputs, labels): logits model(inputs) loss loss_fn(logits, labels) return loss, logits grad_fn mindspore.value_and_grad(forward_fn, grad_positionNone, weightsmodel.trainable_params()) (loss, logits), grads grad_fn(inputs, labels) # 梯度裁剪 grads mindspore.ops.clip_by_global_norm(grads, clip_norm1.0) optimizer(grads) optimizer.learning_rate lr_schedule(step * global_batch_size)注意每次优化器更新后如果学习率是动态调度的需要手动将当前步的学习率更新到优化器。在高层 API 中这一步是自动的在自定义循环里容易遗漏。4.4 梯度裁剪参数如何配合优化器梯度裁剪本身不是优化器参数但它直接作用于优化器更新前的梯度。大模型训练时偶发的异常大梯度是训练发散的主要原因梯度裁剪是最后一道防线。MindSpore 中常用的裁剪方式有两种clip_by_norm对每个梯度张量单独裁剪适合小模型clip_by_global_norm对所有参数的梯度拼接成一个整体计算全局范数后进行裁剪适合大模型。我一般将clip_norm设置为 1.0。这个值经常能避免训练中的偶发发散同时又不会低估有效学习率。如果训练数据质量较差、噪声较多可以降低到 0.5。如果训练非常稳定可以放宽到 2.0 或 3.0以加快收敛。一个重要细节梯度裁剪要避免和 loss scale 冲突。在混合精度场景梯度已经被 loss scale 放大了直接做全局裁剪大概率会频繁触顶导致训练无法正常进行。需要先将梯度除以 loss scale 再做裁剪或者使用 MindSpore 内部已正确处理这个流程的 loss scale 与优化器组合。5. 大模型优化器参数常见问题与排查记录5.1 loss 不下降先查学习率和 warmup在大模型训练中loss 长时间不下降是最让人抓狂的问题。我的排查顺序非常固定第一步检查 loss 曲线在前 50 步有没有任何下降趋势。如果完全没有看学习率是否因为调度器配置错误而没有启动。曾经遇到过一次WarmUpLR的warmup_steps设置得比decay_steps还大前几千步只在 warmup 阶段学习率一直是 0loss 自然纹丝不动。第二步检查优化器的参数分组有没有把某些参数漏掉。如果params列表里只传了一部分trainable_params模型会有一部分参数永远不更新loss 下降速度会异常缓慢甚至停在某个平台期。第三步看梯度是否存在。在自定义训练循环里打印梯度的范数grad_norm mindspore.ops.zeros((), mindspore.float32) for grad in grads: grad_norm grad.pow(2).sum() grad_norm grad_norm.sqrt() print(fstep {step}, grad norm: {grad_norm})如果梯度范数一直非常小比如低于 1e-8说明模型遇到了梯度消失此时单纯调优化器参数无效反而应该检查网络结构、初始化方式或是否开启了残差连接等结构性问题。5.2 loss 震荡剧烈调节 beta 与学习率策略loss 曲线像心电图一样上下剧烈抖动通常原因有两个。第一个原因是学习率过大或 warmup 过短。模型还没稳定就接触到高学习率参数更新步长过大loss 在高点和低点之间来回弹。我把这类问题理解为“步子迈太大扯着蛋”解决方式也很直接降低初始学习率或者延长 warmup 步数。之前在 7B 模型预训练时warmup 从 500 步逐渐调大到 3000 步loss 震荡幅度显著减小。学习率则从 4e-4 逐渐降到 3e-4然后 2.5e-4最终找到稳定区间。第二个原因是 beta1 设置过低。如果 beta1 默认的 0.9 让更新方向平滑度不够可以提高到 0.95 甚至 0.98。这个参数调节的代价是收敛会变得更慢但稳定性会大幅提升。在处理百亿级模型时稳定压倒一切收敛慢几天完全能接受。5.3 OOM 并不全是模型权重的问题显存溢出是最常见也最容易误判的问题。大模型训练显存占用可以拆成四部分模型权重、前向激活值、梯度、优化器状态。优化器状态往往是单一项中占比最大的。遇到 OOM 时的排查清单先用mindspore.get_context查看当前分配到的显存查看优化器类型。如果是普通 AdamW换成AdamOffload看显存占用是否下降检查梯度是否使用了显存换速度的选项。有时候模型并行和优化器状态分片没有同时启用会导致某一张卡显存特别紧张尝试降低eps或使用二阶动量压缩观察显存变化。我遇到过一种很隐蔽的 OOM参数分组时无意中把同一个参数加了两次。这会导致优化器状态为同一个参数分配了双倍内存显存占用直接翻倍训练到一半就 OOM。检查params列表确保每个参数只出现一次。5.4 混合精度下优化器数值不稳定混合精度训练的一个典型表现是前几百步 loss 看起来正常下降突然某一步 loss 变成 NaN之后就再也回不来了。这里面有几种可能。第一种是 loss scale 太小梯度在反向传播时下溢为 0看起来 loss 不变但反向传播实际已经失效。第二种是 loss scale 太大梯度溢出到正无穷优化器更新出 NaN。MindSpore 的动态 loss scale 机制理论上是自适应的但初始值设置不合理时需要很长时间才能调整到合适的范围。第二种是优化器的 eps 太小。在 FP16 或 BF16 下梯度平方值可能小到无法表示二阶动量累加后更新量异常导致参数更新产生 NaN。把 eps 调到 1e-6 或 1e-5往往能解决问题。第三种是梯度裁剪和 loss scale 的配合出错。前面提过梯度先被 loss scale 放大再被 optimizer 处理如果裁剪时拿放大后的梯度去做全局范数约束大概率在训练初期就频繁裁剪导致训练退化或数值异常。确保做裁剪前把梯度还原到原始尺度。5.5 优化器参数常见问题速查表现象可能原因排查方向loss 不降学习率为 0 或参数未更新检查 warmup 步数与 params 列表loss 下降低于预期学习率过小或 weight_decay 过大适当提高学习率降低 weight_decayloss 震荡剧烈学习率过大、warmup 过短或 beta1 过小降低初始 lr、延长 warmup、调大 beta1训练中 NaNloss scale 失配、eps 过小调整 loss scale 与 eps 数值OOM优化器状态占用过高换 AdamOffload启用优化器状态分片不同卡效果差异大参数分组全局不一致检查各卡参数分组配置是否统一5.6 一个真实踩坑记录最后分享一个真实案例。有一个 10B 参数的模型在 32 卡 MindSpore 集群上训练loss 一直维持在 8.0 左右怎么调学习率都降不下去。当时排查了很久最后发现是优化器参数分组出了问题。原来在模型构建时embedding 层用了共享参数tied embedding也就是 embedding 和输出层是同一个权重矩阵。但我做参数分组时把这个参数分别加到了embedding_params和decay_params两组里导致同一个参数被优化器认为有两个副本状态重复分配更新互相覆盖模型相当于只用了一半的更新量在训练。这种问题很难一眼发现因为只有共享参数模型才会触发。排查的办法是打印优化器内部的参数 ID 列表检查是否有重复optimizer_params [id(p) for group in optimizer.group_params for p in group[params]] assert len(optimizer_params) len(set(optimizer_params)), Duplicated params in optimizer!从那之后我所有模型的参数分组代码里都会带这个断言避免同类问题。6. 优化器参数调优的工程化思考6.1 把实验记录和回归测试做成习惯优化器参数太多靠感觉调参很难积累经验。我的做法是每个实验固定记录一组关键指标平均梯度范数、最大梯度范数、loss 曲线的中位数和分位数、学习率变化曲线。这些指标能帮助判断某个参数改动到底影响了什么。MindSpore 的SummaryCollector可以方便地记录这些标量。如果嫌重自定义训练循环里打印到日志文件也行。关键是坚持记录不然下次遇到同样的问题还是从头开始猜。6.2 超参搜索在小规模上做大模型上做验证大模型训练单次实验成本极高不可能像小模型那样做网格搜索。我一般先用缩小的模型比如 1/10 参数量在少量数据上跑一组超参对比找到合理的参数区间再在大模型上验证。比如优化器 beta、学习率峰值、warmup 比例、weight_decay这些参数在小规模模型上的相对优劣通常在大模型上保持一致。直接用大规模模型做全参数网格搜索时间成本划不来。6.3 不要忽视优化器版本与框架版本差异MindSpore 版本更新时优化器内部实现可能有细微调整比如 eps 的默认值、weight_decay 与学习率的耦合方式。这会导致相同参数配置下训练效果不一致。所以复用旧配置前先确认当前 MindSpore 版本的优化器接口和默认值与上次训练时一致。我在一次框架升级后遇到了优化器行为异常后来发现是AdamWeightDecay的weight_decay默认值从 0.0 变成了 0.01相当于凭空给模型加了一层正则化loss 自然降不到之前的水平。6.4 训练中优化器参数动态调整的可行性MindSpore 优化器的学习率可以在训练过程中动态修改但 beta、eps、weight_decay 等参数在训练中改动要非常小心。beta1 和 beta2 是一阶和二阶梯度的指数衰减系数改动后动量估计会突然跳变训练容易出现振荡。我唯一会在训练中动态调整的只有学习率和梯度裁剪阈值。weight_decay 在预训练后期调低一点有概率改善收敛性但收益不稳定目前我不是很推荐。我不太建议在训练中途换优化器类型。比如从 AdamW 换到 SGD动量状态和自适应状态全部被清空参数更新规则突变模型极大概率直接发散。如果非要换要做好从头训练的觉悟。6.5 一套相对通用的起点参数模板分享一套我在多模态和纯文本大模型预训练中都能稳定起步的模板适合 10 亿到 100 亿参数规模的中等训练任务参数推荐值备注优化器AdamWeightDecay默认优先初始学习率2e-4 到 3e-4大批量下可上调到 5e-4最小学习率初始学习率的 1/10 到 1/100cosine 衰减终点warmup 比例总训练步数的 1% 到 5%越大越稳beta10.9震荡时上调到 0.95beta20.99 到 0.999短训练用 0.99eps1e-6混合精度下建议不要低于 1e-6weight_decay0.01 到 0.02embedding 和 bias 单独处理梯度裁剪1.0不稳定时降低到 0.5这个模板不一定最优但大概率不会让你踩太多坑。优化器调参这件事本质上是在稳定性和收敛速度之间找平衡没有绝对正确的答案只有适合你当前任务的配置组合。我在 MindSpore 上从中小模型一路调到大模型最深的感觉就是优化器参数看着不多每个单拎出来都知道是干嘛的组合到一起却能衍生出无穷多的问题。这篇内容是我踩过一轮坑之后的经验沉淀希望对正在和优化器斗智斗勇的你有一点实质性的帮助。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价