5.2 准备 SFT 数据
SFT 数据的结构:四种主流格式、chat template、masking loss 与数据去污染
从"一堆对话"到"可训练的 SFT 数据集"
你拿到数据之后(不管来源是开源数据集、合成还是低资源合成),下一步都是把它做成可被训练器正确消费的格式。本节回答三个问题:
- 用什么格式存?(Alpaca / ShareGPT / OpenAI messages / ChatML)
- 怎么让训练器只在 assistant token 上算损失?(chat template + masking)
- 怎么避免训练集泄漏到测试集?(decontamination)
本节不讨论"去哪里找数据"——那在 5.1 常见数据来源;也不讨论"如何合成数据"——那在 5.3 合成数据 和 5.4 低资源合成。
一、四种主流格式规范
1. Alpaca 格式
最简单、单轮、字段固定:
{
"instruction": "用三句话介绍量子计算",
"input": "",
"output": "量子计算是一种利用量子力学原理进行信息处理的新型计算范式……"
}拼接规则:instruction + "\n" + input(若 input 非空)→ user;output → assistant。
2. ShareGPT 格式
天然支持多轮,conversations 数组按对话顺序排列:
{
"conversations": [
{"from": "human", "value": "什么是机器学习?"},
{"from": "gpt", "value": "机器学习是人工智能的一个分支……"},
{"from": "human", "value": "它和深度学习有什么区别?"},
{"from": "gpt", "value": "深度学习是机器学习的子集……"}
]
}3. OpenAI messages 格式
当前最标准的"通用载体",被 Qwen / Llama / DeepSeek 等的官方 chat template 原生支持:
{
"messages": [
{"role": "system", "content": "你是一个有帮助的助手。"},
{"role": "user", "content": "用三句话介绍量子计算"},
{"role": "assistant", "content": "量子计算是……"}
]
}同时支持 tool 角色用于工具调用:
{
"messages": [
{"role": "user", "content": "2024 年中国 GDP 是多少?"},
{"role": "assistant", "content": null,
"tool_calls": [{"name": "search", "arguments": {"q": "GDP 2024 中国"}}]},
{"role": "tool", "content": "中国 2024 年 GDP 为 134 万亿元……"},
{"role": "assistant", "content": "根据最新数据……"}
]
}4. Qwen ChatML 格式
Qwen 系列底层采用的聊天模板格式:
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
用三句话介绍量子计算<|im_end|>
<|im_start|>assistant
量子计算是……<|im_end|>格式对比速查
| 格式 | 多轮 | 角色支持 | 工具调用 | 推荐用法 |
|---|---|---|---|---|
| Alpaca | 否 | user/assistant | 否 | 快速原型 |
| ShareGPT | 是 | human/gpt | 否 | 纯对话数据 |
| OpenAI messages | 是 | system/user/assistant/tool | 是 | 首选 |
| Qwen ChatML | 是 | system/user/assistant | 是 | 模型底层 |
实用建议:永远以 OpenAI messages 格式存储数据,在训练时用 tokenizer.apply_chat_template() 自动转换成目标模型需要的底层格式。这样可以在不同模型(Qwen、Llama、Mistral)之间无缝迁移。
二、Chat Template 与 Masking Loss
2.1 不要手动拼接
每个模型系列都有自己微妙的 chat template(差几个空格、<|im_end|> vs </s>、BOS 处理方式等),手动拼接是 bug 温床。正确做法:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-1.7B")
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "用三句话介绍量子计算"},
{"role": "assistant", "content": "量子计算是……"},
]
formatted = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=False, # 训练时不加生成提示
)2.2 Masking Loss 的核心公式
SFT 的训练目标是仅在 assistant token 上计算交叉熵损失:
其中 是二值掩码:assistant token 为 1,其他为 0; 是 assistant token 总数。
为什么 user / system 的 token 不贡献梯度?训练"学会说话"而不是"学会重复用户输入"。如果把 user token 也算进 loss,模型会把大量能力分配给"复述指令"。
2.3 用 TRL 一键搞定
from trl import SFTTrainer, SFTConfig
cfg = SFTConfig(
output_dir="./out",
max_length=2048,
packing=False, # 或 True 提升吞吐
)
trainer = SFTTrainer(
model=model,
args=cfg,
train_dataset=dataset, # 含 "messages" 字段即可
processing_class=tokenizer,
)TRL 会自动识别 assistant 片段并生成掩码,无需手动切分。
2.4 常见 Bug 速查
| 症状 | 常见根因 |
|---|---|
| 训练 loss 异常低(<0.1) | 掩码把 user token 也算进去了(训练变成"复述" task) |
| 推理时停不下来 | eos_token 没正确加入或未被作为终止符 |
| 模型"开头说话很奇怪" | BOS 处理不一致(训练时加 BOS,推理时没加) |
| 多轮对话不自然 | 多轮之间用了错误的分隔符(如手动拼接了 \n 而非模板 turn 分隔) |
三、数据去污染(Data Decontamination)
这是在"你交付模型之前"必须做的最后一步:检查 SFT 数据是否泄漏了常用评测集。如果泄漏,模型在基准上的得分会虚高、实际能力被严重误判。
3.1 常见污染源
- Alpaca / FLAN:与 MMLU、BBH 部分重叠
- UltraChat:偶尔复述 MT-Bench 题目
- 自采网页数据:C-Eval、CMMLU 原始题目已广泛被爬
3.2 检测方法
- n-gram 匹配:对评测集每道题抽取 13-gram,检查 SFT 数据中是否命中
- embedding 相似度:对每条 SFT 样本与评测题计算余弦相似度,阈值通常 0.85+ 视为可疑
- 精确子串:对较短评测题(如 GSM8K)直接做 substring 查询
# 伪代码:13-gram 污染扫描
from collections import defaultdict
def ngrams(text, n=13):
toks = text.split()
return {" ".join(toks[i:i+n]) for i in range(len(toks)-n+1)}
eval_ngrams = set()
for item in eval_set:
eval_ngrams |= ngrams(item["question"])
contaminated = []
for idx, sample in enumerate(sft_data):
if ngrams(sample["user_msg"]) & eval_ngrams:
contaminated.append(idx)
print(f"潜在污染: {len(contaminated)} / {len(sft_data)}")发现污染不等于"这条样本不能用",但必须记录、报告、并在发布模型时注明评测集的去污染状态。这是学术伦理,也是工程信誉。
本节小结
| 维度 | 要点 |
|---|---|
| 首选格式 | OpenAI messages,训练时用 apply_chat_template 转换 |
| 掩码损失 | 仅对 assistant token 计算交叉熵 |
| 去污染 | 训练前必须对评测集做 13-gram 扫描 |
| 代码原则 | 永远不要手动拼接聊天模板 |