核心概念:Actor / Critic / Reference / Reward 四个角色
RLHF 训练里同时存在 4 个模型副本,每个干不同的事。它们的角色分工是整个 OpenRLHF 架构的第一性原理—— 之后所有的 Ray 调度、显存优化、算法变体,都是在"如何更高效地让这 4 个角色协作"上做文章。 本章把这 4 个角色一一讲透。
4.1RLHF 训练循环里发生了什么
(= Actor 权重的推理副本)"] Ref["Reference (冻结的 SFT 模型)"] Reward["Reward (RM 或规则函数)"] Critic["Critic (value head)"] Prompt --> Rollout Rollout --> Resp[Response 序列] Resp --> Ref Resp --> Reward Resp --> Critic Ref --> RefLogp[ref_logp] Reward --> R[r] Critic --> V[V] RefLogp --> Adv["Advantage 计算
+ KL 惩罚"] R --> Adv V --> Adv Adv --> Update["Actor + Critic
梯度更新"] Update -.同步.- Rollout
4.2四个角色一字排开
| 角色 | 权重 | 是否更新 | 起源 | 作用 |
|---|---|---|---|---|
| Actor | 1× | 每步 | SFT 模型 | 正在被训练的 policy,目标是最大化期望 reward。 |
| Reference | 1× | ❌ 冻结 | SFT 模型副本 | 提供 KL 锚定基准,避免 actor 漂离原模型太远。 |
| Reward | ~1× | ❌ 冻结 | RM 训练后;或 函数 / API |
给 (prompt, response) 打分。 |
| Critic | ~1× | 每步 | 从 RM 初始化 | 对每个 token 估计 value $V(s_t)$,用于算 advantage。 |
4.3逐一深入
Actor —— 真正在训的模型
Actor 就是你最后要拿走部署的那个模型。它从 SFT 模型初始化,每 step:
- 采样阶段,把当前权重同步到 vLLM rollout engine;
- vLLM 生成 response;
- 训练阶段,actor 在自己的 DeepSpeed engine 里对 response 做 forward + backward;
- 用 PPO loss 更新参数。
PPO loss(policy 部分):
$$ \mathcal{L}_\text{policy} = -\mathbb{E}\!\left[\min\!\big(\rho_t \hat A_t,\;\mathrm{clip}(\rho_t,\,1-\epsilon,\,1+\epsilon)\hat A_t\big)\right] $$
其中 $\rho_t = \pi_\theta(a_t|s_t) / \pi_{\theta_\text{old}}(a_t|s_t)$ 是重要性比,$\hat A_t$ 是 advantage。
Reference —— 冻结的"锚"
Reference 是 SFT 后的模型副本,从训练开始到结束完全不动。它的唯一作用是提供 ref_logp,用来算 KL:
$$ \mathrm{KL}(\pi_\theta \| \pi_\text{ref}) \approx \mathbb{E}\!\left[\log \pi_\theta(y|x) - \log \pi_\text{ref}(y|x)\right] $$
KL 的两种放法(OpenRLHF 都支持):
| 放法 | 含义 | OpenRLHF 配置 |
|---|---|---|
| KL → reward | $r_\text{total} = r - \beta_\text{KL} \cdot \mathrm{KL}$ | --algo.kl.in_reward |
| KL → loss | $\mathcal{L}_\text{total} = \mathcal{L}_\text{policy} + \beta_\text{KL} \cdot \mathrm{KL}$ | --algo.kl.in_loss |
OpenRLHF 默认 KL → reward,更接近 OpenAI 经典 PPO。
Reward —— 给打分
Reward 有三种来源:
- RM 模型(默认):用偏好数据训练好的 reward model,输出标量。
- 规则函数(推理 RL 流行):写一个
def reward_func(queries, prompts, labels) -> dict,返回 0/1 或连续分。 - Remote URL:把 reward 计算放到 HTTP server,训练时调用。
三种通过 train_ppo_ray 不同 flag 切换:
# 1. RM 模型
--reward.model_name_or_path OpenRLHF/Llama-3-8b-rm-700k
# 2. 本地规则函数
--reward.remote_url /openrlhf/examples/python/reward_func.py
# 3. Remote HTTP service
--reward.remote_url http://reward-server:8000/score
第 10 章会详讲怎么写自定义 reward。
Critic —— value 估计
Critic 的存在是 PPO 区别于 REINFORCE 的关键:它给每个 token 估一个 baseline $V(s_t)$,用来算 advantage:
$$ \hat A_t^\text{GAE} = \sum_{l=0}^{\infty} (\gamma\lambda)^l \delta_{t+l}, \quad \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t) $$
这是 GAE(Generalized Advantage Estimation)。它能降低梯度方差,是 PPO 收敛稳定的关键。
Critic 通常用 reward model 的权重初始化(把分类头改成 value head),训练时也走 DeepSpeed engine。 它的 loss 是 value regression:
$$ \mathcal{L}_\text{critic} = \mathbb{E}\!\left[(V_\theta(s_t) - R_t)^2\right] $$
GRPO 的招是同 prompt 采样 G 个 response,用组内归一替代 critic:
$$\hat A_i = \frac{r_i - \mathrm{mean}(\{r_j\})}{\mathrm{std}(\{r_j\})}$$
所以 GRPO 训练时不需要 critic,4 个角色变 3 个,显存省 25%+。这是 DeepSeek-R1 流行的关键工程理由。
4.4第 5 个"隐形"角色:Rollout
严格说还有第 5 个角色——vLLM Rollout,它不是独立的模型,是 actor 权重的"推理副本":
| 维度 | Actor | Rollout |
|---|---|---|
| 权重内容 | 实时训练参数 | 从 actor 同步过来的快照 |
| 计算图 | 有反向 | 只有前向 + KV cache |
| 底层引擎 | DeepSpeed | vLLM |
| 更新时机 | 每个 PPO step | 每个 rollout 阶段从 actor 同步 |
| 显存布局 | fp32 master + bf16 | bf16 weights + PagedAttention KV cache |
这种"训练 actor + 推理 rollout 分离"的设计是 OpenRLHF(也是 verl)相对于 DeepSpeed-Chat 的核心优势。
为什么不直接用 actor 做 generate()?第 6 章详讲。
4.5四角色的协作时序
4.6四个角色在 OpenRLHF 代码里的位置
| 角色 | 源码位置 | 核心类 |
|---|---|---|
| Actor | openrlhf/models/actor.py | Actor |
| Reference | 用 Actor 同一个类,requires_grad=False | — |
| Reward | openrlhf/models/model.py | get_llm_for_sequence_regression() |
| Critic | 同上(也是 sequence regression head) | — |
| Rollout | openrlhf/trainer/ray/vllm_engine.py | LLMRayActor |
每个角色被一个 RayActorGroup 包起来(见第 5 章),controller 通过 RPC 调它们的方法。
4.7从 PPO 切到 GRPO:丢掉哪个角色
| 算法 | 需要 Actor | 需要 Ref | 需要 Reward | 需要 Critic |
|---|---|---|---|---|
| PPO | ✓ | ✓ | ✓ | ✓ |
| GRPO | ✓ | ✓ | ✓ | ✗ |
| RLOO | ✓ | ✓ | ✓ | ✗ |
| REINFORCE++ | ✓ | ✓ | ✓ | ✗ |
| REINFORCE++ baseline | ✓ | ✓ | ✓ | ✗ |
| DPO(offline) | ✓ | ✓ | ✗ | ✗ |
所以"切到 GRPO 后还省一倍显存",本质上是丢掉 critic 这个角色。
4.8Actor 类全字段(源码锚)
四角色里 Actor 是唯一一个"独立类"(其他三个本质都是包装 HF 模型)。openrlhf/models/actor.py:54 的 Actor.__init__ 字段:
| 字段 | 含义 |
|---|---|
pretrain_or_model | HF 仓库 ID / 本地路径 / 已加载的 nn.Module |
attn_implementation | 默认 "flash_attention_2" |
param_dtype | bf16 / fp16;多数情况 bf16 |
load_in_4bit | QLoRA |
lora_rank / lora_alpha | 启用 PEFT LoRA;rank=0 即不开 |
ds_config | DeepSpeed 配置(由 Strategy 注入) |
packing_samples | 多样本拼成长序列,节省 padding |
temperature | 采样温度(影响 log_prob 计算) |
use_liger_kernel | 启用 Liger fused kernel 加速 |
freeze_visual_encoder | VLM 时是否冻结视觉塔 |
experts_implementation | MoE 模型的 expert 实现(grouped_gemm / native 等) |
这个类同时被 actor / reference / ppo_actor 三处复用,区别只在 requires_grad 和 ds_config。
4.9KL 估计的三种方法
KL 在 PPO 里频繁出现。openrlhf/models/utils.py:48 的 compute_approx_kl(log_probs, log_probs_base, kl_estimator) 提供三种 estimator:
| kl_estimator | 公式 | 取舍 |
|---|---|---|
"k1" | $\log p - \log q$ | 原始定义,有正负,方差大 |
"k2" | $\tfrac{1}{2}(\log p - \log q)^2$ | 对称,永远 ≥ 0 |
"k3" | $\exp(\log q - \log p) - (\log q - \log p) - 1$ | 低方差近似(Schulman),实战默认 |
YAML 字段 --algo.kl.estimator k3。k3 是 OpenAI Schulman 论文的写法,方差远小于 k1,PPO 训得更稳。
4.10reward 的 KL 注入:只在 EOS 一次
compute_reward(r, kl_coef, kl, action_mask, reward_clip_range)(utils.py:82)有个容易踩的坑:
r= RM 给的序列级标量分数(只在最后一个 token);kl= 每 token 的 KL(actor 与 ref 的 logp 差);- 合成时
token_reward = -kl_coef * kl(每 token 一个负值惩罚),只在 EOS 那一位再加r; reward_clip_range=(min, max)把 r clip 到一个范围,避免 RM 输出离谱值带偏 advantage。
这就是为什么很多人疑惑"KL 是 token-level 还是 sequence-level"——OpenRLHF 走 token-level 惩罚 + EOS 一次性 reward 注入,跟 OpenAI 经典 PPO 一致。
4.11这章你需要带走的
- RLHF = Actor + Reference + Reward + Critic 四个模型同时在线;
- Actor 是唯一会"长大"的,Reference 永远冻结当锚;
- Reward 可以来自 RM / 规则函数 / Remote API 三种;
- Critic 用 GAE 给 advantage 当 baseline,是 PPO 稳定性的核心;
- vLLM Rollout 是 Actor 的"推理副本",每 step 同步;
- GRPO 不需要 Critic,省 25%+ 显存——这是 R1 流行的关键工程理由。