GRPO 实战:告别 Critic 模型,低成本扩展 LLM 推理能力

Group Relative Policy Optimization (GRPO)

瓶颈:PPO 的显存重负与 DPO 的局限

多年来,Proximal Policy Optimization (PPO) 一直是 RLHF(人类反馈强化学习)的黄金标准。然而,它带来了巨大的基础设施负担:你需要同时在显存中加载四个模型(Actor、Critic、Reference 和 Reward Model)。对于 70B+ 参数的模型来说,除非是头部大厂,否则微调成本高不可攀。

Direct Preference Optimization (DPO) 通过移除强化学习循环,将对齐视为对偏好对(Preference Pairs)的监督分类问题,从而解决了显存问题。但是,DPO 在思维链(Chain-of-Thought, CoT)优化方面表现乏力。它依赖于静态数据集,无法像 RL 那样内在地鼓励模型去探索不同的推理路径。

这就轮到 Group Relative Policy Optimization (GRPO) 登场了。随着 DeepSeek R1/V3 研究 的普及,GRPO 彻底移除了 Critic 模型。它不再估计价值函数(Value Function),而是针对同一个提示词生成一组输出,计算奖励,并利用组平均值作为基线(Baseline)。这种方法在大幅降低显存占用的同时,保留了 RL 的探索优势,使其成为优化数学和代码推理能力的最佳选择。


架构逻辑:GRPO 如何消灭价值函数

在标准的 PPO 中,优势函数(Advantage function) $A_t$ 的计算依赖于一个价值网络(Critic)。GRPO 则用一组采样输出的平均奖励来替代它。

算法流程

  1. 采样 (Sampling): 对于每个提示词 $q$,从旧策略 $\pi_{\theta_{old}}$ 中采样一组 $G$ 个输出 ${o_1, o_2, …, o_G}$。
  2. 打分 (Scoring): 应用奖励模型(或基于规则的验证器)获得奖励 $r_1, …, r_G$。
  3. 优势计算 (Advantage Calculation): 基于组内的相对表现计算每个输出的优势值。
  1. 优化 (Optimization): 最大化 GRPO 目标函数,其中包括 KL 散度惩罚,以保持模型不偏离参考策略太远。

工作流可视化

graph TD
    subgraph "GRPO 工作流"
    A["输入提示词 (q)"] --> B{"策略模型 (Policy)"}
    B --"采样 G 个输出"--> C["输出集 {o1, o2... oG}"]
    C --> D["奖励函数 / 验证器"]
    D --"计算奖励"--> E["计算组均值 & 标准差"]
    E --> F["计算优势值 (Ai)"]
    F --> G["更新策略 (无 Critic 网络)"]
    end
    
    subgraph "传统 PPO"
    X["输入"] --> Y["Actor"]
    X --> Z["Critic (价值网络)"]
    Y --> R["奖励模型"]
    R & Z --> CALC["GAE 估计"]
    end

代码实现

我们将使用 Hugging Face TRL (Transformer Reinforcement Learning) 库来实现 GRPO,该库近期已集成了对组相对策略的支持。

前置条件

  • 硬件: 单张 A100 (80GB) 或同等显存,足以训练 7B-14B 模型。GRPO 非常节省显存。
  • 依赖库: trl>=0.11.0, transformers, torch

Python 实现脚本

本脚本演示了如何使用确定性奖励函数(即“验证器”)而非神经奖励模型来对齐模型以解决数学问题。这复刻了 DeepSeek-Math 的核心思路。

import torch
from datasets import load_dataset
from trl import GRPOTrainer, GRPOConfig
from transformers import AutoTokenizer, AutoModelForCausalLM

# 1. 配置
# 注意:我们不需要加载 Critic 模型
MODEL_ID = "deepseek-ai/deepseek-coder-6.7b-instruct"
OUTPUT_DIR = "./grpo-reasoning-adapter"

training_args = GRPOConfig(
    output_dir=OUTPUT_DIR,
    num_train_epochs=1,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=2,
    learning_rate=5e-6,
    beta=0.04,          # KL 惩罚系数
    max_grad_norm=1.0,
    logging_steps=10,
    save_strategy="steps",
    # GRPO 特有参数
    num_generations=8,  # 组大小 (G)。越大基线估计越准,但显存开销越大。
    max_completion_length=512,
    bf16=True
)

# 2. 奖励函数 (验证器)
# 在此场景中,我们使用简单的算术检查器。
# 在生产环境中,这里应该解析模型的 <answer> 标签并与标准答案(Ground Truth)对比。
def arithmetic_reward_func(prompts, completions, answer, **kwargs):
    rewards = []
    for comp, true_ans in zip(completions, answer):
        # 启发式规则:检查标准答案数字是否出现在输出中
        # DeepSeek 在这里使用了更严格的解析逻辑
        score = 1.0 if str(true_ans) in comp else 0.0
        
        # 行为整形 (Shaping):如果答案错误且废话连篇,给予额外惩罚
        if score == 0.0 and len(comp) > 200:
            score -= 0.1
            
        rewards.append(score)
    return rewards

# 3. 加载数据与模型
dataset = load_dataset("gsm8k", "main", split="train[:1%]") # 仅使用极小集用于演示

tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
tokenizer.pad_token = tokenizer.eos_token

# 加载 4-bit 模型以进一步节省显存(如需)
model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID,
    torch_dtype=torch.bfloat16,
    device_map="auto",
    attn_implementation="flash_attention_2"
)

# 4. 初始化 GRPO Trainer
trainer = GRPOTrainer(
    model=model,
    reward_processing_class=tokenizer, # 内部处理 Tokenization
    args=training_args,
    train_dataset=dataset,
    reward_funcs=arithmetic_reward_func, # 支持多个奖励函数
)

# 5. 执行训练
if __name__ == "__main__":
    print(f"Starting GRPO with Group Size: {training_args.num_generations}")
    trainer.train()
    trainer.save_model(OUTPUT_DIR)

实现步骤详解

  1. 定义验证器 (Verifier): 与需要偏好数据集(Chosen vs Rejected)的 DPO 不同,GRPO 需要提示词标准答案reward_funcs 的逻辑至关重要。对于代码任务,这里运行单元测试;对于数学任务,这里检查数值等价性。
  2. 设置组大小 ($G$):GRPOConfig 中,num_generations 参数控制 $G$。
    • 低 $G$ (如 4): 优势估计方差大,训练不稳定。
    • 高 $G$ (如 16+): 基线更准,但训练时的推理成本线性增加。
  3. 调整 Beta ($\beta$): KL 惩罚在 GRPO 中的作用与 DPO 略有不同。建议从 0.04 开始(参考 DeepSeek-Math),而不是标准的 0.1
  4. 格式控制: 确保你的 Prompt 引导模型输出严格格式的答案(例如 “Put the final answer in \boxed{}“),以便奖励函数能可靠地提取答案。

对比:GRPO vs 其他方案

特性 PPO DPO GRPO
显存占用模型数 4 (Actor, Critic, Ref, RM) 2 (Policy, Ref) 2 (Policy, Ref)
数据需求 偏好对 或 奖励信号 偏好对 (离线) 提示词 + 验证器 (Evaluator)
推理能力 高 (允许探索) 低 (离线克隆) 高 (通过组采样探索)
稳定性 低 (对超参数极敏感) 中-高
最佳应用场景 通用聊天 聊天 / 风格迁移 数学、逻辑、代码

GRPO 代表了从基于代理的优化(训练一个 Reward Model 来猜测人类喜好)到基于结果的优化(直接检查答案是否正确)的转变。通过移除 Critic 模型,你可以释放大约 30-40% 的显存,这让你能够训练更大的模型或增加 Batch Size。

对于 2026 年的重推理工作负载而言:DPO 负责风格,GRPO 负责对错。