Reward:规则奖励、模型奖励与 reward loop
Reward 是 post-training 里最容易“看起来简单、实际出错”的部分。模型生成一段 response 后,verl 最终需要得到一个和 response token 对齐的 reward tensor。中间可以是规则函数、reward model、远端 verifier、sandbox,也可以是它们的组合。
先补一点先验
很多 RLVR 任务只有整条 response 的标量分数,例如“答案对了给 1,错了给 0”。但 PPO/GRPO 训练时希望 reward 和 token mask 对齐,所以 verl 常见做法是:
reward_tensor: shape == responses.shape
非最后有效 response token: 0
最后有效 response token: 标量 reward也就是说,“标量奖励”进入训练时会被放到最后一个有效 response token 上。后续 advantage/reward 计算再结合 response_mask、KL penalty、discount 等逻辑。
传统 reward manager:call 接口
传统路径在 verl/workers/reward_manager/:
| 文件 | 作用 |
|---|---|
abstract.py | AbstractRewardManager 接口和 rm_scores 提取逻辑 |
registry.py | @register(name) 和 get_reward_manager_cls(name) |
naive.py | 逐样本调用 compute_score |
dapo.py | naive 基础上增加 overlong penalty |
batch.py | 批量调用 reward function |
prime.py | PRIME 风格并行 scoring 和 acc |
核心接口是:
class AbstractRewardManager:
def __call__(self, data: DataProto, return_dict: bool = False):
...调用结果通常是 reward_tensor,或者在 return_dict=True 时返回:
{
"reward_tensor": reward_tensor,
"reward_extra_info": {...},
}AbstractRewardManager._extract_reward_from_rm_scores() 很关键:如果 batch 里已经有 rm_scores,manager 会直接把它作为 reward 来源,不再重复跑规则函数。这是 reward model / reward loop 与传统 reward function 汇合的入口。
naive reward:最容易读懂的路径
NaiveRewardManager.__call__() 的流程大致是:
for each sample:
1. 从 batch 中取 prompt_ids / response_ids / attention_mask
2. 用 tokenizer decode response
3. 从 non_tensor_batch 取 data_source、ground_truth、extra_info
4. 调 compute_score(data_source, solution_str, ground_truth, extra_info)
5. 把 score 放到最后一个有效 response tokencompute_score 可以来自 verl.utils.reward_score.default_compute_score,也可以是用户通过 config 指定的自定义函数。小白学习时先读 naive,因为它把 reward manager 的数据形状讲得最清楚。
DAPO reward:规则分 + 超长惩罚
DAPORewardManager 和 naive 很像,但多了 overlong_buffer_cfg。它会先算任务分,再检查 response 是否进入超长惩罚区:
expected_len = max_resp_len - overlong_buffer_len
exceed_len = valid_response_length - expected_len
overlong_reward = min(-exceed_len / overlong_buffer_len * penalty_factor, 0)
reward += overlong_reward这解释了 DAPO 类算法里常见的“不要靠无限长推理刷概率”的工程约束。它不是模型结构的一部分,而是 reward shaping。
BatchRewardManager:什么时候需要批量 reward
有些 reward 函数批量计算更快,例如一次性调用 verifier、一次性跑多个单测,或需要共享上下文。BatchRewardManager 会收集 batch 中的 decoded responses、references、data_source、extra_info,再调用批量版本的 reward function。
学习时注意一点:batch reward 仍然要回到 token-level reward tensor。批量只是 scoring 的执行方式,不改变训练侧需要的输出形状。
reward model 路径:rm_scores 先进入 batch
如果启用了 reward model,trainer 会先得到 rm_scores,然后传统 reward manager 通过 _extract_reward_from_rm_scores() 把它转成 reward_tensor。
概念上是:
reward model / reward loop:
prompt + response -> rm_scores
reward manager:
rm_scores -> token-level reward_tensor
trainer:
batch.batch["token_level_scores"] = reward_tensor所以不要把 rm_scores 和最终 token_level_scores 混为一谈:前者是 reward model 或 reward loop 的输出字段,后者是 trainer 后续 advantage 计算使用的 token 对齐结果。
experimental reward loop:把 reward 计算分布式化
verl/experimental/reward_loop/ 是当前官方文档称为默认 reward computation implementation 的路径。它把 reward 计算从 trainer 进程里拆出来,用 Ray reward workers 并行处理。
关键对象:
| 对象 | 文件 | 职责 |
|---|---|---|
RewardLoopManager | reward_loop.py | driver 侧管理 reward workers 和可选 reward model |
RewardLoopWorker | reward_loop.py | 每个 Ray worker 内逐样本异步算 reward |
RewardModelManager | reward_model.py | 创建 reward model server 和 router |
RewardManagerBase | reward_manager/base.py | experimental reward manager 接口 |
Naive/DAPO/Remote/Limited | reward_manager/*.py | 不同 reward 执行策略 |
NaiveRouter / InnerSGLangRouter | router/*.py | reward model server 的请求路由 |
调用链可以这样读:
RayPPOTrainer.init_workers()
-> RewardLoopManager(config, resource_pool)
-> 创建 RewardLoopWorker actors
-> 如果 reward.reward_model.enable:
RewardModelManager 创建 reward model rollout servers + router
训练中:
RayPPOTrainer._compute_reward(batch)
-> reward_loop_manager.compute_rm_score(batch)
-> data.chunk(num_reward_workers)
-> worker.compute_score_batch.remote(chunk)
-> 每个样本 RewardLoopWorker.compute_score(...)
-> 返回 scores
-> reward_manager_cls.assemble_rm_scores(data, scores)
-> batch.batch["rm_scores"]然后 trainer 再把这个 batch 交给 reward_fn(batch),由 reward manager 生成 token-level reward。
RewardManagerBase:experimental 接口
experimental reward manager 的核心接口不是 __call__,而是:
class RewardManagerBase:
async def run_single(self, data: DataProto) -> dict:
return {
"reward_score": reward,
"reward_extra_info": {...},
}
@classmethod
def assemble_rm_scores(cls, data, scores):
...run_single() 面向“一个样本或一条 trajectory”,适合异步 HTTP、sandbox、GenRM 调用。assemble_rm_scores() 再把标量 scores 放回 rm_scores tensor。
远端/模型奖励路径
reward loop 支持几类复杂 reward:
- 规则 reward:没有 reward model 时,
RewardLoopWorker直接调用 reward manager 的run_single()。 - DisRM:
compute_score_disrm()把 prompt+response 转成 reward model 输入,请求/classify,取概率作为 reward。 - GenRM:必须提供自定义 reward function;函数可以通过
reward_router_address调用生成式 reward model,再自己解析分数。 - Remote reward manager:
remote.py把同步、CPU 重、可能不可 pickle 的compute_score放到单独 Ray worker 里跑,避免堵住主 event loop。 - RateLimited reward manager:
limited.py适合外部 API 有并发/速率限制的场景。
这条路径的好处是 reward 可以和 rollout streaming 起来。官方文档里提到,当是规则 reward,或 reward model 有独立资源池时,agent loop 可以在每条样本 rollout 完成后立刻提交 reward 计算,而不是等整批生成结束。
写 reward function 时最该检查什么
data_source是否能路由到正确 scorer。ground_truth是否在non_tensor_batch["reward_model"]["ground_truth"]。extra_info是否包含题目、工具返回、单测配置等必要信息。- 空输出、超长输出、解析失败是否有稳定惩罚。
- 返回 dict 时是否包含
score或 manager 期望的字段。 - reward 是否确定性足够强;随机 reward 会放大 PPO/GRPO 方差。
这页原本不适合学习的地方
- 只列出 reward 类型,没有解释传统
__call__和 experimentalrun_single()两套接口的区别。 - 没说明
rm_scores与最终 token-level reward tensor 的关系,容易让读者误以为 reward model 输出可以直接进 PPO loss。 - DAPO reward 只被点名,没有解释 overlong buffer 的源码逻辑。
- reward loop 只被描述成“异步”,缺少
RewardLoopManager -> RewardLoopWorker -> assemble_rm_scores的完整流程。 - 缺少远端/模型奖励路径:DisRM、GenRM、router、remote manager、rate limited manager 的边界不清。
本节参考与延伸阅读
- 源码:
verl/workers/reward_manager/abstract.py - 源码:
verl/workers/reward_manager/naive.py - 源码:
verl/workers/reward_manager/dapo.py - 源码:
verl/workers/reward_manager/batch.py - 源码:
verl/workers/reward_manager/prime.py - 源码:
verl/workers/reward_manager/registry.py - 源码:
verl/experimental/reward_loop/reward_loop.py - 源码:
verl/experimental/reward_loop/reward_model.py - 源码:
verl/experimental/reward_loop/reward_manager/base.py - 源码:
verl/experimental/reward_loop/reward_manager/naive.py - 源码:
verl/experimental/reward_loop/reward_manager/dapo.py - 源码:
verl/experimental/reward_loop/reward_manager/remote.py - 源码:
verl/experimental/reward_loop/reward_manager/limited.py - 源码:
verl/experimental/reward_loop/router/naive_router.py - 源码:
verl/experimental/reward_loop/router/inner_sglang_router.py - 官方文档:
docs/advance/reward_loop.rst - 官方文档:
docs/preparation/reward_function.rst - 示例:
source-learning/examples/reward-function.md