fairseq 优化器体系完全指南:从 FairseqOptimizer 基类到 Adam、Adafactor 与 FP16 混合精度实战
【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm
本篇技术指南聚焦 EdgeLM/UniLM 仓库中 fairseq 训练框架的优化器(Optimizer)模块,系统讲解 fairseq.optim 的架构设计、注册机制、内置优化器家族的配置参数与底层实现原理,并覆盖 FP16/AMP 混合精度优化器及学习率调度器的协同机制。读者学完后,将能够熟练通过命令行参数在 fairseq 训练流程中选用、配置并调优合适的优化器,理解其状态管理、梯度裁剪与断点恢复行为。
优化器的核心角色与整体架构
在 fairseq 中,优化器承担"根据梯度更新模型参数"这一职责,其抽象定义见于官方文档 edgelm/docs/optim.rst:Optimizers update the Model parameters based on the gradients。
整个优化器体系以三个层次组织:
- 基类层:FairseqOptimizer 定义了所有优化器与 fairseq 训练循环交互的统一接口;
- 注册层:
fairseq/optim/__init__.py通过 registry 机制把每种优化器绑定到--optimizer命令行选项; - 实现层:
adadelta、adagrad、adafactor、adam、adamax、nag、sgd等具体优化器,以及fp16_optimizer、amp_optimizer等混合精度封装。
从源码结构看,fairseq/optim/__init__.py在导入时会自动遍历optim/目录下所有非下划线开头的.py文件并逐一importlib.import_module,因此新增一个优化器文件即可自动注册,无需手工修改注册表:
for file in sorted(os.listdir(os.path.dirname(__file__))): if file.endswith(".py") and not file.startswith("_"): file_name = file[: file.find(".py")] importlib.import_module("fairseq.optim." + file_name)FairseqOptimizer 基类:统一训练接口
FairseqOptimizer 是所有优化器的抽象基类,核心设计目标是屏蔽底层torch.optim.Optimizer的差异,向训练循环提供一致的操作原语:
| 接口 | 作用 |
|---|---|
optimizer属性 | 返回内部持有的torch.optim.Optimizer实例,并对类型做强制校验 |
params/param_groups | 迭代访问优化器管理的参数与参数分组 |
get_lr()/set_lr(lr) | 读取/设置当前学习率(逐 param_group 生效) |
state_dict()/load_state_dict() | 优化器状态的保存与恢复,支持断点续训 |
backward(loss) | 计算梯度,默认实现即loss.backward() |
multiply_grads(c) | 将全部梯度乘以常数c(FP16 梯度缩放时使用) |
clip_grad_norm(max_norm) | 调用fairseq.utils.clip_grad_norm_做梯度范数裁剪 |
step(closure, scale, groups) | 执行一次参数更新,兼容带缩放系数与分组更新的优化器 |
zero_grad() | 清空梯度(置为None后再调用底层zero_grad) |
值得注意的特性能力(capability)探测属性:supports_memory_efficient_fp16、supports_step_with_scale、supports_groups、supports_flat_params。训练框架据此决定是否启用扁平化参数、是否将缩放系数传入底层step,以及是否使用内存高效的 FP16 训练路径——例如SGD、Adam、NAG都声明supports_flat_params = True,而Adagrad、Adafactor则为False。
load_state_dict还支持optimizer_overrides参数:恢复检查点时,可以保留当前配置的学习率/动量等超参数覆盖检查点中的旧值,这正是"换一个学习率继续训练"这一常见需求的实现基础。
通过--optimizer注册与选择优化器
优化器通过 registry 与命令行选项绑定(见 edgelm/fairseq/optim/init.py):
(_build_optimizer, register_optimizer, OPTIMIZER_REGISTRY, OPTIMIZER_DATACLASS_REGISTRY) = \ registry.setup_registry("--optimizer", base_class=FairseqOptimizer, required=True)--optimizer是必需参数。每种优化器用装饰器注册其名称,例如@register_optimizer("sgd")、@register_optimizer("adam", dataclass=FairseqAdamConfig)、@register_optimizer("adafactor")、@register_optimizer("nag", dataclass=FairseqNAGConfig)、@register_optimizer("composite", dataclass=CompositeOptimizerConfig)。
build_optimizer(cfg, params)会先过滤掉requires_grad=False的参数,再委托_build_optimizer构造实例。使用示例:
fairseq-train>if group["weight_decay"] != 0: p_data_fp32.add_(p_data_fp32, alpha=-group["weight_decay"] * group["lr"])配置参数(FairseqAdamConfig)
| 参数 | 默认值 | 说明 |
|---|---|---|
--adam-betas | (0.9, 0.999) | Adam 的一阶/二阶矩衰减系数 |
--adam-eps | 1e-8 | 分母数值稳定项 |
--weight-decay(--wd) | 0.0 | 权重衰减(L2 惩罚) |
--use-old-adam | False | 强制使用 fairseq 内置 Adam 而非 FusedAdam |
--fp16-adam-stats | False | 使用 FP16 存储 Adam 状态(自动缩放),需 FusedAdamV1 支持 |
底层优化器选择逻辑
FairseqAdam.__init__按以下优先级实例化底层优化器(源码):
- 若
--tpu开启:使用 fairseq 内置Adam(自动把梯度转 FP32); - 否则,若存在 fused adam 内核(
get_fused_adam_class())且 GPU 可用且未强制--use-old-adam:使用FusedAdam,此时--fp16-adam-stats生效; - 否则回退到 fairseq 内置
Adam。
内置Adam实现了完整的 Adam 更新公式(源码),包括:一阶矩exp_avg.mul_(beta1).add_(grad, alpha=1-beta1)、二阶矩exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1-beta2)、偏差校正bias_correction1/2,以及可选的 AMSGrad 变体。它对 FP16/BF16 梯度与参数做了透明升精度处理(grad = grad.float()),更新后再拷回低精度。
average_params()方法专用于 BMUF 分布式训练:将exp_avg、exp_avg_sq除以世界大小并做all_reduce(SUM),实现跨卡状态平均。
SGD:经典动量优化器
SGD 是LegacyFairseqOptimizer的直接子类,底层封装torch.optim.SGD:
| 参数 | 默认值 | 说明 |
|---|---|---|
--momentum | 0.0 | 动量因子 |
--weight-decay/--wd | 0.0 | 权重衰减 |
fairseq-train ... --optimizer sgd --lr 0.25 --momentum 0.9 --weight-decay 0.0001其optimizer_config从args.lr[0]取学习率——fairseq 的学习率配置本身是一个列表(供调度器变化使用),构造底层优化器时取首个元素。supports_flat_params = True使其可以配合扁平化参数实现更高的显存/带宽效率。
Adadelta:自适应学习率
Adadelta 封装torch.optim.Adadelta,特有参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
--adadelta-rho | 0.9 | 平方梯度滑动平均系数 |
--adadelta-eps | 1e-6 | 分母数值稳定项 |
--anneal-eps | False | 是否随时间退火 eps |
--weight-decay | 0.0 | 权重衰减 |
fairseq-train ... --optimizer adadelta --adadelta-rho 0.95 --adadelta-eps 1e-6Adagrad:稀疏梯度场景的朴素自适应
Adagrad 封装torch.optim.Adagrad,仅额外暴露--weight-decay。它在 fairseq 中保留了稀疏/低频特征场景下的自适应学习率能力,但supports_flat_params = False,意味着无法使用参数扁平化优化路径。
FairseqAdafactor:亚线性显存的因子分解自适应优化器
FairseqAdafactor 实现了 Adafactor 算法(参考论文《Adafactor: Adaptive Learning Rates with Sublinear Memory Cost》),其核心卖点是对二维以上参数(如注意力权重矩阵)用行/列统计的秩一近似替代完整二阶矩,将优化器状态显存从 O(n²) 降到 O(n)。
配置参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--adafactor-eps | (1e-30, 1e-3) | 平方梯度与参数尺度的两个正则常数 |
--clip-threshold | 1.0 | 更新 RMS 的裁剪阈值 |
--decay-rate | -0.8 | 二阶矩估计的衰减率(按步数幂律) |
--beta1 | None | 一阶矩系数(可选,默认不使用一阶矩) |
--weight-decay | 0.0 | 权重衰减 |
--scale-parameter | False(fairseq 层) | 用参数 RMS 缩放学习率 |
--relative-step | False(fairseq 层) | 用步数平方根倒数计算学习率 |
--warmup-init | False | 结合 relative_step 的预热初始化 |
注意:源码 docstring 指出,fairseq 层默认scale_parameter=False, relative_step=False(走外部学习率路径),而底层Adafactor类的原生默认值相反。若要使用手动外部学习率调度,应保持scale_parameter=False且relative_step=False;若要让优化器内部自动计算学习率,则设置--relative-step(此时不可再传手动lr)。
因子分解与更新流程(源码级)
step实现的关键路径(edgelm/fairseq/optim/adafactor.py#L151-L167):
_get_options依据张量维度判断是否使用因子分解(factored = len(shape) >= 2)以及是否启用一阶矩;- 对因子分解情况,维护
exp_avg_sq_row与exp_avg_sq_col两个秩一统计,经_approx_sq_grad用行/列因子外积近似完整二阶矩; _get_lr在relative_step=True时计算min(min_step, 1.0 / sqrt(step))的步数相关学习率,在scale_parameter=True时乘以参数 RMS;- 每次更新用
_rms(update)/clip_threshold做裁剪,防止单步更新过大; - 二阶矩衰减采用步数幂律
beta2t = 1.0 - step ** decay_rate(默认-0.8)。
文档同时提示:Adafactor 在 FP16 下存在经验性的收敛问题,可能需要搜索合适的配置。
fairseq-train ... --optimizer adafactor --lr 1e-3 --clip-threshold 1.0 --decay-rate -0.8FairseqNAG:Nesterov 加速梯度
FairseqNAG 实现了 Nesterov 动量更新,配置通过 dataclass 声明:
| 参数 | 默认值 | 说明 |
|---|---|---|
--momentum | 0.99 | Nesterov 动量因子 |
--weight-decay | 0.0 | 权重衰减 |
NAG.step的实现要点(源码):维护动量缓冲momentum_buffer,参数更新为p += momentum²·lr_correct·buf - (1+momentum)·lr·grad,缓冲更新为buf = momentum·lr_correct·buf - lr·grad;其中lr_correct = lr/lr_old用于在调度器改变学习率时对动量做校正。它也声明支持supports_memory_efficient_fp16与supports_flat_params。
fairseq-train ... --optimizer nag --lr 0.1 --momentum 0.99FP16 混合精度优化器:FP16Optimizer 与 MemoryEfficientFP16Optimizer
大模型训练通常采用 FP16 混合精度。fp16_optimizer.py 中的_FP16OptimizerMixin提供统一的 FP16 训练基础设施:
- FP32 主权重:
build_fp32_params为模型参数创建 FP32 副本(支持扁平化flatten=True或逐参数两种形态),所有优化器状态与更新都在 FP32 上维护,避免低精度下状态漂移; - 动态损失缩放:配合 dynamic_loss_scaler.py 中的
DynamicLossScaler自动调整梯度缩放系数,防止 FP16 梯度下溢; - 梯度处理:
step前统一按缩放系数调整梯度(scale=1.0时直接透传,否则multiply_grads(1.0/scale)),更新完成后把 FP32 参数拷回 FP16 模型。
使用 FP16 训练的典型方式:
fairseq-train ... --fp16 --optimizer adamfairseq 会根据优化器是否支持内存高效 FP16(supports_memory_efficient_fp16)自动选择MemoryEfficientFP16Optimizer或标准FP16Optimizer——支持者直接以 FP16 梯度计算、减少显存占用,不支持者走先转 FP32 的常规路径。
扩展优化器家族与组合优化器
除文档列出的优化器外,optim/目录还包含:
- adamax.py:Adamax(Adam 的无穷范数变体);
- fused_adam.py / fused_lamb.py:融合内核加速版;
- cpu_adam.py:CPU 上的 Adam(卸载场景);
- bmuf.py:BMUF 块模型更新滤波,配合
average_params()使用; - composite.py:复合优化器,允许为不同参数分组配置不同优化器与学习率调度器。
Composite:按参数分组配置优化器
FairseqCompositeOptimizer(edgelm/fairseq/optim/composite.py#L40-L60)通过--optimizer composite --composite-optimizer '{"groups": {...}}'使用,要求模型存在多个参数分组(参数对象需带param_group属性;对 FP16 训练需开启fp16_no_flatten_grads: true保留分组结构)。每个分组可独立配置优化器与调度器,例如主干用 Adam、特定模块用 SGD,为分层学习率等高级训练策略提供支撑。
与学习率调度器的协同
优化器只负责"按梯度更新",学习率的变化由调度器驱动。lr_scheduler/目录提供完整调度家族(目录):
fixed_schedule:固定学习率;inverse_square_root_schedule:Transformer 常用的平方根倒数衰减;polynomial_decay_schedule:多项式衰减;cosine_lr_scheduler:余弦退火;triangular_lr_scheduler/tri_stage_lr_scheduler:三角/三阶段波动;reduce_lr_on_plateau:平台期自动降学习率;step_lr_scheduler:按步数阶梯下降;manual_lr_scheduler:手工指定各阶段学习率;pass_through:透传(配合外部调度)。
调度器通过FairseqOptimizer.set_lr()在每个 step 前更新学习率,而优化器从--lr列表读取当前值(底层构造取lr[0])。典型组合:
fairseq-train ... \ --optimizer adam \ --lr 0.0005 --lr-scheduler inverse_sqrt \ --warmup-updates 4000 \ --max-update 30000FairseqNAG中的lr_old/lr_correct机制正是为配合调度器频繁改学习率而设计的动量校正,体现了优化器与调度器之间的深度协同。
实战小结与调试建议
- 默认首选:无特殊需求时使用
--optimizer adam(AdamW 语义),配合--lr-scheduler inverse_sqrt与 warmup,是 NLP 预训练/微调的稳健组合; - 显存受限:大模型场景优先
--optimizer adafactor,利用因子分解把优化器状态显存降到亚线性,或使用--fp16+ FusedAdam +--fp16-adam-stats; - 调参入口:所有优化器参数均挂在
--optimizer选项之下,可用fairseq-train ... --optimizer <name> --help查看完整参数表; - 断点续训:
load_state_dict(state_dict, optimizer_overrides=...)支持在恢复检查点时用新学习率覆盖旧配置; - 问题排查:梯度范数异常时检查
--clip-norm与 FP16 动态损失缩放;Adafactor 在 FP16 下收敛异常时,可尝试调整--clip-threshold/--decay-rate或改用 Adam。
如需深入阅读实现细节,建议按以下顺序研读仓库源码:FairseqOptimizer 基类、注册与构建逻辑、FairseqAdam、FairseqAdafactor、FP16 优化器 与 动态损失缩放。
【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考