Megatron-LM 数据集管线全解析:从 IndexedDataset 二进制格式到 GPTDataset 三索引机制与快速 DataLoader 初始化
【免费下载链接】Megatron-LMOngoing research training transformer models at scale项目地址: https://gitcode.com/GitHub_Trending/me/Megatron-LM
本篇技术文章围绕 Megatron Core 的datasets包展开,系统讲解其数据管线(Data Pipeline)的三层架构:底层IndexedDataset/IndexedDatasetBuilder的二进制存取格式、中上层由BlendedMegatronDatasetConfig与BlendedMegatronDatasetBuilder驱动的分布式感知的 DataLoader 构建流程、GPTDataset的文档/样本/洗牌三索引查表机制,以及离线缓存预生成(tools/prepare_cache.py)、Packing 调度器和三个加速 DataLoader 初始化的配置开关。读完后你可以独立完成数据集预处理、理解训练启动时索引缓存的生成与命中逻辑,并针对大规模数据混合场景配置快速加载路径。
一、整体架构:三层数据接口与构建类
Megatron Core 的数据管线是分层设计的,核心类关系如下(源码位于 megatron/core/datasets/):
| 层级 | 类 | 作用 |
|---|---|---|
| 底层 | IndexedDataset/IndexedDatasetBuilder | 最低层数据接口:读写.bin+.idx二进制文件 |
| 配置 | BlendedMegatronDatasetConfig | 参数化 Builder 与各级数据集,可按训练/推理体制扩展(如GPTDatasetConfig) |
| 构建 | BlendedMegatronDatasetBuilder | 构建最高层数据接口,是分布式感知的构建入口 |
| 中层 | MegatronDataset(抽象基类) | 建立在IndexedDataset之上的高阶抽象,不同任务有不同扩展(如GPTDataset) |
| 顶层 | BlendedDataset | 建立在多个MegatronDataset之上的混合数据集,仅在多个数据分布共同贡献同一个 split 时才需要 |
需要注意一个重要的分布式约定:所有 rank 都必须尝试通过BlendedMegatronDatasetBuilder构建数据集,否则程序会挂起;哪些 rank 真正执行构建逻辑,则由BlendedMegatronDatasetConfig控制。这一约定在源码中体现为 blended_megatron_dataset_builder.py 中build_generic_dataset的 "rank 0 先构建 →torch.distributed.barrier()→ 其余 rank 再构建(缓存命中)" 模式。
二、数据预处理:IndexedDatasetBuilder 与 IndexedDataset
数据预处理围绕两个类展开:
IndexedDatasetBuilderIndexedDataset
官方文档指出:目前端到端的数据预处理实现留给用户完成,详见类文档字符串(实际入口可参考 tools/preprocess_data.py)。
2.1 IndexedDatasetBuilder:构建与合并数据集
IndexedDatasetBuilder位于 indexed_dataset.py(约 930 行起),能够构建并合并IndexedDataset实例。其核心方法包括:
add_item(tensor, mode=0):向数据集追加单条序列,并记录其长度;add_document(tensor, lengths, modes=None):追加整篇文档,lengths给出文档内各序列的长度,同时更新document_indices;add_documents(documents, modes=None, eod_token=None, chunk_size=1_000_000):以批量方式写入 jagged 数组形式的文档列表(依赖awkward库),可自动在文档边界插入 EOD token,分块写入以控制内存;add_index(path_prefix):把另一个已存在的IndexedDataset整体合并进来(拼接索引与数据);finalize(idx_path):关闭数据文件并写入.idx索引文件。
构造参数为bin_path(数据文件路径)、dtype(默认numpy.int32)与multimodal(是否多模态,决定是否记录每条序列的 mode)。
2.2 IndexedDataset:.bin 与 .idx 的二进制布局
IndexedDataset是 Megatron Core 中最低层的数据接口。一个实例引用两个二进制文件:数据文件(.bin)保存文档/序列数据,索引文件(.idx)保存文档/序列元数据。
索引文件的内容分两部分。先存数据集级元数据:
- 索引文件头(为向后兼容保留);
- 索引版本号(为向后兼容保留);
- 一个数字编码,对应写入数据文件所用的数据类型;
- 数据集中的序列数量;
- 数据集中的文档数量。
随后存文档级与序列级元数据:
- 按顺序存每条序列的元素个数(
int32); - 按顺序存每条序列的字节偏移指针(
int64); - 按顺序存每个文档对应的连续序列索引区间
[...)(int64); - 按顺序存每条序列的 mode(仅多模态情形,
int8)。
这与源码中_IndexWriter.write()的落盘顺序完全一致:header + version + dtype code + sequence_count + document_count + sequence_lengths + sequence_pointers + document_indices + sequence_modes,其中_INDEX_HEADER = b"MMIDIDX\x00\x00",版本号为小端8字节整型 1。数据类型编码由DType枚举定义(int32对应编码 4、uint16对应编码 8 等)。
在读取侧,_IndexReader会对.idx做numpy.memmap,然后按偏移量依次还原sequence_lengths(int32)、sequence_pointers(int64)、document_indices(int64),多模态时再还原sequence_modes(int8)。
.bin数据文件有四种读取器实现,由IndexedDataset.__init__根据参数选择:
_MMapBinReader:mmap=True时内存映射读取(默认,本地文件场景);_FileBinReader:mmap=False时用文件指针seek + readinto,内置指数退避重试(默认 3 次重试,起始睡眠 10s 翻倍);_S3BinReader:S3 对象存储场景,以bin_chunk_nbytes为块大小维护内存缓存,按块范围(Range)拉取字节;_MultiStorageClientBinReader:基于 Multi-Storage Client 的范围读。
此外还暴露get(idx, offset, length)方法支持"只取序列一部分"的读取,这正是上层GPTDataset拼接跨文档样本时依赖的关键能力。IndexedDataset还支持fast_cache_load(跳过文件存在性断言)与sequences_per_dataset(直接用计数信息初始化索引,免开.idx文件,配合--per-dataset-sequences-path使用)。
三、数据加载之构建:BlendedMegatronDatasetConfig 与 Builder
DataLoader 的构建是一个分布式感知的过程,围绕五个要素展开:
BlendedMegatronDatasetConfigBlendedMegatronDatasetBuilderIndexedDatasetMegatronDatasetBlendedDataset
3.1 BlendedMegatronDatasetConfig(可扩展)
该类(blended_megatron_dataset_config.py)参数化BlendedMegatronDatasetBuilder,进而参数化MegatronDataset与BlendedDataset。不同训练/推理体制需要不同的扩展,例如 GPT 训练使用的GPTDatasetConfig。
配置类中的关键字段包括:
random_seed、sequence_length:随机种子与序列长度(必填);blend:[[prefix1, prefix2], [0.3, 0.7]]形式的混合定义,权重为 None 时按底层数据集长度推断;不能与blend_per_split同用;blend_per_split:train/valid/test 三个 split 各自独立的混合定义;split:从单一分布抽样时 train/valid/test 的权重字符串(如"99,1,0"),不能与blend_per_split同用;path_to_cache:所有可复用数据集索引的缓存目录;mmap_bin_files(默认 True):.bin用 mmap 还是文件指针读取;mid_level_dataset_surplus(默认 0.005):中层数据集构建时的样本冗余比例,顶层数据集若超采中层数据集需要调大;fast_cache_load/defer_npy_index_mmap:两个快速加载开关(见第七节),都要求path_to_cache非空;num_dataset_builder_threads:构建数据集的线程数。
__post_init__中完成了若干合法性约束:fast_cache_load时禁止与blend同用(应改用--per-split-data-args-path或 per-split data path);blend与blend_per_split互斥;当blend非空时必须提供split;两者都为空时自动进入mock=True的模拟数据模式,并取"1,1,1"的均匀 split。split 字符串经parse_and_normalize_split归一化后,再由convert_split_vector_to_split_matrix转换为 split 矩阵(各 split 在非重叠区间上的书端点区间),例如[0.99, 0.01, 0.0] -> [(0, 0.99), (0.99, 1.0), None]。
GPTDatasetConfig(gpt_dataset.py)在基类之上扩展了reset_position_ids、reset_attention_mask、eod_mask_loss、create_attention_mask、add_extra_token_to_sequence、drop_last_partial_validation_sequence、hybrid_context_parallel、sequences_per_dataset等字段,并在__post_init__中依据 tokenizer 词表大小自动推导token_dtype_code(词表超过 uint16 上限取 int32 编码 4,否则 uint16 编码 8)。
3.2 BlendedMegatronDatasetBuilder:最高层构建器
BlendedMegatronDatasetBuilder(blended_megatron_dataset_builder.py,注意正确路径为 megatron/core/datasets/blended_megatron_dataset_builder.py)构建 Megatron Core 最高层的数据接口。其__init__接收四类参数:
cls:要实例化的MegatronDataset子类;sizes:每个 split 要求的最少样本总数(可为 None);is_built_on_rank:判断当前 rank 是否构建数据集的可调用对象,必须感知 Megatron Core 并行策略(全局 rank、组内 rank、virtual rank 都可能影响返回值),且必须在全局 rank 0 上恰好返回 True;config:数据集配置对象。
build()方法按配置分三种情形处理每个 split:
- split 为 None:什么都不做;
- 单个贡献数据集:
size非 None 时按比例抽样子数据集;size为 None 时不做超额抽样; - 多个贡献数据集:权重与 size 均给定 → 按权重与 size 构建中层与顶层数据集;仅给权重不给 size → 报错;仅给 size → 顶层长度取
size(以各中层长度之和为上限);两者皆无 → 构建穷举索引。
值得注意的实现细节(均可在源码中验证):
- 构建过程使用
ThreadPoolExecutor并行构建各 prefix 的MegatronDataset(_build_megatron_datasets_parallel),每个任务通过contextvars.copy_context()把 OTel 追踪上下文传播进工作线程; - 分布式初始化后,默认 rank 0 先行构建(线程数还会按 GPU 数适度放大),
barrier之后其他 rank 再构建——此时必然命中缓存; - 开启
fast_cache_load时会跳过 "rank 0 先行 + barrier" 的同步点,各 rank 直接并行构建/加载,这正是其提速原理(见第七节); - 指定了混合权重时,每个 prefix 构建的样本数由
_get_size_per_split_per_dataset计算,会乘以(1 + mid_level_dataset_surplus)冗余系数,保证顶层数据集有足够样本可抽。
3.3 MegatronDataset(可扩展)与 BlendedDataset(顶层)
MegatronDataset(megatron_dataset.py)是建立在IndexedDataset之上的高阶抽象基类,子类(如GPTDataset)需实现numel_low_level_dataset与build_low_level_dataset两个静态方法。构造时它会汇总类名、数据集路径、样本数、split、以及_key_config_attributes()返回的关键配置属性(random_seed、sequence_length、split、split_matrix、tokenizer),序列化为 JSON 描述并计算 MD5 得到unique_description_hash——缓存索引文件名的唯一性就锚定在这个 hash 上,因此任何关键参数变化都会导致缓存失效重建。
BlendedDataset(blended_dataset.py)建立在多个MegatronDataset之上,仅当需要混合多个数据分布来贡献某个 split 时才需要它,混合比例通过配置控制。约束包括:各子数据集必须同类、同 split、权重为正、数据集聚合数小于 32767 等;若size非 None 则权重会被归一化。
四、数据加载之实现:GPTDataset 的三索引机制
GPTDataset由以下变量参数化:底层IndexedDataset实例indexed_dataset、split 索引indexed_indices(用于训练/验证/测试的连续文档或序列索引子集)、样本数N、序列长度S、随机种子R。
它创建三个索引映射以支撑查表:
(1)文档索引 Do_idx:一维数组,把i映射到文档索引,长度E * |indexed_indices|,其中E是满足E * |indexed_indices| >= N的最少 epoch 数。文档索引按R洗牌。
Given: N = 15 indexed_indices = [5, 6, 7, 8, 9] E = 3 Then, for example: Do_idx = [8, 8, 9, 6, 7, 5, 8, 5, 6, 6, 5, 9, 7, 7, 9](2)样本索引 Sa_idx:二维数组,把j映射到(i, Do_idx[i] 的偏移)对,形状[N + 1, 2]。行j与j + 1分别作为第j个样本的左、右边界。
Given: S = 1024 Then, for example: Sa_idx[0] = (0, 0) Sa_idx[1] = (0, 1024) => Do_idx[0] has length greater than S Sa_idx[2] = (1, 512) => Do_idx[0] has length 1536 Sa_idx[3] = (2, 0) => Do_idx[1] has length 1536 Sa_idx[4] = (5, 300) => Do_idx[2:5] are shorter documents relative to Do_idx[0:2] Sa_idx[5] = (6, 24) => Do_idx[5] has length 1300(3)洗牌索引 Sh_idx:一维数组,把k映射到j,长度N,按R洗牌。
Given N = 10 Then, for example: Sh_idx = [4, 0, 2, 6, 1, 9, 5, 8, 7, 3]查询第k个样本的过程:
# 1. 用洗牌索引得到样本索引内的索引 j j = Sh_idx[k] # 2. 用样本索引得到样本左右边界在文档索引中的位置及各自起始 token 偏移 i, offset = Sa_idx[j] i_next, offset_next = Sa_idx[j + 1] # 3. 用文档索引从连续的文档中取出 S 个 token sample = [] sample += indexed_dataset[Do_idx[i]][offset:] if i != i_next: sample += indexed_dataset[Do_idx[i + 1:i_next]] sample += indexed_dataset[Do_idx[i_next]][:offset_next]从源码看(gpt_dataset.py 的_query_document_sample_shuffle_indices,约 388-473 行),实际实现与伪代码一致,并做了两点增强:样本跨越单个文档时直接用dataset.get(idx, offset, length)一次取出;跨越多个文档时逐文档get拼接。此外取出的 token 总数为S + add_extra_token_to_sequence(默认多取 1 个 token),__getitem__中据此切分出tokens = text[:-1]与labels = text[1:],保证输入与标签都是完整长度;不足时以 pad token 补齐,并把 pad 位置的 loss mask 置零。
索引构建的关键工程细节(_build_document_sample_shuffle_indices,约 475-701 行):
- epoch 计算:
_get_num_epochs会不断累加 epoch 直至 token 总量满足N * S + add_extra_token的需求; - 末 epoch 分离:若最后一个 epoch 的样本数不足完整 epoch 样本数的 80%(
threshold = 0.80),则把末 epoch 与前面 epoch 分开洗牌(separate_final_epoch),避免最后一个不完整的 epoch 被全局打散后与前面样本过度混合; - C++ 加速:样本索引由 helpers.cpp 中的
build_sample_idx构建;当len(document_index) * 2 > len(sequence_lengths)(访问密度高)时会先把 mmap 的sequence_lengths复制进内存,源码注释解释了这样做的两个好处——顺序预读整个文件,以及进入 C++ 时持有 GIL 提高并行度; - 缓存:三个索引与
description.txt一并保存到缓存目录,文件名为{unique_description_hash}-{ClassName}-{split}-document_index.npy等;fast_cache_load时跳过文件存在性检查直接视为命中,defer_npy_index_mmap时索引不在初始化时加载、延迟到首次访问时以 mmap 方式加载(此时__len__会改用纯算术公式估算样本数,复用 helpers.cpp 的样本计数逻辑)。
4.1 BlendedDataset 的混合索引
BlendedDataset由数据集聚合D、权重W(每个数据集一个)与规模S参数化。它会按权重比例从各贡献数据集抽样,直到达到目标规模;每一步抽样时,从抽样误差(sampling error)最大的那个数据集抽取一个样本。
它创建两个"混合"索引:
- 数据集索引 Da_idx:一维数组,把
i映射到数据集索引,长度S:
Given D = [d0, d1, d2] W = [1/2, 1/4, 1/4] S = 4 Then, for example: Da_idx = [0, 1, 2, 0]- 数据集样本索引 Sa_idx:一维映射,把
i映射到数据集Da_idx[i]内的样本索引,长度S:
Given Da_idx = [0, 1, 2, 0] Then, for example: Sa_idx = [0, 0, 0, 1]查询第k个样本:
sample = D[Da_idx[k]][Sa_idx[k]]同样,为节省初始化时间,各索引在单个 rank 上顺序构建/缓存,再由其他 rank 并行加载;缓存索引锚定在BlendedDataset.__init__生成的 hash 上。源码实现(blended_dataset.py 的_build_indices)中,索引由 helpers.cpp 的build_blending_indices(size非 None)或build_exhaustive_blending_indices(size为 None,穷举模式)构建;构建后还会校验各子数据集是否被超采,若超采会抛出明确提示"增大mid_level_dataset_surplus"的IndexError。
五、离线缓存预生成:tools/prepare_cache.py
对于 GPT 风格训练,上述数据集缓存可以用 tools/prepare_cache.py 提前准备好,而不必等训练启动时 rank 0 构建。该脚本复用了pretrain_gpt.py与pretrain_mamba.py的正常数据集构建路径,包括GPTDataset、BlendedDataset与BlendedMegatronDatasetBuilder。它接受常规的数据集参数,支持 blend 与 per-split 数据集定义,并要求--data-cache-path,以便生成的缓存能被后续训练复用。
对于大型 blend 或大量文件 prefix 的场景尤其有用:构建 document、sample、shuffle 索引可能耗时数分钟,期间所有 GPU 都处于空闲状态,而 rank 0 只做纯 CPU 工作。
如果后续训练任务没有指定--global-batch-size(该参数用于确定数据集规模与 split),应通过--prepare-cache-world-size显式指定缓存准备时使用的 world size(脚本将其直接赋给args.world_size,见 prepare_cache.py 的_normalize_prepare_cache_args)。
明确的限制:tools/prepare_cache.py不支持--mock-data、--sft、--fim-data或--step-batch-size-schedule(源码_validate_prepare_cache_args会对这些选项直接抛ValueError;同时--data-cache-path为必填)。脚本还会在准备阶段强制关闭--dataloader-fast-cache-load与--dataloader-defer-npy-index-mmap(这两个开关的意义只在于消费已存在的缓存),并在运行前打印生效的 world size、DP size、global batch size、缓存路径与各 split 目标样本数。
六、Packing Scheduler:跨 DP×CP rank 的变长序列重调度
Packing 调度器把变长序列重新调度到 DP×CP 各 rank 上,以提升 GPU 利用率。它围绕以下模块构建:
data_schedule
data_schedule.py 包含高层调度逻辑与入口点:
HybridCPDataLoaderWrapper:混合上下文并行(CP)调度的包装类。每次__next__调用它会:(1) 从各 DP rank 拉取一批 packed 样本,(2) 在 DP 组内 all-gather 序列长度,(3) 用BalancedCPScheduler(来自 megatron/core/pipeline_parallel/ 的hybrid_cp_schedule)调度子样本,(4) 通过 all-to-all 通信把子样本重路由到正确的 DPxCP rank。BasePackingScheduler:打包调度器的抽象基类,定义了get_groups_and_subsamples()(调度算法)与run()(完整调度流水线:fetch、schedule、reroute、pack、broadcast 及 VPP 处理)的接口。DpBalancedScheduler:具体调度器,按原始顺序打包序列直到达到每个 DPxCP rank 的最大序列长度限制;支持把 microbatch 数对齐到 DP size 与 VPP stage 的整数倍。wrap_data_iterator():顶层入口,包装已有的data_iterator。它创建合适的调度器、运行调度流水线、广播元数据与新的num_microbatches,返回新的数据迭代器、更新后的 microbatch 数以及 FLOPs 统计。get_batch_on_this_rank_for_sequence_packing():为当前 rank 拉取并广播单个 packed microbatch。处理 TP/PP 广播,构造PackedSeqParams(含cu_seqlens、max_seqlen、qkv_format=thd),并可选地用 Transformer Engine 的thd_get_partitioned_indices在 CP rank 间划分序列。
data_schedule_utils
data_schedule_utils.py 包含调度器使用的工具函数,如broadcast_scalars、broadcast_tensor、build_packed_microbatches、reroute_samples_to_dcp_ranks等(data_schedule.py顶部 import 即列明了全部依赖项)。
七、快速 DataLoader 初始化:三个加速开关
大规模训练中,DataLoader 初始化可能耗时数分钟——因为要打开并内存映射大量文件,还会显著施压文件系统。Megatron Core 提供了三个由配置开关控制的优化:
7.1 --dataloader-fast-cache-load
假定数据集缓存已存在于指定的--data-cache-path中。启用后通过移除同步点与文件检查断言来加速创建过程。从源码看其具体生效点有三处:
- 配置层:blended_megatron_dataset_config.py 断言必须提供
--data-cache-path,且不能与--data-path(blend 形式)同用,应改用--per-split-data-args-path或--train-data-path/--valid-data-path/--test-data-path; - 构建层:blended_megatron_dataset_builder.py 跳过 "rank 0 先行构建 + barrier",各 rank 直接并行构建;同时跳过
indexed_indices的重复计算; - 数据层:indexed_dataset.py 跳过
sequence_lengths.shape[0]系列的一致性断言。
7.2 --dataloader-defer-npy-index-mmap
同样假定缓存已存在。启用后把数据集索引(.npy文件)的内存映射延迟到首次访问时进行。官方推荐与--num-workers > 0搭配使用,让 DataLoader 预取下一批数据,从而用后台预取掩盖索引 mmap 的开销。实现上,GPTDataset/BlendedDataset在_build_indices阶段只记录缓存路径并返回 None 索引,__getitem__首次调用时才safe_numpy_load(..., mmap_mode='r')(见 gpt_dataset.py 与 blended_dataset.py),__len__则改用 token 数算术公式直接推算。
7.3 --per-dataset-sequences-path
通过该配置指定 tools/build_sequences_per_dataset.py 生成的 JSON 文件。该脚本对 blend 中的每个文件 prefix 打开.idx读取序列数与文档数(_IndexReader),汇总为单一文件。此配置在处理数百乃至上千个文件 prefix 时尤其有用:它只需要一次open操作,而不是每个 prefix 一次。该 JSON 经GPTDatasetConfig.sequences_per_dataset传入IndexedDataset后,_IndexReader可跳过解析 34 字节头部之后的完整索引读取,直接用给定的(sequence_count, document_count)初始化(见 indexed_dataset.py)。脚本用法示例(来自其模块 docstring):
python3 tools/build_sequences_per_dataset.py --per-split-data-args-path my-training-dataset-blend.json --per-dataset-sequences-path my-training-dataset-blend-sequences-per-dataset.json八、小结:一次训练启动中数据管线的工作顺序
把以上内容串起来,一次 GPT 训练启动时数据管线的工作顺序为:
- 训练脚本通过
megatron.training的参数解析生成GPTDatasetConfig(含 blend、split、path_to_cache等),并计算各 split 目标样本数; - 所有 rank 调用
BlendedMegatronDatasetBuilder.build():rank 0 先构建(或用--dataloader-fast-cache-load并行构建),为每个 prefix 建IndexedDataset(mmap.bin,解析.idx); - 每个
GPTDataset按unique_description_hash检查缓存:命中则 mmap 加载三个.npy索引(或按defer_npy_index_mmap延迟加载),未命中则由 rank 0 构建(C++ 加速)并写缓存; - 多个数据集的 split 由
BlendedDataset以 "最大抽样误差" 策略生成混合索引; - 运行时
__getitem__依次经 shuffle → sample → document 三级查表拼接出定长样本,再经 DataLoader(及其可选的HybridCPDataLoaderWrapperpacking 调度)送入模型。
相关源码与工具入口速查:
| 主题 | 路径 |
|---|---|
| 底层二进制接口 | megatron/core/datasets/indexed_dataset.py |
| C++ 索引构建加速 | megatron/core/datasets/helpers.cpp / helpers.py |
| GPT 数据集与三索引 | megatron/core/datasets/gpt_dataset.py |
| 混合数据集 | megatron/core/datasets/blended_dataset.py |
| 分布式构建器 | megatron/core/datasets/blended_megatron_dataset_builder.py |
| 配置数据类 | megatron/core/datasets/blended_megatron_dataset_config.py |
| 抽象基类 | megatron/core/datasets/megatron_dataset.py |
| 离线缓存预生成 | tools/prepare_cache.py |
| 每数据集元数据生成 | tools/build_sequences_per_dataset.py |
| Packing 调度器 | megatron/core/datasets/data_schedule.py / data_schedule_utils.py |
适用前提提示:以上行为均基于当前仓库版本,快速加载类开关(fast cache load、defer mmap、per-dataset sequences)都要求缓存已预先构建且配置了--data-cache-path;tools/prepare_cache.py不支持 mock/SFT/FIM/step-batch-size-schedule 路径,离线预生成缓存时应避免这些模式。
【免费下载链接】Megatron-LMOngoing research training transformer models at scale项目地址: https://gitcode.com/GitHub_Trending/me/Megatron-LM
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考