- 人工智能
- 大模型
- 预训练
- 微调
- LoRA
- RLHF
- 强化学习
- 分布式训练
【免费下载链接】PaddleNLP
Easy-to-use and powerful LLM and SLM library with awesome model zoo.
paddlenlp.ops.optimizer是 PaddleNLP 中面向大规模 Transformer 训练调优的优化器工具包,其 API 文档页 paddlenlp.ops.optimizer.rst 通过 Sphinx autodoc 机制聚合了adamwdl与ema两个子模块的完整文档。本文以该文档为主体,结合仓库内 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"]| 名称 | 所属子模块 | 核心用途 |
|---|---|---|
AdamWDL | adamwdl.py | 带 Layer-wise 动态学习率设置的 AdamW 优化器,面向 Transformer 模型 |
layerwise_lr_decay | adamwdl.py | 默认的分层学习率衰减策略函数 |
ExponentialMovingAverage | ema.py | 模型参数的指数滑动平均(EMA),训练中维护影子参数 |
InverseSquareRootSchedule | lr.py | 逆平方根学习率调度(含 warmup 线性升温),常用于预训练 |
其中AdamWDL与layerwise_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_1、moment_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_rate | 0.001 | 学习率,可为 float 或LRScheduler |
beta1 | 0.9 | 一阶矩估计的指数衰减率 |
beta2 | 0.999 | 二阶矩估计的指数衰减率 |
epsilon | 1e-8 | 数值稳定性小量 |
parameters | None | 待更新的参数列表/元组,动态图模式下必填;静态图默认为None(更新全部参数) |
weight_decay | 0.01 | 权重衰减系数,可为 float 或 Tensor |
apply_decay_param_fun | None | 若不为None,仅当apply_decay_param_fun(Tensor.name) == True的参数执行 weight decay |
grad_clip | None | 梯度裁剪策略,支持GradientClipByGlobalNorm/GradientClipByNorm/GradientClipByValue |
lazy_mode | False | 惰性模式:仅更新当前 mini-batch 有梯度的元素,参数极大时可显著加速,但语义与标准 Adam 略有差异 |
multi_precision | False | 是否在权重更新时使用多精度(如 FP32 master weight) |
layerwise_decay | 1.0 | 分层衰减率α |
n_layers | 12 | Transformer encoder 总层数 |
set_param_lr_fun | layerwise_lr_decay | 自定义的参数学习率设置函数,在 Adam 算子执行前调用 |
name_dict | None | 动态名到静态名的映射字典,用model.named_parameters()获取 |
name | None | 优化器名称,一般无需设置 |
构造函数中值得注意的实现细节: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):
- 若
set_param_lr_fun is None,直接退化为原生AdamW._append_optimize_op,不启用分层学习率; - 先调用
_append_decoupled_weight_decay执行解耦的权重衰减(见下文); - 记录当前参数学习率
prev_lr,调用set_param_lr_fun(param)得到ratio,将optimize_attr["learning_rate"]临时乘以ratio; - 调用父类执行 Adam 更新算子;
- 恢复
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参数,允许选择RunningMeanStd或ExponentialMovingAverage作为奖励归一化器,并通过--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.adamwdl、paddlenlp.ops.optimizer.ema,或直接查看源码 paddlenlp/ops/optimizer 目录下的三个实现文件。
- 人工智能
- 大模型
- 预训练
- 微调
- LoRA
- RLHF
- 强化学习
- 分布式训练
【免费下载链接】PaddleNLP
Easy-to-use and powerful LLM and SLM library with awesome model zoo.
相关推荐
Thorium浏览器终极指南:如何构建高性能隐私优化的Chromium分支
Thorium浏览器终极指南:如何构建高性能隐私优化的Chromium分支 Thorium是一款基于Chromium源代码构建的高性能隐私优化浏览器,以放射性元
桌面应用跨平台深度学习学习率调度终极指南:指数衰减与余弦退火实战
深度学习学习率调度终极指南:指数衰减与余弦退火实战 在深度学习模型训练中,学习率调度是优化模型性能的关键技术。学习率决定了模型参数更新的步长,直接影响训练效率和
示例工程教程人工智能深度学习AI4Animation 中的 AdamW 优化器与余弦退火重启调度器:解耦权重衰减与循环学习率实战指南
AI4Animation 中的 AdamW 优化器与余弦退火重启调度器:解耦权重衰减与循环学习率实战指南 本文围绕 AI4Animation https://l
人工智能深度学习图形学游戏开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考