0 目录

1 VeRL简介

  • 框架特点:

    • 算法侧:支持 GRPO、PPO 等 RL 数据流/训练循环的搭建。
    • 工程侧:通过模块化 API 对接既有 LLM infra(如 FSDP、Megatron-LM、vLLM、SGLang 等)。
    • 多机多卡资源编排:官方的多机示例采用 Ray 集群方式启动 head/worker。
  • 相关资料:


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 其他安装方式

2.3 Tips(踩坑经验)

  • 优先使用官方镜像 / Dockerfile:NPU 训练栈对版本组合敏感;先保证能复现,再谈“洁癖式安装”。

  • 挂载目录尽量简单:如果你把宿主机某个包含 vllm/ 源码目录挂进容器,Python import 可能优先命中“挂载目录里的 vllm”,导致版本错配(典型表现就是“明明装了,但 import 报错/找不到某些符号”)。详细报错见:报错详情:5.2

  • 执行训练脚本时建议cd /verl 再跑,避免相对路径或 PYTHONPATH 解析问题(如图5所示是 “未在约定目录执行导致依赖解析异常” 的示例)。

  • 多机训练:每台机器的软件环境、代码版本、模型路径、数据路径必须一致;否则排障成本会指数级上升。


3 双机训练步骤(以 GSM8K 为例)

3.1 数据准备

  • 以 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 奖励函数撰写

  • 另外,奖励函数的入参应包含 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 部分参数简介

4.1 数据与采样涉及参数

  • 数据及采样相关涉及参数如下:

    参数 含义 实战调参要点
    data.train_files / data.val_files 训练/验证集(parquet,可 list) 训练集会被读入内存,官方提示别太大(<100GB)。
    data.train_max_samples / data.val_max_samples 最大样本数;若设成-1 代表用全量 做小规模 sanity / ablation 时很有用。
    data.prompt_key prompt 字段名 多源数据时注意字段一致。)
    data.max_prompt_length prompt 最大长度(会左 padding) 超长会报错(默认 truncation: error)。
    data.max_response_length rollout 生成最大长度 直接影响:rollout 吞吐、KV cache、token 级动态 batch。
    data.train_batch_size 每个 RL iteration 采样的 batch 大小 这是最“上游”的 batch:决定你每轮收集多少条轨迹(trajectories)。
    data.truncation 超长截断策略:error/left/right/middle middle 会保留头尾、丢中间(对长指令有时更合理)。

4.2 几个 batch size:(采样→生成→logprob→更新)

4.2.1 理清训练链路

在 verl 的 GRPO 实战里,虽然入口脚本是 main_ppo,但核心循环可以理解为:

  1. 采样 prompts(全局):从 parquet 中抽一批 prompt

  2. rollout 生成 responses(推理引擎):每个 prompt 采样 n 条 response(形成 trajectories)

  3. 计算 reward(规则/模型打分):得到每条 trajectory 的标量 reward

  4. 算 logprob(forward-only)

    • 用 actor / rollout 侧重算 old_log_prob(训练需要稳定的 token logprob)
    • 用 ref policy 算 ref_log_prob(用于 KL)
  5. 更新 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

记住:

  • Bn 决定“这轮样本量”(大了更稳但更慢)
  • M 决定“每次更新吃多少”(大了更稳但更吃显存/更慢)
  • m 决定“单卡一次能吃多少”(纯粹显存/吞吐开关)

以5.2 节的配置为例:

  • B=256n=16T≈4096 条轨迹样本/iteration
  • M=32updates_per_iter≈4096/32=128(每轮采样会触发很多次小更新)
  • m=1(每卡 micro=1),world size=16 ⇒ (M/W)=2,accum 约 2 步/次 update(常见于显存紧张但想保住 M 的情况)

4.2.4 tips(注意事项)

  1. 所有包含 micro_batch_size 的配置均用于配置每次前向或后向传递的最大样本或tokens数,以避免内存溢出 (OOM), 对于算法本身或者收敛情况没有影响。
  2. 尽管许多配置以 ppo_ 前缀开头,但它们可以在 verl 中的不同 RL 算法中使用,因为 GRPO 训练循环与 PPO 的训练循环类似(没有critic)。
  3. 其中trian batch_size和ppo mini-batch size是全局级别的
  4. mini_batch_size必须要整除micro_batch_seize_per_gpu
  5. 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(实验追踪)

  • 使用方法:两个机子上的容器内先安装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)

那么:

  1. 每个 epoch 的 iteration 数iters_per_epoch = ceil(N / B) = ceil(3000 / 256) = 12
  2. 每次 iteration 产生的轨迹样本数(粗略)T = B * n = 256 * 16 = 4096
  3. 每次 iteration 的更新次数updates_per_iter = ceil(T / M) = ceil(4096 / 32) = 128
  4. trainer.total_epochs=10
    • total_updates ≈ iters_per_epoch * updates_per_iter * total_epochs = 12 * 128 * 10 = 15360

5.2 哪些参数影响显存

A. 序列长度相关(几乎必影响显存)

  • data.max_prompt_lengthdata.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 参考网址

Logo

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

更多推荐