PaddleNLP 优化器模块解析:AdamWDL 分层学习率衰减、EMA 与逆平方根调度器实战指南
2026/9/23 18:31:22 网站建设 项目流程
  • 人工智能
  • 大模型
  • 预训练
  • 微调
  • LoRA
  • RLHF
  • 强化学习
  • 分布式训练

【免费下载链接】PaddleNLP

Easy-to-use and powerful LLM and SLM library with awesome model zoo.

项目地址:https://gitcode.com/gh_mirrors/pa/PaddleNLP
点击查看免费下载

paddlenlp.ops.optimizer是 PaddleNLP 中面向大规模 Transformer 训练调优的优化器工具包,其 API 文档页 paddlenlp.ops.optimizer.rst 通过 Sphinx autodoc 机制聚合了adamwdlema两个子模块的完整文档。本文以该文档为主体,结合仓库内 paddlenlp/ops/optimizer 的源码实现,系统讲解 AdamWDL 优化器(含 Layer-wise Learning Rate Decay 分层学习率衰减)、指数滑动平均 EMA 以及配套的逆平方根学习率调度器,帮助你理解其原理、参数语义并掌握在 Paddle 动态图/静态图训练流程中的落地写法。

模块总览:paddlenlp.ops.optimizer 解决了什么问题

从 模块导出定义 可以看到,该包对外提供四个能力:

from .adamwdl import AdamWDL, layerwise_lr_decay from .ema import ExponentialMovingAverage from .lr import InverseSquareRootSchedule __all__ = ["layerwise_lr_decay", "AdamWDL", "ExponentialMovingAverage", "InverseSquareRootSchedule"]
名称所属子模块核心用途
AdamWDLadamwdl.py带 Layer-wise 动态学习率设置的 AdamW 优化器,面向 Transformer 模型
layerwise_lr_decayadamwdl.py默认的分层学习率衰减策略函数
ExponentialMovingAverageema.py模型参数的指数滑动平均(EMA),训练中维护影子参数
InverseSquareRootSchedulelr.py逆平方根学习率调度(含 warmup 线性升温),常用于预训练

其中AdamWDLlayerwise_lr_decay是文档 toctree 中paddlenlp.ops.optimizer.adamwdl页的核心内容,ExponentialMovingAverage则对应paddlenlp.ops.optimizer.ema页。下面逐一深入。

AdamWDL:基于 AdamW 的动态分层学习率优化器

AdamWDL的类定义位于 paddlenlp/ops/optimizer/adamwdl.py,它在 Paddle 官方paddle.optimizer.AdamW的基础上,增加了"为每个参数单独设置学习率"的能力,文档说明其默认使用layerwise_lr_decay作为动态学习率设置方法,一般用于 Transformer 模型训练。

分层学习率衰减(Layer-wise Decay)原理

所谓 "Layer-wise decay",即自上而下按层指数衰减各层的学习率。以 24 层 Transformer 为例:若第 24 层(顶层)使用学习率l,分层衰减率为α,则第m层的学习率为l * α^(24-m)。其理论出处为论文Don't Stop Pretraining: Adapt Language Models to Domains and Tasks(arXiv:1906.08237,即 Layer-wise Learning Rate Decay / LLRD)。

这样的设计动机是:底层(靠近 embedding 的词向量层与浅层 encoder 层)通常学习更通用的表征,需要更小的学习率以保持稳定性;而顶层更接近任务目标,可以用更大的学习率快速适应。对应到源码 layerwise_lr_decay:

def layerwise_lr_decay(decay_rate, name_dict, n_layers, param): ratio = 1.0 static_name = name_dict[param.name] if "encoder.layers" in static_name: idx = static_name.find("encoder.layers.") layer = int(static_name[idx:].split(".")[2]) ratio = decay_rate ** (n_layers - layer) elif "embedding" in static_name: ratio = decay_rate ** (n_layers + 1) return ratio

其规则非常清晰:

  • 参数名中包含encoder.layers.时,解析出层号layer,学习率系数为decay_rate ** (n_layers - layer)——层号越大(越靠近顶层)系数越接近 1,层号越小(越靠近底层)系数越小;
  • 参数名中包含embedding时,系数固定为decay_rate ** (n_layers + 1),即比最底层 encoder 层还要小,进一步压低 embedding 的学习率;
  • 其余参数(如顶层输出层、归一化层等)系数保持1.0

注意name_dict的语义:键是参数的动态名(param.name),值是模型的静态名,可通过model.named_parameters()构建。

AdamWDL 的更新公式与完整参数说明

AdamWDL 继承了 AdamW 的数学更新形式(其中moment_1moment_2为一阶/二阶矩估计,λ为 weight decay 系数):

t = t + 1 moment_1_out = β1 * moment_1 + (1 - β1) * grad moment_2_out = β2 * moment_2 + (1 - β2) * grad * grad learning_rate = learning_rate * sqrt(1 - β2^t) / (1 - β1^t) param_out = param - learning_rate * (moment_1 / (sqrt(moment_2) + ε) + λ * param)

构造函数签名与各参数语义如下(源自 adamwdl.py 的__init__):

参数默认值说明
learning_rate0.001学习率,可为 float 或LRScheduler
beta10.9一阶矩估计的指数衰减率
beta20.999二阶矩估计的指数衰减率
epsilon1e-8数值稳定性小量
parametersNone待更新的参数列表/元组,动态图模式下必填;静态图默认为None(更新全部参数)
weight_decay0.01权重衰减系数,可为 float 或 Tensor
apply_decay_param_funNone若不为None,仅当apply_decay_param_fun(Tensor.name) == True的参数执行 weight decay
grad_clipNone梯度裁剪策略,支持GradientClipByGlobalNorm/GradientClipByNorm/GradientClipByValue
lazy_modeFalse惰性模式:仅更新当前 mini-batch 有梯度的元素,参数极大时可显著加速,但语义与标准 Adam 略有差异
multi_precisionFalse是否在权重更新时使用多精度(如 FP32 master weight)
layerwise_decay1.0分层衰减率α
n_layers12Transformer encoder 总层数
set_param_lr_funlayerwise_lr_decay自定义的参数学习率设置函数,在 Adam 算子执行前调用
name_dictNone动态名到静态名的映射字典,用model.named_parameters()获取
nameNone优化器名称,一般无需设置

构造函数中值得注意的实现细节:layerwise_decay必须是 float 或paddle.framework.Variable,否则抛出TypeError("coeff should be float or Tensor.");同时通过functools.partial(layerwise_decay, name_dict, n_layers)预绑定进set_param_lr_fun,使自定义函数只需接收param一个参数。

官方文档示例:自定义学习率函数并完成一步更新

adamwdl.py 的 docstring 给出了完整可运行示例——自定义一个"权重参数学习率打五折"的策略,并用AdamWDL完成一次前向、反向与参数更新:

import paddle from paddlenlp.ops.optimizer import AdamWDL def simple_lr_setting(decay_rate, name_dict, n_layers, param): ratio = 1.0 static_name = name_dict[param.name] if "weight" in static_name: ratio = decay_rate**0.5 param.optimize_attr["learning_rate"] *= ratio linear = paddle.nn.Linear(10, 10) name_dict = dict() for n, p in linear.named_parameters(): name_dict[p.name] = n inp = paddle.rand([10, 10], dtype="float32") out = linear(inp) loss = paddle.mean(out) adamwdl = AdamWDL( learning_rate=1e-4, parameters=linear.parameters(), set_param_lr_fun=simple_lr_setting, layerwise_decay=0.8, name_dict=name_dict) loss.backward() adamwdl.step() adamwdl.clear_grad()

要点:

  • set_param_lr_fun的签名固定为(decay_rate, name_dict, n_layers, param),返回值ratio会与参数原本的optimize_attr["learning_rate"]相乘;
  • 自定义函数内部通过name_dict[param.name]拿到静态名,再按自己的规则(如是否含weight)决定缩放比例;
  • name_dict必须与传入的parameters一一对应,否则查找会失败。

底层执行流程:学习率如何在 Adam 算子前被改写

从源码可以清晰还原 AdamWDL 的一次参数更新究竟做了什么(见 _append_optimize_op):

  1. set_param_lr_fun is None,直接退化为原生AdamW._append_optimize_op,不启用分层学习率;
  2. 先调用_append_decoupled_weight_decay执行解耦的权重衰减(见下文);
  3. 记录当前参数学习率prev_lr,调用set_param_lr_fun(param)得到ratio,将optimize_attr["learning_rate"]临时乘以ratio
  4. 调用父类执行 Adam 更新算子;
  5. 恢复prev_lr,避免影响后续参数。

这个"临时改写、用完即恢复"的设计,使得每个参数都能用自己独立的缩放后学习率走一次 Adam 更新,而不会污染其他参数。

关于解耦权重衰减(decoupled weight decay),_append_decoupled_weight_decay 实现了parameter = parameter - parameter * coeff * lr,即 AdamW 标志性的"权重衰减与梯度更新解耦"。其实现有两个工程细节值得注意:

  • 按学习率缓存衰减系数decay_coeff = 1.0 - learning_rate * coeff会被缓存进self._lr_to_coeff,同一学习率只计算一次;在动态图模式下每次apply_gradient后都会清空该缓存(见 _create_optimization_pass),保证每步重算;
  • 支持 FP16 多精度:当multi_precision=True且参数为paddle.float16时,衰减作用在self._master_weights[param.name]主权重上,再写回主权重,避免半精度下数值精度损失。

ExponentialMovingAverage:EMA 影子参数维护

ExponentialMovingAverage位于 paddlenlp/ops/optimizer/ema.py,是一个轻量的参数级 EMA 工具,在训练过程中维护一组"影子参数",用于在验证/推理时获得更平滑的模型权重。其四个方法构成完整生命周期:

class ExponentialMovingAverage(object): def __init__(self, model, decay=0.999): self.model = model self.decay = decay self.shadow = {} self.backup = {}
方法作用
register()遍历model.named_parameters(),为所有stop_gradient == False(即可训练)的参数克隆一份影子参数
update()每步更新影子参数:new_average = (1.0 - decay) * param + decay * shadow,然后写回shadow
apply_shadow()将当前模型参数备份进backup,再把影子参数赋给模型(用于评估/推理)
restore()backup恢复原始模型参数,并清空备份

使用模式通常是:训练每个 step 后调用update();在验证阶段调用apply_shadow()换入平滑权重,验证结束后调用restore()恢复,继续训练。默认decay=0.999意味着影子参数对历史权重保持很强的记忆,适用于长期稳定训练。

在仓库的奖励模型训练脚本中也能看到 EMA 思想的实践——llm/alignment/rm/legacy/run_reward.py 提供了--normalizer_type参数,允许选择RunningMeanStdExponentialMovingAverage作为奖励归一化器,并通过--momentum控制 EMA 动量(更新式EMA_{t+1} = momentum * x + (1 - momentum) * EMA_t),可见该工具在 PaddleNLP 对齐(alignment)训练管线中的实际落地位置。

InverseSquareRootSchedule:带 warmup 的逆平方根学习率调度

尽管InverseSquareRootSchedule位于 paddlenlp/ops/optimizer/lr.py(未出现在 RST 的 toctree 中),但它与AdamWDL、EMA 同属paddlenlp.ops.optimizer包并由__init__.py一并导出,是大模型预训练中常用配套调度器,故在此一并说明。它继承自paddle.optimizer.lr.LRScheduler,其学习率变化分为两段:

def get_lr(self): if self.last_epoch < self.warmup_steps: return self.warmup_init_lr + self.last_epoch * self.lr_step # 线性升温 else: return self.decay_factor * (self.last_epoch**-0.5) # 逆平方根衰减
  • warmup 阶段last_epoch < warmup_steps):学习率从0.0线性升至配置的学习率,步长为learning_rate / warmup_steps
  • 衰减阶段:学习率按更新步数的平方根倒数衰减,衰减因子为learning_rate * sqrt(warmup_steps),保证在 warmup 结束点与配置学习率平滑衔接。

构造参数:warmup_steps(warmup 步数)、learning_rate(默认1.0)、last_epoch(默认-1,可用于断点续训)、verbose(默认False,为True时每次更新打印学习率)。

综合实战:在预训练/微调脚本中组合使用

综合以上三个组件,一个典型的 Transformer 预训练优化器配置可以这样组织:

import paddle from paddlenlp.ops.optimizer import AdamWDL, InverseSquareRootSchedule model = build_transformer_model() # 你的模型,内部含 encoder.layers.* name_dict = {p.name: n for n, p in model.named_parameters()} # 1. 逆平方根学习率调度(warmup + 衰减) scheduler = InverseSquareRootSchedule( warmup_steps=1000, learning_rate=5e-4, ) # 2. 分层学习率 AdamW 优化器 optimizer = AdamWDL( learning_rate=scheduler, # 传入 LRScheduler 实例 parameters=model.parameters(), weight_decay=0.01, layerwise_decay=0.8, # 分层衰减率 α n_layers=12, # 与模型 encoder 层数一致 grad_clip=paddle.nn.ClipGradByGlobalNorm(1.0), )

使用前提与注意事项:

  • n_layers必须与模型实际的 encoder 层数一致,layerwise_lr_decay依赖encoder.layers.<idx>这种静态参数名做解析,如果模型的层命名不同(例如blocks.layers.),需要仿照 layerwise_lr_decay 自定义set_param_lr_fun
  • 动态图模式必须显式传入parameters
  • 分层学习率只影响 Adam 更新阶段,解耦权重衰减系数对所有参数一视同仁;
  • EMA 应在每步optimizer.step()之后调用ema.update(),评估时用apply_shadow()/restore()切换,且只作用于stop_gradient == False的参数。

小结

paddlenlp.ops.optimizer是 PaddleNLP 训练链路中一个"小而精"的优化器模块:

  • AdamWDL以继承paddle.optimizer.AdamW的方式实现动态分层学习率,核心是layerwise_lr_decayα^(n_layers - layer)指数衰减规则,以及_append_optimize_op中"临时改写学习率、用完即恢复"的算子注入机制,并内置了解耦权重衰减与 FP16 多精度支持;
  • ExponentialMovingAverage以轻量影子参数方式提供参数平滑,适用于训练末期稳定与评估场景;
  • InverseSquareRootSchedule提供线性 warmup + 逆平方根衰减的预训练标准学习率曲线。

三者组合,即可支撑大模型训练中"学习率调度 + 分层学习率 + 参数平滑"的完整诉求。进一步阅读可直达 API 文档页 与其子页paddlenlp.ops.optimizer.adamwdlpaddlenlp.ops.optimizer.ema,或直接查看源码 paddlenlp/ops/optimizer 目录下的三个实现文件。

  • 人工智能
  • 大模型
  • 预训练
  • 微调
  • LoRA
  • RLHF
  • 强化学习
  • 分布式训练

【免费下载链接】PaddleNLP

Easy-to-use and powerful LLM and SLM library with awesome model zoo.

项目地址:https://gitcode.com/gh_mirrors/pa/PaddleNLP
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询