执行摘要
提取奖励分数组装为可覆写钩子
如 PR body 所述,目的是将奖励分数组装逻辑提取为可覆写的方法,以便下游包能够处理不同的奖励组装方式,如处理图像、视频等场景。
建议精读,这是一个典型的重构样例:从函数中提取职责,为扩展开放接口。值得关注的决策是使用 classmethod 而非实例方法,因为组装逻辑通常不需要实例状态。
无 review 评论。只有 gemini-code-assist[bot] 的总结性评论和 SamitHuang 的批准,无实质性讨论。
如 PR body 所述,目的是将奖励分数组装逻辑提取为可覆写的方法,以便下游包能够处理不同的奖励组装方式,如处理图像、视频等场景。
建议精读,这是一个典型的重构样例:从函数中提取职责,为扩展开放接口。值得关注的决策是使用 classmethod 而非实例方法,因为组装逻辑通常不需要实例状态。
无 review 评论。只有 gemini-code-assist[bot] 的总结性评论和 SamitHuang 的批准,无实质性讨论。
新增 resolve_reward_manager_cls 函数:在 verl/trainer/ppo/reward.py 中新增该函数,从配置中解析出奖励管理器类而不实例化它。该函数处理 register 和 importlib 两种来源,并处理未知来源的异常。原有的 load_reward_manager 函数中对应的内联代码被替换为调用此函数。
新增 assemble_rm_scores 类方法:在 verl/experimental/reward_loop/reward_manager/base.py 的 RewardManagerBase 中新增 assemble_rm_scores 类方法。该方法的默认实现根据 prompts、responses 和 attention_mask 计算每个样本的有效响应长度,并将奖励分数放置在最后一个有效 token 位置。此方法可被子类覆写以支持自定义组装逻辑。
更新 RewardLoopManager:在 verl/experimental/reward_loop/reward_loop.py 中,RewardLoopManager 新增 self.reward_manager_cls 属性,通过 resolve_reward_manager_cls 解析类。在 compute_rm_score 方法中,原本内联的分数组装逻辑被替换为 self.reward_manager_cls.assemble_rm_scores(data, scores) 调用,同时移除了对 torch 的直接导入。
| 文件 | 模块 | 状态 | 重要度 |
|---|---|---|---|
verl/trainer/ppo/reward.py |
训练器 | modified | 7.05 |
verl/experimental/reward_loop/reward_manager/base.py |
奖励循环 | modified | 6.88 |
verl/experimental/reward_loop/reward_loop.py |
奖励循环 | modified | 5.94 |
verl/trainer/ppo/reward.py
core-logic
新增 `resolve_reward_manager_cls` 函数,将奖励管理器类解析从 `load_reward_manager` 中提取,提升了代码可复用性。配置键和异常处理也在此文件改动。
# verl/trainer/ppo/reward.py ( 新增函数 )
# 从 config 解析出 RewardManager 类,不执行实例化
# 支持两种来源:"register" 和 "importlib"
def resolve_reward_manager_cls(config: DictConfig) -> type[RewardManagerBase]:
reward_manager_cfg: RewardManagerConfig = config.reward.reward_manager
if reward_manager_cfg.source == "register":
from verl.experimental.reward_loop.reward_manager import get_reward_manager_cls
return get_reward_manager_cls(reward_manager_cfg.name)
elif reward_manager_cfg.source == "importlib":
from verl.utils.import_utils import load_extern_object
module_cfg: ModuleConfig | None = reward_manager_cfg.module
assert module_cfg is not None and module_cfg.path is not None, (
f"Module path is required when {reward_manager_cfg.source=}, but got {module_cfg=}"
)
return cast(
"type[RewardManagerBase]",
load_extern_object(module_path=module_cfg.path, object_name=reward_manager_cfg.name),
)
else:
raise ValueError(f"Unknown reward manager source: {reward_manager_cfg.source}")
verl/experimental/reward_loop/reward_manager/base.py
core-logic
新增 `assemble_rm_scores` 类方法,定义了默认的奖励分数组装逻辑,支持子类覆写以处理多模态等场景。
# verl/experimental/reward_loop/reward_manager/base.py (RewardManagerBase 新增方法 )
import torch
@classmethod
def assemble_rm_scores(cls, data: DataProto, scores: list[float]) -> torch.Tensor:
"""将每个样本的 reward scores 组装为 rm_scores 张量。
默认实现将分数放到每个样本的最后一个有效 token 位置。
子类可覆写以支持图像、视频等自定义组装逻辑。
"""
prompt_length = data.batch["prompts"].size(1)
# 计算每个样本的有效响应长度(排除 padding)
valid_response_length = data.batch["attention_mask"][:, prompt_length:].sum(dim=1)
rm_scores = torch.zeros_like(data.batch["responses"], dtype=torch.float32)
# 将分数放置到最后一个有效 token 位置
rm_scores[torch.arange(rm_scores.size(0), device=rm_scores.device), valid_response_length - 1] = (
rm_scores.new_tensor(scores)
)
return rm_scores
当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。
风险较低。变更位于奖励循环模块,且已被 reviewer 验证不会破坏现有功能。但需要注意:
assemble_rm_scores 但未正确处理 attention_mask 或张量设备,可能导致设备不匹配错误。RewardLoopManager 中原来的 torch.tensor(scores, dtype=torch.float32) 现在通过 rm_scores.new_tensor(scores) 创建,确保类型和 device 与 rm_scores 一致,这是一个隐式改进,但若 rm_scores 不在 CPU 上,则 new_tensor 会在该 device 上创建,不会引入风险。当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。
参与讨论