Transformers 学习率调度完全指南:.optimization 模块中的优化器、调度器与 get_scheduler 统一 API
本篇指南基于 Transformers 文档 optimizer_schedules 展开,系统讲解 .optimization 模块提供的三大类能力:支持权重衰减的 Adafactor 优化器、十余种学习率调度器(从常数、线性、余弦到 WSD 与自适应 GreedyLR),以及统一入口 get_scheduler。读完后,你可以独立为微调任务选择并配置合适的学习率策略,并理解 Trainer 内部如何根据 lr_scheduler_type 完成调度器的自动构建。
模块概览:.optimization 提供什么
根据官方文档描述,.optimization 模块提供三类组件:
- 一个固定权重衰减(weight decay fixed)的优化器,可直接用于模型微调;
- 一组继承自 PyTorch
LambdaLR/LRScheduler体系的调度器工厂函数(get_*_schedule); - 支持多批次梯度累积的梯度累积类。
源码实现集中在单文件 src/transformers/optimization.py(约 1300 行),调度器名称枚举则定义在 src/transformers/trainer_utils.py。该模块只依赖 PyTorch 的 torch.optim,因此所有调度器都是标准的 torch.optim.lr_scheduler 对象,可与任意训练循环(不限于 Trainer)配合使用。
调度器类型枚举:SchedulerType
SchedulerType 是一个 ExplicitEnum,它定义了 TrainingArguments 中 lr_scheduler_type 参数的全部合法取值。从 trainer_utils.py 的源码可以看到完整映射关系:
| 枚举值 | 字符串取值 | 对应的调度器函数 |
|---|---|---|
LINEAR |
"linear" |
get_linear_schedule_with_warmup |
COSINE |
"cosine" |
get_cosine_schedule_with_warmup |
COSINE_WITH_RESTARTS |
"cosine_with_restarts" |
get_cosine_with_hard_restarts_schedule_with_warmup |
POLYNOMIAL |
"polynomial" |
get_polynomial_decay_schedule_with_warmup |
CONSTANT |
"constant" |
get_constant_schedule |
CONSTANT_WITH_WARMUP |
"constant_with_warmup" |
get_constant_schedule_with_warmup |
INVERSE_SQRT |
"inverse_sqrt" |
get_inverse_sqrt_schedule |
REDUCE_ON_PLATEAU |
"reduce_lr_on_plateau" |
get_reduce_on_plateau_schedule |
COSINE_WITH_MIN_LR |
"cosine_with_min_lr" |
get_cosine_with_min_lr_schedule_with_warmup |
COSINE_WARMUP_WITH_MIN_LR |
"cosine_warmup_with_min_lr" |
get_cosine_with_min_lr_schedule_with_warmup_lr_rate |
WARMUP_STABLE_DECAY |
"warmup_stable_decay" |
get_wsd_schedule |
GREEDY |
"greedy" |
get_greedy_schedule |
文档同时说明:lr_scheduler_type 默认为 "linear",Trainer 内部会据此取用 get_linear_schedule_with_warmup。
统一入口:get_scheduler
对于手动编写训练循环的场景,推荐使用统一 API get_scheduler 按名称获取任意调度器:
from transformers import get_scheduler
import torch
optimizer = torch.optim.AdamW(model.parameters(), lr=5e-5)
scheduler = get_scheduler(
"cosine", # 调度器名称,对应 SchedulerType 字符串
optimizer=optimizer,
num_warmup_steps=100, # warmup 步数
num_training_steps=2000, # 总训练步数
scheduler_specific_kwargs={}, # 调度器专属参数,如 num_cycles、min_lr 等
)
四个参数的含义(依据源码 docstring):
name(str或SchedulerType):调度器名称;optimizer:训练中使用的torch.optim.Optimizer;num_warmup_steps(可选):warmup 步数。并非所有调度器都需要——constant、reduce_lr_on_plateau、greedy不需要;需要而未提供时函数会抛出ValueError;num_training_steps(可选):总训练步数。除constant、constant_with_warmup、inverse_sqrt、reduce_lr_on_plateau、greedy、warmup_stable_decay外,其余调度器均必需;scheduler_specific_kwargs(可选字典):透传给具体调度器的专属参数,例如cosine_with_restarts的num_cycles、cosine_with_min_lr的min_lr。参数与调度器类型不匹配时,底层函数会抛出TypeError。
get_scheduler 的分发逻辑
从 optimization.py 源码可以看到,模块内维护了一个 TYPE_TO_SCHEDULER_FUNCTION 字典(第 944-957 行),将 12 个 SchedulerType 枚举逐一映射到工厂函数。分发规则为:
- 若传入的是
LayerWiseDummyOptimizer(逐层优化器占位对象),则对其中每个子优化器递归调用get_scheduler,并给参数注册post_accumulate_grad_hook,使各参数组在梯度累积后自动step(),最终返回LayerWiseDummyScheduler——这为后续逐层学习率(layer-wise LR)等高级用法打下基础; CONSTANT直接返回get_constant_schedule(optimizer);REDUCE_ON_PLATEAU与GREEDY仅透传scheduler_specific_kwargs(因为二者基于验证指标而非步数工作);- 其余调度器要求
num_warmup_steps非空,其中CONSTANT_WITH_WARMUP与INVERSE_SQRT不需要num_training_steps;WARMUP_STABLE_DECAY额外要求num_training_steps或num_stable_steps二者之一(通过scheduler_specific_kwargs传入); - 其余调度器要求
num_training_steps非空后,以num_warmup_steps + num_training_steps + **kwargs的形式调用工厂函数。
Trainer 内部如何调用 get_scheduler
在使用 Trainer 时,上述入口会被自动封装。Trainer.create_scheduler 的关键实现是:
self.lr_scheduler = get_scheduler(
self.args.lr_scheduler_type,
optimizer=optimizer,
num_warmup_steps=self.args.get_warmup_steps(num_training_steps),
num_training_steps=num_training_steps,
scheduler_specific_kwargs=self.args.lr_scheduler_kwargs,
)
也就是说,TrainingArguments 中的 --lr_scheduler_type、--warmup_steps(或 warmup_ratio)、--lr_scheduler_kwargs 会在此处被消费。例如想让余弦调度保留最小学习率,可以写:
python train.py ... \
--lr_scheduler_type cosine_with_min_lr \
--lr_scheduler_kwargs '{"min_lr": 1e-6}'
从源码结构看,lr_scheduler_kwargs 是一个字符串形式的 JSON 字典,会被解析后原样展开为工厂函数的关键字参数,因此不同调度器可以复用同一个命令行参数位。
各调度器详解与参数说明
以下逐一说明文档列出的调度器,参数与默认值均来自 optimization.py 中的函数签名与 docstring。所有带 warmup 的调度器行为一致:warmup 阶段学习率从 0 线性上升到优化器初始学习率。
get_constant_schedule
get_constant_schedule(optimizer, last_epoch=-1)
学习率保持优化器设定值不变,返回 LambdaLR。last_epoch 用于断点续训时指定恢复到的 epoch 索引。适合与 ReduceLROnPlateau 思想互补的简单基线,或验证训练管线时使用。
get_constant_schedule_with_warmup
get_constant_schedule_with_warmup(optimizer, num_warmup_steps, last_epoch=-1)
warmup 阶段后保持恒定学习率。其核心 lambda 实现为:
def _get_constant_schedule_with_warmup_lr_lambda(current_step, *, num_warmup_steps):
if current_step < num_warmup_steps:
return float(current_step) / float(max(1.0, num_warmup_steps))
return 1.0
注意分母用 max(1.0, num_warmup_steps) 保护,避免 warmup 步数为 0 时除零。
get_linear_schedule_with_warmup
get_linear_schedule_with_warmup(optimizer, num_warmup_steps, num_training_steps, last_epoch=-1)
Trainer 的默认调度器:warmup 后学习率从初始值线性衰减到 0。核心公式为:
max(0.0, float(num_training_steps - current_step)
/ float(max(1, num_training_steps - num_warmup_steps)))
即衰减斜率由「总步数 − warmup 步数」归一化决定,并在末尾钳制到 0。
get_cosine_schedule_with_warmup
get_cosine_schedule_with_warmup(optimizer, num_warmup_steps, num_training_steps,
num_cycles=0.5, last_epoch=-1)
warmup 后按余弦函数从初始学习率衰减到 0。num_cycles(默认 0.5)表示余弦波数,默认即「半个余弦」——从峰值平滑降到 0:
progress = (current_step - num_warmup_steps) / (num_training_steps - num_warmup_steps)
factor = max(0.0, 0.5 * (1.0 + math.cos(math.pi * 2.0 * num_cycles * progress)))
微调大模型时最常见的选择之一,因其后期学习率趋近于 0,收敛更平稳。
get_cosine_with_hard_restarts_schedule_with_warmup
get_cosine_with_hard_restarts_schedule_with_warmup(optimizer, num_warmup_steps,
num_training_steps,
num_cycles=1, last_epoch=-1)
与上一节的区别在于学习率会多次回到峰值(warmup 后执行 num_cycles 次「硬重启」)。从源码看,其 lambda 对进度做了取模:
progress = (current_step - num_warmup_steps) / (num_training_steps - num_warmup_steps)
if progress >= 1.0:
return 0.0
return max(0.0, 0.5 * (1.0 + math.cos(math.pi * ((num_cycles * progress) % 1.0))))
即把整个衰减区间切成 num_cycles 段,每段内部独立走一次半余弦。注意此处 num_cycles 是 int(硬重启次数),而普通余弦的 num_cycles 是 float。进度超过 1.0 时返回 0,保证训练末尾学习率归零。
get_cosine_with_min_lr_schedule_with_warmup
get_cosine_with_min_lr_schedule_with_warmup(optimizer, num_warmup_steps, num_training_steps,
num_cycles=0.5, last_epoch=-1,
min_lr=None, min_lr_rate=None)
普通余弦的改进版:终点不是 0 而是 min_lr。约束条件(源码第 372-377 行):
min_lr与min_lr_rate只能设置其一,同时设置抛ValueError;- 二者都不设置同样抛
ValueError(错误信息明确提示应通过lr_scheduler_kwargs传入); - 若只给
min_lr,内部会换算为min_lr_rate = min_lr / optimizer.defaults["lr"]。
实现上,衰减因子被重新标定:factor = factor * (1 - min_lr_rate) + min_lr_rate,保证余弦最低点恰好落在 min_lr。
get_cosine_with_min_lr_schedule_with_warmup_lr_rate
get_cosine_with_min_lr_schedule_with_warmup_lr_rate(optimizer, num_warmup_steps, num_training_steps,
num_cycles=0.5, last_epoch=-1,
min_lr=None, min_lr_rate=None,
warmup_lr_rate=None)
在上一个基础上新增 warmup_lr_rate 参数:warmup 起点不再是 0,而是从 warmup_lr_rate 比例线性升到 1。未设置时按 1/num_warmup_steps 处理(源码第 403-404 行)。其进度计算还做了「半步修正」(current_step - num_warmup_steps + 1.0),避免第一步与 warmup 末点的重复采样。min_lr / min_lr_rate 的互斥规则与上一节完全一致。
get_polynomial_decay_schedule_with_warmup
get_polynomial_decay_schedule_with_warmup(optimizer, num_warmup_steps, num_training_steps,
lr_end=1e-7, power=1.0, last_epoch=-1)
warmup 后以多项式衰减到 lr_end。约束与公式(源码第 273-275 行):
- 要求
lr_end < lr_init,否则抛ValueError; lr_init取自optimizer.defaults["lr"];- 衰减项:
decay = lr_range * pct_remaining**power + lr_end,其中pct_remaining = 1 - (current - warmup) / decay_steps; power=1.0时退化为线性衰减(与 fairseq/原始 BERT 实现保持一致);- 若
current_step > num_training_steps(例如训练意外超长),学习率锁定在lr_end/lr_init比例,不会继续下降。
get_inverse_sqrt_schedule
get_inverse_sqrt_schedule(optimizer, num_warmup_steps, timescale=None, last_epoch=-1)
warmup 后按 1/sqrt(step) 衰减,是 Transformer 原始论文中的经典调度。源码注释表明该实现改编自 Google big_vision 工具库。timescale 未提供时默认取 num_warmup_steps(再退化为 10000):
if timescale is None:
timescale = num_warmup_steps or 10_000
decay = 1.0 / math.sqrt((current_step + (timescale - num_warmup_steps)) / timescale)
该调度不需要 num_training_steps,因此在 get_scheduler 的分发分支中它只被要求提供 num_warmup_steps——对总步数不可预知的训练(如流式数据)特别合适。
get_reduce_on_plateau_schedule
get_reduce_on_plateau_schedule(optimizer, **kwargs)
直接包装 PyTorch 的 ReduceLROnPlateau(源码第 71 行仅一行:return ReduceLROnPlateau(optimizer, **kwargs)),学习率恒定、在监控指标停止改善时按 factor 下调。其所有参数(mode、factor、patience、threshold 等)经 **kwargs 透传给 PyTorch。由于它按 epoch/验证轮而非按训练步触发,get_scheduler 对其不要求 num_warmup_steps 与 num_training_steps。
get_wsd_schedule(Warmup-Stable-Decay)
get_wsd_schedule(optimizer, num_warmup_steps, num_decay_steps,
num_training_steps=None, num_stable_steps=None,
warmup_type="linear", decay_type="cosine",
min_lr_ratio=0, num_cycles=0.5, last_epoch=-1)
三阶段调度:warmup → 恒定平台期 → 衰减期。关键约束(源码第 553-566 行):
num_training_steps与num_stable_steps必须至少提供一个,否则ValueError;同时提供时优先使用num_stable_steps并发出 warning;warmup_type/decay_type各自取值"linear"、"cosine"、"1-sqrt",越界抛ValueError;- 三段步数之和应等于
num_training_steps,否则超出部分的学习率直接落到min_lr_ratio对应的最小值。
三阶段 lambda 的核心逻辑:warmup 阶段按所选类型从 min_lr_ratio 升到 1.0;平台期恒为 1.0;衰减阶段按 decay_type 从 1.0 降到 min_lr_ratio,并且每个阶段结束前都做 factor * (1 - min_lr_ratio) + min_lr_ratio 的重标定,保证衰减终点精确落在最小学习率比例上。
GreedyLR 与 get_greedy_schedule:基于指标的双向自适应调度器
scheduler = get_greedy_schedule(optimizer, **kwargs) # 等价于 GreedyLR(optimizer, **kwargs)
GreedyLR(optimization.py 第 621 行起)是本模块中最独特的调度器:它不按步数走预定曲线,而是根据验证指标双向调整学习率——指标持续改善时除以 factor 提升学习率,指标进入平台期时乘以 factor 降低学习率。与 ReduceLROnPlateau 只有「降」不同,GreedyLR 还能「升」,并在长期触底后支持整表重置。
构造参数(默认值来自源码签名):
| 参数 | 默认值 | 说明 |
|---|---|---|
mode |
"min" |
"min":指标停止下降时降 LR;"max":停止上升时降 LR |
factor |
0.95 |
平台期乘该值降 LR,改善期除该值升 LR,必须小于 1.0 |
patience |
10 |
连续多少轮无改善后调整;连续多少轮持续改善后升 LR |
threshold / threshold_mode |
1e-06 / "abs" |
判定「有改善」的阈值,"abs" 或 "rel" |
cooldown |
0 |
降 LR 后暂停调整的轮数 |
warmup |
0 |
升 LR 后暂停调整的轮数 |
min_lr |
1e-3 |
学习率下限,支持按参数组传列表 |
max_lr |
1.0 |
学习率上限,支持列表 |
smooth / window_size |
False / 50 |
是否对指标做滑动窗口平均再决策 |
reset_start |
500 |
所有参数组都降到 min_lr 后再持续多少步触发 _reset() 整表重置 |
源码还配套了 StreamingAverage 类(第 581-618 行)实现滑窗均值,且 GreedyLR 自带 state_dict / load_state_dict,可随检查点保存与恢复。典型用法(docstring 示例):
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
scheduler = GreedyLR(optimizer, mode="min", patience=10)
for epoch in range(100):
train(...)
val_loss = validate(...)
scheduler.step(val_loss) # 注意:传入的是指标值,而不是空 step()
step(metrics) 的决策流程(源码第 749-793 行):先做可选平滑 → 冷却期/升 LR 保护期内跳过 → 与历史最优比较,累计 num_good_epochs / num_bad_epochs → 任一计数超过 patience 即调用 _increase_lr 或 _reduce_lr。降 LR 时每个参数组钳制在 min_lrs[i] 之上,若全部触底则 reset_start 递减,归零后调用 _reset() 恢复初始学习率并清空所有计数与平滑器状态。
Adafactor 优化器
Adafactor 是 fairseq 原版 Adafactor 的 PyTorch 移植,主打亚线性显存开销:对矩阵参数用行/列向量的分解近似来估计二阶矩,而非维护完整同形状缓存。主要参数与默认值:
| 参数 | 默认值 | 说明 |
|---|---|---|
lr |
None |
外部学习率;与 relative_step=True 不可同时使用(源码第 1155 行会抛 ValueError) |
eps |
(1e-30, 1e-3) |
平方梯度与参数尺度的正则化常数 |
clip_threshold |
1.0 |
最终梯度更新 RMS 的裁剪阈值 |
decay_rate |
-0.8 |
二阶矩移动平均的衰减系数 |
beta1 |
None |
一阶矩系数;None 表示不启用一阶矩(纯二阶矩模式) |
weight_decay |
0.0 |
L2 惩罚 |
scale_parameter |
True |
是否按参数 RMS 缩放学习率 |
relative_step |
True |
是否使用内置的相对步长(min(1e-2, 1/sqrt(step)),warmup_init 时更早)而非外部 lr |
warmup_init |
False |
仅当 relative_step=True 时可用(第 1157 行校验) |
源码中的 step() 实现值得注意两点:
- 低精度支持:参数或梯度为
float16/bfloat16时,先转 fp32 计算再写回(第 1220-1292 行);不支持稀疏梯度; - 分解近似:对形状维度 ≥ 2 的参数启用
factored模式,用exp_avg_sq_row与exp_avg_sq_col两个低维向量加_approx_sq_grad(行列因子相乘)近似完整的平方梯度平均——这正是显存开销亚线性的来源。
docstring 给出的 T5 微调推荐配置:
# 关闭内置相对步长,使用外部调度器
Adafactor(model.parameters(), scale_parameter=False, relative_step=False,
warmup_init=False, lr=1e-3)
若使用 lr=None 让 Adafactor 内部自调度,则需配合代理调度器 AdafactorSchedule(第 1297-1324 行),它实现为空操作:get_lr() 直接从优化器的 _get_lr 读取当前真实学习率,供训练循环记录日志。完整示例:
from transformers.optimization import Adafactor, AdafactorSchedule
optimizer = Adafactor(model.parameters(),
scale_parameter=True, relative_step=True,
warmup_init=True, lr=None)
lr_scheduler = AdafactorSchedule(optimizer)
trainer = Trainer(..., optimizers=(optimizer, lr_scheduler))
此外,get_adafactor_schedule(optimizer, initial_lr=0.0) 工厂函数(第 1327 行起)可单独获取该代理调度器。
实践建议速查
结合上文源码证据,给出常见场景的选择依据:
| 场景 | 推荐 lr_scheduler_type |
理由(源码依据) |
|---|---|---|
| BERT 类模型微调(Trainer 默认路径) | linear |
默认值,线性归零衰减实现最简单可预期 |
| 大模型 / 视觉模型微调 | cosine |
半余弦平滑降到 0,后期收敛稳定 |
| 希望保留最低学习率 | cosine_with_min_lr |
必须经 lr_scheduler_kwargs 传 min_lr 或 min_lr_rate,二者互斥 |
| 数据量未知 / 流式训练 | inverse_sqrt |
不需要 num_training_steps,get_scheduler 分支只强制 num_warmup_steps |
| 总步数可分三段规划 | warmup_stable_decay |
可分别指定 warmup / 平台 / 衰减的曲线类型(linear / cosine / 1-sqrt) |
| 按验证集表现动态调参 | greedy 或 reduce_lr_on_plateau |
前者双向调整并可整表重置,后者仅下调;均不需要步数参数 |
| 降低显存的大模型训练 | Adafactor 优化器(可与上述调度器组合) |
分解二阶矩近似,状态量亚线性 |
延伸阅读与相关路径
- 文档原文:optimizer_schedules.md
- 调度器全部实现:src/transformers/optimization.py
- 枚举定义
SchedulerType:src/transformers/trainer_utils.py Trainer调用点:Trainer.create_schedulerlr_scheduler_type/lr_scheduler_kwargs参数声明:src/transformers/training_args.py
以上路径均可在仓库中直接查看源码,用于核对参数默认值、约束条件与错误分支,便于在自定义训练循环中精确复现 Trainer 的调度行为。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00