XTuner Llava 训练数据集离线处理指南:四步告别在线预处理耗时
【免费下载链接】xtunerA Next-Generation Training Engine Built for Ultra-Large MoE Models项目地址: https://gitcode.com/GitHub_Trending/xt/xtuner
导读
本文讲解 XTuner 中 Llava(视觉语言模型)训练数据集的离线处理方案。当训练数据量非常大时,每次训练前在线处理数据(分词、映射对话格式、按模板组装 prompt)会极为耗时,甚至可能触发分布式训练中的超时问题。通过本指南,你将掌握一套完整的四步流程:导出 config、离线处理数据并保存至本地、修改 config 指向离线数据、直接启动训练,让多次重复训练直接复用预处理结果,显著缩短训练启动时间。
为什么需要离线处理 Llava 数据
XTuner 默认采用在线数据预处理策略,即训练启动时通过process_hf_dataset对原始数据实时完成分词、对话映射与 prompt 模板组装,得到训练所需的input_ids、labels等张量。这一策略降低了使用门槛,但存在明显短板:
- 耗时集中在启动阶段:数据量越大,每次训练启动前需要等待的预处理时间越长,且该过程为纯 CPU 任务;
- 可能触发
nccl timeout:在分布式训练中,XTuner 由 rank0 统一完成预处理后通过torch.distributed.broadcast_object_list广播至其他 rank,而该广播的超时时间与 nccl 通信超时时间相同(默认 30 分钟)。当训练集过大时,rank0 可能无法在超时时间内处理完全部数据而导致报错(详见 超大规模数据集 的说明)。
离线处理的思路是:把"数据处理"这一一次性成本从每次训练中剥离出来——先对原始数据做一次完整的文本预处理并保存到本地磁盘,之后无论训练多少次,都直接读取这份已处理好的数据,跳过重复计算。
前置条件
- 已正确安装 XTuner 及其依赖(含
datasets、transformers、mmengine等); - 已准备 Llava 训练所需的原始数据,包括:
- JSON 格式的对话标注文件(如
llava_v1_5_mix665k.json或blip_laion_cc_sbu_558k.json); - 对应的图片目录(
image_folder); - 视觉编码器与 LLM 的预训练权重路径。
- JSON 格式的对话标注文件(如
- 已了解 Llava 训练 config 的基本结构,可参考 xtuner/configs/llava 下的官方示例。
Step 1:导出模板 config 文件
首先查看 XTuner 提供的所有与 Llava 训练相关的 config:
xtuner list-cfg -p llava该命令会列出仓库中所有文件名或路径匹配llava的配置文件。仓库当前提供的 Llava config 覆盖了多种模型组合,例如:
llava_internlm2_chat_7b_clip_vit_large_p14_336_e1_gpu8_pretrain.py(InternLM2-Chat-7B + CLIP ViT-Large/14-336,预训练)llava_internlm2_chat_7b_qlora_clip_vit_large_p14_336_lora_e1_gpu8_finetune.py(同上,QLoRA 微调)- 以及基于 InternLM2-Chat-1.8B/20B、LLaMA3-8B/70B、Phi-3-mini、Vicuna-7B/13B 等底座的各种 pretrain/finetune 配置(完整清单见 xtuner/configs/llava)。
找到需要使用的 config 名称(下文以${CONFIG_NAME}指代)后,将其导出至当前目录:
xtuner copy-cfg ${CONFIG_NAME} .执行后当前目录会生成一个名为${CONFIG_NAME}_copy.py的副本文件(与原配置内容完全一致),后续所有修改都在这个副本上进行。
Step 2:离线处理数据集
使用 XTuner 提供的专用工具脚本离线处理 Llava 训练数据中的文本部分:
python xtuner/tools/process_untokenized_llava_data.py \ ${CONFIG_PATH} \ --save-folder /folder/to/save/processed/dataset参数说明:
| 参数 | 含义 |
|---|---|
${CONFIG_PATH} | Step 1 中导出的 config 文件路径(即${CONFIG_NAME}_copy.py) |
--save-folder | 离线处理后数据集的保存路径,例如/folder/to/save/processed/dataset |
底层实现原理
从源码看,该脚本的核心逻辑非常简洁,位于 process_untokenized_llava_data.py:
- 通过 mmengine 的
Config.fromfile读取 config 文件; - 调用
BUILDER.build(config.train_dataloader.dataset)构建LLaVADataset实例——这一步会完整执行数据加载、dataset_map_fn对话映射、template_map_fn模板组装以及 tokenizer 分词等全部文本预处理; - 取出
llava_dataset.text_data属性(即预处理完成后的 HuggingFace Dataset 对象); - 调用
text_data.save_to_disk(args.save_folder)将结果以磁盘数据集(Arrow 格式)形式保存。
也就是说,Step 2 实质上就是在训练之外单独执行了一遍"在线预处理",并把预处理产物持久化。注意:这一步只处理文本数据(input_ids、labels、图片路径引用等),图片本身仍然以原始文件形式存放于image_folder,训练时再按需读取。
验证离线处理结果
处理完成后,可立即读取验证数据是否符合预期:
from datasets import load_from_disk ds = load_from_disk('/folder/to/save/processed/dataset') print(ds)应能看到保存的 dataset 结构与原始text_data一致,包含input_ids、labels、image等字段。
Step 3:修改 config 文件
对 Step 1 导出的 config 做如下修改,将数据源从"在线处理"切换为"读取离线数据":
####################################################################### # PART 3 Dataset & Dataloader # ####################################################################### llava_dataset = dict( - data_path=data_path, - tokenizer=tokenizer, + offline_processed_text_folder=/folder/to/save/processed/dataset ...)其中/folder/to/save/processed/dataset为 Step 2 中--save-folder指定的路径。
修改后 config 中 LlavaDataset 的完整形态
以仓库中 QLoRA 微调配置(llava_internlm2_chat_7b_qlora_clip_vit_large_p14_336_lora_e1_gpu8_finetune.py)为例,修改后llava_dataset大致为:
llava_dataset = dict( type=LLaVADataset, offline_processed_text_folder='/folder/to/save/processed/dataset', image_folder=image_folder, image_processor=image_processor, dataset_map_fn=llava_map_fn, template_map_fn=dict( type=template_map_fn_factory, template=prompt_template), max_length=max_length, pad_image_to_square=True)源码层面的行为说明
从 llava.py 中LLaVADataset.__init__的实现可以看到,构造时通过assert offline_processed_text_folder or (data_path and tokenizer)保证两种数据来源至少指定其一:
- 当设置了
offline_processed_text_folder时,直接调用load_from_disk(offline_processed_text_folder)加载文本数据,不再执行data_path加载与process_hf_dataset分词流程,这正是节省启动时间的关键; - 若同时设置了
offline_processed_text_folder与data_path,源码会打印一条 WARNING,提示将优先从offline_processed_text_folder加载数据(if offline_processed_text_folder and data_path:分支); - 图片的加载在
__getitem__中按需完成:根据text_data中的image字段,通过Image.open(os.path.join(self.image_folder, image_file))读取并交给image_processor预处理为pixel_values;没有图片的样本则填充全零张量。因此 config 中的image_folder必须保留并指向真实图片目录。
此外,LLaVADataset.modality_length属性(用于LengthGroupedSampler的长度分组采样)同样基于text_data计算,离线加载后依然正常工作。
Step 4:开始训练
使用 Step 3 修改得到的 config 直接训练即可。以预训练 config(llava_internlm2_chat_7b_clip_vit_large_p14_336_e1_gpu8_pretrain.py)为例:
# 单卡 xtuner train llava_internlm2_chat_7b_clip_vit_large_p14_336_e1_gpu8_pretrain_copy.py --deepspeed deepspeed_zero2 # 多卡(torchrun) NPROC_PER_NODE=${GPU_NUM} xtuner train llava_internlm2_chat_7b_clip_vit_large_p14_336_e1_gpu8_pretrain_copy.py --deepspeed deepspeed_zero2 # 多卡(slurm) srun ${SRUN_ARGS} xtuner train llava_internlm2_chat_7b_clip_vit_large_p14_336_e1_gpu8_pretrain_copy.py --launcher slurm --deepspeed deepspeed_zero2其中--deepspeed deepspeed_zero2指定使用 DeepSpeed ZeRO-2 策略,对应配置见 xtuner/configs/deepspeed 目录(可选deepspeed_zero1、deepspeed_zero2、deepspeed_zero3及对应的 offload 版本)。训练启动后,数据加载阶段将直接读取磁盘上的预处理结果,不再执行耗时的大规模分词。
注意事项
- 切换 tokenizer 或修改数据处理超参数后必须重新离线处理:离线数据是"某个 tokenizer + 某组超参数"下的产物。若更换了
llm_name_or_path(tokenizer 随之变化),或调整了单条数据的最大长度max_length、dataset_map_fn、template_map_fn等,都必须重新执行 Step 2,否则直接训练会因 token 序列与配置不匹配而报错; image_folder仍需保留:离线处理仅覆盖文本部分,训练时图片依旧按image_folder实时加载,因此该目录路径不能删除或改动;- 磁盘占用:
save_to_disk以 Arrow 格式落盘,数据量极大时注意预留足够的磁盘空间; - 与纯文本模型的差异:纯语言模型数据集的离线处理使用另一脚本
xtuner/tools/process_untokenized_datasets.py,其会额外生成*_modified.py配置文件并自动改写train_dataloader.dataset;而 Llava 场景的process_untokenized_llava_data.py仅负责保存文本数据,config 的改写需要按 Step 3 手动完成。两者的处理对象与流程不同,使用时应加以区分。
【免费下载链接】xtunerA Next-Generation Training Engine Built for Ultra-Large MoE Models项目地址: https://gitcode.com/GitHub_Trending/xt/xtuner
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考