XTuner 多轮对话 SFT 数据 pipeline 完全指南:从 HuggingFace Hub 与自定义数据集到可训练 Config
【免费下载链接】xtunerA Next-Generation Training Engine Built for Ultra-Large MoE Models项目地址: https://gitcode.com/GitHub_Trending/xt/xtuner
本文围绕 XTuner 的多轮对话指令微调数据管线展开,系统讲解如何将 HuggingFace Hub 开源数据集或自定义 JSON 数据集,通过 map function 映射、模板拼接与打包,转换为可直接驱动 SFT 训练的train_dataset。读完本文,你将掌握 XTuner 多轮对话标准数据格式、process_hf_dataset底层处理流程、oasst1_map_fn等内置映射函数的实现原理,以及从xtuner list-cfg到xtuner train的完整落地步骤。
多轮对话 SFT 与 XTuner 数据 pipeline 概览
多轮对话指令微调(Supervised FineTune,SFT)旨在提升模型的多轮对话能力。与单轮对话不同,多轮对话数据由多轮"指令(问题)+ 对应 GroundTruth 回答"组成,模型需要学会在延续上下文的前提下逐轮应答。
XTuner 在数据处理阶段需要将原始数据转换为其内置支持的数据集格式,整体支持两条数据来源路径:
- HuggingFace Hub 数据集:直接通过
datasets.load_dataset加载,核心工作是把不同数据集的原始格式映射为 XTuner 定义的多轮对话数据格式; - 自定义数据集:推荐用户直接按照多轮对话数据格式构造 JSON 数据集,从而免去映射步骤。
两条路径殊途同归,最终都会汇入 XTuner 统一的数据后处理入口process_hf_dataset(定义于 xtuner/dataset/huggingface.py),依次完成"原始数据加载 → map function 格式映射 → prompt 模板拼接 → tokenize → pack 打包"的流水线处理。
多轮对话数据格式:XTuner 的标准格式
理解标准格式是使用多轮对话数据的前提。XTuner 为统一增量预训练、单轮对话、多轮对话三种数据集格式,引入了"system"、"input"、"output"三个核心字段(见 数据集格式文档):
"system"、"input":保存不参与 loss 计算的文本,例如系统提示词和用户指令;"output":保存需要计算 loss的文本,即指令对应的 GroundTruth 回答。
在训练过程中,一条数据内的多组system/input/output会被拼接后整体输入模型,并行计算每个 token 位置的 loss,但只有output部分的 loss 参与梯度回传,指令部分不参与权重更新。
多轮对话数据集中"conversation"键对应的值是一个列表,列表中每个元素保存一轮对话。列表长度为 n 即可容纳 n 轮对话,因此增量预训练与单轮对话数据集可以看作conversation列表长度为 1 的特例。一个典型的多轮对话数据集长这样:
[{ "conversation":[ { "system": "You are an AI asssistant.", "input": "Hello?", "output": "Hello! How can I help you?" }, { "input": "What's the date today?", "output": "Today is Monday, August 14, 2023." }, { "input": "Thank you!", "output": "You are welcome." } ] }, { "conversation":[ { "system": "You are an AI asssistant.", "input": "Hello?", "output": "Hello! How can I help you?" }, { "input": "How's the weather today in Rosso?", "output": "The weather in Rosso on Wednesday, August 16th, is going to be cloudy for most of the day, together with moderate rain around noon." }, { "input": "Thank you!", "output": "You are welcome." } ] }]注意:
system字段仅在首轮出现,后续轮次可省略(缺省为空字符串)。<EOS>、<BOS>等特殊 token 由模板与 tokenizer 在后续阶段统一处理。
三种主流多轮对话训练方法对比
针对"一条多轮对话数据如何用于训练",业界存在两种常见做法,而 XTuner 采取了更充分高效的方式(详见 dataset_format.md):
- 方法 1(仅末轮参与训练):将 System、User1~User3 全部视为输入,仅把 Assistant3 作为预测目标。弊端是 Assistant1、Assistant2 完全未参与训练,数据利用率低;
- 方法 2(拆分多条):将一条 n 轮对话拆成 n 条独立数据,每轮都参与训练。缺点是需要把数据量膨胀为原来的 n 倍,训练效率下降为 1/n;
- XTuner 方法(整段拼接 + 掩码回传):将多轮对话整体拼接后输入模型,并行计算每个位置的 loss,仅
output部分的 loss 参与回传。既保证了每一轮回答都参与训练,又不需要拆分数据,兼顾数据利用率与训练效率。这也是 XTuner 多轮对话数据格式采用conversation列表结构的原因。
路径一:使用 HuggingFace Hub 数据集
当目标数据集托管在 HuggingFace Hub 上时(如 oasst1、alpaca、open_orca 等),你需要把其原始字段映射为 XTuner 标准格式。下面以 oasst1 数据集为例走完全部流程。
Step 1:映射原始数据集为标准格式
由于不同数据集的字段与组织方式千差万别,XTuner 通过map function实现格式映射。先观察 oasst1 的原始格式:
>>> from datasets import load_dataset >>> ds = load_dataset(path='timdettmers/openassistant-guanaco') >>> ds['train'] Dataset({ features: ['text'], num_rows: 9846 }) >>> ds['train'][0]['text'] '### Human: xxx ### Assistant: xxx ###Human: xxx ###Assistant: xxx'可以看到,oasst1 每一条样本是一个以### Human:/### Assistant:分隔标记的纯文本,天然携带多轮对话信息。这样的数据既可以当作增量预训练语料,也可以处理后作为多轮对话数据集。通过下面的 map function,即可把text解析为conversation列表:
# 假设将该函数存放在 ./map_fn.py 文件中 SYSTEM_OASST1 = '' # oasst1 并未使用 system 字段 def custom_map_fn(example): r""" Example before preprocessing: example['text'] = '### Human: Can you explain xxx' '### Assistant: Sure! xxx' '### Human: I didn't understand how xxx' '### Assistant: It has to do with a process xxx.' Example after preprocessing: example['conversation'] = [ { 'input': 'Can you explain xxx', 'output': 'Sure! xxx' }, { 'input': 'I didn't understand how xxx', 'output': 'It has to do with a process xxx.' } ] """ data = [] for sentence in example['text'].strip().split('###'): sentence = sentence.strip() if sentence[:6] == 'Human:': data.append(sentence[6:].strip()) elif sentence[:10] == 'Assistant:': data.append(sentence[10:].strip()) if len(data) % 2: # The last round of conversation solely consists of input # without any output. # Discard the input part of the last round, as this part is ignored in # the loss calculation. data.pop() conversation = [] for i in range(0, len(data), 2): system = SYSTEM_OASST1 if i == 0 else '' single_turn_conversation = { 'system': system, 'input': data[i], 'output': data[i + 1]} conversation.append(single_turn_conversation) return {'conversation': conversation}该函数的关键逻辑:
- 按
###分隔文本并去掉首尾空白,依据Human:/Assistant:前缀归类到data列表; - 若
data长度为奇数,说明最后一轮只有提问没有回答,直接pop丢弃——因为该部分在 loss 计算中会被忽略,保留无意义; - 两两配对组装成
{'system', 'input', 'output'}字典,仅在首轮写入 system 字段。
这一逻辑与仓库内置的官方实现 xtuner/dataset/map_fns/dataset_map_fns/oasst1_map_fn.py 完全一致,你可以直接对比阅读。除 oasst1 外,xtuner/dataset/map_fns/dataset_map_fns/目录下还提供了alpaca_map_fn、openorca_map_fn、wizardlm_map_fn、sql_map_fn、code_alpaca_map_fn等二十余个内置映射函数,覆盖常见开源 SFT 数据集。
Step 2:列出候选模型名字
XTuner 提供多个开箱即用的配置文件,可通过以下命令查看:
xtuner list-cfg -p internlm-p为模糊查找参数,若想训练其他模型,将internlm替换为 XTuner 支持的其他模型名称即可(例如baichuan、llama、qwen、chatglm、deepseek等,完整列表可查看 xtuner/configs 目录)。
Step 3:复制 config 文件
如果现有配置文件不能满足需求,先将其导出到本地再进行修改:
xtuner copy-cfg ${CONFIG_NAME} ${SAVE_DIR}例如将名为internlm_7b_qlora_oasst1_e3的 config 导出至当前目录:
xtuner copy-cfg internlm_7b_qlora_oasst1_e3 .该配置的原始版本位于 xtuner/configs/internlm/internlm_7b/internlm_7b_qlora_oasst1_e3.py,导出的文件将以拷贝形式落在当前目录供你编辑。
Step 4:修改 config 文件
对 Step 3 复制得到的 config 文件,需要做三处修改:
- 导入 Step 1 中实现的映射函数
custom_map_fn; - 用
custom_map_fn替换train_dataset中的dataset_map_fn; - 调整原始数据集的路径(
load_dataset的具体用法可参考 HuggingFace datasets 官方文档的 loading 章节)。
完整 diff 如下:
from xtuner.dataset import process_hf_dataset from datasets import load_dataset - from xtuner.dataset.map_fns import oasst1_map_fn, template_map_fn_factory + from xtuner.dataset.map_fns import template_map_fn_factory + from mmengine.config import read_base + with read_base(): + from .map_fn import custom_map_fn ... ####################################################################### # PART 1 Settings # ####################################################################### - data_path = 'timdettmers/openassistant-guanaco' + data_path = 'path/to/your/data' ... ####################################################################### # STEP 3 Dataset & Dataloader # ####################################################################### train_dataset = dict( type=process_hf_dataset, dataset=dict(type=load_dataset, path=data_path), tokenizer=tokenizer, max_length=max_length, - dataset_map_fn=oasst1_map_fn, + dataset_map_fn=custom_map_fn, template_map_fn=dict( type=template_map_fn_factory, template=prompt_template), remove_unused_columns=True, shuffle_before_pack=True, pack_to_max_length=pack_to_max_length) ...Step 5:检查数据集(可选)
修改配置文件后,可以运行检查脚本验证数据集是否正确构建:
xtuner check-custom-dataset $CONFIG其中$CONFIG是 Step 4 修改过的 config 文件路径。该命令对应脚本 xtuner/tools/check_custom_dataset.py,它会依次打印dataset_map_fn映射后的conversation、加入模板后的结果、tokenize 后的input_ids/labels以及 pack 到max_length之后的结果,方便你逐环节核对数据是否正确。
路径二:使用自定义数据集
当数据是自己构造的(例如业务场景私有对话数据)时,推荐直接按 XTuner 标准格式构造数据集。若你的自定义数据集是 oasst1 等其他格式,则参考上一节"使用 HuggingFace Hub 数据集"的做法编写 map function 即可。
Step 1:数据集准备
按照多轮对话数据格式准备自定义 JSON 数据:
[{ "conversation":[ { "system": "xxx", "input": "xxx", "output": "xxx" }, { "input": "xxx", "output": "xxx" } ] }, { "conversation":[ { "system": "xxx", "input": "xxx", "output": "xxx" }, { "input": "xxx", "output": "xxx" } ] }]Step 2:列出候选模型名字
xtuner list-cfg -p internlm-p为模糊查找,如需训练其他模型,将internlm替换为 XTuner 支持的其他模型名称。
Step 3:复制 config 文件
xtuner copy-cfg internlm_7b_qlora_oasst1_e3 .Step 4:修改 config 文件
自定义数据集场景下需要两处修改:
- 调整原始数据集的路径;
- 由于数据集已是 XTuner 标准格式,需将
train_dataset中的dataset_map_fn置为None,并将load_dataset的path指定为'json'以加载本地 JSON 文件:
from xtuner.dataset import process_hf_dataset from datasets import load_dataset - from xtuner.dataset.map_fns import oasst1_map_fn, template_map_fn_factory + from xtuner.dataset.map_fns import template_map_fn_factory ... ####################################################################### # PART 1 Settings # ####################################################################### - data_path = 'timdettmers/openassistant-guanaco' + data_path = 'path/to/your/json/data' ... ####################################################################### # STEP 3 Dataset & Dataloader # ####################################################################### train_dataset = dict( type=process_hf_dataset, - dataset=dict(type=load_dataset, path=data_path), + dataset=dict( + type=load_dataset, path='json', data_files=dict(train=data_path)), tokenizer=tokenizer, max_length=max_length, - dataset_map_fn=oasst1_map_fn, + dataset_map_fn=None, template_map_fn=dict( type=template_map_fn_factory, template=prompt_template), remove_unused_columns=True, shuffle_before_pack=True, pack_to_max_length=pack_to_max_length) ...Step 5:检查数据集(可选)
xtuner check-custom-dataset $CONFIG其中$CONFIG是 Step 4 修改过的 config 文件路径。该脚本还会自动做两项合法性校验(见 xtuner/tools/check_custom_dataset.py):
- 若数据不是标准格式且
dataset_map_fn为None,会报错提示你需要提供dataset_map_fn完成格式映射; - 若数据已是标准格式但
dataset_map_fn非空,会报错提示你将其置为None,避免重复映射。
因此,写自定义数据集时务必保证dataset_map_fn的设置与数据实际格式严格匹配。
底层机制:process_hf_dataset 数据处理流水线
理解process_hf_dataset的底层实现(源码见 xtuner/dataset/huggingface.py),有助于你精准排错和调优。一次完整的多轮对话数据处理会经历以下阶段:
- 构建原始数据集:
build_origin_dataset通过注册器BUILDER.build执行 config 中dataset的构造逻辑;若结果是DatasetDict且未指定split,会将其各 split 拼接为一个数据集; - 格式映射:
map_dataset调用dataset_map_fn,将原始字段转换为conversation标准格式。dataset_map_fn既可以是函数,也可以是注册在MAP_FUNC中的字符串名称;映射时通过dataset.map(dataset_map_fn, num_proc=map_num_proc)并行执行; - 模板拼接:
add_template_to_dataset调用template_map_fn为每一轮对话套上 prompt 模板,随后过滤掉conversation为空的数据; - tokenize:
tokenize_dataset调用encode_fn,把文本编码为input_ids与labels,并依据input_ids_with_output决定是否保留 GroundTruth 输出;随后过滤掉labels中完全没有有效标签(全部小于 0)的数据; - 打包:若
pack_to_max_length为 True,先按shuffle_before_pack决定是否打乱,再用Packer将多条短样本拼接到max_length,以提升 GPU 利用率、缩短训练时间; - 附加长度信息:为每条样本计算
length字段,供长度分组采样等模块使用。
其中template_map_fn的实现位于 xtuner/dataset/map_fns/template_map_fn.py,它逐轮处理conversation:
- 用
template.INSTRUCTION格式化input,并在存在非空system时用template.SYSTEM把系统提示词拼到最前面; - 若模板定义了
SUFFIX(如<eos>后缀),将其追加到output末尾; - 为每轮写入
need_eos_token与sep字段,控制本轮回答是否需要补 EOS token 及轮与轮之间的分隔符。
train_dataset中常用参数的作用如下:
| 参数 | 默认值 | 作用 |
|---|---|---|
dataset_map_fn | None | 将原始数据映射为conversation标准格式的函数或注册名 |
template_map_fn | None | 拼接 prompt 模板(template_map_fn_factory按template参数构造) |
max_length | 必填 | 序列最大长度,tokenize 与 pack 阶段的上限 |
max_dataset_length | None | 若数据量过大,可随机抽取指定条数参与映射以节省时间 |
split | 'train' | 加载的数据划分;pack_to_max_length为 True 时只能取train或None |
remove_unused_columns | False | 是否移除训练中不用的列;pack_to_max_length为 True 时会被强制置为 True |
shuffle_before_pack | True | pack 前是否打乱样本 |
pack_to_max_length | True | 是否将样本打包至max_length,通常能提升 GPU 利用率 |
input_ids_with_output | True | 是否把 GroundTruth 输出写入数据集,训练时为 True、测试时通常为 False |
map_num_proc | 32 | 映射阶段的最大并行进程数 |
值得注意的是,在分布式训练场景下process_hf_dataset只在 rank 0 上执行完整处理,随后通过broadcast_object_list将结果广播到其他 rank,并用XTUNER_DATASET_TIMEOUT(默认 60 分钟)控制同步超时,避免各卡重复预处理。
实操示例:examples/demo_data/multi_turn_1
仓库在 examples/demo_data/multi_turn_1 提供了可直接运行的多轮对话演示数据,完整覆盖"数据 + map 函数 + config"三件套:
- data.json:使用
messages字段承载轮次,内部是toy_system/toy_input/toy_output命名的字段; - map_fn.py:
multi_turn_1_map_fn将messages逐条转换为{'system', 'input', 'output'}并组装为conversation列表:
def multi_turn_1_map_fn(example): messages = example['messages'] conversation = [] for msg in messages: conversation.append({ 'system': msg['toy_system'], 'input': msg['toy_input'], 'output': msg['toy_output'] }) return {'conversation': conversation}- config.py:基于
internlm_7b_qlora_json_e3派生,关键改动是用read_base导入multi_turn_1_map_fn作为dataset_map_fn、把data_path指向./data.json,其余训练超参数(max_length=2048、pack_to_max_length=True、QLoRA 4bit 量化、r=64的 LoRA 等)保持可用状态。
启动训练只需:
cd ./examples/demo_data/multi_turn_1 xtuner train config.py常见问题与排错建议
- 数据格式与
dataset_map_fn不匹配:xtuner check-custom-dataset会主动检测"非标准格式但未提供 map 函数"或"标准格式却设置了 map 函数"两种错误,请据此调整 config; - pack 与 split 冲突:
pack_to_max_length=True时split只能为train或None,否则会直接断言失败; - pack 时未清理多余列:
pack_to_max_length=True会强制remove_unused_columns=True,无需手动处理,但若你显式设置了False会收到警告; - 多轮数据末轮无回答:这是正常现象(用户最后一轮往往没有回答),映射函数会将其丢弃,因为该部分不参与 loss 计算;
- system 字段的轮次语义:XTuner 约定
system只在首轮出现,若你的原始数据每轮都带 system,请只在第一轮保留,避免模板重复拼接系统提示词。
通过本文的两种路径与底层原理讲解,你可以根据自己的数据形态,选择"内置 map 函数/HuggingFace Hub"或"标准格式自定义 JSON"任一路径,快速搭建多轮对话 SFT 训练管线,并借助check-custom-dataset在训练前完成数据质量验证。
【免费下载链接】xtunerA Next-Generation Training Engine Built for Ultra-Large MoE Models项目地址: https://gitcode.com/GitHub_Trending/xt/xtuner
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考