Chapter 07

GRPOTrainer:DeepSeek-R1 同款算法

📌 commit 8221a9f去 critic 的 PPO + group baseline + rule-based reward

GRPO(Group Relative Policy Optimization)是 DeepSeek 2024 提出、R1 / Qwen3-RL 等顶流推理模型用的算法。它把 PPO 的 critic 去掉、把 reward 归一化成 group 内 baseline。本章拆解原理与 TRL 用法。

7.1GRPO 公式

对每个 prompt 采样 $G$ 个 response,分别得到 reward $r_1, \ldots, r_G$。归一化 advantage:

$$\hat A_i = \frac{r_i - \mathrm{mean}(r_{1\ldots G})}{\mathrm{std}(r_{1\ldots G})}$$

损失沿用 PPO 的 clip 形式:

$$\mathcal{L}_\text{GRPO} = -\mathbb{E}\Big[\min\!\big(\rho_i \hat A_i,\,\text{clip}(\rho_i,\,1-\epsilon,\,1+\epsilon)\hat A_i\big)\Big] + \beta\mathrm{KL}(\pi\|\pi_\text{ref})$$

$\rho_i = \pi_\theta(y_i|x)/\pi_{\theta_\text{old}}(y_i|x)$ 是 importance ratio。

与 PPO 的差异

PPOGRPO
Critic需要不需要
AdvantageGAEgroup 内 z-score 归一化
每 step rollout 数1G(通常 4-16)
显存4 模型3 模型(无 critic)

7.2用 GRPOTrainer

from trl import GRPOConfig, GRPOTrainer

# 1) reward function(rule-based 或 RM 调用)
def reward_correct_math(completions, **kwargs):
    """每条 completion 返回一个标量 reward"""
    return [check_math_correct(c) for c in completions]

# 2) config
cfg = GRPOConfig(
    output_dir="./grpo_out",
    per_device_train_batch_size=2,
    num_generations=8,              # ★ G
    max_prompt_length=512,
    max_completion_length=1024,
    learning_rate=1e-6,
    beta=0.04,
    num_train_epochs=1,
    bf16=True,
    use_vllm=True,                  # ★ 用 vLLM 加速 rollout
    vllm_mode="colocate",
)

# 3) 数据:只有 prompt 列
ds = load_dataset("openai/gsm8k", "main", split="train[:5000]")
ds = ds.rename_column("question", "prompt")

# 4) 训
trainer = GRPOTrainer(
    model=base_model,
    reward_funcs=[reward_correct_math],
    args=cfg,
    train_dataset=ds,
    processing_class=tok,
)
trainer.train()

7.3reward function 三种来源

来源例子场景
规则验证数学答案正确 / 代码 unit test passR1 / 推理任务
外部 RM(HF 模型)传 RM 路径,TRL 自动调通用对齐
用户自定义 Python 函数关键词命中率、格式检查定制化

函数签名:fn(completions: list[str], **kwargs) -> list[float]kwargs 里有 prompts / dataset 列等,可以做条件 reward。

7.4多 reward function 加权

def reward_format(completions, **kw):
    """要求 <think>...</think><answer>...</answer> 格式"""
    return [1.0 if "<answer>" in c else 0.0 for c in completions]

def reward_correct(completions, ground_truth=None, **kw):
    return [extract_answer(c) == ground_truth[i] for i, c in enumerate(completions)]

cfg = GRPOConfig(..., reward_weights=[0.1, 1.0])

trainer = GRPOTrainer(
    ..., reward_funcs=[reward_format, reward_correct], ...,
)

R1 风格训练通常配两个 reward:格式 reward 教模型用 <think> 思考、答案 reward 评估正确性

7.5核心字段

字段含义建议
num_generations (G)每个 prompt 采几个 response8-16
max_completion_lengthresponse 长度上限推理任务 2K-4K
betaKL 强度0.001-0.04 比 DPO 小
epsilonPPO clip ε0.2 默认
use_vllm用 vLLM 加速★ True
vllm_mode"colocate" / "server"单机 colocate,多机 server
num_iterations每批样本重复 update 几次1(off-policy 风险)
scale_rewardsz-score 归一化(默认 True)True

7.6训练时间预算

Qwen2.5-7B、8×A100-80GB、GSM8K、G=8、4K seqlen:

阶段耗时(每 step)
rollout(vLLM 加速)~12 s(不开 vLLM ~40 s)
reward 计算~1 s
policy update~6 s
~20 s / step(200 prompts/step)
10k step 总训~55 h

7.7常见踩坑

现象处理
reward 一直为 0 / 全一样检查 reward function 返回;group std=0 时 advantage NaN
KL 一直涨beta 太小;从 0.04 起调
响应越来越短 / 长length bias;用 reward 归一化或加 length penalty
rollout 卡死vLLM 显存不够;调小 num_generations
训完只会写 <think> 不算答案format reward 权重太高;降到 0.1

7.84 个内置 reward function

仓库自带 4 个开箱 reward,trl/rewards/__init__.py:21–25。比自己写更省事,又能学函数签名约定:

函数返回场景
accuracy_reward(completions, solution, **kw) list[float | None];正确 1.0、错 0.0、无法解析 Nonemath_verify 校验数学答案
reasoning_accuracy_reward(completions, solution, reasoning_delimiters=[""], **kw)同上;先剥 think 块再校验R1 / 思考链推理
think_format_reward(completions, **kw) list[float];只看 <think>...</think> 完整性format reward
get_soft_overlong_punishment(max_completion_len, soft_punish_cache)工厂函数,返回 callable;超长软惩罚(DAPO Eq.13)限制 response 长度

调用约定:

7.9R1 风格的"两 reward 组合"

trl/scripts/grpo.py:82–101 自带一个 reward registry,CLI 用字符串选 reward:

trl grpo \
    --model_name_or_path Qwen/Qwen2.5-7B \
    --reward_funcs accuracy_reward think_format_reward \
    --reward_weights 1.0 0.1 \
    --use_vllm true

背后逻辑(trl/scripts/grpo.py):

  1. 注册表把字符串 "accuracy_reward" 解析成 trl.rewards.accuracy_reward
  2. 支持点分导入路径--reward_funcs my_lib.rewards.custom_reward 自动 importlib
  3. reward_weights 一一对应 reward_funcs

7.10vLLM 集成:server / colocate 真实差异

本章 7.2 提了 use_vllm=True。v1 把 vllm_mode 默认值从 "server" 改成 "colocate"(ch01.9 已警告)。两者真实差异:

维度servercolocate(v1 默认)
vLLM 进程 独立 Ray actor(先 trl vllm-serve 启) 训练进程内启 vLLM 实例(占独立 CUDA device)
权重同步 HTTP POST 走 NCCL collective 同进程,PyNcclCommunicator 直接广播
多机扩展 ✅ vLLM 服务跨节点 ❌ 仅单节点
启动命令 需要先 trl vllm-serve 再启 trainer 一条命令 trl grpo 就行
CUDA_VISIBLE_DEVICES需手动错开训练卡和 vLLM 卡 需手动错开(colocate 要求 vLLM 用独立卡)

colocate 关键 caveat:训练和 vLLM 必须用不同的GPU。例如 8 卡机:

# 前 4 卡训练,后 4 卡 colocate vLLM
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
trl grpo --use_vllm true --vllm_mode colocate \
         --vllm_gpu_memory_utilization 0.3 \
         ...

"server 还是 colocate?" → 单机选 colocate(v1 默认);多机选 server。

7.11num_generations 与 batch 的整除约束

GRPOConfig.num_generations(grpo_config.py:56–58)默认 8。约束:

违反时报错 num_generations must divide effective batch size。把 num_generations 调到能整除最容易,或反过来调 batch。

7.12这章你需要带走的