REINFORCE 家族:RLOO、ReMax、REINFORCE++
这一组算法的共同目标是:尽量少依赖 critic,用更简单的 baseline 降低 policy gradient 的方差。它们很适合和 GRPO 放在一起学,因为它们都在回答同一个问题:没有 value model 时,advantage 从哪里来?
先补 policy gradient 先验
最朴素的 REINFORCE 梯度是:
其中 x 是 prompt,y 是 response,R(y) 是整条 response 的 reward。问题是方差很大:同一个 prompt 下采样到的回答质量会抖,直接用 reward 乘 logprob 会让训练很吵。
baseline 的作用是把 reward 换成相对值:
只要 baseline b 不依赖当前这条 action 的随机选择,就不会改变期望优化方向,但能降低方差。不同 REINFORCE 家族方法,主要区别就是 baseline 怎么构造。
RLOO:leave-one-out baseline
RLOO 的 baseline 来自同一个 prompt 的其他 response。假设同题采样 K 条,第 i 条 reward 是 R_i,RLOO baseline 是:
所以 advantage 是:
verl 的 compute_rloo_outcome_advantage() 直接写成等价形式:
scores = token_level_rewards.sum(dim=-1)
response_num = len(id2score[index[i]])
scores[i] = scores[i] * response_num / (response_num - 1)
- id2mean[index[i]] * response_num / (response_num - 1)
advantages = scores[:, None] * response_mask变量对应:
| 源码变量 | 公式符号 | 含义 |
|---|---|---|
scores[i] | R_i | 当前 response 总 reward |
index[i] | g | prompt group,也就是 uid |
id2mean[index[i]] | mu_g | 组内平均 reward |
response_num | K | 同 prompt 的采样条数 |
response_mask | mask | 有效 response token |
RLOO 和 GRPO 很像,但 GRPO 默认是 (R_i - mu_g) / std_g,RLOO 是严格 leave-one-out baseline。
ReMax:greedy response 做 baseline
ReMax 不用同组其他采样均值,而是为每个 prompt 额外生成一条 greedy response,把它的 reward 当 baseline:
其中 G_t 是从 token t 到结尾的 reward-to-go。对 outcome reward 来说,它在大多数 token 位置就是最终 reward。
在 core_algos.compute_remax_outcome_advantage() 中:
returns = flip(cumsum(flip(token_level_rewards * response_mask)))
advantages = returns - reward_baselines[:, None] * response_mask在 RayPPOTrainer.fit() 中,ReMax 有一个很重要的生成分支:
sampled rollout: __do_sample__ = True
greedy baseline: __do_sample__ = False
combined_gen_batch = concat(sampled, baseline)
combined_gen_output = generate_sequences(combined_gen_batch)
reward_baselines = baseline_output.rm_scores.sum(-1)
batch.batch["reward_baselines"] = reward_baselines源码把 sampled rollout 和 greedy baseline 放在同一次生成请求里,是为了避免多轮 rollout 与异步/agent loop 状态互相影响。
REINFORCE++:reward-to-go + whitening
REINFORCE++ 在 verl 里的入口是 compute_reinforce_plus_plus_outcome_advantage()。它不需要 group,也不需要 greedy baseline,而是做 reward-to-go:
然后把 returns 白化成 advantage:
running_return = token_level_rewards[:, t] + gamma * running_return
returns[:, t] = running_return
running_return = running_return * response_mask[:, t]
advantages = masked_whiten(returns, response_mask) * response_mask这条路径适合先理解为“最简单 policy gradient 的工程增强版”:不训练 critic,但用 token-level return、mask、whitening 和 KL 控制来提升稳定性。
REINFORCE++ baseline 变体
compute_reinforce_plus_plus_baseline_outcome_advantage() 更像 GRPO 的 baseline 版本:
score_i = sum(token_level_rewards_i)
adv_i = score_i - mean_uid
advantages = masked_whiten(adv_i[:, None] repeated to tokens)它和 RLOO 的差别是:baseline 使用包含自己的组均值,不是 leave-one-out 均值;随后还做 masked whitening。
源码实现怎么读
1. trainer 如何分发不同 estimator
ray_trainer.compute_advantage() 里只有 GAE 和 GRPO 写了显式分支,其他 estimator 走注册表:
adv_estimator_fn = core_algos.get_adv_estimator_fn(adv_estimator)
adv_kwargs = {
token_level_rewards,
response_mask,
config,
}
if "uid" in non_tensor_batch:
adv_kwargs["index"] = uid
if "reward_baselines" in batch:
adv_kwargs["reward_baselines"] = reward_baselines
advantages, returns = adv_estimator_fn(**adv_kwargs)这解释了为什么 RLOO 需要 uid,ReMax 需要 reward_baselines,而 REINFORCE++ 可以不需要 group。
2. actor loss 仍可复用 PPO-style
这些 estimator 只是生成 advantages 和 returns。actor 更新仍由 actor_rollout_ref.actor.policy_loss.loss_mode 决定,默认可以继续走 vanilla clipped PPO loss;启用 rollout correction bypass + loss_type=reinforce 时,也可以走 compute_policy_loss_reinforce()。
compute_policy_loss_reinforce() 的最小化形式是:
pg_losses = -advantages * log_prob
if rollout_is_weights is not None:
pg_losses = pg_losses * rollout_is_weights这里没有 PPO clip,核心就是 -A * log pi。
配置里先看什么
示例脚本:
examples/rloo_trainer/run_qwen3_8b_fsdp.sh:algorithm.adv_estimator=rloo,通常rollout.n >= 2。examples/remax_trainer/run_qwen3_8b_fsdp.sh:algorithm.adv_estimator=remax,trainer 会额外生成 greedy baseline。examples/reinforce_plus_plus_trainer/run_qwen3_8b_fsdp.sh:algorithm.adv_estimator=reinforce_plus_plus,可用环境变量切到reinforce_plus_plus_baseline。
常见组合:
actor_rollout_ref.actor.use_kl_loss=False
algorithm.use_kl_in_reward=True
actor_rollout_ref.rollout.n=5 # RLOO 常见设置但这不是硬规则。先确认 estimator 需要什么输入,再看 KL 放 reward 侧还是 loss 侧。
和 GRPO 的关系
| 方法 | baseline | 是否需要 group | 是否需要额外 rollout |
|---|---|---|---|
| GRPO | 同 prompt 组均值,可除 std | 是 | 否 |
| RLOO | 同 prompt 其他样本均值 | 是 | 否 |
| ReMax | greedy response reward | 否 | 是 |
| REINFORCE++ | 无显式 baseline,使用 reward-to-go + whitening | 否 | 否 |
| REINFORCE++ baseline | 同 prompt 组均值 | 是 | 否 |
抓住 baseline,就抓住了这组算法的一半。
哪些地方不适合初学者硬啃
- 不要把 RLOO 和 GRPO 混成同一个公式。RLOO 是 leave-one-out,GRPO 常见是组均值加标准差归一。
- 不要忘记 ReMax 的 greedy baseline 要额外算 reward。缺
reward_baselines会让 estimator 输入不完整。 - 不要把
returns都理解成 critic 目标。critic-free 算法里returns往往只是为了保持 batch 字段接口一致。 - 不要以为“REINFORCE 家族”一定没有 PPO clip。advantage estimator 和 policy loss 是两层配置。
本节参考与延伸阅读
- verl 源码:
verl/trainer/ppo/core_algos.py的compute_rloo_outcome_advantage()、compute_rloo_vectorized_outcome_advantage()、compute_remax_outcome_advantage()、compute_reinforce_plus_plus_outcome_advantage()、compute_reinforce_plus_plus_baseline_outcome_advantage()、compute_policy_loss_reinforce()。 - verl 源码:
verl/trainer/ppo/ray_trainer.py的compute_advantage()和 ReMax 生成分支。 - verl 官方文档:
docs/algo/ppo.md中 advantage estimator 列表,docs/algo/rollout_corr_math.md中 REINFORCE 与 off-policy PG 推导。 - 示例脚本:
examples/rloo_trainer/README.md、examples/remax_trainer/README.md、examples/reinforce_plus_plus_trainer/README.md。 - 论文:Back to Basics: Revisiting REINFORCE Style Optimization for Learning from Human Feedback in LLMs。
- 论文:ReMax: A Simple, Effective, and Efficient Reinforcement Learning Method for Aligning Large Language Models。
- 论文:REINFORCE++: A Simple and Efficient Approach for Aligning Large Language Models。