最近正在研究如何使用强化学习(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
# 来源于 examples/ppo_trainer/run_deepseek7b_llm.shset-xpython3-mverl.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
# 来源于 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 脚本:
algorithm.adv_estimator=gae- GRPO 脚本:
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:
data.max_response_length=512- GRPO:
data.max_response_length=1024GRPO 脚本允许更长的输出(复杂推理、多样解答),这和 GRPO 的典型使用场景(math / reasoning)是匹配的。没有算法上的硬性要求,只是工程经验:GRPO 经常和更长推理链配合使用。
2.3 Actor 端 batch 设置相关
对比几行:
# PPOactor_rollout_ref.actor.ppo_mini_batch_size=256\actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=16\# GRPOactor_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
对比:
# PPOactor_rollout_ref.actor.use_kl_loss=False\algorithm.use_kl_in_reward=False\# GRPOactor_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 脚本显式写:
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
差异行:
# PPO rolloutactor_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 rolloutactor_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:
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。