verl 中 GRPO(Group Relative Policy Optimization)算法详解:配置、损失实现与 DrGRPO 扩展
【免费下载链接】verlverl/HybridFlow: A Flexible and Efficient RL Post-Training Framework项目地址: https://gitcode.com/GitHub_Trending/ve/verl
导读
本文以 verl 仓库的官方算法文档 docs/algo/grpo.md 为骨架,结合源码(verl/trainer/ppo/core_algos.py、verl/trainer/config/algorithm.py、verl/trainer/config/actor/actor.yaml)与示例脚本(examples/grpo_trainer/run_qwen3_8b_fsdp.sh),系统讲解 GRPO 在 verl 中的工作原理、完整配置项、KL 正则化机制,以及面向长 CoT 稳定性优化的 DrGRPO 扩展。读完本文,你将掌握在 verl 中从零配置并启动一次 GRPO 训练(含分组采样、组内相对奖励、KL 损失、损失聚合模式等关键参数),并能理解每个参数在源码中的实际作用。
GRPO 核心思想:无 Critic 的组相对策略优化
在经典强化学习算法(如 PPO)中,训练过程依赖一个独立的"critic"(价值)模型来估计动作价值,从而指导策略更新。但训练 critic 模型本身会引入大量额外计算与内存开销。
GRPO(Group Relative Policy Optimization,源自 DeepSeekMath 论文)的核心创新在于彻底去掉 critic 模型,改用"组内相对比较"来构造学习信号。其训练循环围绕一个 prompt 展开:
- Group Sampling(分组采样):对同一个问题(prompt),当前策略模型采样生成多条候选答案,构成一个"组"(group)。
- Reward Assignment(奖励分配):根据答案的正确性或质量,为组内每条答案计算奖励。
- Baseline Calculation(基线计算):以该组奖励的平均值作为基线。
- Policy Update(策略更新):将组内每条答案的奖励与该组基线做差,高于平均水平的答案得到正向强化,低于平均水平的答案被抑制,进而更新策略参数。
由于不再训练独立的价值估计模型,GRPO 显著降低了计算开销,使学习过程更加高效。这也是 verl 中所有 GRPO 示例脚本(如examples/grpo_trainer/目录下的各类脚本)所遵循的基本范式。
三个关键组成要素
- No Value Function(无价值函数):与 PPO 不同,GRPO 不训练独立的 value 网络(critic),因此无需 critic 前向与价值损失计算。
- Group Sampling(分组 Rollout):与每个输入只采样一次 rollout 不同,GRPO 对每个 prompt 从当前策略生成多条补全(response),这一组补全称为 group。分组信息通过
index数组在批次中标识。 - Relative Rewards(相对奖励):在组内,各补全按正确性等指标打分,并相对该组做归一化(减去组均值、按组标准差缩放)。
源码中的 GRPO advantage 实现
从源码结构看,GRPO 的 advantage 计算位于 verl/trainer/ppo/core_algos.py 的compute_grpo_outcome_advantage,通过@register_adv_est("grpo")注册为 advantage 估计器。其核心逻辑是:
- 将 token 级奖励沿序列维度求和得到每条 response 的标量分数(
scores = token_level_rewards.sum(dim=-1)); - 按
index(即 group 标识)对分数分组,计算每组的均值id2mean与标准差id2std; - 当
norm_adv_by_std_in_grpo=True时,advantage 为(score - mean) / (std + epsilon);当为False时仅做score - mean去均值; - 最后将组内标量 advantage 广播到 token 维度并与
response_mask相乘,得到与 loss 对齐的 token 级优势。
该函数注释明确指出:norm_adv_by_std_in_grpo=True对应原始 GRPO 论文做法;False对应 DrGRPO(见下文扩展章节)。此外,源码还提供向量化实现compute_grpo_vectorized_outcome_advantage(core_algos.py,注册为grpo_vectorized),逻辑等价但通过group_mean_std批量计算组均值/标准差,性能更好。
verl 中 GRPO 的配置详解
在 verl 中,尽管许多配置以ppo_前缀开头,但由于 GRPO 的训练循环与 PPO 相似(仅去掉 critic),这些配置对 GRPO 等不同 RL 算法同样适用。所有包含micro_batch_size的配置仅用于控制每次前向/反向传播的最大样本数或 token 数,以避免 GPU OOM,不会改变算法的收敛行为。
分组采样相关
| 配置项 | 作用 | 默认值/GRPO 建议 |
|---|---|---|
actor_rollout.ref.rollout.n | 每个 prompt 采样 n 次 | 默认 1;GRPO 必须设为大于 1 以启用分组采样 |
data.train_batch_size | 用于生成一组采样轨迹(rollout)的全局 prompt 批次大小 | 按显存设置 |
actor_rollout_ref.actor.ppo_mini_batch_size | 采样轨迹集被切分为多个 mini-batch(全局大小,跨所有 worker),用于 PPO/GRPO actor 更新 | 默认 256 |
actor_rollout_ref.actor.ppo_epochs | 对同一组采样轨迹执行 GRPO 更新的 epoch 数 | 默认 1 |
actor_rollout_ref.actor.clip_ratio | GRPO 的 clip 范围 | 默认 0.2 |
其中,response/轨迹总数等于data.train_batch_size * actor_rollout_ref.rollout.n,即每条 prompt 采样 n 条补全,共同构成一个"组"。在 examples/grpo_trainer/run_qwen3_8b_fsdp.sh 中,默认ROLLOUT_N=5、train_batch_size=1024、ppo_mini_batch_size=256,即每步 1024 个 prompt × 5 条补全,再切成 256 的 mini-batch 进行更新。
Advantage 估计与损失聚合
| 配置项 | 作用 | 默认值/GRPO 建议 |
|---|---|---|
algorithm.adv_estimator | Advantage 估计器类型 | 默认gae;GRPO 必须设为grpo |
actor_rollout_ref.actor.loss_agg_mode | 损失聚合方式 | 默认token-mean;可选token-mean、seq-mean-token-sum、seq-mean-token-mean等 |
loss_agg_mode的底层实现在 verl/trainer/ppo/core_algos.py 的agg_loss中,支持的聚合模式包括token-mean、token-sum、seq-mean-token-sum、seq-mean-token-sum-norm、seq-mean-token-mean等。原始 GRPO 论文采用样本级损失(seq-mean-token-mean),但在长 CoT(Chain-of-Thought)场景下可能不稳定;因此verl 提供的所有 GRPO 示例脚本均使用默认配置token-mean进行损失聚合。相关字段在 verl/trainer/config/actor/actor.yaml 中有完整注释说明。
KL 正则化(在损失中加入 KL 而非奖励惩罚)
GRPO 与传统 PPO 的另一个重要区别是:不在奖励函数中加 KL 惩罚,而是直接将训练策略与参考策略之间的 KL 散度加入 actor 损失。相关配置:
| 配置项 | 作用 | 默认值/GRPO 建议 |
|---|---|---|
actor_rollout_ref.actor.use_kl_loss | 在 actor 中使用 KL 损失(此时不在奖励函数中加 KL) | 默认False;GRPO 需设为True |
actor_rollout_ref.actor.kl_loss_coef | KL 损失系数 | 默认 0.001 |
actor_rollout_ref.actor.kl_loss_type | KL 散度估计方式 | 支持kl(k1)、abs、mse(k2)、low_var_kl(k3)、full |
kl_loss_type各选项的语义可参考 John Schulman 的 KL 近似分析:
kl(k1):logprob - ref_logprob,即反向 KL 的无偏估计;abs:|logprob - ref_logprob|;mse(k2):0.5 * (logprob - ref_logprob)^2,其梯度是 KL 的无偏估计;low_var_kl(k3):exp(kl) - kl - 1(其中kl = ref_logprob - logprob,并做数值截断),为低方差估计器;full:需要完整词表 logits,当前在 core_algos.py 中标记为NotImplementedError,不要使用。
关于+后缀(straight-through 技巧):kl_loss_type支持在末尾追加+(如k1+、k3+),其原理是:k1 与 k3 估计器的期望值等于真实 KL,但其期望梯度并不等于 KL 的期望梯度;而 k2(mse)给出了正确的梯度估计。因此 verl 通过 straight-through 技巧(core_algos.py 的kl_penalty函数)在前向使用 k1/k3 估计 KL 值,在反向使用 k2 的梯度(backward_score - backward_score.detach() + forward_score.detach()),从而获得无偏的梯度估计。该实现细节在 core_algos.py 注释中有明确说明。
DrGRPO:消除 GRPO 的长度偏差
论文 Understanding R1-Zero-Like Training: A Critical Perspective 指出,GRPO 基于组内奖励归一化计算 advantage 的方式存在优化偏差,会导致模型人为地生成更长的 response,尤其对错误答案更明显。DrGRPO 通过引入全局常量归一化 token 级损失来消除这种长度偏差。
在 verl 中启用 DrGRPO 只需调整以下几项(其余参数与 GRPO 完全一致):
| 配置项 | 值 | 说明 |
|---|---|---|
actor_rollout_ref.actor.loss_agg_mode | seq-mean-token-sum-norm | 关闭序列维度平均 |
actor_rollout_ref.actor.loss_scale_factor | (可选)固定常数(如最大 response 长度) | 确保训练全程归一化一致;不设置则使用当前批次的 response 长度 |
actor_rollout_ref.actor.use_kl_loss | False | DrGRPO 不使用 KL 损失 |
algorithm.norm_adv_by_std_in_grpo | False | 关闭标准差归一化 |
从源码看,seq-mean-token-sum-norm在agg_loss(core_algos.py)中实现:先按序列求和后除以loss_scale_factor(未设置时取loss_mask.shape[-1]即当前批次 response 长度),从而将损失归一化到与序列长度无关的量纲。而norm_adv_by_std_in_grpo=False时,advantage 仅做组内去均值(score - mean),不再除以组标准差(core_algos.py),这正是 DrGRPO 消除长度偏差的关键。此外,algorithm.norm_adv_by_std_in_grpo的默认值为True(见 verl/trainer/config/algorithm.py),DrGRPO 需要显式关闭。
参考示例:Qwen3-8B GRPO 训练
verl 提供了开箱即用的 GRPO 训练脚本 examples/grpo_trainer/run_qwen3_8b_fsdp.sh,支持 NVIDIA GPU 与 Ascend NPU,运行方式:
bash examples/grpo_trainer/run_qwen3_8b_fsdp.sh该脚本完整展示了上文所有关键参数的落地用法(节选核心部分):
# 数据与算法 DATA=( algorithm.adv_estimator=grpo algorithm.use_kl_in_reward=False data.train_batch_size=${train_batch_size} # 默认 1024 data.max_prompt_length=${max_prompt_length} # 默认 1024 data.max_response_length=${max_response_length} # 默认 2048 data.filter_overlong_prompts=True data.truncation='error' ) # Actor:KL 损失与损失聚合 ACTOR=( actor_rollout_ref.actor.ppo_mini_batch_size=${ppo_mini_batch_size} # 默认 256 actor_rollout_ref.actor.use_dynamic_bsz=True actor_rollout_ref.actor.ppo_max_token_len_per_gpu=${ppo_max_token_len_per_gpu} actor_rollout_ref.actor.use_kl_loss=True # GRPO 必需 actor_rollout_ref.actor.kl_loss_coef=${kl_loss_coef} # 默认 0.001 actor_rollout_ref.actor.kl_loss_type=low_var_kl # k3 低方差估计 ) # Rollout:分组采样 ROLLOUT=( actor_rollout_ref.rollout.name=${INFER_BACKEND} # vllm | sglang | trtllm actor_rollout_ref.rollout.tensor_model_parallel_size=${rollout_tp} actor_rollout_ref.rollout.n=${rollout_n} # 默认 5,分组采样 actor_rollout_ref.rollout.log_prob_use_dynamic_bsz=True )脚本中的可调旋钮(Knobs)通过环境变量暴露:ROLLOUT_N(每组采样数,默认 5)、TRAIN_BATCH_SIZE(默认 1024)、PPO_MINI_BATCH_SIZE(默认 256)、KL_LOSS_COEF(默认 0.001)、ACTOR_LR(默认 1e-6)、TOTAL_EPOCHS(默认 15)、INFER_BACKEND(默认 vllm,可选 sglang / trtllm)等。NPU 场景会自动切换到use_kl_loss=True+low_var_kl的组合并设置更大的序列并行度(sp_size=4)与 32K 的max_response_length,适配长 CoT 训练。
注意:训练前需要准备 GSM8K 与 MATH 数据集(脚本默认从$HOME/data/gsm8k与$HOME/data/math读取 parquet 文件),数据集预处理脚本可参考 examples/data_preprocess/gsm8k.py 与 examples/data_preprocess/math_dataset.py。更多基线性能对比可参阅 docs/algo/baseline.md。
总结
GRPO 通过"组内相对奖励"替代 critic 价值函数,在保持策略优化能力的同时大幅降低训练开销,是 verl 中数学推理、长 CoT 等场景 RL 后训练的主力算法。在 verl 中启用 GRPO 只需四步:设adv_estimator=grpo、rollout.n > 1启用分组采样、use_kl_loss=True将 KL 正则化并入损失、按需选择loss_agg_mode聚合方式。若遇到长 response 膨胀问题,可切换 DrGRPO 配置(seq-mean-token-sum-norm+norm_adv_by_std_in_grpo=False)加以缓解。结合 examples/grpo_trainer/ 下的示例脚本,即可快速复现端到端 GRPO 训练。
【免费下载链接】verlverl/HybridFlow: A Flexible and Efficient RL Post-Training Framework项目地址: https://gitcode.com/GitHub_Trending/ve/verl
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考