news 2026/9/18 6:53:43

XTuner 多轮对话 SFT 数据 pipeline 完全指南:从 HuggingFace Hub 与自定义数据集到可训练 Config

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
XTuner 多轮对话 SFT 数据 pipeline 完全指南:从 HuggingFace Hub 与自定义数据集到可训练 Config

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-cfgxtuner 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}

该函数的关键逻辑:

  1. ###分隔文本并去掉首尾空白,依据Human:/Assistant:前缀归类到data列表;
  2. data长度为奇数,说明最后一轮只有提问没有回答,直接pop丢弃——因为该部分在 loss 计算中会被忽略,保留无意义;
  3. 两两配对组装成{'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_fnopenorca_map_fnwizardlm_map_fnsql_map_fncode_alpaca_map_fn等二十余个内置映射函数,覆盖常见开源 SFT 数据集。

Step 2:列出候选模型名字

XTuner 提供多个开箱即用的配置文件,可通过以下命令查看:

xtuner list-cfg -p internlm

-p为模糊查找参数,若想训练其他模型,将internlm替换为 XTuner 支持的其他模型名称即可(例如baichuanllamaqwenchatglmdeepseek等,完整列表可查看 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 文件,需要做三处修改:

  1. 导入 Step 1 中实现的映射函数custom_map_fn
  2. custom_map_fn替换train_dataset中的dataset_map_fn
  3. 调整原始数据集的路径(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 文件

自定义数据集场景下需要两处修改:

  1. 调整原始数据集的路径;
  2. 由于数据集已是 XTuner 标准格式,需将train_dataset中的dataset_map_fn置为None,并将load_datasetpath指定为'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_fnNone,会报错提示你需要提供dataset_map_fn完成格式映射;
  • 若数据已是标准格式但dataset_map_fn非空,会报错提示你将其置为None,避免重复映射。

因此,写自定义数据集时务必保证dataset_map_fn的设置与数据实际格式严格匹配。

底层机制:process_hf_dataset 数据处理流水线

理解process_hf_dataset的底层实现(源码见 xtuner/dataset/huggingface.py),有助于你精准排错和调优。一次完整的多轮对话数据处理会经历以下阶段:

  1. 构建原始数据集build_origin_dataset通过注册器BUILDER.build执行 config 中dataset的构造逻辑;若结果是DatasetDict且未指定split,会将其各 split 拼接为一个数据集;
  2. 格式映射map_dataset调用dataset_map_fn,将原始字段转换为conversation标准格式。dataset_map_fn既可以是函数,也可以是注册在MAP_FUNC中的字符串名称;映射时通过dataset.map(dataset_map_fn, num_proc=map_num_proc)并行执行;
  3. 模板拼接add_template_to_dataset调用template_map_fn为每一轮对话套上 prompt 模板,随后过滤掉conversation为空的数据;
  4. tokenizetokenize_dataset调用encode_fn,把文本编码为input_idslabels,并依据input_ids_with_output决定是否保留 GroundTruth 输出;随后过滤掉labels中完全没有有效标签(全部小于 0)的数据;
  5. 打包:若pack_to_max_length为 True,先按shuffle_before_pack决定是否打乱,再用Packer将多条短样本拼接到max_length,以提升 GPU 利用率、缩短训练时间;
  6. 附加长度信息:为每条样本计算length字段,供长度分组采样等模块使用。

其中template_map_fn的实现位于 xtuner/dataset/map_fns/template_map_fn.py,它逐轮处理conversation

  • template.INSTRUCTION格式化input,并在存在非空system时用template.SYSTEM把系统提示词拼到最前面;
  • 若模板定义了SUFFIX(如<eos>后缀),将其追加到output末尾;
  • 为每轮写入need_eos_tokensep字段,控制本轮回答是否需要补 EOS token 及轮与轮之间的分隔符。

train_dataset中常用参数的作用如下:

参数默认值作用
dataset_map_fnNone将原始数据映射为conversation标准格式的函数或注册名
template_map_fnNone拼接 prompt 模板(template_map_fn_factorytemplate参数构造)
max_length必填序列最大长度,tokenize 与 pack 阶段的上限
max_dataset_lengthNone若数据量过大,可随机抽取指定条数参与映射以节省时间
split'train'加载的数据划分;pack_to_max_length为 True 时只能取trainNone
remove_unused_columnsFalse是否移除训练中不用的列;pack_to_max_length为 True 时会被强制置为 True
shuffle_before_packTruepack 前是否打乱样本
pack_to_max_lengthTrue是否将样本打包至max_length,通常能提升 GPU 利用率
input_ids_with_outputTrue是否把 GroundTruth 输出写入数据集,训练时为 True、测试时通常为 False
map_num_proc32映射阶段的最大并行进程数

值得注意的是,在分布式训练场景下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_fnmessages逐条转换为{'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=2048pack_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=Truesplit只能为trainNone,否则会直接断言失败;
  • 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 6:51:08

IFN-γ ELISpot实验全流程指南:从试剂盒选型到判读标准

做ELISpot这几年&#xff0c;我最大的感受是&#xff1a;这个实验真正难的地方不在操作本身&#xff0c;而在“怎么看懂那张膜上长出来的斑点”。IFN-γ ELISpot试剂盒用好了&#xff0c;能把抗原特异性T细胞的频率和功能状态直接“数”出来&#xff1b;用不好&#xff0c;背景…

作者头像 李华
网站建设 2026/9/18 6:48:04

卷积神经网络特征图尺寸计算:公式推导与 LeNet-5 实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 6:47:43

Vue插槽机制解析与高级应用实践

1. 理解Vue插槽的核心价值在Vue组件化开发中&#xff0c;插槽(Slot)机制就像是给组件预留的"接口插座"。想象你买了一个多功能台灯&#xff0c;灯座是固定的&#xff0c;但你可以根据需要更换不同的灯罩——这就是插槽的直观体现。它完美实现了组件"容器"与…

作者头像 李华
网站建设 2026/9/18 6:46:29

MiroFish:根因清单自动生成Miro鱼骨图

做质量和故障复盘的人对这一幕应该都不陌生&#xff1a;会议室订好了&#xff0c;Miro 画板开好了&#xff0c;六个人围着一块屏幕&#xff0c;主持人先在白板上画一条横线&#xff0c;再斜着拉出六根大骨&#xff0c;写上"人、机、料、法、环、测"&#xff0c;然后大…

作者头像 李华