学习主题:VERL 源码精读 04
从数据文件、RLHFDataset、tokenize、collate_fn 到 dataloader,理解训练 batch 的形成过程。 阅读目标:建立可复述、可定位、可调试的源码理解,不停留在 API 名称层面。
1. 本篇学习范围
从数据文件、RLHFDataset、tokenize、collate_fn 到 dataloader,理解训练 batch 的形成过程。
这一篇关注的不是“怎么把命令跑起来”,而是读清楚源码中每个对象为什么存在、它和前后模块如何传递数据,以及出问题时应该从哪里开始定位。
2. 源码入口文件
本篇建议按下面顺序阅读:
verl/utils/dataset/rl_dataset.pyverl/utils/dataset/dataset_utils.pyverl/utils/dataset/vision_utils.pyverl/trainer/ppo/utils.pydocs/preparation/prepare_data.rst这些文件覆盖了本篇主题的主路径。阅读时不需要一开始就把所有分支展开,先抓主干,再回头看特殊配置、后端差异和异常处理。
3. 核心调用链
本篇主调用链可以压缩为:
`create_rl_dataset()` 根据配置选择 dataset class,默认使用 `RLHFDataset`。`RLHFDataset` 读取 parquet 文件,并保留 prompt、reward_model、data_source、extra_info 等字段。`__getitem__()` 将单条样本转成训练所需结构,包含 tokenized prompt 和非 tensor 元信息。`collate_fn()` 把多条样本合并为 batch,供 trainer dataloader 使用。trainer 从 dataloader 取 batch 后,再包装成 DataProto 或 TensorDict,进入 rollout 和训练流程。这条链路是读源码时的地图。后续遇到新的类、函数或配置字段,都可以先判断它属于链路中的哪一段。
4. 核心概念
VERL 的训练样本不只是 prompt 文本,还包含 reward 函数需要的 ground truth 和数据来源。 多模态任务会经过 processor/vision_utils,把图片或视频相关字段转成模型可接收的输入。 数据阶段通常只准备 prompt 侧输入,response 是 rollout 阶段由策略模型生成的。 数据源字段 data_source经常用于 reward manager 选择不同 reward function。
这些概念共同决定了本篇源码的设计方式。VERL 的一个重要特点是,很多类名看起来像普通工程封装,但背后实际是在解决大模型 RL 的分布式执行问题。
5. 数据在这一层如何流动
在 VERL 中,几乎所有训练阶段都可以用同一套数据流语言描述:
输入 batch-> 按并行度或任务类型拆分-> 分发到本地函数或远程 worker-> 执行高成本计算或轻量控制逻辑-> 收集结果-> 写回 DataProto / TensorDict / TransferQueue-> 进入下一阶段本篇主题对应的数据流重点是:
create_rl_dataset()根据配置选择 dataset class,默认使用 RLHFDataset。RLHFDataset读取 parquet 文件,并保留 prompt、reward_model、data_source、extra_info 等字段。 __getitem__()将单条样本转成训练所需结构,包含 tokenized prompt 和非 tensor 元信息。 collate_fn()把多条样本合并为 batch,供 trainer dataloader 使用。 trainer 从 dataloader 取 batch 后,再包装成 DataProto 或 TensorDict,进入 rollout 和训练流程。
读代码时要始终跟踪字段而不是只跟踪函数名。典型字段包括:
promptsresponsesattention_maskposition_idsold_log_probsref_log_probvaluesrm_scorestoken_level_rewardsadvantagesreturnsmetrics不是每一天都会出现全部字段,但这些字段构成了 VERL PPO/GRPO 训练的共同词汇表。
6. 和前后模块的关系
本篇主题通常不是孤立工作的。它至少会连接三类模块:
上游: 配置、数据、controller 状态、已有 batch 字段。本层: 当前主题负责的调度、计算、转换或封装逻辑。下游: rollout、reward、advantage、loss、metrics、checkpoint 或异步队列。因此读源码时要避免只看单个函数。更稳的方式是:
1. 找到谁调用它。2. 找到它读取哪些字段。3. 找到它写出哪些字段。4. 找到这些字段下一步被谁使用。5. 找到配置项如何改变它的分支。这个五步法适用于 VERL 的大多数文件。
7. 实现细节拆解
本篇源码中最值得关注的细节包括:
create_rl_dataset()根据配置选择 dataset class,默认使用 RLHFDataset。





def create_rl_dataset(data_paths, data_config, tokenizer, processor, is_train=True, max_samples: int = -1):"""Create a dataset.Arguments:data_paths: List of paths to data files.data_config: The data config.tokenizer (Tokenizer): The tokenizer.processor (Processor): The processor.Returns:dataset (Dataset): The dataset."""from verl.utils.dataset.rl_dataset import get_dataset_class# Get the dataset classdataset_cls = get_dataset_class(data_config)# Instantiate the dataset using the determined dataset classdataset = dataset_cls(data_files=data_paths,tokenizer=tokenizer,processor=processor,config=data_config,max_samples=max_samples,)return dataset
RLHFDataset。RLHFDataset读取 parquet 文件,并保留 prompt、reward_model、data_source、extra_info 等字段。 

可以看见这儿有一些对数据集内容的默认读取的表头名称,例如prompt_key默认为prompt。当然这部分也可以在config进行自定义的设置。如果不在config中进行设置就需要将自己的数据集修改成默认的可用格式。 
在数据集初始化的最后,是完成配置后的下载、读取文件、进行tokenize编码准备好数据。 __getitem__()数据准备好后,为了方便使用,当然需要有getitem方法: 将单条样本转成训练所需结构,包含 tokenized prompt 和非 tensor 元信息。 
collate_fn()把多条样本合并为 batch,供 trainer dataloader 使用。 
trainer 从 dataloader 取 batch 后,再包装成 DataProto 或 TensorDict,进入 rollout 和训练流程。
这些步骤背后通常有两类逻辑:
控制逻辑: 判断当前训练需要哪些角色、哪些字段、哪些分支。计算逻辑: 真正执行模型推理、训练、reward、advantage 或 loss。HybridFlow 的设计要求我们把这两类逻辑区分开。控制逻辑更适合在 trainer 或 manager 中读;计算逻辑更适合在 worker、engine 或 core_algos 中读。
8. 配置如何影响本篇路径
VERL 的同一段源码经常会被配置切到不同路径。阅读本篇时尤其要关注这些配置类型:
algorithm: 决定 PPO、GRPO、KL、advantage、rollout correction 等算法行为。actor_rollout_ref: 决定 actor、rollout、reference policy、model path、训练后端和推理后端。critic: 决定是否启用 value model,以及 critic 的训练后端。reward: 决定使用规则 reward、reward model、remote reward 还是 sandbox reward。trainer: 决定训练步数、资源规模、logger、validation、checkpoint 和 V1/V0 模式。读源码前最好先打印 resolved config。否则很容易在一个未启用的分支里浪费时间。
9. 常见误区和调试要点
parquet schema 不满足 reward manager 预期时,错误可能直到 reward 阶段才暴露。 prompt 长度过滤会改变有效训练样本数,排查样本缺失时要看 filter_overlong_prompts。多模态 processor 输出和 tokenizer 输出的 shape 要一起看,不能只看 input_ids。
调试 VERL 时,不建议一上来就改源码。更稳的顺序是:
1. 确认命令行 override 是否真的进入 resolved config。2. 确认当前走 V0 还是 V1 trainer。3. 确认 DataProto / TensorDict 里字段是否存在。4. 确认 batch 维、response 长度、mask 是否一致。5. 确认对应 worker 是否真的被创建。6. 确认 Ray worker 日志里的原始异常。7. 最后再判断是不是算法公式或 loss 本身的问题。这个顺序能避免把配置错误、数据错误、分布式调度错误误判成算法错误。
10. 本篇压缩总结
读懂这一篇后,应该能够回答三个问题:
1. 这一层在 VERL 训练链路中负责什么?2. 它接收哪些字段,又产出哪些字段?3. 它的行为主要由哪些配置项改变?如果这三个问题能答清楚,就说明不是在背目录,而是在按 VERL 的真实执行路径读源码。
11. 下一步阅读
读完本篇后,建议继续沿着训练链路向后走:
入口与配置-> 数据和 DataProto-> WorkerGroup 和 Ray 调度-> Worker 与模型引擎-> Rollout-> Reward-> Advantage-> Actor/Critic loss-> Metrics、Checkpoint、异步扩展VERL 源码量很大,但主线并不乱。只要始终围绕“一个 batch 如何从 prompt 变成 actor update”这条线阅读,就能把分散目录组织成一张完整图。
夜雨聆风