VeRL 强化学习训练框架代码架构解析
作者:昇腾实战派
知识地图链接:强化学习知识地图
在大语言模型(LLM)持续演进的背景下,如何高效、稳定地实现强化学习(RLHF)训练成为关键挑战。为应对多模型、多算法、多硬件平台的复杂需求,我们基于 PyTorch 与 Ray 构建了灵活可扩展的强化学习训练框架 VeRL。该框架支持 PPO、GRPO、DAPO 等主流算法,具备多后端兼容、多模态支持、超大规模训练能力,并通过模块化设计实现训练流程的清晰解耦与高效调度。
本文将从代码主线流程、核心组件设计、训练参数配置到典型任务提交,系统性地解析 VeRL 的技术架构与使用实践,帮助开发者快速上手并深入理解其内部机制。
昇腾平台已支持VeRL框架:verl — 昇腾开源 文档
一、代码主线流程与核心架构
VeRL 采用分层解耦的设计思想,以 PPO 为基线算法,通过封装扩展支持 GRPO、DAPO 等进阶算法。整体代码结构清晰,便于问题定位与功能拓展。
1. 入口程序:main_ppo.py
作为 RL 训练的入口,main_ppo.py 负责初始化核心组件并启动训练流程:
- 奖励函数初始化:支持基于规则(function-based)或模型(model-based)的奖励机制,由 Reward Manager 统一管理,用户可自定义评分逻辑。
- 训练后端选择:支持 FSDP 与 Megatron-LM 两种训练引擎。FSDP 逻辑简洁、扩展性强,适合中等规模模型;Megatron-LM 则在超大规模模型训练中表现更优,支持灵活的张量并行与专家并行。
- 训练启动:调用
RayPPOTrainer的init_workers初始化各角色 WorkerGroup,随后执行fit方法进入完整训练循环。
2. 分布式训练核心:ray_trainer.py
该模块是 VeRL 的分布式控制中枢,负责管理 RL 训练中的多角色协作:
- 角色定义与资源分配:预设 Actor、Critic、Reward、Reference 等角色,通过
WorkerGroup创建实例,支持共置模式(create_colocated_worker_cls)以减少通信开销。 - 资源池管理:
ResourcePoolManager实现计算资源的动态分配与负载均衡,保障各 Worker 资源使用合理。 - 核心功能封装:
- Loss 计算函数(如 PPO 损失、KL 散度)
- Timer 与 Metrics 收集
- 断点续训与 Checkpoint 保存
- DP 负载均衡策略
- 训练主循环:
fit方法驱动完整训练流程,协调各 Worker 执行数据生成、奖励计算、模型更新等任务。
3. 控制流中枢:single_controller
作为 VeRL 的“大脑”,single_controller 实现了训练流程的统一调度与逻辑抽象:
- 集群与任务管理:通过
WorkerGroup管理多节点集群,支持数据并行等训练模式。 - 方法绑定机制:
bindworker_method自动处理用户自定义方法的分发、收集与执行,支持同步执行与异步通信。 - 装饰器系统:提供
@register等装饰器,使 Worker 方法可自动适配分布式执行模式,无需手动配置通信逻辑。 - Ray 后端适配:基于 Ray 管理
WorkerDict与RayWorkerGroup,实现角色动态切换(如从 Rollout 切换至更新阶段),灵活适配训练不同阶段需求。
4. 训练后端实现:fsdp_workers.py 与 megatron_workers.py
以 FSDP 为例,定义了 RL 训练中各角色的具体实现:
ActorRolloutRefWorker:- 支持 Actor、Rollout、Reference 三角色合一或分离部署。
- 基于 vLLM 封装的 Rollout 引擎,高效生成推理序列。
- 提供
generate_sequences(生成)、compute_log_prob(对数概率计算)、update_actor(策略更新)等核心方法。
CriticWorker:训练价值函数,用于优势估计。RewardModelWorker:实现模型级奖励打分,支持规则奖励与自定义函数奖励。
二、核心功能特性一览
| 功能模块 | 支持特性 |
|---|---|
| 多框架兼容 | 训练框架:FSDP、FSDP2、Megatron-LM;推理引擎:vLLM、SGLang、Hugging Face Transformers |
| 模型支持 | 兼容 Hugging Face Transformers、ModelScope Hub;支持 Qwen-3、Qwen-2.5、Llama3.1、Gemma2、DeepSeek-LLM 等主流模型;支持视觉-语言模型(VLMs)如 Qwen2.5-vl、Kimi-VL |
| 强化学习算法 | PPO、GRPO、DAPO、GSPO、ReMax、REINFORCE++、RLOO、PRIME、KL_Cov、Clip_Cov |
| 奖励机制 | 模型级奖励(model-based);函数级奖励(function-based,含可验证奖励);适用于数学、编程等复杂任务 |
| 多模态与交互 | 原生支持多轮对话与工具调用,适配多模态 RL 任务 |
| 性能优化 | Flash Attention 2、Liger-kernel;序列打包(sequence packing);序列并行(DeepSpeed Ulysses);LoRA 轻量化微调 |
| 规模化部署 | 支持最大 671B 参数模型训练;适配数百 GPU 集群;支持专家并行(expert parallelism) |
| 实验追踪 | 兼容 wandb、swanlab、mlflow、tensorboard 等主流工具 |
| 权重转换 | 支持 FSDP、Megatron 权重转为 Hugging Face 格式 |
三、主流算法对比与选型建议
VeRL 支持 PPO、GRPO、DAPO 等多种算法,其核心差异体现在模型结构与训练效率上:
- PPO:保留完整四模型架构(Actor、Critic、Reward、Reference),训练稳定,但资源开销大,适用于小规模或对稳定性要求极高的场景。
- GRPO:在 PPO 基础上移除 Critic 模型,通过组间优势估计实现价值函数替代,显著降低显存占用,提升训练效率,是中等规模模型的优选方案。
- DAPO:进一步移除 Reference 模型,采用动态采样策略增强探索能力,理论性能更优(如 AIME24 上优于 GRPO),但推理时间显著增加,适合追求极致效果且可接受较长训练周期的场景。
注:奖励机制方面,规则奖励因无需额外训练成本,是多数场景的首选;模型奖励适用于需高精度反馈的复杂任务。
四、训练日志指标详解
每步训练完成后,系统输出多维度日志,可划分为五类,便于性能监控与问题诊断:
1. 序列长度指标(数据分布与均衡性)
| 指标 | 含义 |
|---|---|
global_seqlen/min/max/minmax_diff |
全局序列长度的最小值、最大值及差值,反映长度波动范围 |
global_seqlen/balanced_min/balanced_max |
截断/补齐后的有效长度范围 |
response_length/mean/max/min/clip_ratio |
响应长度统计,clip_ratio 表示被截断比例 |
prompt_length/mean/max/min/clip_ratio |
输入 prompt 长度统计 |
2. Actor 模型指标(策略学习效果)
| 指标 | 含义 |
|---|---|
actor/entropy |
策略熵,反映探索程度(高=随机,低=确定) |
actor/pg_loss |
策略梯度损失,应逐步收敛 |
actor/pg_clipfrac |
PPO 中策略更新被 clip 的比例,过高可能影响学习 |
actor/ppo_kl |
新旧策略 KL 散度,过大可能导致训练不稳定 |
actor/grad_norm |
梯度 L2 范数,过大可能引发梯度爆炸 |
actor/lr |
当前学习率 |
3. Critic 模型指标(价值估计效果)
| 指标 | 含义 |
|---|---|
critic/score/mean/max/min |
价值函数对状态的估计值 |
critic/rewards/mean/max/min |
实际奖励值,用于对比价值估计准确性 |
critic/advantages/mean/max/min |
优势函数值,应围绕 0 波动 |
critic/returns/mean/max/min |
折扣累积回报,Critic 应拟合此目标 |
4. 性能与资源指标(硬件利用)
| 指标 | 含义 |
|---|---|
perf/mfu/actor |
模型计算利用率(MFU),越高越好 |
perf/max_memory_allocated_gb |
最大 GPU 内存占用,关注是否接近上限 |
perf/max_memory_reserved_gb |
预留内存,反映内存调度效率 |
perf/cpu_memory_used_gb |
CPU 内存使用,过大可能引发 IO 瓶颈 |
perf/total_num_tokens |
本步处理的总 token 数 |
perf/throughput |
吞吐量(token/秒),反映训练效率 |
train/num_gen_batches |
生成数据的批次数,影响数据多样性 |
5. 时间与效率指标(流程耗时)
| 指标 | 含义 |
|---|---|
timing_s/generate_sequences |
生成序列耗时(主要瓶颈) |
timing_s/gen |
总生成耗时 |
timing_s/update_actor |
策略更新耗时 |
timing_s/step |
单步总耗时 |
timing_s/reward/old_log_prob/adv |
奖励、旧 logprob、优势计算耗时 |
timing_per_token_ms/gen/update_actor |
每 token 的生成与更新耗时 |
五、Ray 分布式基础操作
VeRL 依托 Ray 实现多角色分布式调度,其核心流程为:集群初始化 → 资源调度 → 任务分发 → 并行执行 → 结果聚合。
1. 集群拉起
- Head 节点:执行
ray start --head,输出 worker 加入命令与RAY_ADDRESS。 - Worker 节点:执行 head 提供的
ray start --address=...命令加入集群。 - 状态检查:使用
ray status查看集群资源与节点状态。
2. 任务提交
推荐使用 ray job submit 提交任务,命令结构如下:
ray job submit \
--no-wait \
--runtime-env="${RUNTIME_ENV}" \
--working-dir "${WORKING_DIR}" \
--address "${RAY_ADDRESS}" \
-- python3 -m recipe.dapo.main_dapo \
data.train_files="${TRAIN_FILE}" \
...
关键参数说明:
| 参数 | 说明 |
|---|---|
--address |
Ray 集群地址(如 http://localhost:8265) |
--working-dir |
本地代码目录,自动同步至所有 worker |
--runtime-env |
运行时环境配置(需在 runtime_env.yaml 中定义) |
--no-wait |
异步提交,立即返回 |
注意:
runtime_env中的环境变量需通过 YAML 文件配置,不可在脚本中export。
六、快速上手示例:DAPO 训练 Qwen2.5-32B
以下为一个完整的 DAPO 训练任务脚本,适用于 32B 模型在 NPU 集群上的训练:
#!/usr/bin/env bash
set -xeuo pipefail
project_name='DAPO-Qwen2.5-32B'
exp_name='Qwen2.5-32B-npu-32rank-gbs128'
# 参数配置
adv_estimator=grpo
use_kl_in_reward=False
kl_coef=0.0
use_kl_loss=False
kl_loss_coef=0.0
clip_ratio_low=0.2
clip_ratio_high=0.28
max_prompt_length=$((1024 * 2))
max_response_length=$((1024 * 20))
enable_overlong_buffer=True
overlong_buffer_len=$((1024 * 4))
overlong_penalty_factor=1.0
loss_agg_mode="token-mean"
enable_filter_groups=True
filter_groups_metric=acc
max_num_gen_batches=10
NNODES=2
train_prompt_bsz=128
gen_prompt_bsz=$((train_prompt_bsz * 3))
n_resp_per_prompt=16
train_prompt_mini_bsz=32
# Ray 配置
PWD=./
RAY_ADDRESS=${RAY_ADDRESS:-"http://localhost:8265"}
WORKING_DIR=${WORKING_DIR:-"${PWD}"}
RUNTIME_ENV=${RUNTIME_ENV:-"${WORKING_DIR}/verl/trainer/runtime_env.yaml"}
# 路径配置
RAY_DATA_HOME=${RAY_DATA_HOME:-"${HOME}/verl"}
MODEL_PATH=${MODEL_PATH:-"${RAY_DATA_HOME}/models/Qwen2.5-32B"}
CKPTS_DIR=${CKPTS_DIR:-"${RAY_DATA_HOME}/ckpts/${project_name}/${exp_name}"}
TRAIN_FILE=${TRAIN_FILE:-"${RAY_DATA_HOME}/data/dapo-math-17k.parquet"}
TEST_FILE=${TEST_FILE:-"${RAY_DATA_HOME}/data/aime-2024.parquet"}
# 模型与训练参数
temperature=1.0
top_p=1.0
top_k=-1
val_top_p=0.7
# 性能优化
sp_size=8
use_dynamic_bsz=True
actor_ppo_max_token_len=$(((max_prompt_length + max_response_length) / sp_size))
infer_ppo_max_token_len=$(((max_prompt_length + max_response_length) / sp_size))
offload=True
gen_tp=4
enable_chunked_prefill=True
# 提交任务
ray job submit --no-wait --runtime-env="${RUNTIME_ENV}" \
--working-dir "${WORKING_DIR}" \
--address "${RAY_ADDRESS}" \
-- python3 -m recipe.dapo.main_dapo \
data.train_files="${TRAIN_FILE}" \
data.val_files="${TEST_FILE}" \
data.prompt_key=prompt \
data.truncation='left' \
data.max_prompt_length=${max_prompt_length} \
data.max_response_length=${max_response_length} \
data.gen_batch_size=${gen_prompt_bsz} \
data.train_batch_size=${train_prompt_bsz} \
actor_rollout_ref.rollout.n=${n_resp_per_prompt} \
algorithm.adv_estimator=${adv_estimator} \
algorithm.use_kl_in_reward=${use_kl_in_reward} \
algorithm.kl_ctrl.kl_coef=${kl_coef} \
actor_rollout_ref.actor.use_kl_loss=${use_kl_loss} \
actor_rollout_ref.actor.kl_loss_coef=${kl_loss_coef} \
actor_rollout_ref.actor.clip_ratio_low=${clip_ratio_low} \
actor_rollout_ref.actor.clip_ratio_high=${clip_ratio_high} \
actor_rollout_ref.actor.clip_ratio_c=10.0 \
algorithm.filter_groups.enable=${enable_filter_groups} \
algorithm.filter_groups.max_num_gen_batches=${max_num_gen_batches} \
algorithm.filter_groups.metric=${filter_groups_metric} \
actor_rollout_ref.actor.use_torch_compile=False \
actor_rollout_ref.ref.use_torch_compile=False \
actor_rollout_ref.model.use_remove_padding=True \
actor_rollout_ref.actor.use_dynamic_bsz=${use_dynamic_bsz} \
actor_rollout_ref.ref.log_prob_use_dynamic_bsz=${use_dynamic_bsz} \
actor_rollout_ref.rollout.log_prob_use_dynamic_bsz=${use_dynamic_bsz} \
actor_rollout_ref.actor.ppo_max_token_len_per_gpu=${actor_ppo_max_token_len} \
actor_rollout_ref.ref.log_prob_max_token_len_per_gpu=${infer_ppo_max_token_len} \
actor_rollout_ref.rollout.log_prob_max_token_len_per_gpu=${infer_ppo_max_token_len} \
actor_rollout_ref.rollout.name=vllm \
actor_rollout_ref.model.path="${MODEL_PATH}" \
+actor_rollout_ref.model.override_config.attention_dropout=0. \
+actor_rollout_ref.model.override_config.embd_pdrop=0. \
+actor_rollout_ref.model.override_config.resid_pdrop=0. \
actor_rollout_ref.model.enable_gradient_checkpointing=True \
actor_rollout_ref.actor.optim.lr=1e-6 \
actor_rollout_ref.actor.optim.lr_warmup_steps=10 \
actor_rollout_ref.actor.optim.weight_decay=0.1 \
actor_rollout_ref.actor.ppo_mini_batch_size=${train_prompt_mini_bsz} \
actor_rollout_ref.actor.fsdp_config.param_offload=${offload} \
actor_rollout_ref.actor.fsdp_config.optimizer_offload=${offload} \
actor_rollout_ref.actor.entropy_coeff=0 \
actor_rollout_ref.actor.grad_clip=1.0 \
actor_rollout_ref.actor.loss_agg_mode=${loss_agg_mode} \
actor_rollout_ref.actor.ulysses_sequence_parallel_size=${sp_size} \
actor_rollout_ref.rollout.gpu_memory_utilization=0.90 \
actor_rollout_ref.rollout.tensor_model_parallel_size=${gen_tp} \
actor_rollout_ref.rollout.enable_chunked_prefill=${enable_chunked_prefill} \
actor_rollout_ref.rollout.max_num_batched_tokens=$((max_prompt_length + max_response_length)) \
actor_rollout_ref.rollout.temperature=${temperature} \
actor_rollout_ref.rollout.top_p=${top_p} \
actor_rollout_ref.rollout.top_k="${top_k}" \
actor_rollout_ref.rollout.val_kwargs.temperature=${temperature} \
actor_rollout_ref.rollout.val_kwargs.top_p=${val_top_p} \
actor_rollout_ref.rollout.val_kwargs.top_k=${top_k} \
actor_rollout_ref.rollout.val_kwargs.do_sample=True \
actor_rollout_ref.rollout.val_kwargs.n=1 \
actor_rollout_ref.ref.fsdp_config.param_offload=${offload} \
actor_rollout_ref.ref.ulysses_sequence_parallel_size=${sp_size} \
actor_rollout_ref.actor.fsdp_config.fsdp_size=-1 \
reward_model.reward_manager=dapo \
reward_model.overlong_buffer.enable=${enable_overlong_buffer} \
reward_model.overlong_buffer.len=${overlong_buffer_len} \
reward_model.overlong_buffer.penalty_factor=${overlong_penalty_factor} \
trainer.logger="['console','wandb']" \
trainer.project_name="${project_name}" \
trainer.experiment_name="${exp_name}" \
trainer.n_gpus_per_node=16 \
trainer.nnodes="${NNODES}" \
trainer.val_before_train=True \
trainer.test_freq=5 \
trainer.save_freq=20 \
trainer.total_epochs=1 \
trainer.default_local_dir="${CKPTS_DIR}" \
trainer.device=npu \
trainer.resume_mode=auto \
actor_rollout_ref.actor.fsdp_config.forward_prefetch=True \
actor_rollout_ref.ref.fsdp_config.forward_prefetch=True \
七、总结
VeRL 框架通过清晰的模块化设计、灵活的算法扩展机制与强大的分布式调度能力,为大模型强化学习训练提供了高效、稳定、可扩展的解决方案。开发者可通过统一接口快速切换算法、后端与硬件平台,结合日志指标与 Ray 调度能力,实现从实验验证到大规模部署的无缝衔接。建议在实际使用中结合任务规模、资源条件与性能目标,合理选择算法与配置参数,充分发挥框架潜力。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)