Chapter 08

PPO 与 GRPO:RLHF 全流程

📌 本章基于 src/llamafactory/train/ppo/ 与 src/llamafactory/train/rm/ · commit 01398eb18dd4 。

8.1 经典 RLHF 三阶段回顾

当 DPO 系算法不够用(多步推理、有可验证 reward)时,仍然要走经典 RLHF:

flowchart LR S0([Base 模型]) --> S1[Stage 1: SFT
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/ 里有:

提示 RM 数据 = DPO 数据。可以一份偏好数据集同时训 RM 和做 DPO,互为校验。

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 每步 基座 + 当前 LoRA
reference冻结 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:

$$ \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 上它的位置:

"现在该走 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.pyRLHFArguments 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 重要工程注意