PPO 与 GRPO:RLHF 全流程
8.1 经典 RLHF 三阶段回顾
当 DPO 系算法不够用(多步推理、有可验证 reward)时,仍然要走经典 RLHF:
stage=sft, finetuning_type=lora] S1 --> S2[Stage 2: Reward Model
stage=rm] S2 --> S3[Stage 3: PPO
stage=ppo
reward_model=saves/.../rm] S3 --> Done([对齐模型])
LLaMA-Factory 三阶段都用同一份 CLI,只换 YAML。
8.2 Stage 2:Reward Model 训练
仓库自带 examples/train_lora/qwen3_lora_reward.yaml:
### method
stage: rm # 1) 阶段切到 rm
do_train: true
finetuning_type: lora
lora_rank: 8
lora_target: all
### dataset
dataset: dpo_en_demo # 数据格式 = preference(chosen/rejected)
template: qwen3_nothink
### train
per_device_train_batch_size: 1
gradient_accumulation_steps: 8
learning_rate: 1.0e-4
num_train_epochs: 3.0
RM 的损失函数
把语言模型的 lm_head 换成一个标量头,输出 reward $r(x,y)$。然后在偏好对上做 Bradley-Terry 对比:
$$\mathcal{L}_\text{RM} = -\mathbb{E}_{(x,y_w,y_l)}\!\left[\log \sigma\!\big(r(x,y_w) - r(x,y_l)\big)\right]$$
训练好后 saves/.../rm/ 里有:
- LoRA 适配器(标量头权重在内);
adapter_config.json;value_head.bin(标量头单独保存)。
8.3 Stage 3:PPO
PPO 目标函数
OpenAI 经典 PPO(actor-critic + clipped objective):
$$ \mathcal{L}_\text{PPO}(\theta) = \mathbb{E}\!\left[\min\!\Big(\rho_t \hat A_t,\;\mathrm{clip}(\rho_t, 1-\epsilon, 1+\epsilon)\hat A_t\Big)\right] - c_v \mathcal{L}_v + c_e \mathcal{H}[\pi_\theta] $$
其中 $\rho_t = \tfrac{\pi_\theta(a_t|s_t)}{\pi_{\theta_\text{old}}(a_t|s_t)}$ 是重要性比,$\hat A_t$ 是优势估计。LLaMA-Factory 还会加一项与 reference 的 KL 控制:
$$r_\text{total}(x,y) = r_\phi(x,y) - \beta_\text{KL}\,\mathrm{KL}\!\big(\pi_\theta(\cdot|x)\,\|\,\pi_\text{ref}(\cdot|x)\big)$$
典型 PPO YAML
### model
model_name_or_path: Qwen/Qwen3-4B-Instruct-2507
adapter_name_or_path: saves/qwen3-4b/lora/sft # 从 SFT 后的 adapter 继续
trust_remote_code: true
### method
stage: ppo
do_train: true
finetuning_type: lora
lora_rank: 8
lora_target: all
reward_model: saves/qwen3-4b/lora/reward # ★ 必填
reward_model_type: lora # 这里 RM 也是 LoRA
create_new_adapter: true # ★ 不要把 PPO 写回 SFT adapter
### dataset
dataset: alpaca_en_demo # 只需 prompt,PPO 不要 chosen/rejected
template: qwen3_nothink
cutoff_len: 1024
### train
per_device_train_batch_size: 1
gradient_accumulation_steps: 4
learning_rate: 1.0e-5 # ★ PPO 学习率更小
num_train_epochs: 1.0
bf16: true
### ppo
ppo_buffer_size: 1
ppo_epochs: 4
ppo_score_norm: true
ppo_target: 6.0
ppo_whiten_rewards: true
ppo_logger: tensorboard
PPO 内部 4 个模型的角色
| 模型 | 是否更新 | 显存 | 谁出 |
|---|---|---|---|
| policy | 每步 | 1× | 基座 + 当前 LoRA |
| reference | 冻结 | 1× | SFT 后的模型(基座 + SFT LoRA) |
| reward | 冻结 | ~1× | reward_model 指定 |
| critic | 每步 | ~1× | policy 顶部 value head,权重共享 |
所以 PPO 显存开销 ≈ 4 × LoRA SFT。这就是为什么 RLHF 在 LLaMA-Factory 里通常 7B 起步,70B 必须 ZeRO-3。
8.4 GRPO:去 critic 化的 PPO 变体
DeepSeek-R1 火了之后,GRPO(Group Relative Policy Optimization) 受到广泛关注。它的核心 idea:
- 对同一 prompt 采样 $G$ 个回答;
- 用组内相对值替代 critic:$\hat A_i = \tfrac{r_i - \mathrm{mean}(r)}{\mathrm{std}(r)}$;
- 因此不再需要 critic 模型,显存减少 25%。
$$ \mathcal{L}_\text{GRPO} = \mathbb{E}\!\left[\tfrac{1}{G}\!\sum_{i=1}^G \min\!\Big(\rho_i \hat A_i,\;\mathrm{clip}(\rho_i,1-\epsilon,1+\epsilon)\hat A_i\Big)\right] - \beta\,\mathrm{KL}(\pi_\theta\|\pi_\text{ref}) $$
在 LLaMA-Factory 中,GRPO 通过 stage=ppo + 特定参数实现(仓库正在把它独立成 stage,新版会更直接):
stage: ppo
ppo_grpo: true # 开 GRPO 模式
ppo_grpo_group_size: 8 # 每个 prompt 采样 8 个回答
# critic 不需要了
ppo_whiten_rewards: false # GRPO 自带组内归一
新版 LLaMA-Factory 已经把 GRPO 单独放进了 src/llamafactory/train/mca/(Multi-Choice Advantage 系),有迭代风险,写前 grep 一下确认字段名。
8.5 PPO 不收敛 / 训坏的常见原因
| 现象 | 多半原因 | 怎么调 |
|---|---|---|
| KL 飙升、模型胡说 | policy 漂离 reference 太远 | 调大 ppo_target(自适应 KL 控制器目标);降学习率 |
| reward 一路涨但语义变差 | 典型 reward hacking | RM 数据增强;reward 标签人审;缩短 PPO epoch 数 |
| reward 卡在低位 | 策略不敢探索 | 升 temperature;减小 ppo_score_norm |
| 显存周期性爆炸 | 采样 sequence 太长 | 限制 max_new_tokens;开 gradient_checkpointing |
| 每个 step 都 NaN | fp16 数值范围太小 | 改 bf16;降 learning_rate 至 1e-6 级别 |
| GRPO 训完模型同质化 | 组内 reward 差异过小 | 升 ppo_grpo_group_size;用更难的 prompt |
8.6 RLHF 与 DPO 系怎么选
| 业务诉求 | 建议 |
|---|---|
| "我想让模型回答更礼貌 / 更安全" | 偏好对齐 DPO / ORPO 就够 |
| "我希望模型答数学 / 代码考试" | GRPO + 规则 reward / 可验证奖励 |
| "我有一个可信打分函数(编译通过、单测通过)" | GRPO 最佳(不用训 RM) |
| "我已有大量人类成对偏好" | DPO 系 |
| "我希望严格复现 InstructGPT" | PPO 三阶段 |
| "显存只够跑 LoRA" | DPO 或 ORPO;PPO 要 4× LoRA 显存 |
8.8 EasyR1:把 GRPO 拉出 stage=ppo 的独立路径
2025-02 之后仓库把 R1 风格 GRPO 训练分离到了独立模块——README "Changelog" 里写的 "EasyR1 framework"。本书基线 commit 上它的位置:
- YAML 仍然走
llamafactory-cli train,但stage: ppo + ppo_grpo: true内部会被识别为 EasyR1 路径; - 更新的字段路径在
src/llamafactory/train/mca/(MCA = Multi-Choice Advantage); - 与原 PPO 共享相同的 reward 接口(rule / lora / full / api),但不再需要 value 模型。
"现在该走 PPO 还是 EasyR1 GRPO"的快速决策:
| 条件 | 选谁 |
|---|---|
| 有人类偏好 RM | 经典 PPO |
| 有规则 reward(math_verify / 代码沙箱) | EasyR1 GRPO(无 critic 省一半显存) |
| 想最贴 DeepSeek-R1 训练流程 | EasyR1 GRPO + 长 CoT |
| 只有偏好对,没规则 reward | 退 DPO/ORPO/SimPO(见 ch07) |
8.9 PPO 关键字段:buffer / score_norm / target / type
PPO 字段在 finetuning_args.py 的 RLHFArguments mixin 里。下表是实战调参最容易碰到的 4 个,其余在 ch05 已有总览:
| 字段 | 默认 | 调优影响 |
|---|---|---|
ppo_buffer_size | 1 | PPO 一次采几个 mini-batch 后再训;调大稳定性升、显存升 |
ppo_epochs | 4 | 每批数据训几轮;太多容易 reward hacking |
ppo_score_norm | false | reward 是否归一化;规则 reward 通常开 true,神经 RM 看分布 |
ppo_target | 6.0 | 自适应 KL 控制器的目标 KL;超出会自动加大 KL 惩罚 |
ppo_whiten_rewards | false | GRPO 模式下要关掉,否则双重归一 |
reward_model | — | RM 路径(必填) |
reward_model_type | lora | lora / full / api 三选一 |
reward_model_adapters | — | RM 走 LoRA 时的 adapter path |
三种 reward_model_type 的差异
| type | 含义 | 典型场景 |
|---|---|---|
lora | RM = base + LoRA adapter(最常用,省显存) | 同一 base 训 SFT → RM → PPO 整条线 |
full | RM = 独立完整 HF 模型 | 外部预训练好的奖励模型 |
api | RM 是远程 HTTP 服务 | 用 GPT-4 / Claude / 自家服务打分 |
API 模式特别有用——可以用更强的 LLM 当 judge,避免训本地 RM。仓库在 train/ppo/ 里有相应的 client。
8.10 value head:PPO 独有的"第二个头"
经典 PPO 需要 critic 估 value function。LLaMA-Factory 走的是 TRL 的 AutoModelForCausalLMWithValueHead:在 base 顶上加一个 linear 输出 scalar value。两条加载路径:
| 情形 | 行为 |
|---|---|
| actor 和 critic 共享 base | 同一份权重,actor 头出 logits,critic 头出 value |
| actor 和 critic 分开 | 各自加载(reward_model_type: full 时常见) |
save 时 value head 会被单独 dump 成 v_head.safetensors。load 时同名加载。merge LoRA 时不会带 value head——这就是为什么 export 的模型可以直接拿来 inference,不会有冗余的 value 输出。
8.11 重要工程注意
- PPO YAML 必须显式写
create_new_adapter: true,否则会把 PPO 的更新写回 SFT adapter,破坏前一阶段成果。 - RM checkpoint 路径放在
reward_model,并且reward_model_type要选对(lora / full / api)。api模式允许 reward 来自外部 HTTP,方便集成 GPT-4 评分。 - PPO 启动时显存峰值在采样阶段,
per_device_train_batch_size=1并不能保证不 OOM——还要看采样 sequence 长度。 - 多卡 PPO 推荐配合 DeepSpeed ZeRO-2/3,详见第 10 章。