shuffle(数据混洗)是影响模型收敛效果的关键环节。如果训练样本按固定顺序输入,模型会学习到样本顺序带来的虚假规律,造成过拟合、loss 震荡、泛化能力变差。昇思 MindSpore 与 MindSpore‑Transformers 针对普通数据集、Megatron bin/idx 二进制索引数据集提供了完整的 shuffle 实现,区分单机 shuffle、分布式多卡 shuffle,同时存在全局混洗、文件级混洗、文件内混洗多种模式。本文结合实操代码、yaml 配置、常见踩坑案例,讲解昇思大模型场景 shuffle 原理、使用方式与调优方案,适配 CANN 8.5、MindSpore‑Transformers 1.9 环境。
一、shuffle 基础原理
shuffle 核心目标是随机打乱样本读取顺序,消除数据集原始顺序带来的偏见。昇思框架底层有两种实现逻辑:
1.缓冲区 shuffle:维护一块内存缓冲区buffer_size,读取数据填充缓冲区,每次从缓冲区随机选出一条样本输出,再补充新样本。buffer_size越大混洗效果越好,但占用内存越高;当buffer_size等于样本总数,等价于全局完整 shuffle。
2.索引 shuffle(Megatron bin/idx 数据集):不改动磁盘上 bin/idx 原始文件,仅在内存中打乱样本索引序号,训练时按照打乱后的索引去读取二进制数据集,适合 TB 级超大预训练数据集,避免把全部数据加载进内存。
重要区别:离线预处理阶段不要 shuffle。bin/idx 数据集预处理输出保持原始顺序,shuffle 在训练加载阶段内存中完成;不要预处理时做全局打乱,会极大消耗磁盘 IO 与预处理时间。
分布式训练下,数据并行各卡会拿到数据集的分片,shuffle 分为两个层级:
● 本地 shuffle:每张卡只在自己分到的分片内打乱,不同卡之间样本没有交叉,混洗效果有限;
● 全局 shuffle:所有样本统一随机排序,再分片分配给各个卡,混洗效果最好,大数据集会带来一定内存开销。
二、环境准备与基础单机代码示例
环境:Atlas 300I Pro,openEuler 22.03 ARM64,CANN8.5.0,MindSpore 2.3,MindSpore‑Transformers1.9。
# 加载CANN环境变量 echo "source /usr/local/Ascend/ascend‑toolkit/8.5.0/set_env.sh" >> ~/.bashrc source ~/.bashrc # 校验版本 python3 -c " import mindspore import mindspore.dataset as ds print('mindspore版本:', mindspore.__version__) "基础 GeneratorDataset 单机 shuffle 代码,演示 buffer_size 对混洗效果的影响:
import numpy as np import mindspore.dataset as ds # 设置随机种子保证可复现 ds.config.set_seed(42) def sample_generator(): """模拟大模型token样本生成器""" for i in range(10): yield (np.array([i]*128, dtype=np.int32), ) # 创建数据集 dataset = ds.GeneratorDataset(source=sample_generator, column_names=["input_ids"]) print("===== shuffle关闭,原始顺序 =====") for item in dataset.create_dict_iterator(num_epochs=1, output_numpy=True): print(item["input_ids"][0], end=" ") # buffer_size=3,缓冲区混洗 dataset_shuffle = dataset.shuffle(buffer_size=3) print("\n===== buffer_size=3 局部shuffle =====") for item in dataset_shuffle.create_dict_iterator(num_epochs=1, output_numpy=True): print(item["input_ids"][0], end=" ") # buffer_size等于总样本数,实现全局shuffle dataset_global = dataset.shuffle(buffer_size=10) print("\n===== buffer_size=10 全局shuffle =====") for item in dataset_global.create_dict_iterator(num_epochs=1, output_numpy=True): print(item["input_ids"][0], end=" ")运行可以观察:buffersize 较小时,样本只是局部打乱;buffersize 等于全部样本数量,才能实现完整全局混洗。
三、Megatron bin/idx 数据集 shuffle 配置(大模型预训练)
针对 Megatron bin/idx 二进制数据集,使用BlendedMegatronDatasetDataLoader,shuffle 不在 dataset.shuffle () 接口调用,而是在 yaml 配置中控制,支持True/False/infile/files/global多种模式。
shuffle 取值 | 模式说明 |
False | 关闭混洗,按原始索引顺序读取 |
True/global | 全局 shuffle,跨全部样本打乱索引,预训练推荐开启 |
infile | 仅在单个数据文件内部打乱,文件之间顺序不变 |
files | 只打乱文件顺序,文件内部样本顺序不变 |
yaml 配置示例(预训练任务)
train_dataset: &train_dataset data_loader: type: BlendedMegatronDatasetDataLoader datasets_type: "GPTDataset" column_names: ["input_ids", "labels", "loss_mask"] shuffle: True # 开启全局shuffle num_parallel_workers: 8 drop_remainder: True config: seed: 42 # shuffle随机种子,保证实验可复现 seq_length: 2048 split: "1,0,0" data_path: "./dataset/mindspore_pretrain_text_document"Python 代码直接加载 bin/idx 并开启 shuffle:
from mindformers.dataset.megatron.indexed_dataset import IndexedDataset from mindformers.dataset import BlendedMegatronDataset prefix = "./dataset/mindspore_pretrain_text_document" dataset = BlendedMegatronDataset( data_path=prefix, seq_length=2048, shuffle=True, seed=42 ) # 读取shuffle之后的样本 for idx in range(5): sample = dataset[idx] print(f"sample {idx}, input_ids前10:{sample['input_ids'][:10]}")注意:bin/idx 的 shuffle 操作只打乱内存中的读取索引,不会修改磁盘 bin、idx 文件,每次训练启动都会重新执行混洗。
四、分布式多卡训练 shuffle 要点
多卡数据并行场景,如果配置不当会出现 “每张卡内部 shuffle,但全局样本顺序没有打散”,影响收敛。
1. 设置统一seed,保证所有卡初始化随机数状态一致;
2.BlendedMegatronDatasetDataLoader内部会先做全局索引 shuffle,之后再执行分片,避免仅本地分片打乱;
3. 不要手动使用num_shards/shard_id之后再调用 dataset.shuffle (),会降级成本地 shuffle 模式。
错误示例(不推荐):
# 错误:先分片再shuffle,只能本地混洗 dataset = ds.GeneratorDataset(source=gen, num_shards=8, shard_id=0) dataset = dataset.shuffle(buffer_size=10000)正确做法:优先交给BlendedMegatronDatasetDataLoader,配置shuffle: True,框架内部完成全局打乱再分片。
五、常见问题排查与调优
问题 1:shuffle 效果差,样本局部聚集
现象:loss 震荡,连续多个 batch 样本主题高度相似。
根因:普通 dataset 的buffer_size设置过小;Megatron 数据集 shuffle 配置为infile/files,没有开启global。
解决:普通数据集调大buffer_size;bin‑idx 数据集 yaml 设置shuffle: True。
问题 2:开启 shuffle 内存占用飙升,OOM
现象:开启全局 shuffle 后数据加载进程内存溢出。
根因:全局 shuffle 需要保存全部样本索引列表,TB 数据集样本量巨大。
优化:Megatron bin‑idx 索引占用内存远小于原始 token;超大数据集可使用infile折中方案,平衡内存与混洗效果。
问题 3:多次训练结果不一致,复现失败
排查:没有固定seed;多卡环境各个进程 seed 不统一。
解决:yaml 配置 config.seed,同时设置ds.config.set_seed(42),保证随机数一致。
问题 4:SFT 微调 shuffle 失效
SFT 使用 HFDataLoader、MindDataset,需要在 dataloader 层设置shuffle: True;SFT 数据集不要使用 Megatron bin‑idx 的 shuffle 逻辑,指令样本标签 labels 需要和 input_ids 同步打乱。
六、总结
shuffle 是大模型数据链路中不可忽略的环节,直接影响模型收敛与泛化性能。昇思 MindSpore 有两套实现:普通数据集使用dataset.shuffle(buffer_size),buffer_size 决定混洗效果;Megatron bin/idx 预训练数据集,优先在 yaml 配置中设置shuffle: True,框架在内存打乱样本索引,不修改磁盘文件。
分布式预训练重点区分全局 shuffle 和本地 shuffle,尽量使用框架内置 DataLoader,避免手动分片后再 shuffle。同时权衡混洗效果与内存开销,固定随机种子保障实验可复现,规避因 shuffle 配置错误带来的训练不收敛问题。