SFT 指令微调
数据格式、chat template 与 loss mask,把 base model 训成能听指令的 instruct model
核心要点:
- SFT loss 仍是 CLM,但仅在 assistant response 上算 loss (user/system 部分 mask)
- 数据格式 (chat template) 把多轮对话编码进字符串
- 三条数据路线:规模 (UltraChat 1.5M) / 质量 (LIMA 1000 超 GPT-4) / Phi (高质量合成)
- Llama 3 用 25M 合成样本 + 6 轮 rejection sampling
- SFT lr 1-5e-5 (比预训练小 6-30×),1-3 epoch
- LIMA 提出 Superficial Alignment Hypothesis:知识在预训练学到,SFT 只教格式
名词定义
本篇共享名词在 7.1 总览 已定义 (SFT / Chat template / Base model / Instruct model)。本篇新引入:
| 名词 | 定义 |
|---|---|
| Self-instruct | Wang 2022 提出的合成范式:用强 LLM (GPT-3.5/4) 生成指令-回答对,大幅降低数据成本 |
| Evol-Instruct | WizardLM 提出的"指令演化":从种子指令迭代生成更复杂版本 |
| Rejection sampling | 每个 prompt 采样 K 个回答,用 reward model 选最好的作 SFT 数据 (Llama 3 用) |
| Superficial Alignment Hypothesis | LIMA 提出:知识在预训练已学到,SFT 只需教"对话格式与风格" |
apply_chat_template | HuggingFace tokenizer 方法,按 Jinja2 模板把对话列表转成字符串 |
| ignore_index | PyTorch CrossEntropyLoss 参数,标记不参与 loss 的位置 (典型 -100) |
@tbl-sft-glossary 本篇新引入名词
Base model vs Instruct model:差在哪?
核心问题:06 章训出来的 base model 已经在万亿 token 上学语言,为什么直接问它问题它不会答?SFT 改了什么?
Base model 只会续写,不会"按指令回答"; SFT 用 (指令,回答) 对继续训练,教模型"看到指令就生成回答" 的对话格式。
Base model 的局限
把"请解释什么是 Transformer" 喂给 base model:
- Base model 看作输入续写:可能输出 "请解释什么是 RNN" "请解释什么是 CNN" (它见过这类列表的续写)
- 也可能输出乱七八糟的 web 内容 (训练数据是 web 爬取的)
- 不知道"我应该回答这个问题"
Base model 实际上没有 "user / assistant 角色"概念**——它见过的训练数据都是平铺的文本流,不知道什么时候该"回答"。
SFT 教模型听指令
SFT 用 (指令,回答) 对继续训练。例如:
User: 请解释什么是 Transformer
Assistant: Transformer 是一种基于自注意力机制的神经网络架构...
这是把 base model 已有的语言能力"重新定向" 到对话格式,不是教新知识。LIMA 论文[1] 实证 (后文展开):SFT 只需 1000 条高质量样本,模型在对话评估上能超过 GPT-4——说明能力已经在 base model 里,SFT 只是激活了对话格式。
Superficial Alignment Hypothesis
LIMA 提出:"对齐的知识在预训练就已经习得,SFT 阶段只需要教模型用什么样的格式和风格回答"。
- 1000 条精选样本 (750 社区精选 + 250 人工撰写) 微调 LLaMA-65B
- 43% 评估胜过 GPT-4, 65% 胜过 DaVinci003 (text-davinci-003)
- 无任何 RLHF
对业界的影响:让 SFT 数据从"量大优先" 转向"质量优先",推动了 Phi 系合成数据路线。
SFT 数据有哪几种路线?
核心问题:SFT 需要 (指令,回答) 对,这些数据从哪里来?业界有几种典型路线?
业界对 SFT 数据有三条主路线:规模 (UltraChat / Vicuna 类) / 质量 (LIMA / Phi) / 工业大规模 (Llama 3 / Qwen 25M+ 合成 + rejection sampling)。
规模路线:FLAN → Alpaca → UltraChat
| 数据集 | 规模 | 关键贡献 |
|---|---|---|
| FLAN-v2 (Google 2022) | 1840 tasks / 17.5M pairs | 把 NLP 现有 benchmark 转成统一指令格式,任务多样性 |
| Stanford Alpaca (2023) | 52K | GPT-3.5 self-instruct 合成,成本 < $500,把"合成数据成本"打到地板 |
| Vicuna (LMSYS 2023) | 70K 真实 ShareGPT 多轮 | LLaMA-13B 微调声称达 ChatGPT 质量 90% |
| WizardLM Evol-Instruct (2023) | 从 Alpaca 52K 滚动 4 轮演化 250K,采样 70K | 用 GPT-4 把指令逐步"演化"成更复杂版本 |
| UltraChat (Tsinghua 2023) | 1.5M 多轮对话 (3-7 轮/条) | 把 SFT 数据规模推到 M 级 |
@tbl-sft-scale 规模路线数据集 (按时间)
质量路线:LIMA + Phi
LIMA (Meta NeurIPS 2023) 是转折点:
- 1000 条精选样本 (750 社区精选 + 250 人工撰写)
- LLaMA-65B 微调,43% 胜过 GPT-4, 65% 胜过 DaVinci003
- 完全无 RLHF
- 提出 Superficial Alignment Hypothesis (见上节)
Phi 系 (Microsoft) 把同一思路从 SFT 推到预训练端:
- Phi-1 (1.3B, 1B tokens 合成教科书) HumanEval 50.6%
- Phi-3-mini (3.8B, 3.3T tokens 高质量合成) MMLU 69% 比肩 Mixtral 8×7B
- 核心思路:高质量合成数据 ("Textbooks Are All You Need")
工业大规模路线
Llama 3 SFT (官方 §4):
- > 25M 合成样本
- 6 轮 rejection sampling 迭代:每 prompt 采样 K = 10-30 个回答,用 reward model 选最好的作 SFT 数据
- 通过这个迭代过程逐步提升数据质量
DeepSeek V2 / V3:
- 各 1.5M 条
- 按领域定制生成策略 (数学用 DeepSeek-R1 生成,代码用执行验证)
Qwen2/3:
-
500K 条
- 用 InsTag 自动本体筛选 + self-evolution 提难度
精选算法的极致简化
学术界 2024 年探索"用更少数据"路线:
- DEITA (ICLR 2024): 6K 精选数据达 MT-bench 7.22
- Cherry LLM (NAACL 2024):用 IFD (Instruction Following Difficulty) score 选 10% 数据即可媲美全量
2024 年趋势:
- 工业界走"规模 + 验证反馈" 双轨 (Llama 3 / DeepSeek)
- 学术界走"IFD/complexity 评分极致精简" (DEITA / Cherry)
Chat template:多轮对话怎么编码?
核心问题:SFT 数据是 (指令,回答) 对,多轮对话,系统消息——怎么把这些结构化数据变成模型能读的字符串?
Chat template 是各模型自定义的字符串格式,用 special token 标记 user / assistant / system 角色边界。
ChatML 格式 (OpenAI / Qwen)
<|im_start|>system
你是一个有用的助手。<|im_end|>
<|im_start|>user
请解释 Transformer<|im_end|>
<|im_start|>assistant
Transformer 是一种...<|im_end|>
<|im_start|>/<|im_end|>是单个 special token (不是字符串切分得到的)- role (system / user / assistant) 是普通字符串
<|im_end|>兼作 EOS token; Qwen SFT 时需显式eos_token='<|im_end|>'
Llama 2 格式
<s>[INST] <<SYS>>
{sys}
<</SYS>>
{user} [/INST] {assistant} </s><s>[INST] {user2} [/INST]
- 使用字符串 tag (
[INST],<<SYS>>) 而非 special token - 系统提示嵌入第一个 turn 的
<<SYS>>块
Llama 3 格式 (重设)
<|begin_of_text|><|start_header_id|>system<|end_header_id|>
{sys}<|eot_id|><|start_header_id|>user<|end_header_id|>
{user}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
{assistant}<|eot_id|>
- 所有 boundary 都是 special token
- header 后双换行
<|eot_id|>为 turn 结束符
HuggingFace apply_chat_template
messages = [
{"role": "system", "content": "你是一个有用的助手。"},
{"role": "user", "content": "请解释 Transformer"},
{"role": "assistant", "content": "Transformer 是一种..."}
]
# 训练时 add_generation_prompt=False (含完整 assistant 回答)
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
# 推理时 add_generation_prompt=True (在末尾加 assistant header 提示开始生成)
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
Tokenizer 内部读 tokenizer.chat_template 字段 (Jinja2 字符串模板),随 save_pretrained 持久化,模型上传时一并提供。
SFT 的 loss masking:只在 response 上算
核心问题:SFT 训练数据是整段对话字符串 (含 user + assistant + system),但 user 和 system 部分不应该让模型学着生成。怎么实现?
用 ignore_index = -100 标记 user/system 部分的 token, PyTorch CrossEntropyLoss 跳过这些位置,只在 assistant response 上算 CLM loss。
Loss masking 实现
# 1. 序列化对话
text = tokenizer.apply_chat_template(messages, tokenize=False)
input_ids = tokenizer(text, return_tensors="pt").input_ids
# 2. 构造 labels: user/system tokens 设 -100, assistant tokens 保留 token id
labels = input_ids.clone()
labels[user_token_mask] = -100 # 这些位置不参与 loss
# (system 同理)
# 3. F.cross_entropy 自动跳过 labels=-100 的位置
loss = F.cross_entropy(logits, labels, ignore_index=-100)
关键:仅 assistant response 部分参与 CLM loss。Llama 3 paper 原文:"masking loss on prompt tokens"。
TRL 框架的自动 masking
HuggingFace TRL 框架提供两种自动 masking 方式:
# 新式 API (TRL >= 0.10)
SFTConfig(assistant_only_loss=True) # 需 template 含 {% generation %} 标记
# 老式 API
DataCollatorForCompletionOnlyLM(
response_template="[/INST]",
instruction_template="[INST]"
)
return_assistant_tokens_mask=True (HF tokenizer 参数) 返回 0/1 数组直接用于构造 labels,无需手动定位。
为什么要 mask user 部分?
不 mask 会怎样:
- 模型学着"也生成 user 提问" — 训练目标污染
- 推理时模型可能在 user 部分胡乱生成 ("帮我接着问问题")
- 实证训练效果差,训练 token 浪费在不需要学的部分
SFT 超参与预训练有何不同?
核心问题:SFT 是 CLM loss 继续训练,公式跟预训练一致。但超参 (lr / batch / schedule / epoch) 业界都怎么设?
SFT 用比预训练显著小的 lr,训 1-3 epoch, batch 在样本级别 (32-256) 而非 token 级别。
超参对照表
| 超参 | SFT (TRL 默认) | 预训练 (Llama 3 405B) | 比例 |
|---|---|---|---|
| Learning rate | $2 \times 10^{-5}$ | $8 \times 10^{-5}$ | 4× 小 (其他 SFT 用 1e-5 ~ 5e-5) |
| Epoch | 1-3 | < 1 (走不完数据) | — |
| Batch | 32-256 样本 | $\sim 4M$ token | 完全不同尺度 |
| Warmup | warmup_ratio=0.03-0.1 | 8000 step | — |
| Schedule | linear warmup + cosine 或 constant | linear warmup + cosine 至 10% | 类似 |
| Weight decay | 0.0 | 0.1 | SFT 不用 |
@tbl-sft-hparam SFT vs 预训练超参对照
关键差异:
- LR 显著降低:预训练让 base model 走到能力上限,SFT 只需"调一点"对话格式,大 lr 会破坏预训练知识
- Epoch 1-3: SFT 数据小,多 epoch 能学得更牢
- Batch 在样本而非 token:单 SFT 样本通常 100-2000 token, batch 32-256 样本相当于几万 token,远小于预训练 4M
- Weight decay 0: SFT 数据量小,正则化会损失精度
Takeaway
| 知识点 | 核心结论 |
|---|---|
| Base vs Instruct | base 只会续写,SFT 教模型按对话格式回答 |
| Superficial Alignment Hypothesis (LIMA) | 知识在预训练已学,SFT 只教格式 |
| 数据三路线 | 规模 (UltraChat 1.5M) / 质量 (LIMA 1000) / 工业 (Llama 3 25M + rejection sampling) |
| LIMA 实证 | 1000 样本 LLaMA-65B 微调,43% 胜过 GPT-4,完全无 RLHF |
| Phi-3-mini | 3.8B + 高质量数据 MMLU 69%,比肩 Mixtral 8×7B |
| Llama 3 | 25M 合成样本 + 6 轮 rejection sampling 迭代 |
| DEITA / Cherry | 学术:6K 或 10% 数据达到接近全量水平 |
| Chat template | ChatML / Llama 2 / Llama 3 各家自定义 special token 边界 |
| HF apply_chat_template | 读 tokenizer.chat_template Jinja2 模板生成字符串 |
| Loss masking | user/system 部分 ignore_index=-100,只在 assistant response 算 CLM |
| TRL 自动 masking | assistant_only_loss=True 或 DataCollatorForCompletionOnlyLM |
| SFT 超参 | lr 2e-5 (4× 小于预训练) / 1-3 epoch / batch 样本级 / wd=0 |
开放问题
- 质量 vs 规模哪条路是终点:LIMA 与 Llama 3 路线相反,业界仍在演化,是否会收敛仍未定
- SFT 是否最终被 DPO/RLHF 吸收:ORPO (04 篇) 已经把 SFT + 偏好合一阶段,是否 SFT 会变成"对齐流程的一部分"而非独立步骤
- Synthetic data 的天花板:Phi 路线证明小规模有效,大规模 (>100B) 上是否仍 work 待验证
- Rejection sampling 的迭代次数:Llama 3 用 6 轮,是否更多轮收益持续,还是早期 diminishing returns
- Chat template 标准化:各家各异让模型互操作困难,是否会有"行业标准"出现 (ChatML 是候选),但 Llama 3 反向改格式说明分裂仍在
延伸阅读
- 上一篇:总览 → 7.1 总览
- 下一步:RLHF → 7.3 RLHF
- CLM loss + shift-by-one → 06-预训练/02-语言建模目标
- HuggingFace chat templating → https://huggingface.co/docs/transformers/chat_templating
- TRL SFTTrainer → https://huggingface.co/docs/trl/sft_trainer
- LIMA 原始论文 → https://arxiv.org/abs/2305.11206
参考资料
- Zhou et al. LIMA: Less Is More for Alignment. NeurIPS 2023. https://arxiv.org/abs/2305.11206