GRPOTrainer:DeepSeek-R1 同款算法
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 的差异
| PPO | GRPO | |
|---|---|---|
| Critic | 需要 | 不需要 |
| Advantage | GAE | group 内 z-score 归一化 |
| 每 step rollout 数 | 1 | G(通常 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 pass | R1 / 推理任务 |
| 外部 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 采几个 response | 8-16 |
max_completion_length | response 长度上限 | 推理任务 2K-4K |
beta | KL 强度 | 0.001-0.04 比 DPO 小 |
epsilon | PPO clip ε | 0.2 默认 |
use_vllm | 用 vLLM 加速 | ★ True |
vllm_mode | "colocate" / "server" | 单机 colocate,多机 server |
num_iterations | 每批样本重复 update 几次 | 1(off-policy 风险) |
scale_rewards | z-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、无法解析 None | math_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 长度 |
调用约定:
- 函数签名
fn(completions, **kwargs) → list[float | None]; completions是list[list[dict]](每条 completion 是[{"role": "assistant", "content": "..."}]);**kwargs接收 trainer 传入的 dataset 其余列(如solution);- 返回
None表示"跳过这条样本"(不计入 group 统计); - 返回
list[float]长度必须等于 completions 数量。
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):
- 注册表把字符串
"accuracy_reward"解析成trl.rewards.accuracy_reward; - 支持点分导入路径:
--reward_funcs my_lib.rewards.custom_reward自动importlib; reward_weights一一对应reward_funcs。
7.10vLLM 集成:server / colocate 真实差异
本章 7.2 提了 use_vllm=True。v1 把 vllm_mode 默认值从 "server" 改成 "colocate"(ch01.9 已警告)。两者真实差异:
| 维度 | server | colocate(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。约束:
- 同一 prompt 内才能算 group 内 advantage;
- effective batch size 必须整除 num_generations,否则 group 分组对不齐;
- 实战:
per_device_train_batch_size × num_processes × gradient_accumulation_steps应当是 num_generations 的整数倍。
违反时报错 num_generations must divide effective batch size。把 num_generations 调到能整除最容易,或反过来调 batch。
7.12这章你需要带走的
- GRPO = PPO 去 critic + group baseline + 通常配 rule-based reward;
- 显存比 PPO 省 1 份(无 critic);rollout 慢但效果好;
- reward function 是
list[str] -> list[float],可以多个加权; - R1 风格 = format reward (0.1) + correctness reward (1.0);
- 用
use_vllm=Truerollout 速度提 3-4×。