Prhub

#6024 [trainer] fix: add missing rollout dump and corrected validation logging in main_ppo_sync

原始 PR 作者 guillemgt 合并时间 2026-04-17 11:12 文件变更 1 提交数 4 评论 2 代码增减 +134 / -3

执行摘要

修复 main_ppo_sync 验证阶段多输出过滤和 dump 缺失

PR body 指出三个 issue:

1) _dump_generations 被调用但未定义会运行时崩溃;
2) 验证时获取了多输出 agent loop 的所有中间键,导致指标重复;
3) extra_fields 未获取,自定义 reward 指标未出现在日志中。

值得精读,特别是多输出过滤的实现和修复过程,展示了如何处理 agent loop 中 session 级别的数据聚合。

讨论亮点

Gemini Code Assist 机器人指出 reward_extra_info 累积逻辑不安全:如果某些样本缺少某个键,对应列表长度会比其他列表短,可能导致 process_validation_metrics 崩溃或 _dump_generations 跳过部分键。作者回复已修复,最终修复方式是在累积时对缺失键填充 None 并维护 n_prior 对齐。

实现拆解

  1. 添加缺失方法:在 PPOTrainer 中新增 _dump_generations()(含 numpy 类型 JSON 序列化)和 _log_rollout_data() 方法,并在训练循环 fit() 中添加 rollout_data_dir 配置检查,按 uid 排序后调用 dump。
  2. 修正验证键过滤:在 _validate() 中,解析 batch 中每个键的格式 {uid}_{session_id}_{index},用 session_max 字典记录每个 session 的最高 index,最后只保留这些 final_keys 用于指标计算。
  3. 收集 extra_fields:在 _validate() 中增加 extra_fields 字段的获取,然后从每个样本的 reward_extra_info 中提取键值对,填充到 reward_extra_infos_dict 中,并确保与其他列表长度对齐。
  4. 调整 import:增加 import json。
文件 模块 状态 重要度
verl/trainer/main_ppo_sync.py 训练器 modified 7.97

关键符号

_dump_generations json_encode_default _log_rollout_data _validate

关键源码片段

verl/trainer/main_ppo_sync.py bugfix

唯一修改的文件,修复验证和日志中的三个问题,改动量 +134/-3

# 验证方法中过滤每个 session 最终输出的关键片段
# 原本直接使用 batch.keys 获取所有中间输出,现在只保留每个 session 最高 index 的 key
​
# Keys 格式为 {uid}_{session_id}_{index}
# 例如 : 'abc123_0_0', 'abc123_0_1' 中的 index=1 是最终输出
final_indices = []
session_max: dict[str, tuple[int, int]] = {} # session_key -> (max_index, position)
for pos, key in enumerate(batch.keys):
    parts = key.rsplit("_", 2)
    if len(parts) == 3:
        session_key = f"{parts[0]}_{parts[1]}"
        index = int(parts[2])
        if session_key not in session_max or index > session_max[session_key][0]:
            session_max[session_key] = (index, pos)
    else:
        # 不是多输出格式的 key,直接保留
        session_max[key] = (0, pos)
final_indices = sorted(pos for _, pos in session_max.values())
final_keys = [batch.keys[i] for i in final_indices]
​
# 改用 final_keys 获取数据,确保指标只基于最终输出
fields = ["uid", "prompts", "responses", "rm_scores", "num_turns", "reward_model", "data_source", "extra_fields"]
data = tq.kv_batch_get(keys=final_keys, partition_id=batch.partition_id, select_fields=fields)
# extra_fields 收集和 padding 逻辑
# 确保 reward_extra_infos_dict 中各列表长度一致
extra_fields_list = data.pop("extra_fields", None)
if extra_fields_list is not None:
    n_prior = len(reward_extra_infos_dict["reward"]) - len(extra_fields_list.tolist())
    for extra_field in extra_fields_list.tolist():
        reward_extra_info = (
            extra_field.get("reward_extra_info", {}) if isinstance(extra_field, dict) else {}
        )
        # 对已有 key 中当前样本缺失的填充 None
        for key in reward_extra_infos_dict:
            if key != "reward" and key not in reward_extra_info:
                reward_extra_infos_dict[key].append(None)
        for key, value in reward_extra_info.items():
            if key not in reward_extra_infos_dict:
                reward_extra_infos_dict[key] = [None] * n_prior
            reward_extra_infos_dict[key].append(value)
        n_prior += 1

评论区精华

reward_extra_info 累积逻辑安全性 正确性

Gemini Code Assist 机器人指出如果某些样本缺少特定 key,列表长度不一致可能导致崩溃或数据错位。

结论:作者回复已修复,通过填充 None 并维护 n_prior 对齐长度。 · 已解决

风险与影响

新增的 reward_extra_info 累积逻辑虽然在评审后修复了长度不一致问题,但仍需确认所有分支都能正确对齐。改动集中在同步 PPO 训练器 main_ppo_sync.py,不影响其他训练器或异步路径。没有新增测试,依赖人工验证。

影响使用 main_ppo_sync 训练并启用 validation 和 rollout dump 的用户:修复了潜在的运行时崩溃和错误的验证指标,使 multi-output agent loop 的日志更准确。rollout_data_dir 配置现在生效。

训练路径变更 列表对齐风险

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论