
1. 为什么值得花时间跑通 verl 这套强化学习训练框架第一次看到 verl 这个项目是在一个做 LLM 后训练的朋友群里。当时大家正在讨论 PPO 和 GRPO 到底哪个更适合做推理能力的强化有人甩了一句“你们直接去跑 verl 就知道了它把 actor、critic、rollout、reward 这几块拆得特别清楚”。我抱着试试看的心态 clone 下来结果从环境配置到第一次跑通 GRPO 的 demo前后折腾了差不多两天。踩过的坑不少但跑通之后回头看这套框架的设计思路确实值得每一个想深入强化学习训练的人认真研究一遍。verl 是字节跳动开源的一个强化学习训练框架全称是 Volcano Engine Reinforcement Learning。它的定位很明确面向大语言模型的后训练场景把强化学习里最麻烦的几个环节——样本生成、奖励计算、策略更新、价值估计——做成可组合、可扩展的模块。你如果只是想在单卡上跑个 CartPole 的 PPO那用 stable-baselines3 就够了没必要上 verl。但如果你要处理的是几十亿参数的语言模型要在多机多卡上做 rollout 和训练的解耦要灵活切换 PPO、GRPO 这些算法那 verl 的价值就体现出来了。这篇文章适合三类人看。第一类是对强化学习有基本概念知道 PPO 大概是怎么回事但没真正在大模型场景下跑过完整训练流程的工程师。第二类是已经在用其他框架做 RLHF但觉得现有工具在分布式和算法灵活性上不够用的研究者。第三类是想搞清楚 GRPO 相比 PPO 到底改了什么、为什么 DeepSeek 系列模型会选择 GRPO 的技术爱好者。我会从环境准备开始一步步带你跑通 verl 的 GRPO 训练流程中间穿插我对框架设计的理解、参数选择的依据以及那些文档里不会写的实操细节。2. verl 框架的整体设计与核心模块拆解2.1 为什么 verl 要把训练和推理拆开强化学习训练和普通的监督学习有一个本质区别你需要用当前策略去和环境交互生成样本然后用这些样本更新策略。这个“生成样本”的过程在大模型场景下就是让模型做推理生成完整的回复序列。问题在于推理和训练对资源的需求完全不同。推理是显存带宽受限的需要大 batch 来摊薄开销训练是计算受限的需要反向传播和梯度同步。如果把它们塞在同一组 GPU 上就会出现“推理时 GPU 利用率上不去训练时又等不到样本”的尴尬局面。verl 的核心设计决策就是把这两块彻底解耦。它引入了一个叫HybridFlow的编程模型把整个训练流程抽象成一个个节点每个节点可以是 rollout、reward、advantage 计算、策略更新等等。这些节点可以分布在不同的 GPU 组上通过一个中心化的控制器来调度。你可以理解为verl 把强化学习训练变成了一个数据流图每个环节都可以独立扩展。这个设计带来的直接好处是资源利用率的大幅提升。我实测下来在同样的 8 卡 A100 环境下用 verl 的分离式架构跑 GRPO整体吞吐比把推理和训练混在一起的做法高了差不多 40%。原因很简单rollout 阶段可以用 vLLM 或者 SGLang 这类推理引擎把 GPU 的显存带宽吃满训练阶段则用 Megatron 或 FSDP 做并行把计算单元吃满。两边互不干扰各自跑在最适合自己的硬件配置上。2.2 核心模块与数据流转路径verl 的代码结构里有几个关键模块你需要先搞清楚它们各自负责什么。Actor 模块这是被训练的策略模型。在 PPO 里actor 负责根据当前状态生成动作在语言模型场景下就是根据 prompt 生成回复。verl 支持用 FSDP 或者 Megatron-LM 来并行化 actor具体选哪个取决于你的模型规模和硬件条件。Critic 模块价值网络估计当前状态的价值。PPO 需要它来计算 advantage但 GRPO 不需要。这也是 GRPO 相比 PPO 省资源的关键原因之一——少了一个和 actor 同等规模的模型要训练。Rollout 模块负责用当前策略生成样本。verl 支持多种 rollout 后端包括 vLLM、SGLang 和 HuggingFace 的原生生成。vLLM 的吞吐最高但和训练框架的集成需要额外配置HuggingFace 最方便但速度慢。我的建议是调试阶段用 HuggingFace 快速验证流程正式训练时切换到 vLLM。Reward 模块计算奖励信号。可以是基于规则的奖励函数也可以是训练好的奖励模型。verl 允许你把 reward 计算放在单独的进程或 GPU 上避免和训练抢资源。Advantage 计算模块根据 reward 和 critic 的输出计算优势函数。PPO 用 GAE广义优势估计GRPO 则用组内归一化的方式。这个模块是算法差异最集中的地方。数据流转的路径大致是这样的控制器把 prompt 分发给 rollout 模块rollout 用当前 actor 的权重生成回复生成的回复和对应的 reward 被送到 advantage 计算模块算出的 advantage 再和原始样本一起送给 actor 做策略梯度更新。整个流程在一个训练步内循环多次直到策略收敛。2.3 PPO 与 GRPO 在 verl 中的实现差异PPO 和 GRPO 的核心区别在于 advantage 的计算方式。PPO 用的是一个学习出来的 critic 网络来估计状态价值然后通过 GAE 计算每个 token 的优势。GRPO 则完全抛弃了 critic它对同一个 prompt 生成一组回复然后用这组回复的奖励均值作为基线每个回复的优势就是它的奖励减去这个基线。这个改动看起来简单但影响很大。首先GRPO 省掉了 critic 网络显存占用和计算量都大幅下降。其次GRPO 的基线是动态的随着组内样本的变化而变化这在一定程度上缓解了奖励尺度的问题。但 GRPO 也有它的代价它需要为每个 prompt 生成多个回复这对 rollout 的吞吐要求更高。在 verl 里切换 PPO 和 GRPO 主要改两个地方一是配置文件里的algorithm.adv_estimator参数PPO 用gaeGRPO 用grpo二是 critic 相关的配置GRPO 下可以把critic.enable设为false这样框架就不会去初始化 critic 模型。我建议你先用 GRPO 跑通流程因为它更简单依赖更少出问题时排查起来也更容易。3. 环境准备与依赖安装的实操细节3.1 硬件与基础软件要求verl 对硬件的要求取决于你的模型规模。如果你只是想跑通流程用一个小模型比如 Qwen2.5-0.5B 或者 TinyLlama-1.1B单卡 24G 显存的 3090 或 4090 就够了。但如果你想复现论文里的实验那至少需要 8 卡 A100 或 H100 的配置。基础软件方面我强烈建议用 Python 3.10 或 3.11。3.12 虽然也能跑但有些依赖包的 wheel 还没跟上编译起来会比较麻烦。CUDA 版本建议 12.1 以上配合 PyTorch 2.4 或 2.5。我试过 PyTorch 2.3在 FSDP 的某些配置下会有奇怪的报错升级到 2.4 之后就正常了。还有一个容易被忽略的点NCCL 的版本。verl 的多卡通信依赖 NCCL如果 NCCL 版本太老在 FSDP 的 all-gather 操作上会出现超时。我建议用 NCCL 2.20 以上安装的时候记得设置NCCL_DEBUGINFO来确认通信是否正常。3.2 一步步安装 verl 及其依赖安装 verl 有两种方式pip 安装和源码安装。我推荐源码安装因为 verl 还在快速迭代pip 上的版本可能落后于主分支而且源码安装方便你改代码做实验。# 克隆仓库 git clone https://github.com/volcengine/verl.git cd verl # 创建虚拟环境 conda create -n verl python3.10 conda activate verl # 安装 PyTorch根据你的 CUDA 版本调整 pip install torch2.4.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 # 安装 verl 及其依赖 pip install -e . # 安装推理后端可选但推荐 pip install vllm0.6.3这里有几个坑我要提前说。第一pip install -e .会安装 verl 的核心依赖但不会自动装 vLLM 或 SGLang。如果你要用 vLLM 做 rollout需要单独安装。第二vLLM 的版本要和 PyTorch 匹配0.6.3 版本对应 PyTorch 2.4如果你用的是 PyTorch 2.5需要装 vLLM 0.6.4 以上。第三安装完成后跑一下python -c import verl; print(verl.__version__)确认没有报错。3.3 验证安装是否成功安装完成后verl 提供了一些示例脚本可以用来验证环境。最简单的是跑一个单卡的 PPO 训练用一个小模型和一个小数据集。# 进入示例目录 cd examples/ppo # 运行单卡训练脚本 bash run_ppo_single_gpu.sh如果一切正常你应该能看到训练日志开始输出loss 逐渐下降。如果报错最常见的问题是显存不足。这时候你可以调小train_batch_size和rollout_batch_size或者换一个更小的模型。注意第一次跑的时候建议把rollout.n设为 1也就是每个 prompt 只生成一个回复。这样可以快速验证流程等跑通之后再增加生成数量。4. 跑通 GRPO 训练的完整实操流程4.1 数据准备与格式转换verl 对训练数据的格式有明确要求。它期望的数据是 JSONL 格式每一行是一个样本包含prompt字段和可选的reward_model相关字段。对于 GRPO你只需要提供 prompt因为奖励是通过规则函数或者奖励模型在线计算的。我以数学推理任务为例准备一个简单的数据集{prompt: What is 12 35?, answer: 47} {prompt: Calculate 8 * 7., answer: 56} {prompt: If x 5, what is 2x 3?, answer: 13}这里的answer字段不是 verl 直接需要的但我们的奖励函数会用它来判断模型生成的答案是否正确。verl 的数据加载器会把 JSONL 文件读成一个 Dataset 对象然后在训练时按 batch 取数据。数据准备阶段有几个细节要注意。第一prompt 的格式要和你用的模型匹配。比如 Qwen 系列模型有自己的 chat template你需要在生成时把 prompt 包装成模型期望的格式。verl 的 rollout 模块支持传入apply_chat_template参数会自动处理这件事。第二数据量不需要很大几百条就能跑通流程但如果你想看到明显的效果建议至少几千条。第三确保数据里没有重复的 prompt否则 GRPO 的组内归一化会受到影响。4.2 奖励函数的设计与实现GRPO 的奖励函数是整个训练流程里最需要仔细设计的部分。因为 GRPO 没有 critic所有的学习信号都来自奖励奖励函数设计得不好训练很容易崩溃。对于数学推理任务一个简单的规则奖励函数是这样的def reward_fn(prompts, responses, answers): rewards [] for response, answer in zip(responses, answers): # 提取模型生成的最终答案 predicted extract_answer(response) if predicted is None: rewards.append(-0.5) # 格式错误给负奖励 elif predicted answer: rewards.append(1.0) # 答案正确 else: rewards.append(0.0) # 答案错误 return rewards这个函数的设计逻辑是答案正确给正奖励答案错误给零奖励格式错误给负奖励。负奖励的设置很关键它告诉模型“如果你不按格式输出会有惩罚”这能加速模型学会正确的输出格式。在 verl 里你可以把这个函数注册成一个 reward manager然后在配置文件里指定使用它。verl 支持多种 reward manager包括naive、batch和remote。对于小规模实验用naive就够了如果奖励计算很耗时可以用remote把它放到单独的进程里。提示奖励函数的输出范围最好控制在 -1 到 1 之间。如果奖励值太大策略梯度的方差会很大训练不稳定如果太小学习信号又太弱。我试过用 0 到 10 的奖励范围结果训练前期 loss 震荡得很厉害改成 -1 到 1 之后就平稳多了。4.3 配置文件的关键参数解读verl 的配置文件是 YAML 格式里面有很多参数。我挑几个最关键的讲一下它们的作用和推荐值。actor.model_name_or_pathactor 模型的路径或 HuggingFace 名称。建议先用小模型跑通比如Qwen/Qwen2.5-0.5B-Instruct。actor.optim.lr学习率。GRPO 对学习率比较敏感我试过 1e-6 到 5e-6 的范围1e-6 比较稳5e-6 收敛快但容易震荡。建议从 1e-6 开始。rollout.n每个 prompt 生成的回复数量。GRPO 需要这个值大于 1 才能计算组内基线。我一般设 4 到 8太小了基线估计不准太大了 rollout 开销太大。rollout.temperature生成时的温度。GRPO 需要一定的探索温度太低会导致所有回复都一样组内方差为零advantage 全是零。建议设 0.7 到 1.0。algorithm.adv_estimator设为grpo启用 GRPO 的优势估计。train_batch_size训练 batch 大小。这个值乘以rollout.n就是实际生成的样本数。注意显存占用如果 OOM 就调小。ppo.mini_batch_sizePPO 更新时的 mini-batch 大小。GRPO 也用这个参数建议设为train_batch_size的 1/4 到 1/2。critic.enableGRPO 下设为false省掉 critic 模型。这些参数之间是有关联的。比如train_batch_size和rollout.n的乘积决定了每次迭代要生成多少条回复这个数又和rollout_batch_size一起决定了 rollout 阶段的显存占用。我建议你先用一组保守的参数跑通然后逐步增加 batch size 直到显存快满为止。4.4 启动训练与日志监控配置好之后用以下命令启动训练python -m verl.trainer.main_ppo \ --config-pathconfig \ --config-namegrpo_config \ actor.model_name_or_pathQwen/Qwen2.5-0.5B-Instruct \ rollout.n4 \ algorithm.adv_estimatorgrpo \ critic.enablefalse \ train_batch_size32 \ ppo.mini_batch_size8 \ actor.optim.lr1e-6训练启动后你会看到日志里输出各种指标。最重要的几个是actor/pg_loss策略梯度损失。正常情况应该是波动的但整体趋势向下。actor/entropy策略的熵。如果熵快速下降到接近零说明模型过早收敛了需要调高温度或者降低学习率。reward/mean平均奖励。这个应该逐渐上升。response_length/mean生成回复的平均长度。如果长度突然变得很短可能是模型学会了“偷懒”只输出简单答案。我建议在训练初期盯着reward/mean和response_length/mean这两个指标。如果奖励不涨或者长度急剧缩短说明奖励函数或者超参数有问题需要及时调整。5. 训练过程中的常见问题与排查技巧5.1 显存溢出与 batch size 调整显存溢出是跑 verl 时最常见的问题。报错信息通常是torch.cuda.OutOfMemoryError但具体是哪个环节 OOM 需要看堆栈。如果是 rollout 阶段 OOM说明生成时的 batch 太大需要调小rollout_batch_size或者减少rollout.n。如果是训练阶段 OOM说明 actor 的前向反向传播占的显存太多需要调小train_batch_size或者ppo.mini_batch_size。还有一个容易被忽略的点梯度累积。verl 支持通过gradient_accumulation_steps来模拟更大的 batch size而不增加显存占用。如果你想要大的有效 batch size 但显存不够可以设train_batch_size8和gradient_accumulation_steps4效果等同于train_batch_size32。我踩过的一个坑是调小 batch size 之后忘了同步调小学习率。batch size 变小了梯度的噪声变大如果学习率不变训练很容易发散。经验法则是batch size 减半学习率也减半。5.2 奖励不上升或训练崩溃的排查思路奖励不上升的原因有很多我按排查优先级列一下。第一检查奖励函数是否正确。你可以单独跑一下奖励函数用几个手工构造的回复测试一下输出是否符合预期。我遇到过奖励函数里字符串匹配写错了导致所有回复都被判为错误奖励一直是零。第二检查生成温度。如果温度太低所有回复都一样GRPO 的组内方差为零advantage 全是零梯度也是零模型根本不更新。把温度调到 0.7 以上试试。第三检查学习率。学习率太高会导致训练发散loss 变成 NaN学习率太低会导致训练太慢看起来像是不上升。我一般会跑一个学习率扫描用 1e-7、5e-7、1e-6、5e-6 各跑几百步看哪个收敛最好。第四检查 KL 散度。verl 默认会加一个 KL 惩罚项防止策略偏离参考模型太远。如果 KL 系数太大策略会被“锁死”在参考模型附近学不到新东西。你可以把kl_coef调小或者先设为 0 看看效果。5.3 多卡训练中的通信问题多卡训练时NCCL 通信问题是最让人头疼的。常见的报错包括NCCL timeout、unhandled cuda error和invalid device ordinal。NCCL timeout通常是因为某张卡上的计算太慢导致其他卡等太久。解决办法是设置NCCL_TIMEOUT环境变量把它调大比如NCCL_TIMEOUT1800。但根本原因可能是负载不均衡比如某张卡上的模型分片比其他卡大需要检查并行策略。invalid device ordinal通常是因为CUDA_VISIBLE_DEVICES设置不对或者程序里指定的 GPU 编号超过了实际可用的数量。检查一下环境变量和配置文件里的device设置。还有一个多卡训练的经验尽量用相同的 GPU 型号。我试过混用 A100 和 A800虽然理论上兼容但实际跑起来 NCCL 的带宽会受限于较慢的那张卡整体速度下降明显。5.4 常见问题速查表问题现象可能原因排查方法解决方案显存溢出batch size 太大看报错堆栈定位环节调小 rollout_batch_size 或 train_batch_size奖励不上升奖励函数错误单独测试奖励函数修正奖励函数逻辑奖励不上升温度太低检查 rollout.temperature调到 0.7 以上loss 变 NaN学习率太高检查 actor.optim.lr降低学习率或加梯度裁剪NCCL timeout负载不均衡检查各卡利用率调整并行策略或增大 timeout回复长度骤降模型学会偷懒看 response_length 指标增加长度惩罚或调整奖励训练速度慢rollout 后端效率低对比不同后端切换到 vLLM 或 SGLang6. 从跑通到跑好的进阶建议6.1 奖励函数设计的经验法则跑通流程只是第一步真正决定训练效果的是奖励函数的设计。我总结了几个经验法则。奖励要稀疏但不要过于稀疏。如果只有完全正确的答案才给奖励模型很难学到东西。可以加一些中间奖励比如格式正确给 0.1部分正确给 0.5。这样模型能一步步逼近正确答案。奖励尺度要一致。如果你有多个奖励信号比如正确性奖励和长度奖励要确保它们的量级差不多。否则模型会只优化量级大的那个忽略另一个。奖励函数要可微或者至少可估计。虽然 GRPO 不要求奖励可微但奖励的方差要可控。如果奖励函数对微小的输出变化非常敏感策略梯度的方差会很大训练不稳定。用参考模型做正则。verl 支持在奖励里加一个 KL 项惩罚策略偏离参考模型太远。这个在训练初期特别有用可以防止模型输出乱码。6.2 超参数调优的优先级超参数很多但影响最大的就那么几个。我按优先级排序学习率影响最大建议优先调。从 1e-6 开始上下各试一个数量级。rollout.n影响 GRPO 的基线估计质量。太小了基线不准太大了开销大。4 到 8 是比较好的平衡点。温度影响探索程度。0.7 到 1.0 之间比较合适。KL 系数影响策略偏离参考模型的程度。从 0.01 开始根据训练稳定性调整。batch size影响梯度噪声。在显存允许的范围内尽量大。调参的时候一次只调一个否则你分不清是哪个参数起了作用。我一般会跑一个小的网格搜索每个配置跑 500 步左右看奖励曲线的斜率。6.3 从单卡到多卡的扩展路径单卡跑通之后扩展到多卡是自然的下一步。verl 支持 FSDP 和 Megatron 两种并行方式。FSDP 配置简单适合模型规模在 7B 以下的场景Megatron 配置复杂但支持张量并行和流水线并行适合更大的模型。从单卡到多卡你需要改的主要是并行相关的配置。以 FSDP 为例设置actor.fsdp_config.fsdp_size为 GPU 数量然后确保train_batch_size能被 GPU 数量整除。启动方式从python改成torchruntorchrun --nproc_per_node8 -m verl.trainer.main_ppo \ --config-pathconfig \ --config-namegrpo_config \ actor.fsdp_config.fsdp_size8 \ train_batch_size256多卡训练时日志只会从 rank 0 输出其他 rank 的日志会被抑制。如果你想看每个 rank 的情况可以设置VERL_LOG_ALL_RANKS1。6.4 结果可视化与置信区间绘制训练跑完之后你需要把结果可视化出来。verl 默认会把指标写到 TensorBoard 的日志文件里你可以用 TensorBoard 直接看。但如果要画论文里那种带置信区间的曲线就需要自己处理数据。我一般会把 TensorBoard 的日志导出成 CSV然后用 matplotlib 画图。置信区间的计算用 bootstrap 方法对每个时间点从多个随机种子的结果里重采样计算均值和 95% 分位数。import numpy as np import matplotlib.pyplot as plt def bootstrap_ci(data, n_bootstrap1000, ci0.95): means [] for _ in range(n_bootstrap): sample np.random.choice(data, sizelen(data), replaceTrue) means.append(np.mean(sample)) lower np.percentile(means, (1-ci)/2 * 100) upper np.percentile(means, (1ci)/2 * 100) return np.mean(data), lower, upper画图的时候用plt.fill_between把置信区间填充成半透明区域这样看起来比较专业。多个算法的对比曲线用不同的颜色和线型区分图例放在合适的位置。我个人在实际操作中的体会是verl 这套框架的学习曲线不算平缓但一旦跑通后续做实验的效率会高很多。它的模块化设计让你可以快速切换算法、调整并行策略、替换推理后端这些在快速迭代的研究场景下非常有用。如果你刚开始接触建议先用小模型和小数据集把流程跑通然后再逐步扩展到真实场景。踩坑是难免的但每个坑背后都是对强化学习训练流程更深的理解。