最近正在研究如何使用强化学习(RL)技术微调大模型,读 VeRL 框架 的代码。VeRL 代码库在 examples/ 目录下提供了一些 RL 训练的示例脚本。其中,ppo_trainer/run_deepseek7b_llm.sh 和 grpo_trainer/run_deepseek7b_llm.sh 这两个脚本均使用 gsm8k 数据集训练 deepseek 7b 模型,不过一个使用 PPO 算法,另一个使用 GRPO 算法。经比较,这两个脚本的内容只有一些参数不同。
笔者感到很好奇,这些不同的参数表示什么含义,如何跟具体算法对应起来,对训练的影响又是什么?本篇博客记录了笔者的学习过程。
1 PPO 脚本 vs GRPO 脚本内容比较
1.1 PPO 脚本 examples/ppo_trainer/run_deepseek7b_llm.sh
bash
# 来源于 examples/ppo_trainer/run_deepseek7b_llm.sh
set -x
python3 -m verl.trainer.main_ppo \
algorithm.adv_estimator=gae \
data.train_files=$HOME/data/gsm8k/train.parquet \
data.val_files=$HOME/data/gsm8k/test.parquet \
data.train_batch_size=1024 \
data.max_prompt_length=512 \
data.max_response_length=512 \
data.filter_overlong_prompts=True \
data.truncation='error' \
actor_rollout_ref.model.path=deepseek-ai/deepseek-llm-7b-chat \
actor_rollout_ref.actor.optim.lr=1e-6 \
actor_rollout_ref.model.use_remove_padding=True \
actor_rollout_ref.actor.ppo_mini_batch_size=256 \
actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=16 \
actor_rollout_ref.actor.fsdp_config.param_offload=False \
actor_rollout_ref.actor.fsdp_config.optimizer_offload=False \
actor_rollout_ref.actor.use_kl_loss=False \
actor_rollout_ref.model.enable_gradient_checkpointing=True \
actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=32 \
actor_rollout_ref.rollout.tensor_model_parallel_size=4 \
actor_rollout_ref.rollout.name=vllm \
actor_rollout_ref.rollout.gpu_memory_utilization=0.4 \
critic.optim.lr=1e-5 \
critic.model.use_remove_padding=True \
critic.model.path=deepseek-ai/deepseek-llm-7b-chat \
critic.model.enable_gradient_checkpointing=True \
critic.ppo_micro_batch_size_per_gpu=32 \
critic.model.fsdp_config.param_offload=False \
critic.model.fsdp_config.optimizer_offload=False \
algorithm.use_kl_in_reward=False \
trainer.critic_warmup=0 \
trainer.logger='["console","wandb"]' \
trainer.project_name='verl_example_gsm8k' \
trainer.experiment_name='deepseek_llm_7b_function_rm' \
trainer.n_gpus_per_node=8 \
trainer.nnodes=1 \
trainer.save_freq=20 \
trainer.test_freq=1 \
trainer.use_legacy_worker_impl=auto \
trainer.total_epochs=15 $@
1.2 GRPO 脚本 examples/grpo_trainer/run_deepseek7b_llm.sh
bash
# 来源于 examples/grpo_trainer/run_deepseek7b_llm.sh,仅展示不同的行
algorithm.adv_estimator=grpo \
data.max_response_length=1024 \
actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=80 \
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.rollout.log_prob_micro_batch_size_per_gpu=160 \
actor_rollout_ref.rollout.tensor_model_parallel_size=2 \
actor_rollout_ref.rollout.gpu_memory_utilization=0.6 \
actor_rollout_ref.rollout.n=5 \
actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=160 \
actor_rollout_ref.ref.fsdp_config.param_offload=True \
# 以及,GRPO 脚本没有以下这些行
critic.optim.lr=1e-5 \
critic.model.use_remove_padding=True \
critic.model.path=deepseek-ai/deepseek-llm-7b-chat \
critic.model.enable_gradient_checkpointing=True \
critic.ppo_micro_batch_size_per_gpu=32 \
critic.model.fsdp_config.param_offload=False \
critic.model.fsdp_config.optimizer_offload=False \
2 脚本差别讲解
2.1 核心算法选择:adv_estimator
- PPO 脚本:
bash
algorithm.adv_estimator=gae
- GRPO 脚本:
bash
algorithm.adv_estimator=grpo
含义:
gae:使用 GAE(Generalized Advantage Estimation),需要一个 value function(critic)来估计 V(s),然后用 TD(λ) 的形式算 advantage。grpo:改用 GRPO 的 group-relative advantage,不需要单独的 critic 网络,而是:- 对同一个 prompt 采样一组 response(group),
- 根据组内 reward 做归一化,直接得到 advantage。
在 VeRL 的实现里,这是通过把 adv_estimator 设为对应的枚举值(AdvantageEstimator.GRPO)然后调用 compute_grpo_outcome_advantage / compute_grpo_vectorized_outcome_advantage 等函数来实现的。
2.2 响应长度:data.max_response_length
- PPO:
bash
data.max_response_length=512
- GRPO:
bash
data.max_response_length=1024
GRPO 脚本允许更长的输出(复杂推理、多样解答),这和 GRPO 的典型使用场景(math / reasoning)是匹配的。没有算法上的硬性要求,只是工程经验:GRPO 经常和更长推理链配合使用。
2.3 Actor 端 batch 设置相关
对比几行:
bash
# PPO
actor_rollout_ref.actor.ppo_mini_batch_size=256 \
actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=16 \
# GRPO
actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=80 \
# (示例里没再显式设 mini_batch_size,保持默认)
含义(根据 VeRL config 文档):
ppo_mini_batch_size:一个 PPO epoch 里,每次 optimizer step 使用的样本数(全局计数)。ppo_micro_batch_size_per_gpu:每块 GPU 一次前向/反向的微批大小,控制显存占用。
GRPO 脚本把 ppo_micro_batch_size_per_gpu 提高到 80,是利用了下面两个事实:
- GRPO 没有 critic,总体显存压力小了,可以把 micro batch 放大。
- GRPO 一般会配合
n>1的 group 采样,需要吞吐更多样本。
2.4 KL 相关:use_kl_loss、kl_loss_coef、kl_loss_type、use_kl_in_reward
对比:
bash
# PPO
actor_rollout_ref.actor.use_kl_loss=False \
algorithm.use_kl_in_reward=False \
# GRPO
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 \
# (仍然保持 algorithm.use_kl_in_reward=False,也就是不用 reward 级的 KL 惩罚)
关键点:
- PPO 脚本里:
use_kl_loss=False+use_kl_in_reward=False
=> 既没有在 loss 里用 KL,也没有在 reward 里扣 KL(纯靠 reward / advantage)。
- GRPO 脚本里:
- 关掉 reward 中的 KL:
algorithm.use_kl_in_reward=False - 改为在 loss 里加 KL 正则:
actor_rollout_ref.actor.use_kl_loss=True - 系数:
kl_loss_coef=0.001,决定total_loss = ppo_loss + 0.001 * kl_loss - 形式:
kl_loss_type=low_var_kl,即使用 "低方差 KL 近似公式",具体实现下面会讲。
- 关掉 reward 中的 KL:
这也符合官方 GRPO 文档里的推荐:GRPO 建议在 actor loss 上加 KL,而不是在 reward 上手动减一个 KL penalty。
2.5 熵系数:entropy_coeff
- PPO 脚本没有显式改,使用 config 默认值(文档里默认是 0 或很小的值)。
- GRPO 脚本显式写:
bash
actor_rollout_ref.actor.entropy_coeff=0
意思是完全关掉熵正则。因为 GRPO 已经通过:
- group 相对奖励(鼓励好样本,抑制差样本),以及
- KL loss 控制和 ref policy 的距离,
来进行探索/正则了,额外的熵正则很多工作里会直接设为 0。
在 loss 公式中,PPO/GRPO 的 actor loss大致是:
L actor = L ppo + λ KL ⋅ KL_loss − λ ent ⋅ H ( π ) L_{\text{actor}} = L_{\text{ppo}} + \lambda_{\text{KL}} \cdot \text{KL\loss} - \lambda{\text{ent}} \cdot H(\pi) Lactor=Lppo+λKL⋅KL_loss−λent⋅H(π)
GRPO 时把 λ ent = 0 \lambda_{\text{ent}} = 0 λent=0。
2.6 rollout & ref 计算相关:log prob batch、并行度和 n
差异行:
bash
# PPO rollout
actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=32 \
actor_rollout_ref.rollout.tensor_model_parallel_size=4 \
actor_rollout_ref.rollout.gpu_memory_utilization=0.4 \
# GRPO rollout
actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=160 \
actor_rollout_ref.rollout.tensor_model_parallel_size=2 \
actor_rollout_ref.rollout.gpu_memory_utilization=0.6 \
actor_rollout_ref.rollout.n=5 \
actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=160 \
actor_rollout_ref.ref.fsdp_config.param_offload=True \
各字段含义:
rollout.log_prob_micro_batch_size_per_gpu:- 生成/算 logprob 时的微批大小。
- 从 32 提到 160,提升推理吞吐量(注意 GRPO 需要 多条 response/组)。
rollout.tensor_model_parallel_size:- 模型张量并行的切分数,PPO 用 4,GRPO 用 2。
- 这通常是工程上在"并行粒度 vs 单卡显存需求"之间的折中配置,与
gpu_memory_utilization连动调参。 - GRPO 脚本把
gpu_memory_utilization提高到 0.6,说明它希望单卡吃更多显存、换取更高吞吐。
rollout.n=5(GRPO 特有):- 关键参数:每个 prompt 采样几条 candidate response。
- GRPO 算 advantage 时,是 在同一个 prompt 的 n 条 response 之间做"组内对比归一化" 。
这里n=5就是 group size = 5。
actor_rollout_ref.ref.*:ref.log_prob_micro_batch_size_per_gpu=160:参考模型算 logprob 的微批大小。ref.fsdp_config.param_offload=True:参考模型参数做 FSDP param offload(卸到 CPU 或 NVMe),节省 GPU 显存。
因为 ref policy 通常是 冻结的,只用来算 KL,所以可以 aggressive 地 offload。
PPO 脚本中没有 actor_rollout_ref.ref.*,原因是:那套 PPO 示例没有使用 "单独的 ref 模型 + KL loss",而 GRPO 示例引入了显式 KL loss,对 ref 模型的配置就要写出来。
2.7 Critic 模块:PPO 有,GRPO 去掉
PPO:
bash
critic.optim.lr=1e-5 \
critic.model.use_remove_padding=True \
critic.model.path=deepseek-ai/deepseek-llm-7b-chat \
critic.model.enable_gradient_checkpointing=True \
critic.ppo_micro_batch_size_per_gpu=32 \
critic.model.fsdp_config.param_offload=False \
critic.model.fsdp_config.optimizer_offload=False \
GRPO:完全没有上面这些 critic 行。
这正是 GRPO 的核心特征之一:critic-less。
- PPO:
- 需要训练一个 value net(通常是同 arch 的 LLM 头 + value head),
- 用来估计 (V(s)),然后通过 GAE 算 advantage。
- GRPO:
- 不再训练 value net。
- 各样本的"优势"直接由组内 reward 做相对归一化得到(见下一节)。
在 VeRL 代码里,这体现为:当 adv_estimator=grpo 时,trainer 会走到 GRPO 的 advantage 计算分支(compute_grpo_outcome_advantage / compute_grpo_vectorized_outcome_advantage),而不会用 GAE、也不会用 critic logits / value。