作者:昇腾实战派
知识地图链接:强化学习知识地图

在大语言模型(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 则在超大规模模型训练中表现更优,支持灵活的张量并行与专家并行。
  • 训练启动:调用 RayPPOTrainerinit_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 管理 WorkerDictRayWorkerGroup,实现角色动态切换(如从 Rollout 切换至更新阶段),灵活适配训练不同阶段需求。

4. 训练后端实现:fsdp_workers.pymegatron_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 调度能力,实现从实验验证到大规模部署的无缝衔接。建议在实际使用中结合任务规模、资源条件与性能目标,合理选择算法与配置参数,充分发挥框架潜力。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐