TRL documentation
异步 GRPO 训练器
异步 GRPO 训练器
概述
AsyncGRPOTrainer 实现了相同的 GRPO 算法,但将 rollout 生成与训练解耦。后台工作进程持续从 vLLM 服务器流式获取补全结果,同时训练循环消费这些结果,从而使生成与梯度更新重叠进行,而非交替执行。
此训练器由 Amine Dirhoussi 和 Quentin Gallouédec 贡献。
快速入门
本示例演示了如何使用异步 GRPO 方法训练模型。我们使用 DeepMath-103K 数据集 中的提示词来训练 Qwen3.5 2B 模型。您可以在这里查看该数据集的数据:
以下是用于训练模型的脚本。Trainer 需要 vLLM 和 kernels,而 accuracy_reward 需要 math-verify,您可以通过 pip install trl[vllm,kernels,math_verify] 进行安装。
# train_async_grpo.py
from datasets import load_dataset
from trl import AsyncGRPOTrainer
from trl.rewards import accuracy_reward
if __name__ == "__main__": # the rollout worker is a spawned process that re-imports this script
dataset = load_dataset("trl-lib/DeepMath-103K", split="train")
trainer = AsyncGRPOTrainer(
model="Qwen/Qwen3.5-2B",
reward_funcs=accuracy_reward,
train_dataset=dataset,
)
trainer.train()生成任务在 vLLM 服务器上运行,使用的 GPU 与训练器不同。请在 GPU 0 上启动它:
CUDA_VISIBLE_DEVICES=0 VLLM_SERVER_DEV_MODE=1 vllm serve Qwen/Qwen3.5-2B \
--weight-transfer-config '{"backend": "nccl"}' \
--logprobs-mode processed_logprobs \
--api-server-count 8 \
--max-model-len 4096然后使用以下命令在 GPU 1 上执行该脚本:
CUDA_VISIBLE_DEVICES=1 python train_async_grpo.py
兼容性与约束
当前 AsyncGRPOTrainer 实现中,部分参数组合被有意限制:
- 生成始终在独立的 vLLM 服务器上运行,该服务器通过
vllm serve启动。不存在共置模式。 - 数据集中的提示必须采用对话格式。如果使用
environment_factory,数据集可以没有prompt列,由reset()提供该列。 - 奖励函数必须是 Python 可调用对象。不支持以模型 ID 或 PreTrainedModel 形式提供的奖励模型,请参阅 使用自定义奖励函数。
- Trainer 需要
transformers>=5.2.0。 - 分片训练使用 FSDP2。不支持 DeepSpeed,也不支持上下文并行和序列并行(
parallelism_config中的cp_size > 1或sp_size > 1)。 use_liger_kernel=True不受支持。- 针对
lm_head的 PEFT 适配器不受支持。 - 不支持带有注意力汇聚(attention sinks)的模型。唯一支持的非注意力层是混合模型(Qwen3.5、Qwen3.6、Qwen3-Next 等)中的门控 DeltaNet 层,这需要
transformers>=5.15.0。 - 不支持图像,请参阅 训练视觉语言模型。
训练会通过 kernels 库从 Hub 加载其注意力内核:在 AMD GPU(ROCm)上使用 kernels-community/aiter-flash-attn,在 NVIDIA Blackwell GPU 上使用 kernels-community/flash-attn4,在其他 NVIDIA GPU 上使用 kernels-community/flash-attn3。对注意力 logits 设置上限(attn_logit_softcapping)的模型(例如 Gemma 2)不受 AMD GPU 或 NVIDIA Blackwell GPU 支持。
深入探讨异步 GRPO 方法
在 GRPOTrainer 中,生成和训练是顺序执行的:生成一个批次,计算损失,更新权重,然后重复。即使在 colocate 模式 下,一个阶段也必须完成后另一个阶段才能开始。在 AsyncGRPOTrainer 中,rollout 工作进程在后台进程中生成并评估补全,同时训练循环计算损失并更新模型。要理解其工作原理,可以将其分解为三个主要步骤:生成补全、计算优势 和 计算损失。由于没有参考模型,因此没有 KL 散度项(GRPO 的默认值为 beta=0.0)。
生成补全
Rollout 工作进程最多同时向 vLLM 服务器发送 max_concurrent_rollouts 个请求。对于每个提示,它会生成一组 个补全(num_generations) ),待整组完成后对其进行评分并计算优势值。生成的训练样本会被放入队列,供训练循环从中拉取。仅当生成进度领先训练 max_staleness 个步骤时,生成才会等待(参见 并发), ),而训练步骤仅在队列为空时才会等待。
每隔 weight_sync_steps 个训练步骤,trainer 会通过 NCCL 将其更新后的权重发送到 vLLM 服务器,因此后续的 completion 将来自最新的 policy。因此,某个 completion 可能来自比当前正在训练的 policy 旧几个步骤的版本:当 trainer 拉取某个样本时,如果该样本落后于当前权重更新超过 max_staleness 次,则会被丢弃。
指标和本页其余部分使用以下术语来描述补全所经历的各个阶段:
- a 组 是一个提示及其 个 rollout。优势值在其中计算。
- rollout 是为某个提示词生成的一次完整对话,包含所有工具调用。它由一个或多个 turn 组成,每个 turn 对应一次 vLLM 请求。
- sample 是用于计算损失的单元:即一次 rollout 的 token 及其对应的 advantage。多轮 rollout 可能会产生多个 sample(参见 Multi-turn rollouts)。
- 行是指一个进程在一个微批次中前向传播的内容:多个样本打包在一起(参见树打包)。一个微批次包含每个进程的一行,
gradient_accumulation_steps个微批次构成一个步,即优化器步。
Token 数量分为三种:generated token 是模型生成的 token,forwarded token 是样本中的所有 token(包括提示词和工具结果),trained token 是用于计算损失的 token。
计算优势
对于每个 组中的补全,奖励是各个奖励函数奖励的总和。优势值在组内进行归一化,与 GRPO 相同:
没有 scale_rewards 选项:优势值始终由该组的标准差进行缩放。对于所有奖励函数均返回 None 的补全,将不计入该组的均值和标准差,其优势值设为零。
奖励全部相等的组会被丢弃:在默认优势函数下,其值处处为零,因此对其进行训练只会产生一次前向和反向传播,却不会带来任何参数更新。此类组所占的比例会记录为 frac_reward_zero_std。训练步骤由保留下来的组填充,因此被丢弃的组越多,每个步骤所需的生成量就越大。被丢弃的组仍会计入 epoch,而 epoch 始终表示对提示词的一次完整遍历。
计算损失
vLLM 服务器的对数概率值永远不会与训练器完全一致:不同的内核、不同的精度,以及滞后几个步骤的策略(参见 处理训练-推理不匹配问题). 训练器将采样器的对数概率值作为旧策略,因此单个重要性比率同时涵盖了不匹配和滞后问题:
比例超出 [vllm_importance_sampling_clip_min, vllm_importance_sampling_clip_max] 范围的 Token 将从损失中剔除。有关更多信息,请参阅 Masked Importance Sampling。
默认情况下(score_centering=True), 训练器使用 Score Centering. 仅靠重要性权重无法消除这种不匹配:采样 token 的得分 在采样器 , 下并不平均为零,因此每次更新都会将策略拉向采样器。Score centering 在每个位置减去该期望值:
其中 在 内,且在 外。内部期望是对采样器在每个位置最可能的 num_sampler_logprobs 个 token 取的,这些 token 由 rollout worker 向服务器请求。
当 score_centering=False, 时,训练器改用 GRPO 的截断代理目标,并使用相同的掩码比率:
其中 是 epsilon , 是 epsilon_high.
在这两种情况下,损失值都会根据所有进程中微批次内已训练 token 的数量进行归一化,并在一个步骤的 gradient_accumulation_steps 个微批次上取平均值。
记录的指标
在训练过程中,我们记录以下指标:
num_tokens:迄今为止转发的 token 总数,包括 prompt 和 completion。在使用 tree packing 时,多个样本共享的前缀只计算一次。completions/mean_length:生成补全的平均长度。使用工具时,仅统计非工具 token。completions/min_length:生成补全的最小长度。使用工具时,仅统计非工具 token。completions/max_length:生成补全的最大长度。使用工具时,仅计算非工具 token。completions/mean_terminated_length:以 EOS 终止的生成补全的平均长度。使用工具时,仅统计非工具 token。completions/min_terminated_length:以 EOS 终止的生成补全的最小长度。使用工具时,仅统计非工具 token。completions/max_terminated_length:以 EOS 终止的生成补全的最大长度。使用工具时,仅统计非工具 token。completions/clipped_ratio:截断(clipped)补全的比例,即最后一轮未以 EOS 标记结尾的补全所占的比例。completions/duplicate_frac:组内逐 token 重复之前 rollout 的 rollout 所占比例。参见 饱和与重复。tools/call_frequency:所有 rollout(包括随后被丢弃的组)中,每次补全的平均工具调用次数。仅在训练器配置了工具时记录。tools/failure_frequency:工具调用失败的比例(工具未找到或抛出异常),分母为所有工具调用,包括随后被丢弃的组。在没有工具调用的日志窗口中,该值为 NaN,而 GRPO 会记录0.0。仅在训练器具有工具时记录。rewards/{reward_func_name}/mean:特定奖励函数在所有其评分的完成项上的平均奖励,包括随后被丢弃的组。当环境通过get_reward拥有奖励时,{reward_func_name}为该环境的类名。rewards/{reward_func_name}/group_std:特定奖励函数的奖励标准差,在每个组内计算,并对训练样本取平均值。reward:在跨函数求和奖励后,对所有已评分的补全(包括随后被丢弃的组)计算得出的总体平均奖励。这是策略的奖励,因此是需要跟踪的指标:reward_kept会排除奖励值全部相等的组。reward_group_std:各函数求和奖励的标准差,在每组内计算,并对训练样本取平均。优势值会除以该值,这与 GRPO 的reward_std不同,后者是在整个批次上计算的。reward_kept:训练样本的平均奖励,不包括被丢弃的组。reward_dropped:被丢弃的组的平均奖励,即它们停滞的奖励值。请参阅 饱和与重复,结合frac_reward_zero_std和reward来解读该指标。frac_reward_zero_std:奖励标准差为零(所有回答均正确或均错误)或可评分补全少于两个的组所占的比例。这些组将被丢弃。sampling/sampling_logp_difference/mean:采样器(vLLM)返回的对数概率与训练模型重新计算的对数概率之间,在已训练 token 上的平均绝对差值。sampling/sampling_logp_difference/max:此类绝对差值的最大值。sampling/importance_sampling_ratio/min: 最小的重要性采样比率 ,即模型与采样器之间在训练 token 上的比率,且在丢弃[vllm_importance_sampling_clip_min, vllm_importance_sampling_clip_max]之外的 token 之前。sampling/importance_sampling_ratio/mean:此类重要性采样比率的平均值。sampling/importance_sampling_ratio/max:最大的重要性采样比率。sampling/masked_mean:由于重要性采样比率落在[vllm_importance_sampling_clip_min, vllm_importance_sampling_clip_max]之外而从损失中丢弃的已训练 token 的比例。sampling/sampler_mass:采样器最可能的num_sampler_logprobs个 token 所覆盖的平均概率质量。仅在score_centering=True时记录。entropy:训练 token 上 token 预测的平均熵。aux_loss:混合专家(Mixture-of-Experts)模型的负载均衡辅助损失,在乘以router_aux_loss_coef并加到总损失之前。仅当模型为 MoE 模型且router_aux_loss_coef非零时记录。clip_ratio/region_mean: GRPO 目标被裁剪以保持在信任区域内的 token 概率比率: . 当score_centering=True时,损失不会进行裁剪,因此clip_ratio/*指标统计的是信任区域之外的 token 数量。clip_ratio/low_mean: 信任区域下界被截断的 token 概率的平均比率: .clip_ratio/low_min: 信任区域下界处被裁剪的每个 completion 的最小 token 比例: .clip_ratio/high_mean: 信任区域上界被裁剪的 token 概率的平均比率: .clip_ratio/high_max: 信任区域上界处被裁剪的每个 completion 中 token 的最大比例: .
其他指标用于描述异步流水线。
指标在每个日志窗口记录一次:记录自上次日志以来的所有内容,涵盖最近logging_steps个优化器步骤。在该窗口内:
- 像
reward、rollout/turns_mean和tools/latency_s这样按 rollout、样本或调用测量的均值,会在窗口内的所有 rollout、样本或调用上进行平均。像rollout/generated_tokens_per_s这样的速率,则是总 token 数除以总秒数。 - 否则,名称最后一部分包含单词
count的指标会被求和:sample/dropped_stale_count表示自上次日志以来被丢弃的样本数量,而非自训练开始以来的数量。 - 名称的最后一部分包含单词
max或min(例如sample/staleness_max或completions/max_length)时,将保留最大值或最小值。 - 其他任何指标均为窗口内记录值的平均值。
单位:
_s:秒。tokens_per_s:每秒生成的 token 数。_frac:0 到 1 之间的分数。_per_step:单个优化器步长中的数量,在窗口内的步长上取平均值。
Rollout 指标
rollout/duration_s:单次 rollout 的挂钟时间,从分发开始到 rollout 循环收集完成为止。rollout/generated_tokens_per_s:自上次报告以来,以每秒生成的 token 数衡量的生成吞吐量。rollout/concurrency:发送到 vLLM 服务器的并发 rollout 数量。rollout/concurrency_cap:当前正在进行的 rollout 数量上限,该值会随服务器负载变化。请参阅 并发。rollout/turns_mean、rollout/turns_max:每次 rollout 的平均轮数和最大轮数。rollout/samples_per_rollout:每次 rollout 的训练样本数量。高于1.0时,部分 rollout 被分叉;低于1.0时,部分 rollout 未产生任何训练 token。参见 多轮 rollout。rollout/fork_frac:提示词未以保留 token 开头的轮次转换所占的比例,即该轮次开启了一个新的样本。rollout/drift_tokens_mean、rollout/drift_tokens_max:分叉重新渲染的保留 token 数量,按轮次转换取平均值(未发生分叉的轮次计为0)。新样本共享分叉点之前的 token,因此该值表示每个分叉为前向传播新增的 token 数。rollout/score_queue_size:等待评分的已完成组数。rollout/queue_size:在 trainer 拉取样本的位置,等待打分的 rollout 队列中的样本数量。参见 rollout 队列。rollout/score_s:对一组进行评分所需的时间。rollout/score_wait_s:已完成的组在评分前等待的时间。rollout/score_block_s:由于评分队列已满,生成被阻塞所花费的时间。rollout/backpressure_s:由于生成比训练超前了max_staleness步,生成在开始 rollout 前等待的时间,取日志窗口内各次等待的平均值。参见 并发。rollout/vllm_retry_count:重试的 vLLM 请求数量。否则,性能下降的服务器会表现为无法解释的延迟。rollout/vllm_kv_cache_usage_frac:vLLM KV 缓存使用率(取各引擎的最大值),在日志窗口内的多次轮询中取平均值。rollout/vllm_running_requests:服务器引擎正在解码的请求数量(即 vLLM 的num_requests_running,对所有引擎求和)。它与rollout/concurrency之间的差值,是位于 API 服务器进程中、正在构建响应的部分。rollout/vllm_queued_requests:服务器已接受但尚未调度的请求数量(即 vLLM 的num_requests_waiting,对所有引擎求和)。rollout/vllm_preemption_count:服务器抢占的请求数量(即 vLLM 的num_preemptions_total,对所有引擎求和)。权重同步会重置服务器缓存,从而抢占所有正在运行的请求,因此该指标并非压力信号。
工具指标
tools/latency_s、tools/{tool_name}/latency_s:执行工具调用的耗时,包括所有工具的总耗时以及每个工具的单独耗时。tools/{tool_name}/call_count、tools/{tool_name}/failure_count:每个工具的调用次数和失败调用次数。tools/unknown_name_count:调用不存在工具的次数。与运行后抛出异常的工具不同,这属于策略错误。tools/parallel_calls_mean:调用工具的助手消息中工具调用次数的平均值。tools/loop_exhausted_frac:在max_tool_calling_iterations处被截断且仍在调用工具的 rollout 占比,分母为所有 rollout,包括未调用工具的 rollout 以及随后被丢弃的组。仅在训练器配置了工具时记录。
HarnessAsyncGRPOTrainer 会以相同的名称记录其中大部分内容。有关差异,请参阅 在 harness 上进行训练。
环境指标
env/reset_s:在 rollout 开始前,环境reset()所花费的时间。仅在提供了environment_factory时记录。env/reward_s:rollout 结束后,在环境的get_reward()中花费的时间。仅当环境定义了get_reward时才会记录。
示例指标
sample/forwarded_tokens_mean、sample/forwarded_tokens_max:每个样本中转发 token 的平均数量和最大数量。sample/trained_tokens_mean:每个样本的平均训练 token 数。sample/time_in_queue_s:样本在被训练前在 rollout 队列中等待的时间。sample/staleness_mean、sample/staleness_max:生成样本的策略与训练该样本的策略之间经历的权重更新次数。sampling/importance_sampling_ratio/*和sampling/sampling_logp_difference/*指标将此陈旧度与训练-推理不匹配相结合:在陈旧度为零时,比率偏离1.0或对数概率差偏离0.0表明存在不匹配,例如与服务器的数据类型(dtype)差异。sample/dropped_stale_count:因超过max_staleness而被丢弃的样本数量。
批量指标
batch/forwarded_tokens_per_step、batch/trained_tokens_per_step:所有进程上每个步骤的前向和训练 token 数量。batch/samples_per_step、batch/groups_per_step:每个步骤中的样本数和不同组数。它们之间的差异等于每组中的样本数。batch/microbatches_per_step:每个步骤中的 micro-batch 数量。batch/masked_token_frac:未参与训练的转发 token 所占的比例。batch/samples_per_row、batch/row_tokens_mean、batch/row_tokens_max:每行的样本数,以及每行转发 token 的平均数和最大值。batch/row_fill_frac:各行使用的token_budget的比例。batch/row_imbalance:最昂贵行的注意力成本与平均成本之比。拥有最昂贵行的进程会在梯度 all-reduce 时阻塞其他进程;1.0表示完全均衡。batch/packing_ratio:每个前向传播 token 在打包前的 token 数量:打包使前向传播缩减的倍数,当没有共享时为1.0。batch/pad_frac:为使各行长度一致而添加的填充比例。它仅产生传输开销,在前向传播前会被移除。batch/dropped_oversize_count:因超出token_budget而被丢弃的样本数量。
性能指标
perf/step_s:一个 step 的墙钟时间,包括优化器、权重同步以及等待样本的时间。perf/fwd_bwd_s、perf/fwd_s:分别表示一个步骤中前向和反向传播所花费的时间,以及仅前向传播所花费的时间。perf/optimizer_s:优化器步骤所花费的时间。perf/rollout_wait_s:训练因 rollout 队列为空而等待的时间。参见 rollout 队列。perf/weight_sync_s:权重同步的耗时,分为perf/weight_sync_pause_s(等待 vLLM 暂停)、perf/weight_sync_barrier_s(等待其他进程)和perf/weight_sync_transfer_s(发送权重)。perf/fwd_bwd_forwarded_tokens_per_s、perf/wall_clock_forwarded_tokens_per_s:每秒转发的 token 数量,分别对应前向与反向传播,或整个训练步骤。perf/wall_clock_trained_tokens_per_s:整个步骤中每秒训练的 token 数量。perf/fwd_bwd_mfu、perf/wall_clock_mfu:训练设备在正向和反向传播期间,或在整个训练步骤中的模型 FLOPs 利用率。它们不包含 vLLM 服务器。仅当已知训练设备在训练精度下的峰值计算能力,且 FLOPs 估算支持该模型的架构(例如 Llama、Qwen2、Qwen3、Qwen3-MoE 或 Qwen3-VL)时,才会记录这些指标。
自定义
配置 vLLM 服务器
Trainer 通过 vLLM 服务器进行生成,该服务器位于 vllm_server_base_url(默认为 http://localhost:8000):
VLLM_SERVER_DEV_MODE=1 vllm serve <model_name> \
--weight-transfer-config '{"backend": "nccl"}' \
--logprobs-mode processed_logprobs \
--api-server-count 8 \
--max-model-len 4096VLLM_SERVER_DEV_MODE=1和--weight-transfer-config会暴露训练器用于发送权重的端点。--logprobs-mode processed_logprobs使服务器返回在应用温度和 top-k/top-p 之后所采样的分布的 log-probabilities,这正是重要性比率所比较的对象。--api-server-count 8会保留对评分中心请求而言服务成本较低的 top log-probabilities(参见下方的警告)。--max-model-len是您预期的最大总序列长度(包括 prompt 和 completion)。较低的取值会降低服务器上的 GPU 内存占用,从而为 KV cache 释放更多内存并提高吞吐量。一个好的起点是 prompt 长度加上配置中的max_completion_length。在使用 tools 时,每一轮都会增加最多max_completion_length个 token 以及工具结果,因此请将其设置为预期的最长对话长度。如果某一轮的 prompt 加上max_completion_length超过了--max-model-len,服务器将拒绝该请求,运行会因服务器错误而停止。若使用了top_k或top_p,服务器会在该轮达到--max-model-len时停止该轮,而仅拒绝长度本身已达到该值的 prompt。它同时也是token_budget的默认值。
请确保服务器使用的 GPU 与训练器不同,否则可能会遇到 NCCL 错误。您可以通过
CUDA_VISIBLE_DEVICES环境变量来指定要使用的 GPU。
在 vLLM 的 API 服务器中,序列化 top log-probabilities 属于 CPU 工作,而默认的单个 API 服务器在
num_sampler_logprobs=8设置下会使生成吞吐量减半。在 vLLM 0.32 或更高版本中,请使用--api-server-count 8启动服务器:在单张 H100 上对 1.5B 至 7B 模型进行测量,top-8 log-probabilities 的开销占生成吞吐量的比例低于 3%。在 vLLM 0.31 中,相同的标志仍会导致 20% 至 40% 的性能损失,该问题已通过上游的 vllm-project/vllm#58181 修复。
并发
三个限制条件决定了同时运行的 rollout 数量。前两个会自动调整,第三个由您自行设置。
陈旧度。 max_staleness 设置生成过程可以领先训练器多少个训练步骤。由于每个步骤中的样本数量会随着补全变长或变短而变化,工作进程以 token 为单位跟踪此限制。在每个步骤之后,训练器统计其使用的 token 数量,并允许工作进程保留最多 max(max_staleness, 1) 倍该数量的 token 处于排队或进行中状态。在第一个步骤之前,它假设每一行都是满的。工作进程仅在该限制之下才会启动新一组 rollout。在最初阶段,它尚不知道一个 rollout 会产生多少 token,因此每次只启动一组,直到第一组完成为止。如果一个样本最终仍然比 max_staleness 个步骤更陈旧,则会被丢弃。
vLLM 负载。 如果同时向 vLLM 发送过多请求,其速度会变慢:KV cache 会被填满,vLLM 不得不暂停并重做部分请求。因此,worker 还会对同时运行的 rollout 数量保持一个实时上限,记录为 rollout/concurrency_cap,并每 5 秒检查一次服务器的 /metrics 来调整该上限。该上限的初始值为 KV cache 在最大长度下能容纳的请求数(KV cache tokens / max_model_len),以避免服务器在启动时过载。只要 KV cache 使用率保持在 60% 以下且没有等待的请求,上限就会增加 25%,但每完成一个上限数量的 rollout 最多只增加一次。当使用率超过 80% 时,上限会降低 30%:正在运行的请求继续执行,新请求则等待。如果服务器没有 /metrics 端点,则上限为 max_concurrent_rollouts。
资源。 max_concurrent_rollouts(默认值为 512)是同时运行的 rollout 数量上限。这是唯一需要您自行设置的限制,因为 trainer 无法获知您的环境和工具能够承受多大的负载。每个正在运行的 rollout 都会占用一个环境,可以在一个大小为此值的线程池上执行同步工具调用,并且会保持与 vLLM 服务器的连接处于打开状态。请将其设置为这些组件能够承受的值,例如您的环境服务器可接受的会话数:
from trl import AsyncGRPOConfig
training_args = AsyncGRPOConfig(
...,
max_concurrent_rollouts=64,
)如果设置过低,生成速度会变慢。Rollout 在运行工具或等待轮次之间时也会保留其槽位,而不仅仅是在 vLLM 生成期间,因此调用大量工具的 rollout 需要更多槽位才能保持 vLLM 处于繁忙状态。如果设置远高于默认值,vLLM 服务器可能会开始丢弃连接。
记录的指标显示了哪个限制正在阻碍生成:
rollout/backpressure_s大于零:表示陈旧度限制。生成进度领先于训练,当推理速度比训练快时,这种情况是预期内的。rollout/concurrency达到rollout/concurrency_cap,低于max_concurrent_rollouts:vLLM 服务器的负载。rollout/concurrency达到max_concurrent_rollouts的上限,同时rollout/vllm_kv_cache_usage_frac较低且perf/rollout_wait_s较高:这表明已触及瓶颈。如果环境和工具允许,请提高max_concurrent_rollouts。
Rollout 队列
Worker 将已评分的样本推入队列,Trainer 从中拉取。四个指标用于描述该队列:
rollout/queue_size:等待的样本数量。sample/time_in_queue_s:单个样本在被训练前等待了多长时间,即其陈旧度(staleness)中的时间部分。perf/rollout_wait_s:由于队列为空,训练等待了多长时间。rollout/backpressure_s:由于生成比训练超前了max_staleness步,生成所等待的时长。
最后两个是镜像关系。结合队列大小一起阅读,它们可以告诉你哪一侧是瓶颈:
- 队列接近空,
perf/rollout_wait_s较高:生成受限。训练器处于饥饿状态;请查看rollout/generated_tokens_per_s、rollout/concurrency和rollout/score_queue_size。 - 队列中大约持有
max_staleness个步骤的 token,rollout/backpressure_s较高:受训练器限制。生成过程被阻塞,sample/staleness_mean接近其上限。 - 两者均偏高:
max_staleness限制本身过于严格。正在进行的 rollout 在其样本到达队列之前就已填满该限制,导致训练器等待,而生成过程被阻塞。请调高max_staleness。 - 两者均接近零:平衡。
饱和与重复
策略从一组 rollout 之间的差异中学习:每个优势值都是相对于该组的平均奖励计算的。当一组中的所有 rollout 都获得相同的奖励时,它们的优势值均为零,因此该组会被丢弃(参见 记录的指标),并且不会为策略提供任何学习信号。随着运行趋于饱和,越来越多的组会呈现这种状态,原因可能是所有 rollout 都解决了提示,或者没有一个 rollout 能解决提示。有四个指标可以区分这些情况,每个指标都回答其自身的问题,因此您可以直接从它们的图表中读取:
| metric | question |
|---|---|
frac_reward_zero_std | 没有信号的组所占的比例 |
reward_dropped | 这些组卡在了哪里:它们的平均奖励。对于 0/1 奖励,这表示所有 rollout 都通过的组所占的比例,因此 0.9 意味着几乎所有组都已解决,而 0.1 则意味着几乎所有组在每次 rollout 中都失败 |
reward | 策略在所有评分组(包括被丢弃的组)上的表现 |
completions/duplicate_frac | 采样是否仍会为相同的提示词生成不同的结果 |
将前三项放在一起阅读,并对照你的奖励范围:
frac_reward_zero_std | reward_dropped | reward | 诊断 | 该怎么做 |
|---|---|---|---|---|
| 低 | any(在少数组上存在噪声) | any | healthy:大多数组都会生成梯度 | nothing |
| climbing | 接近范围的上限 | climbing | 在顶部饱和:提示对策略来说变得过于简单,这通常是一次成功运行的结束方式 | 停止运行,或切换到更难的提示 |
| 高 | 范围的下端附近 | 低且平坦 | 卡在底部:提示太难,或者奖励函数从未给予任何奖励 | 首先检查 completions/clipped_ratio,因为如果补全在给出答案前被截断,其得分为 0;然后阅读几条补全,以确认奖励函数能够正确解析它们。 |
| climbing | 下降 | 下降 | regression:原本通过的测试组现在一起失败 | 检查 completions/duplicate_frac 和 entropy 以判断是否出现坍缩 |
| 高 | 在中间 | any | 混合:在 0/1 奖励下,一些提示词过于简单,另一些则过于困难。在使用部分得分奖励时,组也可能卡在相同的部分得分上 | 读取 rollout |
completions/duplicate_frac 表示在所有轮次中,与同组内先前某次 rollout 逐 token 完全重复的 rollout 所占的比例。由于相同的 rollout 会获得相同的奖励,因此重复项也会推高 frac_reward_zero_std。
completions/duplicate_frac | 诊断 |
|---|---|
| 接近 0,在那些完成内容短到可以偶然重复的任务上存在一个较小的基线 | healthy:该组的 rollout 是独立采样的 |
趋向于 (num_generations - 1) / num_generations | 采样不再是随机的:每个组返回的都是同一次 rollout 的副本。例如,温度为 0,或者数据并行的 vLLM 引擎因为共享同一个采样种子而解码出相同的文本(worker 会在每个请求中发送一个唯一的种子以防止这种情况) |
缓慢上升,entropy 下降 | collapse:策略正在收敛到每个提示词只有一个答案 |
这些指标是在 worker 对某个 group 进行评分时记录的,而不是在其样本被用于训练时记录的,因此它们会略微领先于训练过程,并且包含那些因超过 max_staleness 而导致样本随后被丢弃的 group。如果一个 group 中的所有 rollout 都无法被评分(因为每个奖励函数都返回了 None),该 group 将被丢弃并计入 frac_reward_zero_std,但由于它没有奖励,reward_dropped 不会将其包含在内。
树打包
微批次的样本被打包到每个进程的一行中,无需填充。一行是一个前缀树,而非简单的拼接:多个样本共享的 token 只会被前向传播一次,而注意力机制仍确保每个样本只能看到其自身的历史。一个组的所有 rollout 都会重复其提示词,多轮 rollout 的每一轮都会重复迄今为止的对话,因此前向传播的计算量会按打包比率(batch/packing_ratio)缩小。这仅仅是布局上的改变:每个用于训练的 token 都保留其自身的目标、优势值和旧对数概率,且其对数概率与单独前向传播该样本时的结果一致。
在针对同一提示词 p 的两次生成中,其中 a 和 b 为两个补全结果:
1. Rollouts r0: p1 p2 p3 a1 a2 10 tokens
r1: p1 p2 p3 b1 b2
2. Trie S0 [p1 p2] ─┬─ S1 [p3 a1 a2] p3 is not shared: a position predicts
└─ S2 [p3 b1 b2] one token, and p3 predicts a1 or b1
3. Packed row index 0 1 | 2 3 4 | 5 6 7
(depth-first) input_ids p1 p2 | p3 a1 a2 | p3 b1 b2 8 tokens: everything but attention
position_ids 0 1 | 2 3 4 | 2 3 4 runs on 8 instead of 10
segment S0 | S1 | S2
4. Attention segment queries keys and values: the segment's path in the trie
(one call) S0 p1 p2 p1 p2 index 0 1
S1 p3 a1 a2 p1 p2 p3 a1 a2 index 0 1 2 3 4
S2 p3 b1 b2 p1 p2 p3 b1 b2 index 0 1 5 6 7
5. Loss r0: a1 a2 predicted at index 2 3 each trained token is predicted at the index
r1: b1 b2 predicted at index 5 6 of the token before it in its rollout一些推论:
- 任何公共前缀都会被共享:同一提示词的不同 rollout,以及跨提示词的公共系统提示词或工具 schema。样本会被分配到已经持有其所属组的行中,因为该行的提示词已经存在。当该行已满时,如果某个进程仍有空行,或者分配不会增加负载最高的行(该行决定了 micro-batch 的耗时),样本就会被分配到另一行;否则,该 micro-batch 将关闭。
- 未共享任何内容即为普通打包:此时每个样本都是其自身的分段,即普通的无填充打包。
- 仅复制 K/V:每个后代片段只收集一次共享前缀,其大小与未打包训练所持有的量相当,在使用分组查询注意力时则更小。
- 支持 Gated DeltaNet 混合模型(Qwen3.5、Qwen3.6、Qwen3-Next 等):其卷积操作读取每个 token 自身的前驱,而分块 delta 规则则从父段的最终状态开始处理每个分段。
token_budget 限制了每行的 token 数量,共享的 token 只计算一次。默认情况下,它等于服务器的 max_model_len,因此任何样本都不会超过该上限。在没有共享内容时,树打包的运行速度与普通的无填充打包大致相当,且加速比随打包率的增长而提升(在 Qwen3-0.6B 和 1.7B 上,当打包率为 3.4 时,在 H100 上约为 2.8 到 3 倍)。打包率等于共享前缀长度乘以每行的样本数,因此 batch/samples_per_row 是首要指标:当其接近 1 时,几乎没有内容可以共享,此时调节手段是 token_budget。当样本较长时,batch/row_fill_frac 变得重要:一个 10k token 的样本对 32k 预算的填充效果很差(三个能放下,四个永远放不下,因此规划器通常只能放入两个,行的填充率约为 77%),而 1k token 的样本则能几乎完美地填充该预算。
根据吞吐量来评估树打包,即 batch/trained_tokens_per_step / perf/fwd_bwd_s。打包后的微批次包含更多样本,因此 perf/fwd_bwd_s 会上升,而除以前向传播 token 数的 perf/fwd_bwd_mfu 会下降:这两者均不属于性能回退。每行包含更多样本也意味着每步会采样更多 rollout,因此受生成限制的运行会变得更加受限。
批量指标由以下部分组成:一个 step 包含 gradient_accumulation_steps × num_processes 行,因此 batch/samples_per_step 大约是 batch/samples_per_row 的相应倍数,token 同理。由于每步指标是求和,而每行指标是均值,两侧仅在 step 内各 micro-batch 之间的差异范围内保持一致。
任何使行数变长或变少的操作都会破坏共享。一个从不分叉的多轮对话就是一个长样本,
rollout/samples_per_rollout表示一个对话会产生多少个样本。
Top-k 和 top-p 采样
Top-k 和 top-p 是在受限的候选集上,而非整个词表上,对采样分布进行重新归一化。服务器的对数概率反映了这一点(--logprobs-mode processed_logprobs),因此训练器必须在相同的集合上进行归一化,否则每个重要性比率都会产生偏差:某个 token 在整个词表下的概率低于其在被采样自的候选集下的概率(DeepSeek-V3.2,第 3.1 节)。当 top_p < 1 或 top_k > 0 时,worker 会向服务器请求每个采样 token 的候选集,训练器则在该集合上对其对数概率进行重新归一化。这需要额外的两个服务器标志,并且必须设置 top_k,因为 vLLM 使用它来限制候选集的大小:
VLLM_SERVER_DEV_MODE=1 vllm serve <model_name> \
--weight-transfer-config '{"backend": "nccl"}' \
--logprobs-mode processed_logprobs \
--api-server-count 8 \
--max-model-len 4096 \
--return-sampling-mask \
--enable-scale-outfrom trl import AsyncGRPOConfig
training_args = AsyncGRPOConfig(
...,
top_p=0.95,
top_k=512,
)--return-sampling-mask 使 vLLM 记录候选集(vLLM 0.28 或更高版本,在其 V2 模型运行器上:如果服务器拒绝该标志,则设置 VLLM_USE_V2_MODEL_RUNNER=1),而 --enable-scale-out 暴露了返回该集合的 token-in-token-out 端点。工作进程在启动时会在服务器上检查这两项。旧版 vLLM 无法返回该集合,因此训练器会继续在整个词汇表上进行归一化,并针对有偏的比率发出警告。
使用 PEFT 训练适配器
我们支持与 🤗 PEFT 库的紧密集成,让您能够训练适配器并将其共享到 Hub,而无需训练整个模型。
from datasets import load_dataset
from peft import LoraConfig
from trl import AsyncGRPOTrainer
from trl.rewards import accuracy_reward
if __name__ == "__main__":
dataset = load_dataset("trl-lib/DeepMath-103K", split="train")
trainer = AsyncGRPOTrainer(
model="Qwen/Qwen3.5-2B",
reward_funcs=accuracy_reward,
train_dataset=dataset,
peft_config=LoraConfig(r=32, lora_alpha=64, target_modules="all-linear"),
)
trainer.train()每次权重同步都需要将更新后的策略发送到服务器。默认情况下,训练器会将适配器合并到基础模型中,并发送完整的合并权重,对于 7B 模型,每次同步大约需要 15 GB。使用 --enable-lora 启动 vLLM 服务器后,它只会发送适配器,在 r=32 时约为 160 MB:
VLLM_SERVER_DEV_MODE=1 VLLM_ALLOW_RUNTIME_LORA_UPDATING=1 vllm serve <model_name> \
--weight-transfer-config '{"backend": "nccl"}' \
--logprobs-mode processed_logprobs \
--api-server-count 8 \
--max-model-len 4096 \
--enable-lora \
--max-lora-rank 32 \
--max-loras 6VLLM_ALLOW_RUNTIME_LORA_UPDATING=1 会暴露训练器用于提交每个新适配器的端点。即使使用了 --enable-lora,也要保留 --weight-transfer-config:训练器只在启动时选择同步模式,而那时服务器已经启动,合并同步是回退方案。
--max-lora-rank 必须是 1, 8, 16, 32, 64, 128, 256, 320, 512 之一。它设置的是服务器能够支持的最高秩,而非实际服务的秩,因此 r=4 的适配器在 8 下可以正常工作。--max-loras 必须至少为 max_staleness + 2:训练器会注册 max_staleness + 1 个适配器版本,以便在旧策略下开始的 rollout 能够在该策略下完成,而不是在生成中途切换策略;并且每次同步都会在卸载最旧版本之前加载下一个版本。训练器在启动时会检查这两个值,并告知你应使用什么参数重启服务器。
vLLM 从磁盘而非网络加载适配器,因此训练器会将每个版本写入 <output_dir>/.vllm_lora/ 下,并将该路径提供给服务器。如果服务器运行在另一台机器上,该目录必须位于两台机器都能访问的文件系统上。
<output_dir>/.vllm_lora/是一个服务缓存,并非存放训练后适配器的位置。它仅保留最近版本的适配器,且每个版本在超出过期窗口后不久即被删除。检查点才是保存训练适配器的关键,因此请勿将其关闭:若设置save_strategy="no",数小时的训练任务结束后将不会留下任何产物。output_dir必须可被服务器读取,且其生命周期需长于任务本身,因此节点本地临时存储不可行,尽管它能避免在缓慢的共享文件系统上进行同步。
Adapter 同步并不总是更快。与提供合并后的权重相比,vLLM 在提供 LoRA adapter 时,每个 token 的生成速度大约慢 1.33 到 1.39 倍,而在强化学习中,生成通常主导了实际耗时。作为回报,每次同步时的暂停时间会大幅缩短,且同步带宽占用约为 1%。如果生成吞吐量是你的瓶颈,合并同步通常仍是更好的选择。
使用自定义奖励函数
AsyncGRPOTrainer 支持使用自定义奖励函数。为确保兼容性,您的奖励函数必须满足以下要求:
奖励函数可以是同步的 Python 可调用对象,也可以是异步的 async def 协程。当您提供多个奖励函数时,它们会并发执行:异步函数会被一起等待,而同步函数则在线程中运行,因此它们的延迟会相互重叠。
输入参数:
该函数必须接受以下关键字参数:
prompts(包含提示词),completions(包含生成的补全内容),completion_ids(包含分词后的补全内容),- 数据集可能包含的所有列名(
prompt除外)。例如,如果数据集包含名为ground_truth的列,则函数将以ground_truth作为关键字参数被调用。
满足此要求的最简单方法是在函数签名中使用
**kwargs。数据集采用对话格式,因此
prompts和completions是消息字典的列表。
返回值:该函数必须返回一个浮点数列表。每个浮点数代表对应单个补全的奖励。
奖励函数在 rollout worker(一个独立进程)中运行,因此传入其中的所有内容都会被 pickled。每个奖励函数、工具以及
environment_factory(以及它们所闭包捕获的任何内容)都必须可 pickle:请使用模块级函数、functools.partial或 可调用类实例。Lambda 函数和闭包会在trainer.train()时引发TypeError。这与 GRPOTrainer 不同,在后者中奖励函数在进程内调用,闭包可以正常工作。Rollout 过程同样在
CUDA_VISIBLE_DEVICES=""下运行,因此无法使用 GPU。基于 GPU 的奖励模型(例如AutoModelForSequenceClassification评分器)加载时不会报错,但会静默回退到 CPU。请保持奖励函数轻量(例如accuracy_reward等验证器,或格式、长度检查)。如果需要 GPU 奖励模型,请将其部署在独立的 GPU 上,并通过独立的推理引擎(vLLM、TGI 等)提供服务,然后让一个可 pickle 的奖励函数通过 HTTP 调用它。
示例 1:奖励更长的补全
以下是一个奖励较长补全的奖励函数示例:
def reward_func(completion_ids, **kwargs):
"""Reward function that assigns higher scores to longer completions (in terms of token count)."""
return [float(len(ids)) for ids in completion_ids]您可以按以下方式测试:
>>> prompts = [[{"role": "user", "content": "The sky is"}], [{"role": "user", "content": "The sun is"}]] # not used in the reward function, but the trainer will pass it
>>> completions = [[{"role": "assistant", "content": " blue."}], [{"role": "assistant", "content": " in the sky."}]] # not used in the reward function, but the trainer will pass it
>>> completion_ids = [[6303, 13], [304, 279, 12884, 13]]
>>> reward_func(prompts=prompts, completions=completions, completion_ids=completion_ids)
[2.0, 4.0]示例 2:奖励具有特定格式的补全
以下是一个奖励函数的示例,用于检查补全内容是否具有特定格式。该示例受论文 DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning 中使用的 format reward 函数启发。 它专为对话格式设计,其中提示和补全内容均由结构化消息组成。
import re
def format_reward_func(completions, **kwargs):
"""Reward function that checks if the completion has a specific format."""
pattern = r"^<think>.*?</think><answer>.*?</answer>$"
completion_contents = [completion[0]["content"] for completion in completions]
matches = [re.match(pattern, content) for content in completion_contents]
return [1.0 if match else 0.0 for match in matches]您可以按以下方式测试此函数:
>>> prompts = [
... [{"role": "user", "content": "What is the result of (1 + 2) * 4?"}],
... [{"role": "user", "content": "What is the result of (3 + 1) * 2?"}],
... ]
>>> completions = [
... [{"role": "assistant", "content": "<think>The sum of 1 and 2 is 3, which we multiply by 4 to get 12.</think><answer>(1 + 2) * 4 = 12</answer>"}],
... [{"role": "assistant", "content": "The sum of 3 and 1 is 4, which we multiply by 2 to get 8. So (3 + 1) * 2 = 8."}],
... ]
>>> format_reward_func(prompts=prompts, completions=completions)
[1.0, 0.0]示例 3:基于参考的奖励完成
下面是一个奖励函数的示例,用于检查补全结果是否正确。该示例受论文 DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning 中使用的 accuracy reward 函数启发。数据集中包含一个名为 ground_truth 的列。
import re
def reward_func(completions, ground_truth, **kwargs):
# Regular expression to capture content inside \boxed{}
matches = [re.search(r"\\boxed\{(.*?)\}", completion[0]["content"]) for completion in completions]
contents = [match.group(1) if match else "" for match in matches]
# Reward 1 if the content is the same as the ground truth, 0 otherwise
return [1.0 if c == gt else 0.0 for c, gt in zip(contents, ground_truth)]您可以按以下方式测试此函数:
>>> prompts = [
... [{"role": "user", "content": "Solve the equation $2x + 3 = 7$."}],
... [{"role": "user", "content": "Solve the equation $3x - 5 = 10$."}],
... ]
>>> completions = [
... [{"role": "assistant", "content": r"The solution is \boxed{2}."}],
... [{"role": "assistant", "content": r"The solution is \boxed{6}."}],
... ]
>>> ground_truth = ["2", "5"]
>>> reward_func(prompts=prompts, completions=completions, ground_truth=ground_truth)
[1.0, 0.0]示例 4:多任务奖励函数
以下是在 AsyncGRPOTrainer 中使用多个奖励函数的示例。在此示例中,我们定义了两个针对特定任务的奖励函数:math_reward_func 和 coding_reward_func。math_reward_func 根据数学问题的正确性给予奖励,而 coding_reward_func 则根据代码解决方案是否可运行来给予奖励。
from datasets import Dataset
from trl import AsyncGRPOTrainer
# Define a dataset that contains both math and coding problems
dataset = Dataset.from_list(
[
{"prompt": [{"role": "user", "content": "What is 2+2?"}], "task": "math"},
{"prompt": [{"role": "user", "content": "Write a function that returns the sum of two numbers."}], "task": "code"},
{"prompt": [{"role": "user", "content": "What is 3*4?"}], "task": "math"},
{"prompt": [{"role": "user", "content": "Write a function that returns the product of two numbers."}], "task": "code"},
]
)
# Math-specific reward function
def math_reward_func(prompts, completions, task, **kwargs):
rewards = []
for prompt, completion, t in zip(prompts, completions, task):
if t == "math":
# Calculate math-specific reward
correct = check_math_solution(prompt, completion)
reward = 1.0 if correct else -1.0
rewards.append(reward)
else:
# Return None for non-math tasks
rewards.append(None)
return rewards
# Coding-specific reward function
def coding_reward_func(prompts, completions, task, **kwargs):
rewards = []
for prompt, completion, t in zip(prompts, completions, task):
if t == "code":
# Calculate coding-specific reward
works = test_code_solution(prompt, completion)
reward = 1.0 if works else -1.0
rewards.append(reward)
else:
# Return None for non-coding tasks
rewards.append(None)
return rewards
# Use both task-specific reward functions
if __name__ == "__main__":
trainer = AsyncGRPOTrainer(
model="Qwen/Qwen2.5-0.5B-Instruct",
reward_funcs=[math_reward_func, coding_reward_func],
train_dataset=dataset,
)
trainer.train()在此示例中,math_reward_func 和 coding_reward_func 旨在配合包含数学和编程问题的混合数据集使用。数据集中的 task 列用于确定对每个问题应用哪个奖励函数。如果数据集中的某个样本没有相关的奖励函数,该奖励函数将返回 None,AsyncGRPOTrainer 将继续使用有效的函数和任务。这使得 AsyncGRPOTrainer 能够处理具有不同适用性的多个奖励函数。
请注意,AsyncGRPOTrainer 会忽略奖励函数返回的 None 奖励,仅考虑相关函数返回的奖励。这确保了模型在相关任务上进行训练,并忽略没有相关奖励函数的任务。
示例 5:异步奖励函数
自定义奖励函数也可以定义为 async def 协程。如果您的奖励依赖于缓慢的 I/O(例如,调用远程服务),这将非常有用。当您传入多个异步奖励函数时,AsyncGRPOTrainer 会并发执行它们,从而使它们的延迟相互重叠。
以下是一个异步奖励函数的最小示例,用于模拟 I/O 密集型操作:
import asyncio
async def async_reward_func(prompts, completions, **kwargs):
# Simulate an I/O-bound call (e.g., HTTP request, database lookup)
await asyncio.sleep(0.01)
# Simple toy reward: 1.0 if the completion is non-empty, else 0.0
return [1.0 if completion else 0.0 for completion in completions]将奖励函数传递给训练器
要使用自定义奖励函数,请将其传递给 AsyncGRPOTrainer,如下所示:
from trl import AsyncGRPOTrainer
trainer = AsyncGRPOTrainer(
reward_funcs=reward_func,
...,
)您可以将多个奖励函数作为列表传入;该列表可以同时包含同步函数和异步函数:
from trl import AsyncGRPOTrainer
trainer = AsyncGRPOTrainer(
reward_funcs=[reward_func, async_reward_func1, async_reward_func2],
...,
)奖励将计算为每个函数奖励的总和。
自定义 rollout 循环
生成、奖励、优势计算和过滤均在 AsyncRolloutLoop 中的 rollout 流程里执行。若要修改其中某一步,请子类化该循环,重写对应步骤的方法,并将该子类设置为 trainer 子类的 loop_cls。trainer 的构造函数保持不变。
| 方法 | 调用 | 默认 |
|---|---|---|
filter_prompt(row_index, row, epoch) | 每个提示词一次,在生成之前 | 接受所有提示 |
generate_one(prompt, tools, group_id) | 每次 rollout 一次 | 多轮工具循环 |
generate_one_turn(prompt_ids) | 每个采样请求一次 | 一个使用配置中采样设置的 vLLM 请求 |
compute_advantages(group) | 在奖励被求和到 group.rewards 之后,每个组执行一次 | 组内标准化的奖励 |
setup(), close() | 循环开始和结束的时间 | nothing |
例如,一种课程策略会停止采样策略已经解决的提示,其阈值位于配置子类中:
from dataclasses import dataclass
import numpy as np
from trl import AsyncGRPOConfig, AsyncGRPOTrainer
from trl.trainer.async_grpo_trainer import AsyncRolloutLoop
@dataclass
class CurriculumConfig(AsyncGRPOConfig):
pass_rate_cap: float = 0.9
class CurriculumRolloutLoop(AsyncRolloutLoop):
def filter_prompt(self, row_index, row, epoch):
return self.pass_rates.get(row_index, 0.0) < self.config.pass_rate_cap
async def setup(self):
self.pass_rates = {}
def compute_advantages(self, group):
scored = group.rewards[~np.isnan(group.rewards)]
if len(scored):
self.pass_rates[group.row_index] = float(np.mean(scored > 0))
return super().compute_advantages(group)
class CurriculumTrainer(AsyncGRPOTrainer):
loop_cls = CurriculumRolloutLoop奖励是对 reward_funcs、定义了 get_reward 的环境,以及自定义 generate_one 在其 Rollout 上设置的 reward 进行求和:若要通过裁判模型评分,请传入异步奖励函数,或在 generate_one 内部进行评分。compute_advantages 会为每个组运行,包括那些因所有已评分奖励相等而被丢弃的组;丢弃操作检查的是奖励,而非优势值。该循环在派生的进程中构建,因此请在模块级别定义子类,并将训练代码放在 if __name__ == "__main__": 之下。请在 setup 中创建客户端,因为它运行在循环的事件循环上,而不是重写 __init__。被 filter_prompt 拒绝的提示词在该 epoch 中会被跳过,并计入该 epoch 的计数,类似于被丢弃的组。如果它拒绝了某个 epoch 中的所有提示词,循环将抛出异常。
从检查点恢复
基础的 Trainer 通过跳过并重放其已训练过的批次来恢复训练,但这不适用于实时 rollout 队列,因此 ignore_data_skip 始终为 True。取而代之的是,尚未训练的第一个 prompt 的索引会随每个 checkpoint 一起保存到 rollout_state.json 中,并在恢复时还原,从而让 worker 快速前进到该 prompt,而无需重放样本。这是 trained 位置,而非生成器的位置:worker 会超前于训练 rollout 队列深度的量,这些缓冲的样本在运行结束时会被丢弃,因此从生成器的位置恢复会跳过那些已生成但从未训练过的 prompt。流式数据集(IterableDataset)无法重新定位,因此其 worker 在恢复时会从第一个 prompt 重新开始。
Agent 训练
异步 GRPO 支持智能体训练:模型在生成过程中调用工具,并根据结果进行学习。
- 工具是暴露给模型的普通 Python 函数(同步或异步)。对于无状态调用(如计算器、网络搜索),请使用
tools。 - 环境是更通用的形式:一个在每次 rollout 时全新构建的有状态对象,其公开方法作为工具暴露,并包含一个
reset生命周期钩子以及一个可选的get_reward,使其能够自行管理奖励。当您需要每次 rollout 的独立状态、重置钩子或由环境管理的奖励时,请使用environment_factory。
它们可以组合使用——你可以在 environment_factory 旁边传入独立的 tools。
工具
tools 参数需要一个 Python 函数(同步或异步)列表,用于定义智能体可用的工具:
from trl import AsyncGRPOTrainer
trainer = AsyncGRPOTrainer(
tools=[tool1, tool2],
...,
)每个工具必须是一个标准的 Python 函数,具有带类型提示的参数和返回类型,并附带一个Google 风格的 docstring,用于描述其用途、参数和返回值。 有关更多详细信息,请参阅传递工具指南。
模型通过名称调用工具,因此每个工具都以其 __name__ 进行注册,并且必须是一个 模块级函数,这也有助于保持其可被 pickle 序列化,以便供 rollout worker 使用。同步工具运行在大小为 max_concurrent_rollouts 的线程池中,因此一个缓慢的工具不会阻塞其他正在进行的 rollout。在单个 assistant 回合内,工具调用按照模型发出的顺序执行。因此,最多可以有 max_concurrent_rollouts 个同步工具同时运行,所以任何读取或写入共享状态的工具都必须是线程安全的。
工具调用循环要求聊天模板是前缀保持的(追加一条工具消息时,不得改变之前消息的渲染方式)。对于已知的模型系列(例如 Qwen3、DeepSeek-V3),当启用工具时,TRL 会自动替换为经过修补的训练模板。完整的列表请参阅聊天模板。
在 AsyncGRPOConfig 中使用 max_tool_calling_iterations 来限制工具调用轮次的数量。默认情况下没有上限,当模型生成一个不包含工具调用的响应轮次时,生成即停止。
示例:
from trl import AsyncGRPOTrainer
def multiply(a: int, b: int) -> int:
"""
Multiplies two integers.
Args:
a: The first integer.
b: The second integer.
Returns:
The product of the two integers.
"""
return a * b
async def async_add(a: int, b: int) -> int:
"""
Asynchronously adds two integers.
Args:
a: The first integer.
b: The second integer.
Returns:
The sum of the two integers.
"""
return a + b
trainer = AsyncGRPOTrainer(
tools=[multiply, async_add],
...,
)环境
你还可以通过 environment_factory 提供工具。在此模式下,AsyncGRPOTrainer 会为每个 rollout 创建一个环境实例,并将该环境的公开方法作为工具暴露出来。
使用 environment_factory 时,环境拥有数据:不存在外部的 train_dataset。在每次 rollout 中,reset() 会生成任务——从环境持有的语料库中自采样,或以程序化方式生成状态——并返回提示词。max_steps 设置训练长度。以下是一个最小示例,展示了一个自采样目标并暴露 increment 方法(作为工具暴露)的环境。
import random
from trl import AsyncGRPOConfig, AsyncGRPOTrainer
class IncrementEnv:
# Reserved methods
def reset(self, **kwargs) -> str | None: # required; called at the start of each rollout
self.counter = 0
self.target = random.randint(1, 6) # self-sample the task
return f"Increment the counter by {self.target}." # returned string becomes the prompt
def get_reward(self) -> float: # optional: the environment scores itself from its own state
return float(self.counter == self.target)
# Public methods (exposed as tools)
def increment(self, step: int) -> int:
"""
Increment the internal counter.
Args:
step: Value to add to the counter.
Returns:
The updated counter value.
"""
self.counter += step
return self.counter
if __name__ == "__main__":
trainer = AsyncGRPOTrainer(
model="Qwen/Qwen3-0.6B",
args=AsyncGRPOConfig(max_steps=1000, chat_template_kwargs={"enable_thinking": False}),
environment_factory=IncrementEnv,
)
trainer.train()环境类有两个保留方法:reset 和 get_reward。与其他任何公共方法不同,这些方法不会作为工具暴露给模型。
reset(必需)在 rollout 开始时被调用。它可以返回None或一个字符串。如果返回字符串,该字符串即为用户提示词。get_reward(可选,同步或异步)不接收任何参数,并返回一个float:环境根据其自身的内部状态对刚刚运行的回合进行评分(游戏是否以胜利结束?单词是否被猜中?)。它在每个完成的 rollout 中被调用一次,并作为奖励来源。
两者都在工作进程的线程池中运行,因此较慢的环境只会延迟其自身的 rollout,而不会影响其他正在进行的 rollout。它们的耗时分别记录为 env/reset_s 和 env/reward_s。
由于环境在每次
reset()时进行自采样,GRPO 组中的G个成员可能不共享相同的初始状态,这使得组基线略微更具噪声。对于组内相同的状态,请让reset()从共享的键中确定性地派生状态。
提供外部数据集(可选)
与其让环境自行采样,您可以提供一个外部的 train_dataset。例如,当您的任务已经存在于数据集中,或者需要在 多个环境 之间进行路由时。在每次 rollout 中,训练器会采样一行,将其 "prompt" 列用作提示,并将该行的各列作为关键字参数传递给 reset()(以便环境从数据集中读取任务)。
from datasets import Dataset
from trl import AsyncGRPOTrainer
# Each row carries the prompt and the task data (here, the counter target to reach).
dataset = Dataset.from_dict(
{
"prompt": [[{"role": "user", "content": f"Increment the counter to {i}."}] for i in range(1, 7)],
"target": list(range(1, 7)),
}
)
class IncrementEnv:
def reset(self, target, **kwargs) -> None: # the row's `target` column arrives as a keyword argument
self.counter = 0
self.target = target
def get_reward(self) -> float:
return float(self.counter == self.target)
def increment(self, step: int) -> int: # exposed as a tool (see the full docstring above)
self.counter += step
return self.counter
if __name__ == "__main__":
trainer = AsyncGRPOTrainer(
model="Qwen/Qwen3-0.6B",
train_dataset=dataset, # the dataset owns the task; the "target" column reaches reset(target=...)
environment_factory=IncrementEnv,
)
trainer.train()奖励
环境拥有的奖励和训练器拥有的奖励并非互斥。对于从补全结果中自然计算得出的奖励(例如对文本进行格式检查),请传入 reward_funcs,并让环境通过 get_reward 返回其状态相关的奖励。所有来源的奖励均以权重 1 进行求和。
def format_reward(completions, **kwargs): # trainer-owned: scores the completion text
...
trainer = AsyncGRPOTrainer(
model="Qwen/Qwen3-0.6B",
args=AsyncGRPOConfig(max_steps=1000),
reward_funcs=format_reward, # trainer-owned: scores the completion
environment_factory=IncrementEnv, # env-owned: scores its internal state via `get_reward`
)多个环境
要在单次运行中混合训练多种任务(例如编码任务和游戏任务),请传入一个将环境名称映射到工厂的字典,而不是单个可调用对象。每次 rollout 都会通过数据集中的 environment 字段选择其环境,并且 仅暴露该环境的工具。这可以避免泄露无关工具(例如将游戏的 move 工具暴露给编码示例,反之亦然)。每个环境仍然拥有自己的数据,并从 reset() 返回提示,因此数据集只需要 environment 列来在它们之间进行路由。
import random
from datasets import Dataset
from trl import AsyncGRPOConfig, AsyncGRPOTrainer
class CodingEnv:
def reset(self, **kwargs) -> str:
n = random.randint(1, 20)
return f"Compute the {n}th Fibonacci number."
def run_code(self, code: str) -> str: # exposed as a tool only for "coding" examples
"""Run the given Python code and return its stdout.
Args:
code: The Python source to execute.
Returns:
The captured standard output.
"""
...
class GameEnv:
def reset(self, **kwargs) -> str:
return "Reach the goal tile."
def move(self, action: str) -> str: # exposed as a tool only for "game" examples
"""Apply an action to the game and return the new observation.
Args:
action: One of "up", "down", "left", "right".
Returns:
The observation after the move.
"""
...
# The dataset only routes: each row picks the environment for that rollout
if __name__ == "__main__":
dataset = Dataset.from_dict({"environment": ["coding", "game"] * 500})
trainer = AsyncGRPOTrainer(
model="Qwen/Qwen3-0.6B",
args=AsyncGRPOConfig(chat_template_kwargs={"enable_thinking": False}),
train_dataset=dataset,
reward_funcs=reward_func,
environment_factory={"coding": CodingEnv, "game": GameEnv},
)
trainer.train()环境实例会在多个步骤之间复用(在回合之间重置),因此昂贵的 __init__ 只需支付一次,而不是每个步骤都支付。environment 字段由训练器使用,并且 不会 转发给 reset。
多轮展开
每一轮都会通过聊天模板渲染整个对话并从中生成。聊天模板并不总是以相同的方式渲染不断增长的对话(例如,某些模板会丢弃之前轮次的推理内容),因此某一轮的提示词 token 可能与当前已持有的 token 不同。在 rollout 结束时,worker 会将各轮次整合为训练样本:如果某一轮的提示词以已持有的 token 开头,则将其追加到同一个样本中;否则,该轮次将开启一个新样本。每个生成的 token 恰好在一个样本中参与训练,因此分支(fork)只会产生前向传播的 token 开销,而不会带来额外的训练信号。
Rollout 中的每个样本都会获得该 rollout 的优势值。rollout/fork_frac 和 rollout/drift_tokens_* 展示了各轮次是如何进行协调的。
环境集成
要训练在自身运行环境中执行的智能体(例如在 Harbor 沙箱中运行的编码智能体),请参阅 OpenEnv 指南中的 在运行环境中训练。
训练视觉语言模型
视觉语言模型(Qwen3.5、Qwen3.6、Qwen3-VL 等)可以在纯文本数据集上进行训练。照常传入模型 ID 即可,其他部分无需更改。
视觉塔已被冻结。纯文本数据集永远不会生成图像标记,因此前向传播过程不会调用该塔。文本模型之外的所有组件(视觉塔、多模态投影器)均设置为 requires_grad=False:它们不会接收梯度,也不占用优化器状态,权重同步过程会跳过它们,因此服务器会保留从检查点加载的原始值。由于该塔仍会被加载,其权重会占用 GPU 内存。
目前尚不支持图像。包含图像的提示词不会被传递到 vLLM 服务器或训练前向传播中。多模态训练还需要无填充打包路径来从图像网格构建 3D M-RoPE 位置,但此处尚未实现。
AsyncGRPOTrainer
类 trl.AsyncGRPOTrainer
< source >( model: str | PreTrainedModelreward_funcs: collections.abc.Callable[..., list[float]] | list[collections.abc.Callable[..., list[float]]] | None = Noneargs: trl.trainer.async_grpo_config.AsyncGRPOConfig | None = Nonetrain_dataset: datasets.arrow_dataset.Dataset | datasets.iterable_dataset.IterableDataset | None = Noneprocessing_class: transformers.tokenization_utils_base.PreTrainedTokenizerBase | None = Nonecallbacks: list[transformers.trainer_callback.TrainerCallback] | None = Noneoptimizers: tuple[typing.Optional[torch.optim.Optimizer], typing.Optional[torch.optim.lr_scheduler.LambdaLR]] = (None, None)peft_config: PeftConfig | None = Nonetools: list[collections.abc.Callable] | None = Noneenvironment_factory: collections.abc.Callable[[], trl.trainer.async_grpo_trainer._SupportsReset] | dict[str, collections.abc.Callable[[], trl.trainer.async_grpo_trainer._SupportsReset]] | None = None )
参数
- model (
str或 PreTrainedModel) — 要训练的模型。可以是以下之一: 一个字符串,即托管在 huggingface.co 模型仓库中的预训练模型的模型 ID,或者是使用 save_pretrained 保存模型权重的目录路径,例如 './my_model_directory/'。模型将根据其 config 中声明的架构进行加载:对于视觉-语言检查点(其视觉塔会被加载但冻结,参见 Training Vision Language Models),使用 AutoModelForImageTextToText;否则使用 AutoModelForCausalLM。 一个 PreTrainedModel 对象。其注意力实现会切换为训练器的树注意力后端,用于打包训练。config 中的模型名称用于在用于生成的 vLLM 服务器上识别该模型。- 一个字符串,作为托管在 huggingface.co 模型仓库中的预训练模型的 model id,或指向使用 save_pretrained 保存模型权重的 directory 的路径,例如
'./my_model_directory/'。模型将按其配置中声明的架构加载:对于视觉-语言检查点(其视觉塔会被加载但冻结,参见 Training Vision Language Models),使用 AutoModelForImageTextToText;否则使用 AutoModelForCausalLM。 - 一个 PreTrainedModel 对象。其注意力实现会切换为训练器的树注意力后端,用于打包训练。
config 中的模型名称用于标识生成所使用的 vLLM 服务器上的模型。
- 一个字符串,作为托管在 huggingface.co 模型仓库中的预训练模型的 model id,或指向使用 save_pretrained 保存模型权重的 directory 的路径,例如
- reward_funcs (
RewardFunc | list[RewardFunc], 可选) — 用于计算奖励的奖励函数。为了计算奖励,我们会使用提示词和补全内容调用所有奖励函数,并将奖励求和。当奖励由环境通过environment_factory((见下文)提供时,可以省略。可以是以下形式之一:- 单一奖励函数:该函数会接收提示词和生成的补全内容,以及数据集中的任何其他列。它应返回一个奖励列表。奖励函数可以是同步的,也可以是异步的,并且当奖励不适用于某些样本时,也可以返回
None。这在多任务训练中非常有用,因为不同的奖励函数适用于不同类型的样本。当某个奖励函数对某个样本返回None时,该奖励函数将被排除在该样本的奖励计算之外。有关更多详细信息,请参阅 使用自定义奖励函数。 - 奖励函数列表,其中每个元素都是如上所述的奖励函数。所有函数的奖励值将被求和。
与 GRPOTrainer 不同,奖励在派生的子进程中计算,因此每个奖励函数(连同
tools和environment_factory)都必须可被 pickle 序列化:请使用模块级函数、functools.partial或可调用类实例——lambda 和闭包会在启动时失败。工具会以其__name__进行注册,因此它们必须是模块级函数。子进程还会在CUDA_VISIBLE_DEVICES=""环境下运行,因此基于 GPU 的奖励模型将在 CPU 上运行(速度较慢),而不是在训练器的 GPU 上运行。 - 单一奖励函数:该函数会接收提示词和生成的补全内容,以及数据集中的任何其他列。它应返回一个奖励列表。奖励函数可以是同步的,也可以是异步的,并且当奖励不适用于某些样本时,也可以返回
- args (AsyncGRPOConfig,可选) —
此训练器的配置。如果为
None,则使用默认配置。 - train_dataset (Dataset 或 IterableDataset, 可选) —
用于训练的 Dataset。它必须包含一个名为
"prompt". 数据集中的其他列将被忽略。样本必须采用 对话 格式:每个样本包含结构化的消息(例如 role 和 content)。仅当提供了
environment_factory且环境拥有(或程序化生成)数据,并从其reset()方法返回提示时,才可以省略。在这种情况下,必须设置max_steps以定义训练长度。 - processing_class (PreTrainedTokenizerBase, 可选) —
用于处理数据的处理类。填充侧必须设置为“left”。如果为
None,则使用 from_pretrained 从模型名称加载处理类。必须设置填充标记tokenizer.pad_token。如果处理类未设置填充标记,则默认使用tokenizer.eos_token。 - callbacks (列表 TrainerCallback, 可选) —
用于自定义训练循环的回调列表。这些回调将被添加到 此处.
如果要移除其中一个默认使用的回调,请使用 remove_callback 方法。
- optimizers (
tuple[torch.optim.Optimizer | None, torch.optim.lr_scheduler.LambdaLR | None],可选,默认为(None, None)) — 一个包含要使用的优化器和调度器的元组。默认情况下,将在您的模型上使用AdamW的实例,并使用由args控制的、通过 get_linear_schedule_with_warmup 给出的调度器。 - peft_config (PeftConfig, 可选) —
用于包装模型的 PEFT 配置。如果为
None,则不包装模型。当 vLLM 服务器以--enable-lora启动时,LoRA 适配器将作为适配器而非合并权重同步到该服务器;请参阅 AsyncGRPO 文档中的 LoRA 部分。 - tools(
Callable列表,可选)— 一个可调用工具函数(同步或异步)的列表,模型可在生成过程中调用这些工具。每个工具应是一个标准的 Python 函数,具有正确类型提示的参数和返回值,以及一个 Google 风格的 docstring,用于描述其用途、参数和返回值。更多详情,请参阅:https://huggingface.co/docs/transformers/en/chat_extras#passing-tools。模型利用函数的名称、类型提示和 docstring 来确定如何调用它。请确保模型的 chat template 支持工具使用,并且该模型已针对工具调用进行了微调。 - environment_factory (
EnvironmentFactory或dict[str, EnvironmentFactory], 可选) — 一个可调用对象,用于创建并返回环境实例;或一个将环境名称映射到此类可调用对象的字典。环境类应定义可在生成期间作为工具调用的方法。每个方法都应符合上述tools中描述的相同要求。环境还必须实现一个可调用reset方法,用于在生成之间重置状态。reset方法应返回None或字符串:当返回字符串时,该字符串会在生成前追加到最后一条用户消息中。环境还可以定义一个get_reward方法,该方法不接受参数并返回float: :如果存在该方法,则环境拥有奖励,并且get_reward会在每个完成的 rollout 中被调用一次,根据环境的内部状态对其进行评分。它作为额外的奖励来源(权重为 1,记录在环境的类名下),与reward_funcs, 此时变为可选。使用单个可调用对象时,所有示例都使用相同的环境,每个 rollout 对应一个实例,从而确保它们的交互相互隔离。使用字典时,每个示例必须包含一个
environment字段,通过名称选择其环境,并且只有该环境的工具会暴露在其提示中——这使得单次运行可以混合不同的任务(例如,一个编码环境和一个游戏)。此功能处于实验阶段,可能会随时更改或移除,恕不另行通知。
用于组相对策略优化(GRPO)方法的 Trainer。该算法最初由论文 DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models 提出。此 Trainer 是 GRPO 的异步版本,其中生成任务被卸载到外部 vLLM 服务器,该服务器与训练过程异步并行运行,从而将 rollout 与梯度更新循环解耦。
示例:
>>> from trl import AsyncGRPOTrainer
>>> from trl.rewards import accuracy_reward
>>> from datasets import load_dataset
>>> dataset = load_dataset("trl-lib/DeepMath-103K", split="train")
>>> trainer = AsyncGRPOTrainer(
... model="Qwen/Qwen2.5-0.5B-Instruct",
... reward_funcs=accuracy_reward,
... train_dataset=dataset,
... )
>>> trainer.train()若要自定义采样哪些提示、如何生成 rollout 或如何计算优势,请子类化 AsyncRolloutLoop 并将其设置为训练器的 loop_cls。TRL 会在
训练
< source >( resume_from_checkpoint: str | bool | None = Nonetrial: optuna.Trial | dict[str, Any] | None = Noneignore_keys_for_eval: list[str] | None = None ) → ~trainer_utils.TrainOutput
参数
- resume_from_checkpoint (
str或bool,可选) — 如果为str,则为之前Trainer实例保存的检查点的本地路径。如果为bool且等于True,则加载之前Trainer实例保存在 args.output_dir 中的最后一个检查点。如果存在,训练将从此处加载的模型/优化器/调度器状态中恢复。 - trial (
optuna.Trial或dict[str, Any],可选) — 用于超参数搜索的试验运行或超参数字典。 - ignore_keys_for_eval (
list[str],可选) — 在训练期间收集预测结果以进行评估时,应忽略的模型输出(如果输出是字典)中的键列表。
返回值
~trainer_utils.TrainOutput
包含全局步数、训练损失和指标的 Object。
主要训练入口。
将保存模型,以便您可以使用 from_pretrained() 重新加载它。
仅从主进程保存。
push_to_hub
< source >( commit_message: str | None = 'End of training'blocking: bool = Truetoken: str | None = Nonerevision: str | None = None**kwargs )
将 self.model 和 self.processing_class 上传到 🤗 模型库中的仓库 self.args.hub_model_id。
AsyncGRPOConfig
类 trl.AsyncGRPOConfig
< source >( output_dir: str | None = Noneper_device_train_batch_size: int = 8num_train_epochs: float = 3.0max_steps: int = -1learning_rate: float = 1e-06lr_scheduler_type: str = 'constant'lr_scheduler_kwargs: dict | str | None = Nonewarmup_steps: float = 0optim: transformers.training_args.OptimizerNames | str = 'adamw_torch_fused'optim_args: str | None = Noneweight_decay: float = 0.0adam_beta1: float = 0.9adam_beta2: float = 0.999adam_epsilon: float = 1e-08optim_target_modules: None | str | list[str] = Nonegradient_accumulation_steps: int = 1average_tokens_across_devices: bool = Truemax_grad_norm: float = 1.0label_smoothing_factor: float = 0.0bf16: bool | None = Nonefp16: bool = Falsebf16_full_eval: bool = Falsefp16_full_eval: bool = Falsetf32: bool | None = Nonegradient_checkpointing: bool = Truegradient_checkpointing_kwargs: dict[str, typing.Any] | str | None = Nonetorch_compile: bool = Falsetorch_compile_backend: str | None = Nonetorch_compile_mode: str | None = Noneuse_liger_kernel: bool = Falseliger_kernel_config: dict[str, bool] | None = Noneuse_cache: bool = Falseneftune_noise_alpha: float | None = Nonetorch_empty_cache_steps: int | None = Noneauto_find_batch_size: bool = Falselogging_strategy: transformers.trainer_utils.IntervalStrategy | str = 'steps'logging_steps: float = 1logging_first_step: bool = Falselog_on_each_node: bool = Truelogging_nan_inf_filter: bool = Trueinclude_num_input_tokens_seen: str | bool = 'no'log_level: str = 'passive'log_level_replica: str = 'warning'disable_tqdm: bool | None = Nonereport_to: None | str | list[str] = 'none'run_name: str | None = Noneproject: str = 'huggingface'trackio_space_id: str | None = Nonetrackio_bucket_id: str | None = Nonetrackio_static_space_id: typing.Union[str, NoneType, typing.Literal[False]] = Noneeval_strategy: transformers.trainer_utils.IntervalStrategy | str = 'no'eval_steps: float | None = Noneeval_delay: float = 0per_device_eval_batch_size: int = 8prediction_loss_only: bool = Falseeval_on_start: bool = Falseeval_do_concat_batches: bool = Trueeval_use_gather_object: bool = Falseeval_accumulation_steps: int | None = Noneinclude_for_metrics: list[str] = <factory>batch_eval_metrics: bool = Falsesave_only_model: bool = Falsesave_strategy: transformers.trainer_utils.SaveStrategy | str = 'steps'save_steps: float = 500save_on_each_node: bool = Falsesave_total_limit: int | None = Noneenable_jit_checkpoint: bool = Falsepush_to_hub: bool = Falsehub_token: str | None = Nonehub_private_repo: bool | None = Nonehub_model_id: str | None = Nonehub_strategy: transformers.trainer_utils.HubStrategy | str = 'every_save'hub_always_push: bool = Falsehub_revision: str | None = Noneload_best_model_at_end: bool = Falsemetric_for_best_model: str | None = Nonegreater_is_better: bool | None = Noneignore_data_skip: bool = Truerestore_callback_states_from_checkpoint: bool = Falsefull_determinism: bool = Falseseed: int = 42data_seed: int | None = Noneuse_cpu: bool = Falseaccelerator_config: dict | str | None = Noneparallelism_config: accelerate.parallelism_config.ParallelismConfig | None = Nonedataloader_drop_last: bool = Falsedataloader_num_workers: int = 0dataloader_pin_memory: bool = Truedataloader_persistent_workers: bool = Falsedataloader_prefetch_factor: int | None = Nonedataloader_multiprocessing_context: str | None = Nonedataloader_in_order: bool = Trueremove_unused_columns: bool = Falselabel_names: list[str] | None = Nonetrain_sampling_strategy: str = 'random'length_column_name: str = 'length'ddp_find_unused_parameters: bool | None = Noneddp_bucket_cap_mb: int | None = Noneddp_broadcast_buffers: bool | None = Noneddp_static_graph: bool | None = Noneddp_backend: str | None = Noneddp_timeout: int = 1800fsdp: str | None = Nonefsdp_config: dict[str, typing.Any] | str | None = Nonedeepspeed: dict | str | None = Nonedebug: str | list[transformers.debug_utils.DebugOption] = ''skip_memory_metrics: bool = Truedo_train: bool = Falsedo_eval: bool = Falsedo_predict: bool = Falseresume_from_checkpoint: str | None = Nonelocal_rank: int = -1model_init_kwargs: dict[str, typing.Any] | str | None = Nonedtype: str = 'float32'trust_remote_code: bool = Falserouter_aux_loss_coef: float | None = Nonenum_generations: int = 8max_completion_length: int = 2048temperature: float = 1.0top_p: float = 1.0top_k: int = 0min_p: float | None = Nonechat_template_kwargs: dict | str | None = Nonemax_tool_calling_iterations: int | None = Nonevllm_server_base_url: str = 'http://localhost:8000'vllm_server_timeout: float = 1800.0epsilon: float = 0.2epsilon_high: float | None = Nonescore_centering: bool = Truevllm_importance_sampling_clip_min: float = 0.5vllm_importance_sampling_clip_max: float = 5.0num_sampler_logprobs: int = 8token_budget: int | None = Nonemax_concurrent_rollouts: int = 512max_staleness: int = 4weight_sync_steps: int = 1rollout_worker_timeout: float = 300.0log_completions: bool = Falsenum_completions_to_print: int | None = None )
控制模型的参数
- model_init_kwargs (
dict[str, Any]或str,可选) — 用于 from_pretrained 的关键字参数,在从路径实例化模型时使用。revision的值在加载处理类时也会用到。 - dtype (
str, 可选, 默认为"float32") — 加载模型时使用的数据类型,可选值之一为"auto","bfloat16","float16"或"float32". 默认 为"float32",因为该 Trainer 所针对的训练-推理不匹配问题(通过 FP16 消除 训练-推理不匹配) )对 Trainer 自身的精度很敏感。要端到端地弥合这一差距,还需要以相同的 dtype 提供 vLLM 服务器(vllm serve --dtype); ;不匹配会在训练开始时记录为警告。dtype中的model_init_kwargs优先。这仅设置了权重加载时的 dtype。在 FSDP 混合精度下(
bf16=True为默认值),accelerate 会在分片前将每个可训练参数上转为 fp32,因此无论dtype如何设置,主权重和优化器状态均为 fp32,而前向和反向传播则在 bf16 或 fp16 下运行。随后,检查点将以该混合精度 dtype 写入,而非 fp32。在没有 FSDP 的情况下,不会进行任何上转:dtype="bfloat16"配合bf16=True会保持 bf16 权重和优化器状态,即纯 bf16 训练。 - trust_remote_code (
bool, optional, defaults toFalse) — Whether to allow loading models and tokenizers that ship custom Python code from the Hub. Forwarded to from_pretrained and from_pretrained. - router_aux_loss_coef (
float, 可选) — 混合专家(MoE)模型中负载均衡辅助损失的系数,以该权重添加到训练损失中。如果未设置,则使用模型配置中声明的值。设置为0.0可将其禁用。与非 MoE 模型一起使用时会失败。
控制生成的参数
- num_generations (
int,可选,默认为8) — 每个提示词采样的生成数量。 - max_completion_length (
int,可选,默认为2048) — 每个 vLLM 请求生成的最大 token 数,即在多轮 rollout 中每轮生成的最大 token 数。 - temperature (
float,可选,默认为1.0) — 用于采样的温度。温度越高,生成的补全内容越随机。 - top_p (
float,可选,默认为1.0) — 控制所考虑的前几个 token 的累积概率的浮点数。取值必须在 (0, 1] 之间。设置为 1.0 表示考虑所有 token。若小于1.0,则必须同时设置top_k,并且 vLLM 服务器(>= 0.28)需以--return-sampling-mask --enable-scale-out启动:训练器会像服务器一样,在每个 token 被采样时,对其候选集上的对数概率进行重新归一化。旧版 vLLM 会保留整个词表。 - top_k (
int,可选,默认为0) — 用于 top-k 过滤时保留的最高概率词表 token 数量。如果为0,则禁用 top-k 过滤,所有 token 均被考虑。其他任何值都需要top_p下所述的服务器标志。 - min_p (
float, 可选) — 最小 token 概率,该值会按最可能 token 的概率进行缩放。它必须是 0.0 到 1.0 之间的值。典型值在 0.01-0.2 范围内。当值大于0.0时,还必须设置top_k,并使用top_p下描述的服务器标志。 - chat_template_kwargs (
dict[str, Any], 可选) — 在生成补全时传递给apply_chat_template函数的其他关键字参数。 - max_tool_calling_iterations (
int, 可选) — 训练智能体时工具调用轮次的最大数量。如果为None,则没有上限,生成过程将在模型生成一个不包含工具调用的响应轮次时停止。
控制 vLLM 服务器的参数
- vllm_server_base_url (
str,可选,默认为"http --//localhost:8000"): 用于生成的 vLLM 服务器的基础 URL(例如"http://localhost:8000")。 - vllm_server_timeout (
float,可选,默认为1800.0) — 对 vLLM 服务器调用的超时时间(秒):包括启动时等待服务器就绪、每个生成请求以及每次权重同步调用。等待服务器就绪和权重同步调用在超时会抛出异常;超时的生成请求会被重试。
控制训练的参数
- epsilon (
float, 可选, 默认为0.2) — 用于裁剪的 Epsilon 值。 - epsilon_high (
float, 可选) — 用于裁剪的上限 epsilon 值。如果未指定,则默认与参数epsilon中指定的下限值相同。论文 DAPO 建议设置为0.28。 - score_centering (
bool,可选,默认为True) — 是否使用分数中心化(论文)而非截断目标进行训练:即每个采样 token 的重要性加权分数减去其在 vLLM 采样器下的期望值,从而避免恒定的优势值将策略拉向采样器。该期望是在每个位置最可能的num_sampler_logprobs个 token 上计算的,采样器会报告这些 token 的对数概率。 - vllm_importance_sampling_clip_min (
float,可选,默认值为0.5) — 策略与 vLLM 采样器之间重要性比值的下界 C_min。比值落在 [vllm_importance_sampling_clip_min,vllm_importance_sampling_clip_max] 区间之外的 token 将从损失中剔除(掩码重要性采样)。 - vllm_importance_sampling_clip_max (
float,可选,默认值为5.0) — 重要性比值的上界 C_max。 - num_sampler_logprobs (
int,可选,默认为8) — 从 vLLM 采样器为每个生成的 token 请求的 top log-probabilities 数量,用于 score centering。数量越多,覆盖的采样器概率质量越大,但序列化开销也越高;服务器端的--max-logprobs会对其上限进行约束。 - token_budget (
int, 可选) — 单行(单个 DP rank 的前向传播)中转发 token 的最大数量。TokenBudgetBatcher会构建注意力成本均衡的微批次,其中每一行都保持在该预算之内,从而独立于样本数量地限制峰值内存(每行的样本数量变为动态)。该预算限制的是唯一 token 而非原始 token,因此被多个样本共享的 token 只计算一次,并且它也限制该行的损失项。如果为None(默认值),则设置为 vLLM 服务器的max_model_len(在训练开始时查询)——即提示 + 补全长度的上限——因此任何 rollout 样本都不会超出预算。长度超过token_budget的样本无法放入任何一行,将被丢弃并产生警告。
控制异步 rollout 流水线的参数
- max_concurrent_rollouts (
int,可选,默认为512) — 同时运行的 rollout 最大数量。每个 rollout 都会占用一个环境,可能执行同步工具调用,并维持与 vLLM 服务器的连接,因此请根据这些资源的承受能力来设置该值。在此上限之下,正在进行的 rollout 数量取决于 vLLM 服务器的 KV-cache 使用情况以及max_staleness。 - max_staleness (
int,可选,默认为4) — 在 rollout 样本被丢弃之前,其权重更新步骤最多可以落后于当前模型版本的步数。生成过程受此约束:只有当排队中和正在传输中的 token 数量能够被训练器在max(max_staleness, 1)步内消耗完时,才会启动新一组 rollout。 - weight_sync_steps (
int,可选,默认为1) — 向 vLLM 服务器同步权重之间的训练步数。 - rollout_worker_timeout (
float, 可选,默认为300.0) — 自 rollout worker 上次心跳以来,经过多少秒后训练器将其视为挂起并中止。
控制日志记录的参数
AsyncGRPOTrainer 的配置类。
此类仅包含异步 GRPO 训练特有的参数。完整的训练参数列表请参阅 TrainingArguments 文档。请注意,此类中的默认值可能与 TrainingArguments 中的不同。
这些参数的默认值与 TrainingArguments 不同:
logging_steps: 默认值为1,而不是500。gradient_checkpointing:默认值为True,而非False。bf16:如果未设置fp16,则默认值为True,而不是False。learning_rate:默认值为1e-6,而非5e-5。lr_scheduler_type:默认值为constant,而非linear(详见下文)。ignore_data_skip:默认值为True而非False;基础 Trainer 的跳过并重放循环不适用于异步 rollout 队列。remove_unused_columns:默认值为False而非True,且必须保持为False:数据集的每一列都会传递给奖励函数。
在消息模式协调下的训练时长和学习率: 多轮对话可能会分叉成数量不定的训练行(对话的重写会开启新的一行),因此每个 epoch 的样本数,进而优化器步数,在事先是未知的。由此导致:
num_train_epochs通过完整遍历 prompt 数据集来限制训练,其计数依据是实际参与训练的不同 prompt 的数量。这与 fork 产生的行数无关,因此请求 N 个 epoch 始终意味着对数据进行 N 次完整遍历。当max_steps未设置时,此参数即为停止条件,而max_steps仅作为安全上限。max_steps,如果显式设置(> 0),将作为停止条件(以优化器步数而非 epoch 数作为上限),并禁用基于 epoch 的停止。lr_scheduler_type的默认值为constant,因为衰减周期以优化器步数为单位,而当步数取决于 fork rate 时,无法预先确定该值。若需使用衰减学习率,请设置一个衰减调度器并显式指定max_steps。
AsyncRolloutLoop
类 trl.trainer.async_grpo_trainer.AsyncRolloutLoop
< source >( config: AsyncGRPOConfigmodel_name: strdataset: Datasetreward_funcs: list[collections.abc.Callable[..., list[float]]]processing_class: PreTrainedTokenizerBaseeos_token_ids: list[int]rollout_buffer: Queuemodel_version_value: Synchronizedtoken_allowance: Synchronizedheartbeat_value: Synchronizedfailed_event: Eventexception_info_queue: Queuemetrics_queue: Queuetools: list[collections.abc.Callable] | None = Noneenvironment_factory: collections.abc.Callable[[], object] | dict[str, collections.abc.Callable[[], object]] | None = Nonescore_queue_maxsize: int = 16lora_name: str | None = Nonedataset_start_index: int = 0 )
在生成的 rollout 进程中,为 AsyncGRPOTrainer 生成并评估 rollout。
默认实现为 GRPO。若要自定义训练流程,请继承该类,仅重写需要更改的方法,并将子类设置为训练器的 loop_cls。每个提示词将按以下顺序依次处理:
- filter_prompt():是否针对提示词生成内容。
- generate_one():一次完整的 rollout,会为每个采样请求调用 generate_one_turn()。
num_generations组 rollout 会被评分:其奖励为训练器的reward_funcs、定义了get_reward的环境以及 Rollout.reward的总和。随后,compute_advantages() 每组运行一次。
setup() 和 close() 在循环的事件循环上创建和释放资源,例如 HTTP 客户端。钩子可以读取 self.config(AsyncGRPOConfig 的子类可以携带自己的字段)和 self.tokenizer。由于循环是在派生进程中构建的,因此该子类必须可导入(在模块级别定义)。
filter_prompt
< source >( row_index: introw: dict[str, typing.Any]epoch: int ) → bool
是否针对某个提示词生成。被拒绝的提示词将在当前 epoch 中被跳过,并计为已消耗,类似于被丢弃的组。默认情况下接受所有提示词。如果拒绝了一个完整 epoch 中的所有提示词,则会引发异常,因为此后没有任何批次能够到达训练器。
generate_one
< source >( prompt: list[dict[str, str]]tools: list[collections.abc.Callable]group_id: int = 0 ) → Rollout
为提示词生成一个 rollout。
默认情况下,多轮工具循环的工作方式为:每一轮都会使用聊天模板渲染整个对话,通过 generate_one() 采样回复,并执行其中包含的工具调用,直到模型停止调用工具或达到 max_tool_calling_iterations 为止。您可以重写此逻辑以采用不同的生成方式,例如使用智能体框架。请在 Rollout.turns 中返回所有采样请求,因为训练序列是基于它们构建的,并在此处设置 Rollout.reward 来对 rollout 进行评分。
generate_one_turn
< source >( prompt_ids: list[int] ) → TurnRecord
从 vLLM 服务器采样一条回复,使用配置中的采样设置(temperature、top_p、top_k、min_p、repetition_penalty 和 max_completion_length)。
compute_advantages
< source >( group: RolloutGroup ) → np.ndarray
参数
- group (RolloutGroup) —
一个提示的
num_generations个 rollout,其总奖励位于group.rewards中。
返回值
np.ndarray
每个 rollout 的一个优势,顺序与 group.rewards 一致。
为组中的每次 rollout 计算一个优势值。默认情况下,每个奖励在组内进行标准化;奖励为 NaN 的 rollout 其优势值为 0,且不参与均值和标准差的计算。
它会对每个组执行。如果某个组的得分奖励全部相等,则该组会被丢弃,无论由此返回的优势值是什么。
创建其他钩子所需的资源,例如 HTTP 客户端。在第一个提示之前,在循环的事件循环上运行。默认情况下不执行任何操作。
释放 setup() 创建的资源。在循环停止时运行,包括失败时。默认情况下不执行任何操作。
Rollout
类 trl.trainer.async_grpo_trainer.Rollout
< source >( completion: list[dict[str, str]]completion_ids: list[int]turns: list[trl.trainer.async_grpo_trainer.TurnRecord]tool_call_count: int = 0tool_failure_count: int = 0reward: float | None = Nonerollout_id: str = <factory> )
参数
- completion (
list[dict[str, Any]]) — Messages generated after the prompt, in conversational format. Passed to the reward functions ascompletions. - completion_ids (
list[int]) — 生成的补全的 Token ID。作为completion_ids传递给奖励函数。 - turns (
list[TurnRecord]) — 按顺序排列的、生成该 rollout 的采样请求。训练序列由它们构建而成:对采样得到的 token 进行训练,每个 prompt 的其余部分作为上下文。 - tool_call_count (
int,可选,默认为0) — 在 rollout 期间进行的工具调用次数。 - tool_failure_count (
int,可选,默认为0) — 这些工具调用中失败的数量。 - reward (
float, 可选) — 在生成过程中计算得到的奖励,例如由外部评分器计算。该值会累加到奖励函数和环境产生的奖励中。如果为None,则此 rollout 不会从该来源获得奖励。 - rollout_id (
str, 可选) — 用于在训练行中标识该 rollout,便于调试。默认值为一个新的随机十六进制 id。
由 generate_one() 返回的一次完成的 rollout。
TurnRecord
类 trl.trainer.async_grpo_trainer.TurnRecord
< source >( prompt_ids: list[int]output_ids: list[int]output_log_probs: list[float] = <factory>output_mask: list[int] | None = Noneoutput_sampling_mask: list[list[int]] | None = Noneoutput_sampler_logprobs: list[dict[int, float]] | None = None )
参数
- prompt_ids (
list[int]) — 本轮发送的完整提示词的 Token ID,包括对话中所有更早的轮次。 - output_ids (
list[int]) — 采样器生成的 Token ID。 - output_log_probs (
list[float],可选,默认为[]) —output_ids中每个 token 的采样器对数概率。当其中任何一个被训练时,此项为必填。 - output_mask (
list[int],可选) —output_ids中每个 token 对应一个值:1表示对该 token 进行训练,0表示仅将其作为上下文保留。如果为None,则对所有输出 token 进行训练。 - output_sampling_mask (
list[list[int]],可选) —output_ids中每个 token 对应一个列表:在top_k/top_p截断下,采样器可以从中抽取的 token id。如果为None,则为完整词表。 - output_sampler_logprobs (
list[dict[int, float]], 可选) —output_ids中每个 token 对应一个字典:采样器对其最可能 token 的对数概率,以 token id 为键。用于分数中心化(num_sampler_logprobs)。
一次采样请求:发送给采样器的提示词及其生成的标记。
RolloutGroup
类 trl.trainer.async_grpo_trainer.RolloutGroup
< source >( prompts: list[list[dict[str, str]]]reward_kwargs: dict[str, list[typing.Any]]completions: list[list[dict[str, str]]]completions_ids: list[list[int]]completions_sequences: list[list[trl.trainer.async_grpo_trainer.TrainingSequence]]tool_call_counts: list[int]tool_failure_counts: list[int]model_version: intgroup_id: introw_index: intenv_rewards: list[tuple[type, float] | None]rollout_rewards: list[float | None]queued_at: float = 0.0rewards: numpy.ndarray | None = None )
参数
- prompts (
list[list[dict[str, Any]]]) — 每个 rollout 的 Prompt。 - reward_kwargs (
dict[str, list[Any]]) — 提示词数据集中该行的其他列,作为关键字参数传递给奖励函数。 - completions (
list[list[dict[str, Any]]]) — 每个 rollout 的补全消息。 - completions_ids (
list[list[int]]) — 每个 rollout 的补全 token id。 - completions_sequences (
list[list[TrainingSequence]]) — 由每次 rollout 构建的训练序列。如果某次 rollout 在轮次之间重新分词后的历史发生了变化,则会生成多个序列。 - tool_call_counts (
list[int]) — 每个 rollout 中的工具调用次数。 - tool_failure_counts (
list[int]) — 每个 rollout 中失败的工具调用次数。 - model_version (
int) — 该组首次 rollout 开始时的策略版本。 - group_id (
int) — 组的标识符,在 rollout 过程中从0开始计数。 - row_index (
int) — 提示词在训练数据集中的行索引。 - env_rewards (
list[tuple[type, float] or None]) — 对于其环境定义了get_reward的 rollout,为环境类及其奖励;否则为None。 - rollout_rewards (
list[float or None]) — 每个 Rollout.reward。 - queued_at (
float,可选,默认为0.0) — 该组被加入评分队列时的time.monotonic()时间。 - rewards (
np.ndarray, 可选) — 每次 rollout 的总奖励,汇总了所有奖励来源,若没有任何来源对其进行评分则为 NaN。在调用 compute_advantages() 之前设置。
单个提示词的 num_generations 次生成结果,并一起进行评分。每个列表包含每次生成结果的一个条目。