Skip to content

REINFORCE 家族:RLOO、ReMax、REINFORCE++

这一组算法的共同目标是:尽量少依赖 critic,用更简单的 baseline 降低 policy gradient 的方差。它们很适合和 GRPO 放在一起学,因为它们都在回答同一个问题:没有 value model 时,advantage 从哪里来?

先补 policy gradient 先验

最朴素的 REINFORCE 梯度是:

θJ(θ)=E[R(y)θlogπθ(y|x)]

其中 x 是 prompt,y 是 response,R(y) 是整条 response 的 reward。问题是方差很大:同一个 prompt 下采样到的回答质量会抖,直接用 reward 乘 logprob 会让训练很吵。

baseline 的作用是把 reward 换成相对值:

(Rb)θlogπθ(y|x)

只要 baseline b 不依赖当前这条 action 的随机选择,就不会改变期望优化方向,但能降低方差。不同 REINFORCE 家族方法,主要区别就是 baseline 怎么构造。

RLOO:leave-one-out baseline

RLOO 的 baseline 来自同一个 prompt 的其他 response。假设同题采样 K 条,第 i 条 reward 是 R_i,RLOO baseline 是:

bi=1K1jiRj

所以 advantage 是:

Ai=Ribi=KK1(Riμg)

verl 的 compute_rloo_outcome_advantage() 直接写成等价形式:

text
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]gprompt group,也就是 uid
id2mean[index[i]]mu_g组内平均 reward
response_numK同 prompt 的采样条数
response_maskmask有效 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:

At=Gtbgreedy

其中 G_t 是从 token t 到结尾的 reward-to-go。对 outcome reward 来说,它在大多数 token 位置就是最终 reward。

core_algos.compute_remax_outcome_advantage() 中:

text
returns = flip(cumsum(flip(token_level_rewards * response_mask)))
advantages = returns - reward_baselines[:, None] * response_mask

RayPPOTrainer.fit() 中,ReMax 有一个很重要的生成分支:

text
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:

Gt=rt+γGt+1

然后把 returns 白化成 advantage:

text
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 版本:

text
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 走注册表:

text
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 只是生成 advantagesreturns。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() 的最小化形式是:

text
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.shalgorithm.adv_estimator=rloo,通常 rollout.n >= 2
  • examples/remax_trainer/run_qwen3_8b_fsdp.shalgorithm.adv_estimator=remax,trainer 会额外生成 greedy baseline。
  • examples/reinforce_plus_plus_trainer/run_qwen3_8b_fsdp.shalgorithm.adv_estimator=reinforce_plus_plus,可用环境变量切到 reinforce_plus_plus_baseline

常见组合:

text
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 其他样本均值
ReMaxgreedy 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 学习文档。