verl 中 PPO 训练器的完整实战指南:从核心配置到源码级原理
【免费下载链接】verlverl/HybridFlow: A Flexible and Efficient RL Post-Training Framework项目地址: https://gitcode.com/GitHub_Trending/ve/verl
导读
PPO(Proximal Policy Optimization,近端策略优化)是 verl 中首个、也是目前最完整的 on-policy RL 后训练算法家族之一。本文以examples/ppo_trainer/README.md为核心,结合仓库中可运行的规范脚本与verl/trainer/ppo/core_algos.py等源码实现,系统讲解 PPO 在 verl 中的关键组件、全部核心配置项、KL 发散控制与 Dual-clip 扩展,并给出基于 Qwen3-8B + GSM8K/MATH 的可复现训练方案。读完本文,你将掌握 verl 中 PPO(actor + critic 双模型)的配置语义、脚本运行方式,以及 GAE、KL 罚项与裁剪目标在源码中的真实计算过程。
PPO 算法背景与 verl 中的定位
PPO 是 OpenAI 在 2017 年提出的策略梯度算法家族,在简洁性、稳定性与性能之间取得了良好平衡,是现代 RL 应用(包括大规模语言模型微调)中使用最广泛的算法之一。传统策略梯度方法(如 REINFORCE、Vanilla Policy Gradient)存在两个痛点:
- 方差高、样本效率低;
- 策略更新过大导致训练不稳定。
PPO 通过一个**裁剪的替代目标(clipped surrogate objective)**解决上述问题——在无需二阶导数的情况下限制单次更新的步长,从而稳定训练。这一点正是它与 GRPO、RLOO 等算法的根本区别之一:PPO 需要同时维护 actor(策略)与 critic(价值)两个模型,而 GRPO、RLOO 不需要 critic。
在 verl 中,PPO 的完整实现由三部分构成:
| 层次 | 位置 | 职责 |
|---|---|---|
| 入口 | verl/trainer/main_ppo.py | Hydra 配置解析、Ray 集群初始化、训练循环调度 |
| 核心算法 | verl/trainer/ppo/core_algos.py | GAE、KL 控制器、策略/价值损失、优势估计器注册表 |
| 规范脚本 | examples/ppo_trainer/ | 面向 Qwen3-8B 等模型的端到端可运行示例 |
关键组件
verl 中 PPO 训练器由三个关键组件构成,理解它们是读懂一切配置的前提:
Actor-Critic 架构:PPO 同时要求 actor 模型(策略)与 critic 模型(价值函数)。actor 负责生成响应并更新策略,critic 负责估计状态价值、为优势计算提供基线。critic 通常与 actor 共享同一基础模型,但通过
critic.model.path可以独立指定(例如复用同一 checkpoint 或使用不同的模型)。广义优势估计(GAE):PPO 使用 GAE 计算优势值(advantage),在降低策略梯度估计方差的同时保持低偏差。GAE 通过
algorithm.gamma(折扣因子)与algorithm.lam(偏差-方差权衡参数)两个超参控制。裁剪替代目标:PPO 的核心在于对策略更新幅度的约束——将新旧策略的概率比裁剪到
[1 - clip_ratio, 1 + clip_ratio]区间内,防止单次更新过于激进。
核心配置详解
PPO 训练配置全部通过 Hydra 覆盖(override)传入main_ppo.py。下面按分组逐一说明。
需要注意:所有包含
micro_batch_size的配置仅用于控制每次前向/反向传播的最大样本数或 token 数,以避免 GPU OOM,不会改变算法或收敛行为。critic 的大部分配置与 actor 类似,以下说明中 critic 模型不再单独图示。
数据与批次维度
| 配置项 | 说明 |
|---|---|
data.train_batch_size | 用于生成一组采样轨迹(rollout)的全局 prompt 批量大小。生成的响应/轨迹总数为data.train_batch_size * actor_rollout_ref.rollout.n(n为每个 prompt 的采样数)。 |
actor_rollout_ref.actor.ppo_mini_batch_size | 将采样得到的一组轨迹切分为多个 mini-batch 用于 actor 的 PPO 更新。该值是跨所有 worker 的全局大小。 |
critic.ppo_mini_batch_size | 将采样轨迹切分为多个 mini-batch 用于 critic 的 PPO 更新,同样为全局大小。 |
裁剪与更新轮数
| 配置项 | 说明 | 默认值 |
|---|---|---|
actor_rollout_ref.actor.clip_ratio | PPO 裁剪范围 ε | 0.2 |
actor_rollout_ref.actor.ppo_epochs | 对同一组采样轨迹执行 PPO 更新的轮数(actor) | — |
critic.ppo_epochs | 对同一组采样轨迹执行 PPO 更新的轮数(critic) | 继承actor_rollout_ref.actor.ppo_epochs |
算法超参
| 配置项 | 说明 |
|---|---|
algorithm.gamma | 折扣因子 |
algorithm.lam | GAE 估计器中偏差与方差权衡的 λ 参数 |
algorithm.adv_estimator | 优势估计器,支持gae、grpo、reinforce_plus_plus、reinforce_plus_plus_baseline、rloo、rloo_vectorized |
从源码看,这些优势估计器在 core_algos.py 中以AdvantageEstimator枚举集中注册,并提供了可扩展的注册机制:用户既可以直接使用内置估计器,也可以通过register_adv_est注册自定义优势估计函数,通过get_adv_estimator_fn按名获取。例如 GAE 的实现入口为compute_gae_advantage_return,它按时间步反向递推计算nextvalues与lastgaelam,最终产出 shape 为(batch_size, response_length)的优势与回报张量。
高级扩展:KL 发散控制
为了防止策略偏离参考策略(reference policy)过远,verl 提供两种 KL 控制机制:KL reward penalty(把 KL 作为奖励罚项)与KL loss(把 KL 作为 actor 损失的一部分)。这两种方式一般二选一——使用 KL loss 时不再在奖励函数中施加 KL。
方式一:使用 KL Loss(作用于 actor 损失)
| 配置项 | 说明 | 默认值 |
|---|---|---|
actor_rollout_ref.actor.use_kl_loss | 是否在 actor 中使用 KL loss。启用时不再在奖励函数中施加 KL | False |
actor_rollout_ref.actor.kl_loss_coef | KL loss 的系数 | 0.001 |
actor_rollout_ref.actor.kl_loss_type | 支持kl(k1)、abs、mse(k2)、low_var_kl(k3)与full;在末尾追加+(如k1+、k3+)将启用 straight-through 技巧,用 k2 做无偏梯度估计而保留原始 KL 值估计 | — |
方式二:使用 KL Penalty(作用于奖励函数)
| 配置项 | 说明 | 默认值 |
|---|---|---|
algorithm.use_kl_in_reward | 是否在奖励中启用 KL 罚项 | False |
algorithm.kl_penalty | 计算 actor 与参考策略之间 KL 的方式,支持kl(k1)、abs、mse(k2)、low_var_kl(k3)、full,具体取值见 core_algos.py 中的kl_penalty | — |
algorithm.kl_ctrl.kl_coef | in-reward KL 罚项的(初始)系数 | 0.001 |
algorithm.kl_ctrl.type | fixed对应FixedKLController,adaptive对应AdaptiveKLController | — |
algorithm.kl_ctrl.horizon | 详见AdaptiveKLController源码 | — |
algorithm.kl_ctrl.target_kl | 详见AdaptiveKLController源码 | — |
源码级原理:KL 罚项到底怎么算
在 core_algos.py 中,带 KL 罚项的 token 级奖励计算为:
kl = old_log_prob - ref_log_prob return token_level_scores - kl * kl_ratio即每个 token 的奖励 = 环境奖励 − KL 估计 × KL 系数。而 KL 估计的具体形式由kl_penalty_forward决定(core_algos.py):
k1:logprob - ref_logprob(一阶近似,方差大但有偏);abs:|logprob - ref_logprob|;k2:0.5 * (logprob - ref_logprob)^2(二阶近似,无偏梯度);k3:先对ref_logprob - logprob做[-20, 20]裁剪,再计算exp(ratio) - ratio - 1并裁剪到[-10, 10];full:需要完整词表 logits,当前实现抛NotImplementedError。
对于以+结尾的写法(如k3+),kl_penalty会先剥离+得到基础估计方式(k3),然后使用 straight-through 技巧:前向使用原始估计值,反向则替换为 k2 的梯度(backward_score - backward_score.detach() + forward_score.detach()),从而在保持估计形式的同时获得无偏梯度。这正是 README 中提到的“无论 KL 值如何估计,都用 k2 做无偏梯度估计”的机制来源。
KL 控制器
FixedKLController与AdaptiveKLController在 core_algos.py 中实现,get_kl_controller作为工厂函数按kl_ctrl.type创建实例:
- Fixed:KL 系数恒定,
update为空操作; - Adaptive:根据当前 KL 与
target_kl的相对误差(裁剪到 ±0.2)按horizon步数线性调整系数:mult = 1 + proportional_error * n_steps / horizon。当实际 KL 超过目标时增大罚项系数,反之减小,实现自适应收紧/放松约束。
高级扩展:Dual-clip PPO
Dual-clip PPO 在标准裁剪目标的基础上,对优势为负时的策略比率施加一个下界:当优势小于零时,即使策略比率被放得很大,其贡献也不会超过指定的下界。这可以防止灾难性的大幅更新(尤其是当策略比率因数值原因被异常放大时)。
| 配置项 | 说明 | 默认值 |
|---|---|---|
actor_rollout_ref.actor.clip_ratio_c | Dual-clip PPO 的比率下界 | 3.0 |
在源码层面,compute_policy_loss(core_algos.py)的 docstring 明确要求clip_ratio_c > 1.0,否则会抛出断言错误。该实现同时支持标准裁剪(cliprange)与 Dual-clip(cliprange_low/cliprange_high+clip_ratio_c),对应论文 Dual-Clip PPO(2019)的思想。
规范脚本:端到端运行 PPO
脚本命名与通用约定
examples/ppo_trainer/下的所有脚本遵循run_<model>_<infer-backend>_<train-backend>[_<platform>].sh命名规范,并具备以下约定:
- 使用
MODEL_PATH环境变量指定模型(可覆盖,如MODEL_PATH=Qwen/Qwen3-14B bash run_qwen3_8b_fsdp.sh); - 默认启用动态批大小(
use_dynamic_bsz=True)与批次平衡(trainer.balance_batch=True); - 仅使用当前 API 的 Hydra 覆盖写法;
- 其余环境变量(
TRAIN_BATCH_SIZE、PPO_MINI_BATCH_SIZE、ACTOR_LR、CRITIC_LR、ROLLOUT_TP、NNODES、NGPUS_PER_NODE等)在各脚本顶部集中定义并给出默认值,可直接覆盖。
脚本一览
结合仓库实际文件(README 中的表格与仓库文件略有出入,此处以真实文件为准):
| 脚本 | 推理后端 | 训练后端 | 平台 |
|---|---|---|---|
| examples/ppo_trainer/run_qwen3_8b_fsdp.sh | vLLM(INFER_BACKEND=vllm,可切换 sglang) | FSDP | NVIDIA GPU / NPU 自适应 |
| examples/ppo_trainer/run_qwen3_8b_megatron.sh | vLLM | Megatron | NVIDIA GPU |
| examples/ascend_extras/ppo_trainer/run_qwen3_8b_fsdp.sh | vLLM | FSDP | Ascend NPU |
FSDP 版本脚本通过探测torch_npu自动识别设备(DEVICE变量),因此同一份run_qwen3_8b_fsdp.sh在 GPU 与 NPU 上均可运行;在 GPU 且推理后端为 vLLM/SGLang 时,脚本通过uv run --frozen --all-packages --extra vllm --extra fsdp启动(设置VERL_USE_UV=0可回退到系统 Python)。
默认配置速查(FSDP 版)
| 环境变量 | 默认值 | 对应 Hydra 覆盖 |
|---|---|---|
MODEL_PATH | Qwen/Qwen3-8B | actor_rollout_ref.model.path |
TRAIN_BATCH_SIZE | 1024 | data.train_batch_size |
PPO_MINI_BATCH_SIZE | 256 | actor_rollout_ref.actor.ppo_mini_batch_size |
MAX_PROMPT_LENGTH | 1024 | data.max_prompt_length |
MAX_RESPONSE_LENGTH | 2048 | data.max_response_length |
PPO_MAX_TOKEN_LEN_PER_GPU | 24576 | actor/critic/rollout/ref 的*_max_token_len_per_gpu |
ACTOR_LR/CRITIC_LR | 1e-6 / 1e-5 | actor_rollout_ref.actor.optim.lr/critic.optim.lr |
ROLLOUT_TP | 2 | actor_rollout_ref.rollout.tensor_model_parallel_size |
ROLLOUT_N | 1 | actor_rollout_ref.rollout.n |
TOTAL_EPOCHS/SAVE_FREQ/TEST_FREQ | 15 / 20 / 5 | trainer.* |
Megatron 版本额外暴露ACTOR_TP/ACTOR_PP/CRITIC_TP/CRITIC_PP(默认 2/2/2/2),并通过model_engine=megatron与EXTRA数组切换训练引擎;同时该脚本以export CUDA_DEVICE_MAX_CONNECTIONS=1规避 Megatron 训练中的连接数限制问题。
数据准备
两个脚本默认使用GSM8K + MATH数据集(训练/验证共 4 个 parquet 文件,路径由GSM8K_TRAIN_FILE、GSM8K_TEST_FILE、MATH_TRAIN_FILE、MATH_TEST_FILE指定)。对应的预处理脚本位于 examples/data_preprocess/gsm8k.py 与 examples/data_preprocess/math_dataset.py,可先运行预处理生成 parquet,再执行训练脚本。数据侧还默认开启data.filter_overlong_prompts=True与data.truncation='error',对超长 prompt 进行过滤而非截断。
运行方式
在仓库根目录执行(以 FSDP 版为例):
bash examples/ppo_trainer/run_qwen3_8b_fsdp.sh # 覆盖模型与环境变量: MODEL_PATH=Qwen/Qwen3-14B TRAIN_BATCH_SIZE=512 PPO_MINI_BATCH_SIZE=128 bash examples/ppo_trainer/run_qwen3_8b_fsdp.shNPU(Ascend)上则使用:
bash examples/ascend_extras/ppo_trainer/run_qwen3_8b_fsdp.shNPU 版本示例会显式设置actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=1、ulysses_sequence_parallel_size=2、actor_rollout_ref.rollout.enable_chunked_prefill=True以及 vLLM 的cudagraph_mode="FULL_DECODE_ONLY"等 NPU 适配参数,并在trainer.total_training_steps=15的短步数下快速验证链路。
从配置到训练循环:verl PPO 的数据流
结合脚本与源码,一次 PPO 训练迭代的完整数据流为:
- Rollout 采样:以
data.train_batch_size为全局批次输入 prompt,vLLM/SGLang 推理引擎按ROLLOUT_N生成响应轨迹; - 参考策略与 log-prob 计算:actor(当前策略)与 ref(参考策略)分别对轨迹计算 log-probability(动态批大小由
log_prob_use_dynamic_bsz与log_prob_max_token_len_per_gpu控制); - 奖励与优势:环境奖励函数打分后,若启用
use_kl_in_reward则按kl_penalty形式扣除 KL 罚项(compute_rewards),再调用 GAE(或所选优势估计器)计算优势; - PPO 更新:actor 与 critic 分别按各自的
ppo_mini_batch_size切分 mini-batch,迭代ppo_epochs轮,计算裁剪后的策略损失与价值损失;若启用use_kl_loss,actor 损失中额外叠加kl_loss_coef加权的 KL loss; - 批次平衡:
trainer.balance_batch=True时按全局批大小在各 DP rank 间平衡数据,保证并行配置不影响收敛行为。
参考性能
以下为 README 中给出的 verl v0.2 时代、Qwen2.5-0.5B-Instruct 在 GSM8K 上的 PPO 参考成绩,仅作对照基线:
| 模型 | 方法 | 分数 |
|---|---|---|
| Qwen/Qwen2.5-0.5B-Instruct | 预训练模型(直接评估) | 36.4 |
| Qwen/Qwen2.5-0.5B-Instruct | PPO | 56.7 |
注意:该数据来自 README 中标注的参考实验,性能会随模型、数据、超参与 verl 版本变化,请以自己复现的结果为准。
小结与延伸
verl 的 PPO 训练器是理解其 RL 后训练体系的绝佳入口:它完整覆盖了 on-policy 训练所需的 rollout、参考策略、GAE、裁剪目标与 KL 控制全链路,且算法组件(优势估计器、KL 控制器、损失函数)均采用注册表模式实现,便于扩展。
进一步探索建议:
- 对比阅读 examples/grpo_trainer/ 与 examples/rloo_trainer/ 的脚本,观察无 critic 算法在配置上的差异;
- 深入阅读 verl/trainer/ppo/ray_trainer.py 与 verl/trainer/ppo/v1/trainer_base.py,理解
kl_penalty、use_kl_in_reward等配置在训练循环中的调度位置; - 如需切换推理后端,将
INFER_BACKEND=sglang传入 FSDP 脚本即可复用同一套训练配置。
【免费下载链接】verlverl/HybridFlow: A Flexible and Efficient RL Post-Training Framework项目地址: https://gitcode.com/GitHub_Trending/ve/verl
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考