跳到主要内容

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-instructWang 2022 提出的合成范式:用强 LLM (GPT-3.5/4) 生成指令-回答对,大幅降低数据成本
Evol-InstructWizardLM 提出的"指令演化":从种子指令迭代生成更复杂版本
Rejection sampling每个 prompt 采样 K 个回答,用 reward model 选最好的作 SFT 数据 (Llama 3 用)
Superficial Alignment HypothesisLIMA 提出:知识在预训练已学到,SFT 只需教"对话格式与风格"
apply_chat_templateHuggingFace tokenizer 方法,按 Jinja2 模板把对话列表转成字符串
ignore_indexPyTorch 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)52KGPT-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)
Epoch1-3< 1 (走不完数据)
Batch32-256 样本$\sim 4M$ token完全不同尺度
Warmupwarmup_ratio=0.03-0.18000 step
Schedulelinear warmup + cosine 或 constantlinear warmup + cosine 至 10%类似
Weight decay0.00.1SFT 不用

@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 Instructbase 只会续写,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-mini3.8B + 高质量数据 MMLU 69%,比肩 Mixtral 8×7B
Llama 325M 合成样本 + 6 轮 rejection sampling 迭代
DEITA / Cherry学术:6K 或 10% 数据达到接近全量水平
Chat templateChatML / Llama 2 / Llama 3 各家自定义 special token 边界
HF apply_chat_templatetokenizer.chat_template Jinja2 模板生成字符串
Loss maskinguser/system 部分 ignore_index=-100,只在 assistant response 算 CLM
TRL 自动 maskingassistant_only_loss=TrueDataCollatorForCompletionOnlyLM
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 反向改格式说明分裂仍在

延伸阅读

参考资料

  1. Zhou et al. LIMA: Less Is More for Alignment. NeurIPS 2023. https://arxiv.org/abs/2305.11206