Prhub

#6849 [data, rollout, worker] feat: add Open-R1 multimodal and TinyLLaVA-Video-R1 preprocessing and training scripts

原始 PR 作者 lihanwen7 合并时间 2026-07-01 15:10 文件变更 5 提交数 1 评论 6 代码增减 +614 / -0

执行摘要

新增 Open-R1 多模态和 TinyLLaVA-Video 数据预处理及训练脚本

为了在 verl 中支持多模态 GRPO 训练,需要提供数据预处理和训练配置示例。该 PR 基于社区需求,使得用户能够直接使用 Open-R1 多模态数据和 TinyLLaVA-Video-R1 数据集进行训练,无需自行编写数据转换逻辑。同时修复了多模态训练中出现的 position_ids 损坏问题。

该 PR 提供了完整的多模态数据集集成范例,值得阅读和参考。特别是图像预处理中保留原始字节的方式,以及训练脚本中设备自动检测的写法。但注意奖励函数需用户自行实现,直接运行示例脚本前需创建对应的 reward score 文件。

讨论亮点
  1. 奖励函数文件缺失:gemini-code-assist 指出训练脚本引用的奖励函数文件未包含在 PR 中。作者回应称,按照 verl 最新策略(wuxibin89 在 PR#6793 的指导),框架不再内置维护每个数据集的奖励函数,用户需自行提供。该讨论已关闭。
  2. 图像自动解码问题:gemini-code-assist 指出 openr1mm.py 中 datasets.load_dataset 默认会解码图像,导致后续的 isinstance 检查失效。作者在最终代码中添加了 cast_column('image', datasets.Image(decode=False)) 以保留原始字节。
  3. 列选择问题:同样在 openr1mm.py 中,gemini-code-assist 指出 Dataset.map 后未移除原始列,会导致已解码的图像数据写入 Parquet 文件。作者在 final 版本中添加了 select_columns(...) 仅保留必要列。

实现拆解

  1. 数据预处理脚本:新增 examples/data_preprocess/openr1mm.pyexamples/data_preprocess/tinyllava_video_r1.py,分别处理图像和视频数据集。
    - 图像脚本使用 datasets 库加载数据,通过 cast_column(decode=False) 保留原始字节,避免有损重新编码。
    - 视频脚本从 JSONL 解析问题、答案和视频路径,构建标准的 think/answer 格式 prompt。
    - 两个脚本最终输出统一的 verl parquet 格式。
  2. 训练启动脚本:新增 examples/grpo_trainer/run_qwen3_5_2b_openr1_fsdp.shexamples/grpo_trainer/run_qwen3_5_2b_video_fsdp.sh,基于 FSDP 的 GRPO 训练配置。
    - 脚本自动检测 GPU 或 NPU 设备,设置数据路径、模型路径、奖励函数路径等参数。
    - 奖励函数需用户自行提供(位于 verl/utils/reward_score/*.py)。
  3. 引擎层修复:修改 verl/workers/engine_workers.py,在 train_mini_batch 循环起始位置添加 maybe_fix_3d_position_ids(mini_batch_td) 调用,修正多模态数据中可能错乱的 3D position_ids,防止 _ragged_idx 损坏。
文件 模块 状态 重要度
examples/data_preprocess/tinyllava_video_r1.py 数据预处理 added 8.87
examples/data_preprocess/openr1mm.py 数据预处理 added 7.83
examples/grpo_trainer/run_qwen3_5_2b_video_fsdp.sh 训练脚本 added 5.31
examples/grpo_trainer/run_qwen3_5_2b_openr1_fsdp.sh 训练脚本 added 5.31
verl/workers/engine_workers.py 引擎 modified 5.19

关键符号

build_prompt_text make_map_fn process_fn load_jsonl maybe_fix_3d_position_ids

关键源码片段

examples/data_preprocess/tinyllava_video_r1.py dependency-wiring

新增的视频数据集预处理脚本核心文件,实现了从 JSONL 到 parquet 的完整转换流程,包括视频路径解析和 prompt 构建。

def make_map_fn(
    data_source: str,
    video_dir: str,
    split: str,
    video_fps: Optional[float] = None,
    video_max_frames: Optional[int] = None,
):
    """工厂函数,遵循 verl 的标准闭包模式。"""
​
    def process_fn(example, idx):
        problem = example["problem"]
        solution = example["solution"] # 答案已为 "<answer>X</answer>" 格式
​
        # 解析视频绝对路径:去掉 "./" 前缀并与 video_dir 拼接
        video_rel = example["video_filename"].lstrip("./")
        video_path = os.path.join(video_dir, video_rel)
        if not os.path.exists(video_path):
            print(f"[WARN] 视频文件不存在: {video_path}", file=sys.stderr)
​
        prompt_content = build_prompt_text(problem) # 生成 "<video>\n{problem}\n\n{INSTRUCTION}"
​
        # 视频采样参数:默认 fps=1, max_frames=32
        video_entry = {"video": video_path}
        if video_fps is not None:
            video_entry["fps"] = video_fps
        if video_max_frames is not None:
            video_entry["max_frames"] = video_max_frames
​
        return {
            "data_source": data_source,
            "prompt": [{"role": "user", "content": prompt_content}],
            "videos": [video_entry],
            "ability": "video_qa",
            "reward_model": {"style": "rule", "ground_truth": solution},
            "extra_info": {
                "split": split,
                "index": idx,
                "question": problem,
                "answer": solution,
                "video_path": video_path,
            },
        }
​
    return process_fn
examples/data_preprocess/openr1mm.py dependency-wiring

新增的图像数据集预处理脚本核心文件,演示了如何保留图像原始字节并构建 parquet 数据集。

def make_map_fn(split):
    def process_fn(example, idx):
        problem = example.pop("problem")
        solution = example.pop("solution")
        img = example.pop("image")
​
        # 构建 prompt :将 <image> 占位符嵌入问题前
        prompt_content = f"<image>\n{problem}\n\n{instruction}"
​
        # 保持图像为原始字节形式,避免 Qwen VL Processor 再次编解码
        if isinstance(img, dict) and "bytes" in img:
            image_data = img
        elif isinstance(img, bytes):
            image_data = {"bytes": img}
        else:
            # 若已经为 PIL.Image,则直接使用(但很少执行到此)
            image_data = img
​
        return {
            "data_source": data_source,
            "prompt": [{"role": "user", "content": prompt_content}],
            "images": [image_data],
            "ability": "math",
            "reward_model": {"style": "rule", "ground_truth": solution},
            "extra_info": {
                "split": split,
                "index": idx,
                "question": problem,
                "answer": solution,
            },
        }
    return process_fn# 阻止 datasets 自动解码图像
full_dataset = full_dataset.cast_column("image", datasets.Image(decode=False))

评论区精华

奖励函数文件缺失 正确性

gemini-code-assist 指出两个训练脚本引用的奖励函数文件 (openr1mm.py 和 tinyllava_video_r1.py) 未包含在 PR 中,运行时会报错。

结论:作者回应称奖励函数文件有意不包含,遵循最新策略(wuxibin89 在 PR#6793 的指导),用户需自行提供。PR 审核通过。 · 已解决

图像自动解码问题 正确性

gemini-code-assist 指出 load_dataset 默认解码图像,导致 isinstance 检查失败,建议使用 cast_column(decode=False)。

结论:作者在最终代码中添加了 cast_column('image', datasets.Image(decode=False)),已修复。 · 已解决

未选择列导致图像数据残留 性能

gemini-code-assist 指出 map 后未移除原始列,会导致已解码图像被写入 parquet,增加文件大小。

结论:作者在最终代码中添加了 select_columns(...) 仅保留必要列,已修复。 · 已解决

风险与影响

  • 奖励函数依赖:训练脚本引用 custom_reward_function.path 指向 verl/utils/reward_score/ 下的文件,但该目录未包含在 PR 中。用户直接运行脚本会因找不到模块而报错。需提醒用户自行编写奖励函数。
  • 图像解码性能:若未正确处理图像列(如未设置 decode=False),可能导致 Parquet 文件体积膨胀或损失图像质量。当前脚本已修复此问题。
  • position_ids 回退maybe_fix_3d_position_ids 被无条件调用,若模型不支持 3D position_ids 可能引入额外开销或错误,但该函数内部应具有安全判断。
  • 配置严格性:训练脚本使用特定参数(如 data.image_patch_size=16),若用户数据集不匹配可能导致训练失败。
  • 用户:新数据集用户可以直接使用提供的预处理脚本和训练脚本,快速启动多模态 GRPO 训练。
  • 系统:engine_workers.py 的修改影响所有调用 train_mini_batch 的流程,但已通过 GPU 和 NPU 验证。
  • 团队:该 PR 明确了奖励函数维护的策略(不内置,由用户提供),为未来数据集集成提供了范例。
奖励函数脚本缺失 图像解码导致文件膨胀 position_ids 修复影响范围 训练脚本依赖路径正确

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论