VeRL× 昇腾NPU:GRPO 强化训练全链路实战
是字节跳动 Seed 团队发起、社区共同维护的 LLM 强化学习(RL/RLHF)训练框架。是论文的开源实现版本。框架特点:算法侧:支持 GRPO、PPO 等 RL 数据流/训练循环的搭建。工程侧:通过模块化 API 对接既有 LLM infra(如 FSDP、Megatron-LM、vLLM、SGLang 等)。多机多卡资源编排:官方的多机示例采用 Ray 集群方式启动 head/worker。
0 目录
1 VeRL简介
- VeRL(Volcano Engine Reinforcement Learning for LLMs) 是字节跳动 Seed 团队发起、社区共同维护的 LLM 强化学习(RL/RLHF)训练框架。 是论文 HybridFlow: A Flexible and Efficient RLHF Framework 的开源实现版本。
-
框架特点:
- 算法侧:支持 GRPO、PPO 等 RL 数据流/训练循环的搭建。
- 工程侧:通过模块化 API 对接既有 LLM infra(如 FSDP、Megatron-LM、vLLM、SGLang 等)。
- 多机多卡资源编排:官方的多机示例采用 Ray 集群方式启动 head/worker。
-
相关资料:
- 仓库地址: volcengine/verl
- 参数解释: Config Explanation
- 性能调优: Performance Tuning Guide
2 环境准备
2.1 镜像下载
- 我们使用的机器是 Ascend 910B3。为了避免 CANN / torch_npu / vLLM-ascend 等版本不匹配,建议优先使用官方每日构建镜像或基于官方 Dockerfile 构建镜像。官方也给出了镜像命名规则与公开镜像地址说明。
- 本文使用的镜像 tag是:
verl-8.3.rc1-910b-ubuntu22.04-py3.11-latest(官方命名规则:verl-{CANN版本}-{NPU设备类型}-{OS}-{Python}-latest)。 镜像地址
-
该镜像对应的相关版本如下:
software version Python == 3.11 CANN == 8.3.RC1 torch == 2.7.1 torch_npu == 2.7.1 torchvision == 0.22.1 vllm == v0.11.0 vllm-ascend == v0.11.0rc1 verl == 0.7.0.dev0 -
点击右侧
下载按钮,然后选择Docker Pull(by tag)–Copy Command。 -
服务器上配置下代理,参考https://3ms.huawei.com/km/blogs/details/21607167
-
进到服务器,输入上面拷贝的命令,拉取镜像;拉取完成后用
docker images检查(如图3所示)。
- 挂载目录并启动容器(仅供参考,建议根据你机器驱动路径调整):
docker run -it -d --net=host --shm-size=10g \ --privileged \ --name verl_ylx_1125_simple \ --device=/dev/davinci0 \ --device=/dev/davinci1 \ --device=/dev/davinci2 \ --device=/dev/davinci3 \ --device=/dev/davinci4 \ --device=/dev/davinci5 \ --device=/dev/davinci6 \ --device=/dev/davinci7 \ --device=/dev/davinci_manager \ --device=/dev/devmm_svm \ --device=/dev/hisi_hdc \ -v /usr/local/dcmi:/usr/local/dcmi \ -v /usr/local/Ascend/driver:/usr/local/Ascend/driver:ro \ -v /usr/local/Ascend/firmware:/usr/local/Ascend/firmware \ -v /usr/local/sbin:/usr/local/sbin:ro \ -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \ -v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \ -v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \ -v /etc/ascend_install.info:/etc/ascend_install.info \ -v /data2/models/:/data2/models/ \ 8cc1aab7dceb bash - 进入容器后,verl 代码(含 vLLM / vLLM-ascend 等)通常已经在预置路径下(如图4所示)。
docker exec -it --user <USER> <CONTAINER_NAME> bash eg: docker exec -it --user root verl_ylx_1126_simple bash
2.2 其他安装方式
- 使用官方 Dockerfile 构建镜像。参考:https://github.com/volcengine/verl/blob/2319bab040eacfe2d191e417b6ea4de6d6978ef2/docs/ascend_tutorial/dockerfile_build_guidance.rst
- 手动安装(conda / 源码 / 自建镜像)可行,但版本组合更容易踩坑,建议只有在你明确要改动某些底层依赖时再走这条路。参考: https://github.com/volcengine/verl/blob/2319bab040eacfe2d191e417b6ea4de6d6978ef2/docs/ascend_tutorial/ascend_quick_start.rst
https://github.com/volcengine/verl/blob/2319bab040eacfe2d191e417b6ea4de6d6978ef2/docker/ascend/Dockerfile.ascend_8.3.rc1_a2#L21
2.3 Tips(踩坑经验)
-
优先使用官方镜像 / Dockerfile:NPU 训练栈对版本组合敏感;先保证能复现,再谈“洁癖式安装”。
-
挂载目录尽量简单:如果你把宿主机某个包含
vllm/源码目录挂进容器,Python import 可能优先命中“挂载目录里的 vllm”,导致版本错配(典型表现就是“明明装了,但 import 报错/找不到某些符号”)。详细报错见:报错详情:5.2 -
执行训练脚本时建议先
cd /verl再跑,避免相对路径或 PYTHONPATH 解析问题(如图5所示是 “未在约定目录执行导致依赖解析异常” 的示例)。 -
多机训练:每台机器的软件环境、代码版本、模型路径、数据路径必须一致;否则排障成本会指数级上升。
3 双机训练步骤(以 GSM8K 为例)
3.1 数据准备
- verl 的 RL 数据准备文档明确要求:训练数据应为 parquet 格式(并约定了字段结构与示例脚本),详细见:https://github.com/volcengine/verl/blob/d66120d7705989f9fda71b1fb3b45cba250e68c4/docs/preparation/prepare_data.rst
- 如果不是公开数据(例如领域自建数据),可以先落成
jsonl再转换为parquet,转化脚本如下:def json2parquent(input_path, out_path): df = pd.read_json(input_path, lines=True) df.to_parquet(out_path, engine="pyarrow", index=False)
- 以 gsm8k 为例,每行是一个 dict:
{ "data_source": "openai/gsm8k", "prompt": [ { "role": "user", "content": "Natalia sold clips ... Let's think step by step and output the final answer after \"####\"" } ], "ability": "math", "reward_model": { "style": "rule", "ground_truth": ["72"] } }
3.2 奖励函数撰写
- 自定义奖励函数支持两种常见接入方式,详细可以见官网介绍:https://github.com/volcengine/verl/blob/d66120d7705989f9fda71b1fb3b45cba250e68c4/docs/preparation/reward_function.rst
- 通过
custom_reward_function.path+custom_reward_function.name指向你自己的函数文件与函数名; - 走 verl 内置 registry(按
data_source分发到对应 reward 计算逻辑)——适用于需要维护“多数据源、多 reward 策略”的情况。
- 通过
- 另外,奖励函数的入参应包含
data_source / solution_str / ground_truth / extra_info(可选) - 可以直接看代码
verl/workers/reward_manager/naive.py的调用链,对齐“何时、以什么粒度”计算 reward。verl/workers/reward_manager/naive.py
3.3 环境变量配置
- 多机时建议显式设置网卡与若干开关(否则可能出现通信初始化/推理侧异常)详细报错信息见:报错详情:5.1
export HCCL_SOCKET_IFNAME=eth6 export GLOO_SOCKET_IFNAME=eth6 export VLLM_ASCEND_ENABLE_NZ=0
3.4 启动 Ray
- Head 节点:
ray start --head --port=6379
- Worker 节点(连接 head):
ray start --address="<head_ip>:6379"
3.5 参数设置(脚本示例)
- verl 官方提供了不同 base 模型、不同设备后端的脚本示例(末尾以npu结尾的代表是基于npu训练的),详细可以见:https://github.com/volcengine/verl/tree/312263169b0288d7225d011979d0d04457aec703/examples/grpo_trainer
- 我们最早就是基于
gsm8k跑通的,详细参数如下:#!/bin/bash set -x python -m verl.trainer.main_ppo \ algorithm.adv_estimator=grpo \ +data.source=gsm8k \ data.train_files=/data2/models/gsm8k/train.parquet \ data.val_files=/data2/models/gsm8k/test.parquet \ data.train_batch_size=128 \ data.max_prompt_length=512 \ data.max_response_length=1024 \ data.filter_overlong_prompts=True \ data.truncation='error' \ actor_rollout_ref.model.path=/data2/models/Qwen2-0.5B-Instruct \ actor_rollout_ref.actor.optim.lr=1e-6 \ actor_rollout_ref.actor.use_torch_compile=False \ actor_rollout_ref.model.use_remove_padding=True \ actor_rollout_ref.actor.ppo_mini_batch_size=32 \ actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=4 \ actor_rollout_ref.actor.use_kl_loss=True \ actor_rollout_ref.actor.kl_loss_coef=0.001 \ actor_rollout_ref.actor.kl_loss_type=low_var_kl \ actor_rollout_ref.actor.entropy_coeff=0 \ actor_rollout_ref.model.enable_gradient_checkpointing=True \ actor_rollout_ref.actor.fsdp_config.param_offload=False \ actor_rollout_ref.actor.fsdp_config.optimizer_offload=False \ actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=4 \ actor_rollout_ref.rollout.tensor_model_parallel_size=2 \ actor_rollout_ref.rollout.name=vllm \ actor_rollout_ref.rollout.gpu_memory_utilization=0.6 \ actor_rollout_ref.rollout.n=4 \ actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=4 \ actor_rollout_ref.ref.fsdp_config.param_offload=True \ algorithm.use_kl_in_reward=False \ trainer.critic_warmup=0 \ trainer.logger='["console"]' \ trainer.project_name='verl_grpo_example_gsm8k' \ trainer.experiment_name='qwen0.5b_function_tmp_1124' \ trainer.n_gpus_per_node=8 \ trainer.nnodes=2 \ trainer.save_freq=20 \ trainer.test_freq=5 \ trainer.total_epochs=30 \ trainer.device=npu $@ - 新建sh脚本文件,然后把上面内容放到sh脚本中,直接
bash执行脚本,便开启RL训练了,eg:bash recipe/r1_ascend/scripts_1201/rl_verl_req_round2_1204.sh 2>&1 | tee -a ylx_log/log_file_1205.txt
4 部分参数简介
- 详细的参数介绍可以见:https://verl.readthedocs.io/en/latest/examples/config.html, 这边已经讲地非常清楚,或者也可以看代码
verl/trainer/config/ppo_trainer.yaml, 下面对一些常用或者重要的参数做个简介:
4.1 数据与采样涉及参数
-
数据及采样相关涉及参数如下:
参数 含义 实战调参要点 data.train_files/data.val_files训练/验证集(parquet,可 list) 训练集会被读入内存,官方提示别太大(<100GB)。 data.train_max_samples/data.val_max_samples最大样本数;若设成 -1代表用全量做小规模 sanity / ablation 时很有用。 data.prompt_keyprompt 字段名 多源数据时注意字段一致。) data.max_prompt_lengthprompt 最大长度(会左 padding) 超长会报错(默认 truncation: error)。data.max_response_lengthrollout 生成最大长度 直接影响:rollout 吞吐、KV cache、token 级动态 batch。 data.train_batch_size每个 RL iteration 采样的 batch 大小 这是最“上游”的 batch:决定你每轮收集多少条轨迹(trajectories)。 data.truncation超长截断策略: error/left/right/middlemiddle会保留头尾、丢中间(对长指令有时更合理)。
4.2 几个 batch size:(采样→生成→logprob→更新)
4.2.1 理清训练链路
在 verl 的 GRPO 实战里,虽然入口脚本是 main_ppo,但核心循环可以理解为:
-
采样 prompts(全局):从 parquet 中抽一批 prompt
-
rollout 生成 responses(推理引擎):每个 prompt 采样
n条 response(形成 trajectories) -
计算 reward(规则/模型打分):得到每条 trajectory 的标量 reward
-
算 logprob(forward-only):
- 用 actor / rollout 侧重算
old_log_prob(训练需要稳定的 token logprob) - 用 ref policy 算
ref_log_prob(用于 KL)
- 用 actor / rollout 侧重算
-
更新 actor(有反向):把 trajectories 切成 mini-batch,再在每卡用 micro-batch 做 forward/backward(必要时梯度累积)
4.2.2 batch size 家族表
-
batch_size其实就是每次取数据的数量,参数中涉及的batch_size有很多,整个batch size涉及的家族表如下:
层级 参数 作用域 用在什么阶段 通俗理解 采样层(最上游) data.train_batch_size全局 每个iteration 从数据集抽多少个 prompt “这轮要收集多少条轨迹:用于生成一组采样轨迹结果的全局批次大小。 trajectories轨迹=data.train_batch_size * actor_rollout.ref.rollout.n” PPO 更新层(mini) actor.ppo_mini_batch_size全局 把 trajectories 切多大一份做一次 optimizer update “一次 update 吃多少条样本(全局),train_batch_size // ppo_mini_batch_size => ppo training 多少次” PPO 前/反向层(micro) actor.ppo_micro_batch_size_per_gpu每 GPU/NPU 单卡单次 forward/backward 处理多少样本(用于梯度累积) 继续拆分ppo_mini_batch_size, 也是真正的 training batch size: “显存不够就减它;速度不够就尽量加它” Ref logprob 计算 ref.log_prob_micro_batch_size_per_gpu每 GPU/NPU 计算 ref_log_prob(用于 KL 等)用 Reference policy对同一批 prompt+response 做一次 forward,算出 ref_log_prob Rollout logprob 重计算 rollout.log_prob_micro_batch_size_per_gpu每 GPU/NPU 对 rollout 生成出来的样本 “重算 log_prob rollout 引擎负责“生成”;但训练更新时需要稳定拿到每个 token 的 logprob(例如 PPO ratio、KL 等),因此会在训练侧再跑一遍 forward 来计算 logprob。”
4.2.3 最重要的几个关系式
设:
B = data.train_batch_size(每次迭代采样 prompts 数,全局)n = actor_rollout_ref.rollout.n(每个 prompt 生成的 responses 数)T ≈ B * n(本轮可用于更新的轨迹样本数)M = actor.ppo_mini_batch_size(一次更新吃的样本数,全局)m = actor.ppo_micro_batch_size_per_gpu(micro-batch,每卡)W = trainer.nnodes * trainer.n_gpus_per_node(world size)
那么:
(1) 本轮大概会更新多少次(假设 ppo_epochs=1)
updates_per_iter ≈ ceil(T / M)
(2) 每次更新,每卡分到多少样本(本地 mini)
local_mini = M / W
(3) 每次更新的梯度累积步数(理想整除时)
accum_steps ≈ local_mini / m
记住:
B和n决定“这轮样本量”(大了更稳但更慢)M决定“每次更新吃多少”(大了更稳但更吃显存/更慢)m决定“单卡一次能吃多少”(纯粹显存/吞吐开关)
以5.2 节的配置为例:
B=256,n=16⇒T≈4096条轨迹样本/iteration- 若
M=32⇒updates_per_iter≈4096/32=128(每轮采样会触发很多次小更新) - 若
m=1(每卡 micro=1),world size=16 ⇒(M/W)=2,accum 约 2 步/次 update(常见于显存紧张但想保住 M 的情况)
4.2.4 tips(注意事项)
- 所有包含 micro_batch_size 的配置均用于配置每次前向或后向传递的最大样本或tokens数,以避免内存溢出 (OOM), 对于算法本身或者收敛情况没有影响。
- 尽管许多配置以 ppo_ 前缀开头,但它们可以在 verl 中的不同 RL 算法中使用,因为 GRPO 训练循环与 PPO 的训练循环类似(没有critic)。
- 其中trian batch_size和ppo mini-batch size是全局级别的
- mini_batch_size必须要整除micro_batch_seize_per_gpu
- train_batch_size >= ppo_mini_batch_size
4.3 custom_reward_function.path
- 用于指定奖励函数文件路径,需配合
custom_reward_function.name(函数名)使用。官方文档也明确给了这种 Customized 方式
eg:custom_reward_function.path=./recipe/r1_ascend/scripts_1201/adc_reward_func_round3_reqAgent_1201.py \
4.4 trainer.logger(实验追踪)
- 用于配置训练日志与实验追踪,常见选择:console / mlflow / wandb / tensorboard 等。
- 本实验使用 mlflow,详细介绍见https://mlflow.org/docs/latest/ml/getting-started/quickstart/。
- 使用方法:两个机子上的容器内先安装mlflow(
pip install mlflow), 然后输入指令如下(其中sqlite:////tmp/mlruns.db是指 MLflow Tracking Server 的后端存储(backend store)地址):export MLFLOW_TRACKING_URI=sqlite:////tmp/mlruns.db mlflow ui --backend-store-uri sqlite:////tmp/mlruns.db --host 0.0.0.0 --allowed-hosts --cors-allowed-origins --port 5000 - 接着打开浏览器,输入
http://localhost:5000/,点开具体的实验,就能看到对应的曲线了
4.5 actor_rollout_ref.actor.entropy_coeff (熵系数)
actor_rollout_ref.actor.entropy_coeff 用于在 GRPO(以及沿用 PPO 训练循环的实现)中控制 熵正则项(entropy regularization) 的权重,默认值通常为 0。
**熵(entropy)**可以衡量策略分布的不确定性:熵越大,动作分布越“平坦”,探索性越强;熵越小,策略越“确定”,更偏向利用已有最优动作。
-
actor_rollout_ref.actor.entropy_coeff用于在 GRPO(以及沿用 PPO 训练循环的实现)中控制 熵正则项(entropy regularization) 的权重,默认值通常为 0。 -
**熵(entropy)**可以衡量策略分布的不确定性:熵越大,动作分布越“平坦”,探索性越强;熵越小,策略越“确定”,更偏向利用已有最优动作。
-
在策略梯度方法(如 PPO)中,常在目标函数中加入 熵项 来鼓励探索、避免策略过早收敛到次优解。直观理解:
entropy_coeff就是该熵项在policy loss中的系数(权重)。 -
实践建议:
-
若训练中观察到策略很快变得“过于确定”(多样性下降、容易陷入局部最优/得分停滞),可将
entropy_coeff从 0 逐步小幅上调(例如1e-4 ~ 1e-3级别),观察探索是否改善。 -
若训练不稳定、输出长期发散或随机性过强(表现为 reward 波动大、策略难以收敛),应降低该值或直接置 0。
5 其他
5.1 step总步数怎么计算
设:
- 训练集 prompt 总数
N=3000 B = data.train_batch_size = 256(每次 iteration 采样 prompts 数,全局)n = actor_rollout_ref.rollout.n = 16(每个 prompt 采样 responses 数)M = actor.ppo_mini_batch_size = 32(每次 update 的 mini batch,全局)- 假设
ppo_epochs=1(否则还要再乘 ppo_epochs)
那么:
- 每个 epoch 的 iteration 数:
iters_per_epoch = ceil(N / B) = ceil(3000 / 256) = 12 - 每次 iteration 产生的轨迹样本数(粗略):
T = B * n = 256 * 16 = 4096 - 每次 iteration 的更新次数:
updates_per_iter = ceil(T / M) = ceil(4096 / 32) = 128 - 若
trainer.total_epochs=10:total_updates ≈ iters_per_epoch * updates_per_iter * total_epochs = 12 * 128 * 10 = 15360
5.2 哪些参数影响显存
A. 序列长度相关(几乎必影响显存)
data.max_prompt_length、data.max_response_length:长度越大,attention/激活/KV cache 压力越大。
B. 训练更新相关(actor 训练侧显存峰值)
actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu:每卡一次 forward/backward 处理的样本数;越大越吃显存,但吞吐更高。actor_rollout_ref.model.enable_gradient_checkpointing=True:用算力换显存(通常显存下降、速度下降)。actor_rollout_ref.actor.fsdp_config.param_offload / optimizer_offload:把参数/优化器状态 offload 到 CPU,显存下降但通信与耗时上升。[1])
C. logprob 重算相关(最容易 OOM 的“第二战场”)
actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu:ref policy 计算 logprob 的 micro batch(per-GPU),越大越吃显存。actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu:rollout 侧重算 logprob 的 micro batch(per-GPU),越大越吃显存。
D. vLLM 推理侧(rollout 引擎显存常驻)
actor_rollout_ref.rollout.gpu_memory_utilization:分给 vLLM KV cache 的比例更大,吞吐可能更好,但更容易把卡“吃满”。actor_rollout_ref.rollout.tensor_model_parallel_size:TP 会改变单卡权重/KV 分摊方式,进而影响显存与吞吐。
6 参考网址
- verl单机多卡与多机多卡使用经验总结: https://zhuanlan.zhihu.com/p/1891186611787653556
- verl 官网:https://github.com/volcengine/verl/blob/f623c146ca3042f0f0015db3b58bfcb105704486/recipe/deepeyes/run_deepeyes_grpo.sh#L72
- 【强化学习】verl框架下使用GRPO微调Qwen2.5-vl-7b:https://3ms.huawei.com/km/blogs/details/21613201
- 基于verl进行grpo相关参数简介:https://github.com/volcengine/verl/blob/main/examples/grpo_trainer/README.md
- 框架官方解读:https://mp.weixin.qq.com/s/JYQQs2vqnhRz82rtDI-1OQ
- 陈丹琦团队反常识突破!RLMT只奖励结果,不看过程,效果超强
更多推荐


所有评论(0)