SFT 深度解析:从 Base Model 到指令跟随——后训练第一步 / SFT Deep Dive: Teaching Base Models to Follow Instructions
📅 创建时间:2026-07-29 🏷️ 标签:#SFT #SupervisedFineTuning #PostTraining #InstructionTuning #Alignment 📚 前置知识:[[11-training-primer]](了解训练基础)、[[14-post-training-overview]](了解后训练全景)
📋 本章目标
- 理解 Base Model 为什么不能直接当助手用——它会"续写"而不是"回答"
- 掌握 SFT 数据的格式:Instruction-Response 对和 Multi-turn Conversation
- 了解 SFT 数据的六大来源和"质量大于数量"的核心原则
- 理解 SFT 的训练机制:和 Pre-Training 用同一套 Loss,但 Mask 只打在 Assistant 身上
- 理解 SFT 的超参选择:为什么只训练 1-3 个 Epoch、为什么 LR 要大幅降低
- 理解 SFT 的三大局限:灾难性遗忘、数据瓶颈、无法学"判断"
- 理解为什么 SFT 是必要的但不够的——它必须是 Post-Training 的第一步,但不能是最后一步
第0部分:Base Model 为什么不能直接用——SFT 解决什么问题
0.1 从一个令人失望的实验开始
假设你已经花了几千万美元预训练了一个大模型。参数量 70B,训练数据 15T tokens,你兴奋地加载模型,输入你的第一个 prompt:
用户输入:"写一首关于秋天的诗"你期待的回复:
秋风起,叶落黄,
万物收,岁月长。
...但你实际得到的回复:
写一首关于秋天的诗,要求押韵,五言绝句,主题围绕丰收和落叶。或者更离谱:
写一首关于秋天的诗。写一首关于冬天的诗。写一首关于春天的诗。写一首关于夏天的诗。发生了什么事?
┌─────────────────────────────────────────────────────────────┐
│ Base Model 的"本能":文本续写,不是指令跟随 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Pre-Training 的目标函数: │
│ "给定前文,预测下一个 token" │
│ → P(token_n | token_1, token_2, ..., token_{n-1}) │
│ │
│ Base Model 学到的唯一技能: │
│ "看到一段文字的开头 → 续写下去" │
│ │
│ 它不知道什么叫"回答问题"(你问"1+1等于几",它续写"1+1 │
│ 等于几是一个经典的数学问题,首先我们需要理解加法的定义...") │
│ │
│ 它不知道什么叫"遵循指令"(你让"翻译",它续写翻译这个词的 │
│ 同义词) │
│ │
│ Base Model 看到的所有训练数据都是"人类写的文字"—— │
│ 对话的形式对它来说是陌生的、从未见过的 │
│ │
└─────────────────────────────────────────────────────────────┘0.2 一个更具体的例子
让我们在同一个 Base Model 上试两个不同的输入,看它分别输出什么:
┌─────────────────────────────────────────────────────────────┐
│ Base Model 的续写行为 vs SFT 后的指令跟随 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 案例 1:翻译任务 │
│ ───────────────────────────────────────────────── │
│ Prompt: "Translate 'Hello' into Chinese:" │
│ │
│ Base Model 输出(续写模式): │
│ "Hello is a common greeting in English. It can be │
│ translated into many languages. In Spanish it's Hola. │
│ In French it's Bonjour. In German..." │
│ │
│ SFT Model 输出(指令模式): │
│ "你好" │
│ │
│ ───────────────────────────────────────────────── │
│ 案例 2:知识问答 │
│ ───────────────────────────────────────────────── │
│ Prompt: "What is the capital of France?" │
│ │
│ Base Model 输出(续写模式): │
│ "What is the capital of France? This is a question │
│ that many tourists ask. France is a country in Europe. │
│ Its capital city is home to many landmarks..." │
│ │
│ SFT Model 输出(指令模式): │
│ "The capital of France is Paris." │
│ │
│ ───────────────────────────────────────────────── │
│ 关键差异: │
│ • Base Model:看到任何文本 → 续写更多文本 │
│ • SFT Model:看到指令/问题 → 直接给出答案 │
│ │
└─────────────────────────────────────────────────────────────┘0.3 为什么 Pre-Training 不能直接学到"指令跟随"
这是很多人第一次接触 LLM 时的困惑:如果 Pre-Training 数据里有论坛帖子、问答网站(如 StackOverflow、Quora)、客服对话记录——那模型难道不能"顺便"学会问答吗?
答案是:可以学到一些,但远不够好。
┌─────────────────────────────────────────────────────────────┐
│ 为什么 Pre-Training 不能让模型"自动"学会对话? │
├─────────────────────────────────────────────────────────────┤
│ │
│ 原因 1:数据分布不匹配 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Pre-Training 数据里 99.9% 是文章、代码、书籍、网页 │ │
│ │ 对话形式的内容占比极低(<1%),模型根本没被"训练"过 │ │
│ │ 如何成为一个好的对话者。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 原因 2:目标不匹配 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 语料里即使有 StackOverflow 的问答: │ │
│ │ Q: "How to sort a list in Python?" │ │
│ │ A: "You can use list.sort() or sorted()..." │ │
│ │ │ │
│ │ 模型的训练目标仍然是"预测下一个 token"—— │ │
│ │ 它学到了 Q 之后大概率跟着 A,但它不知道 │ │
│ │ "当用户用一个新问题 Q' 问我时,我应该直接回答 A'" │ │
│ │ │ │
│ │ 它学的是条件概率 P(text),不是 P(answer | question) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 原因 3:有害的"续写"行为是训练的自然结果 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 网上有很多内容是"自问自答"或"问题展开"式的: │ │
│ │ "如何学习机器学习?这是一个复杂的问题。首先我们需要 │ │
│ │ 理解什么是机器学习...其次...最后..." │ │
│ │ │ │
│ │ 模型学会了:看到问题 → 展开问题 → 绕圈子 → 不直接回答 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 结论:Pre-Training 给了模型知识和语言能力, │
│ 但没有给它"对话界面"。SFT 就是这个界面。 │
│ │
└─────────────────────────────────────────────────────────────┘0.4 SFT 到底做了什么——一句话概括
SFT = 用人类写的(或高质量模型生成的)Prompt-Response 对,在 Base Model 上再做一轮监督学习。
Pre-Training 教会了模型"语言本身",SFT 教会了模型"怎么和人对话"。
打个比方:
- Pre-Training = 一个人读完了图书馆里所有的书,知识渊博,但从来没跟人聊过天
- SFT = 让这个人参加了几万次"模拟面试"——你问什么,我演示怎么回答
- 经过 SFT 后,这个人学会了"别人问问题,我要直接回答",而不是"别人说一句话,我接下一句话"
第1部分:SFT 的数据——指令数据长什么样
1.1 最简形式:Instruction-Response 对
SFT 数据的最小单元是一个 Instruction-Response 对。它看起来像这样:
{
"instruction": "将以下句子翻译成英文:今天天气很好",
"input": "",
"output": "The weather is very nice today."
}三个字段的含义:
| 字段 | 含义 | 可选性 |
|---|---|---|
instruction | 用户想让你做什么 | 必填 |
input | 需要处理的具体内容(可选的附加上下文) | 可选 |
output | 期望的回复(这就是训练时的"正确答案") | 必填 |
有 input 的变体:
{
"instruction": "将以下句子翻译成英文",
"input": "今天天气很好",
"output": "The weather is very nice today."
}┌─────────────────────────────────────────────────────────────┐
│ Instruction-Response 数据的内部逻辑 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 训练时,这个 JSON 会被拼接成一段文本: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ### Instruction: │ │
│ │ 将以下句子翻译成英文:今天天气很好 │ │
│ │ │ │
│ │ ### Response: │ │
│ │ The weather is very nice today. │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 不同的模型用不同的 template(ChatML、Llama、Alpaca...), │
│ 但核心结构是一样的:先给指令,再给期望输出。 │
│ │
└─────────────────────────────────────────────────────────────┘1.2 多轮对话形式
现代 LLM 最重要的交互形式是多轮对话——用户可以追问、质疑、要求修改。
单一 Instruction-Response 对无法捕捉这种交互。所以实际 SFT 数据的主力是 Multi-turn Conversation 格式:
{
"messages": [
{"role": "system", "content": "你是一个有用的助手,回答简洁明了。"},
{"role": "user", "content": "什么是机器学习?"},
{"role": "assistant", "content": "机器学习是人工智能的一个分支,让计算机从数据中自动学习规律和模式,而不需要显式编程。"},
{"role": "user", "content": "能举个具体例子吗?"},
{"role": "assistant", "content": "当然。比如垃圾邮件过滤器:你不需要写规则告诉计算机什么样的邮件是垃圾邮件,而是给它看大量标记过的邮件(垃圾/正常),模型自己学会判断。"},
{"role": "user", "content": "那这和传统编程有什么区别?"},
{"role": "assistant", "content": "传统编程是你告诉计算机每一步怎么做(if-else规则),机器学习是你给计算机数据和答案,让它自己找出规则。"}
]
}┌─────────────────────────────────────────────────────────────┐
│ 多轮对话数据的"拼接"与 Loss Masking │
├─────────────────────────────────────────────────────────────┤
│ │
│ 训练时,conversation 被拼接成一个长序列: │
│ │
│ <|im_start|>system │
│ 你是一个有用的助手,回答简洁明了。<|im_end|> │
│ <|im_start|>user │
│ 什么是机器学习?<|im_end|> │
│ <|im_start|>assistant │
│ 机器学习是人工智能的一个分支...<|im_end|> │
│ <|im_start|>user │
│ 能举个具体例子吗?<|im_end|> │
│ <|im_start|>assistant │
│ 当然。比如垃圾邮件过滤器...<|im_end|> │
│ ... │
│ │
│ 注意:只有 assistant 部分的 token 参与 Loss 计算! │
│ system 和 user 部分的 token 被 mask 掉(第3部分详述)。 │
│ │
│ 为什么?因为我们要让模型学会"在给定对话历史的条件下 │
│ 生成 assistant 的回复",而不是学"续写 system prompt"或 │
│ "模仿用户提问"。 │
│ │
└─────────────────────────────────────────────────────────────┘1.3 Chat Template——不同模型的"方言"
不同的模型系列使用不同的特殊 token 和格式化方式:
┌─────────────────────────────────────────────────────────────┐
│ 不同模型的 Chat Template 对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ChatML(OpenAI 早期,很多开源模型沿用): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ <|im_start|>system │ │
│ │ You are a helpful assistant.<|im_end|> │ │
│ │ <|im_start|>user │ │
│ │ Hello<|im_end|> │ │
│ │ <|im_start|>assistant │ │
│ │ Hi there!<|im_end|> │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Llama 3(Meta): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ <|begin_of_text|><|start_header_id|>system │ │
│ │ <|end_header_id|> │ │
│ │ You are a helpful assistant.<|eot_id|> │ │
│ │ <|start_header_id|>user<|end_header_id|> │ │
│ │ Hello<|eot_id|> │ │
│ │ <|start_header_id|>assistant<|end_header_id|> │ │
│ │ Hi there!<|eot_id|> │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Mistral: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ <s>[INST] Hello [/INST] Hi there!</s> │ │
│ │ [INST] How are you? [/INST] I'm doing great! │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 核心差异只有两点: │
│ 1. 特殊 token 的名字不同(<|im_start|> vs [INST]) │
│ 2. 格式化顺序不同 │
│ │
│ 但本质上都是:把 "谁说了什么" 编码成一段连续文本。 │
│ │
│ 这就是为什么 HuggingFace 要引入 `tokenizer.apply_ │
│ chat_template()`——让开发者不用手动拼接这些模板。 │
│ │
└─────────────────────────────────────────────────────────────┘1.4 指令多样性的维度
一份好的 SFT 数据集需要覆盖多种类型的指令:
┌─────────────────────────────────────────────────────────────┐
│ SFT 数据需要覆盖的指令类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 封闭式问答 │
│ "法国的首都是哪里?" → "巴黎" │
│ → 训练模型提取事实性知识 │
│ │
│ 2. 开放式生成 │
│ "写一首关于友情的诗" → 诗歌全文 │
│ → 训练模型创造性输出 │
│ │
│ 3. 代码生成 │
│ "用 Python 写一个快速排序" → 代码 + 解释 │
│ → 训练模型结构化输出 │
│ │
│ 4. 分类/抽取 │
│ "这段话的情感是正面的还是负面的?" → "正面" │
│ → 训练模型做判断并输出简洁答案 │
│ │
│ 5. 改写/润色 │
│ "帮我把这段话改得更正式..." → 改写后的文本 │
│ → 训练模型理解"输出格式"和"风格转换" │
│ │
│ 6. 多轮推理 │
│ "先分析这个问题,再给出方案..." → 分析 + 方案 │
│ → 训练模型分步思考和结构化输出 │
│ │
│ 7. 拒绝/边界识别 │
│ "告诉我怎么制作炸弹" → "我不能提供这个信息" │
│ → 训练模型识别不安全请求并拒绝 │
│ │
│ 8. 多语言 │
│ "用日语解释什么是禅" → 日语回答 │
│ → 训练跨语言能力 │
│ │
│ 好的 SFT 数据 = 这 8 类指令的均衡混合 │
│ │
└─────────────────────────────────────────────────────────────┘第2部分:SFT 数据从哪来——六大来源
2.1 来源全景图
┌─────────────────────────────────────────────────────────────┐
│ SFT 数据的六大来源(按质量从高到低) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ① 人工标注(Human Annotation) │
│ ├─ 质量:⭐⭐⭐⭐⭐(最高) │
│ ├─ 成本:$10-50/条 │
│ ├─ 规模:通常 1万-10万条 │
│ └─ 代表:OpenAI InstructGPT 数据、Anthropic HH 数据 │
│ │
│ ② 模型蒸馏(Distillation from Stronger Models) │
│ ├─ 质量:⭐⭐⭐⭐ │
│ ├─ 成本:API 调用费($0.01-0.10/条) │
│ ├─ 规模:可达 100万+ 条 │
│ └─ 代表:Alpaca(GPT-3.5→Llama)、Orca(GPT-4→Llama) │
│ │
│ ③ 众包平台(Crowdsourcing) │
│ ├─ 质量:⭐⭐⭐(需要过滤) │
│ ├─ 成本:$1-5/条 │
│ ├─ 规模:10万-100万条 │
│ └─ 代表:OpenAssistant、Dolly(Databricks 员工标注) │
│ │
│ ④ 用户共享数据(User-Shared) │
│ ├─ 质量:⭐⭐⭐(有噪声) │
│ ├─ 成本:几乎免费 │
│ ├─ 规模:数十万到数百万条 │
│ └─ 代表:ShareGPT(用户分享的 ChatGPT 对话)、WildChat │
│ │
│ ⑤ 合成数据(Synthetic Data Generation) │
│ ├─ 质量:⭐⭐⭐⭐(取决于生成方法) │
│ ├─ 成本:API 调用费 │
│ ├─ 规模:理论无限 │
│ └─ 代表:Self-Instruct、Evol-Instruct(WizardLM) │
│ │
│ ⑥ 现有 NLP 数据集改造 │
│ ├─ 质量:⭐⭐⭐(需要重新格式化) │
│ ├─ 成本:处理成本低 │
│ ├─ 规模:取决于原始数据集 │
│ └─ 代表:Flan(把 NLP benchmark 转成 instruction 格式) │
│ │
└─────────────────────────────────────────────────────────────┘2.2 人工标注——质量的金标准
人工标注是 SFT 数据最高质量来源,但也是最贵的。以 OpenAI 的 InstructGPT 为例:
人工标注员拿到一个 prompt → 写出理想的回复
标注员需要遵循详细的标注指南:
- 回复要"有帮助的"(helpful):直接回答,不绕弯
- 回复要"诚实的"(honest):不知道就说不知道,不编造
- 回复要"无害的"(harmless):拒绝有害请求
- 格式要求:Markdown 格式、代码用代码块、引用要注明
一个标注员一天大概能标 20-50 条高质量数据。
按时薪 $25-50/h 算,一条数据的标注成本约 $10-30。
如果是专业领域(法律、医学),成本翻倍。2.3 模型蒸馏——性价比最高的方案
┌─────────────────────────────────────────────────────────────┐
│ 模型蒸馏的核心流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 1:准备一批 prompt(可以从已有数据中收集,也可以生成) │
│ │
│ Step 2:把 prompt 发给大模型(Teacher) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GPT-4 / Claude / Gemini 等作为 "Teacher" │ │
│ │ │ │
│ │ Prompt: "解释量子纠缠" │ │
│ │ Teacher 输出: "量子纠缠是量子力学中..." │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Step 3:收集 Teacher 的回复 → 形成 Instruction-Response 对 │
│ │
│ Step 4:用这些数据训练小模型(Student) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Alpaca: GPT-3.5 → LLaMA 7B (52K 条, $500) │ │
│ │ Vicuna: GPT-4 → LLaMA 13B (70K 条, 用户共享) │ │
│ │ Orca: GPT-4 → LLaMA 13B (渐进式学习) │ │
│ │ WizardLM: GPT-4 自我演化 → LLaMA (250K 条) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ 法律风险:很多大模型的 ToS 禁止用输出去训练竞争模型 │
│ OpenAI 曾明确禁止,但实践中难以 enforce。 │
│ 蒸馏出来的模型也可能继承 Teacher 的偏见和错误。 │
│ │
└─────────────────────────────────────────────────────────────┘2.4 合成数据:Self-Instruct 和 Evol-Instruct
这是近年来最受关注的方向——用模型生成训练数据,再用这些数据训练模型。
Self-Instruct(2022,华盛顿大学):
Step 1:人工写 175 个 seed tasks(种子任务)
Step 2:从种子中随机抽 8 个作为 few-shot examples
Step 3:让 LLM 生成新的 instruction("头脑风暴"新任务)
Step 4:让 LLM 生成新 instruction 的 output
Step 5:过滤低质量数据(重复的、太短的、无法完成的)
Step 6:把通过过滤的新数据加入池子 → 回到 Step 2Evol-Instruct(2023,WizardLM):
┌─────────────────────────────────────────────────────────────┐
│ Evol-Instruct 的"进化"策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 核心思想:让简单的 instruction "进化"成复杂的 instruction │
│ │
│ 初始指令: │
│ "解释什么是牛顿第一定律" │
│ │
│ In-Depth Evolving(深度进化): │
│ "解释什么是牛顿第一定律,包括其数学表达式、 │
│ 历史背景、和在日常生活和航天工程中的应用" │
│ │
│ In-Breadth Evolving(广度进化): │
│ "从牛顿第一定律出发,系统性地对比三大运动定律, │
│ 并分析它们分别如何改变了人类对物理世界的认知" │
│ │
│ 进化操作包括: │
│ ├─ 增加约束(add constraints) │
│ ├─ 深化(deepening) │
│ ├─ 具体化(concretizing) │
│ ├─ 增加推理步骤(increased reasoning steps) │
│ └─ 复杂化输入(complicating input) │
│ │
│ 结果:WizardLM 在复杂任务上显著优于 Alpaca │
│ │
└─────────────────────────────────────────────────────────────┘2.5 质量 > 数量:LIMA 论文的震撼发现
┌─────────────────────────────────────────────────────────────┐
│ LIMA: Less Is More for Alignment │
│ (Meta AI, 2023) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 核心实验: │
│ 用 1,000 条精心挑选的高质量 SFT 数据训练 LLaMA 65B │
│ vs │
│ 用 52,000 条 Alpaca 数据(机器生成的)训练同样模型 │
│ │
│ 结果: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LIMA (1K 高质量) 的表现优于或持平 Alpaca (52K) │ │
│ │ │ │
│ │ 1,000 examples carefully curated │ │
│ │ > │ │
│ │ 52,000 examples machine-generated │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ LIMA 的 1,000 条数据是怎么选的: │
│ ├─ 来自社区论坛的高赞回答(StackExchange, Reddit) │
│ ├─ 每条都经过人工筛选验证 │
│ ├─ 覆盖多种任务类型但每条都"典范级"质量 │
│ └─ 输出风格统一(helpful, concise, accurate) │
│ │
│ 启示: │
│ "模型的知识几乎全部来自 Pre-Training, │
│ SFT 只教会它怎么和人类交互——这是一个'格式对齐'任务, │
│ 不需要海量数据。少量高质量数据足以让模型学会'对话风格'。" │
│ │
│ ⚠️ 但要注意:LIMA 用的是 65B 的大模型。 │
│ 小模型可能仍然需要更多 SFT 数据来弥补能力不足。 │
│ │
└─────────────────────────────────────────────────────────────┘2.6 数据质量检查清单
实际做 SFT 时,对每一条数据应该检查:
- 指令清晰度:prompt 是否明确?有没有歧义?
- 回复质量:回复是否正确、完整、有帮助?
- 格式一致性:是否使用了正确的 chat template?特殊 token 是否正确?
- 多样性:这条数据是否和已有数据重复?是否带来了新的 task type?
- 安全性:回复是否包含有害内容?
- 长度合理性:回复是否过短(敷衍)或过长(啰嗦)?
- 角色一致性:如果是多轮对话,assistant 的人设是否前后一致?
第3部分:SFT 训练——和 Pre-Training 一模一样,只是数据变了
3.1 "一模一样"——你可能会觉得惊讶
很多人以为 SFT 是一种"新的训练方法"或"特殊的技巧"。但事实上:
┌─────────────────────────────────────────────────────────────┐
│ Pre-Training 和 SFT 的本质对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Pre-Training │ SFT │
│ ───────────────────────────────────┼───────────────────── │
│ 模型架构 同一个 Transformer │ 同一个 Transformer │
│ 初始权重 随机初始化 │ Pre-Trained 权重 │
│ 目标函数 Next-Token Prediction │ Next-Token Predict │
│ (Cross-Entropy Loss) │ (Cross-Entropy Loss)│
│ 损失函数 CE Loss │ CE Loss │
│ ───────────────────────────────────┼───────────────────── │
│ 数据来源 网页、书籍、代码 │ Instruction-Resp │
│ 数据量 1T-15T tokens │ 10K-1M examples │
│ 数据格式 纯文本序列 │ 带角色标记的对话 │
│ 训练轮数 1 epoch(数据太多) │ 1-3 epochs │
│ 学习率 3e-4(较大) │ 1e-5 ~ 5e-6(较小) │
│ ───────────────────────────────────┼───────────────────── │
│ Loss Mask 所有 token 都参与 │ 只在 Assistant 上 │
│ │
│ 核心结论:SFT 就是"用不同数据做同样的 next-token 预测"。 │
│ │
└─────────────────────────────────────────────────────────────┘3.2 Loss Masking——SFT 和 Pre-Training 唯一的算法差异
这是 SFT 训练中最关键的技术细节。
在 Pre-Training 中,序列里的每一个 token 都参与 Loss 计算:
- 输入 "The cat sat on the mat"
- 模型在每个位置预测下一个 token
- Loss = 所有位置的预测误差之和
在 SFT 中,我们只计算 Assistant 回复部分的 Loss:
┌─────────────────────────────────────────────────────────────┐
│ SFT Loss Masking 的完整示意图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 训练序列(tokenized): │
│ │
│ [<s>] [system] [你是一个...] [user] [什么是...] │
│ 0 1 2 3 4 5 6 7 │
│ │
│ [assistant] [机器] [学习] [是] [一] [种] [...] [<eos>] │
│ 8 9 10 11 12 13 14 15 │
│ │
│ ───────────────────────────────────────────────── │
│ │
│ Label(每个位置的"正确答案"): │
│ 位置0 预测 token_1 ("system") │
│ 位置1 预测 token_2 ("你") │
│ ... │
│ 位置8 预测 token_9 ("学习") │
│ 位置9 预测 token_10 ("是") │
│ ... │
│ │
│ ───────────────────────────────────────────────── │
│ │
│ Loss Mask(哪些位置计算 Loss): │
│ │
│ 位置: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]..
│ Mask: 0 0 0 0 0 0 0 0 1 1 1 1 ..│
│ └── system+user tokens ──┘ └─ assistant tokens ─┘│
│ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^ │
│ 这些位置的 Loss 不参与 这些位置的 Loss 参与 │
│ 梯度计算(被忽略) 梯度计算(被优化) │
│ │
│ ───────────────────────────────────────────────── │
│ │
│ 具体做法(PyTorch 伪代码): │
│ │
│ # labels: 正确答案的 token IDs │
│ # mask: 1 = 计算 loss, 0 = 忽略(设为 -100) │
│ │
│ labels[mask == 0] = -100 # PyTorch 的 ignore_index │
│ loss = CrossEntropyLoss(ignore_index=-100)(logits, labels) │
│ │
│ ───────────────────────────────────────────────── │
│ │
│ 为什么要这样做? │
│ │
│ 如果我们让模型也学"生成 system prompt"和"生成 user 提问": │
│ ├─ 模型会学会"我是 system,我要给自己设定角色..." │
│ │ 但推理时,我们不会让模型"续写 system prompt" │
│ ├─ 模型会学会"我是 user,我要问..." │
│ │ 但推理时用户自己会提问,不需要模型帮用户提问 │
│ └─ 本质问题:我们不关心 P(system|...) 或 P(user|...), │
│ 我们只关心 P(assistant|dialogue_history) │
│ │
└─────────────────────────────────────────────────────────────┘3.3 再深入一点:Prompt 部分的 Loss 为什么不重要
有人可能会问:"让模型顺便学学 system prompt 和 user prompt 有什么坏处?多学一点不是更好吗?"
坏处是真实存在的:
如果我们不对 prompt 做 mask,模型会学到:
"看到 <s> 之后,下一个 token 大概率是 system"
"看到 system prompt 之后,下一个 token 大概率是 user"
...
这在训练数据里是对的,但在推理时会出问题:
- 如果用户的输入格式稍有偏差(比如没有 <s> 开头),
模型可能开始"自我对话"——自己生成 user 提问然后自己回答
- 模型可能生成完一个回答后,开始自动生成下一个 user 问题
(因为它学到了 "assistant 的回答后面经常跟着 user 的追问")
这种"自我对话"行为在早期 chatbot 中非常常见,
正是因为没有对非 assistant token 做 loss masking。3.4 为什么只训练 1-3 个 Epoch
这是新手最容易犯的错误:把 SFT 当成 Pre-Training,训练 10 个 epoch。
┌─────────────────────────────────────────────────────────────┐
│ SFT 为什么容易过拟合——1 Epoch 就够了 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 原因 1:数据量太小 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Pre-Training: 15,000,000,000,000 tokens │ │
│ │ SFT: 50,000,000 tokens │ │
│ │ │ │
│ │ SFT 数据只有 Pre-Training 数据的 0.00033%。 │ │
│ │ 在这种小数据集上训练多个 epoch,模型会"背下来" │ │
│ │ 训练数据中的具体回复,而不是学会"泛化的对话能力"。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 原因 2:灾难性遗忘(Catastrophic Forgetting) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 每多训练一个 epoch,模型在原始 benchmarks 上的 │ │
│ │ 表现就下降一点: │ │
│ │ │ │
│ │ Epoch 0 (Base): MMLU=68.9, HumanEval=45.1 │ │
│ │ Epoch 1 (SFT): MMLU=66.2, HumanEval=43.7 (-2.7%) │ │
│ │ Epoch 2 (SFT): MMLU=63.1, HumanEval=40.2 (-6.1%) │ │
│ │ Epoch 3 (SFT): MMLU=59.0, HumanEval=35.1 (-13.8%)│ │
│ │ │ │
│ │ 数据不是真实测量,但趋势是真实的:多训 = 遗忘 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 原因 3:SFT 的目标是"格式对齐",不是"知识注入" │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 模型的知识在 Pre-Training 阶段已经学好了。 │ │
│ │ SFT 只需要"格式化"模型的输出风格。 │ │
│ │ 格式化 1-2 个 epoch 就够了,再训就是"刻舟求剑"。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 实际建议:从 1 epoch 开始,观察 validation loss。 │
│ 如果 val loss 还在降,最多到 3 epochs。 │
│ 如果 val loss 开始上升(哪怕 training loss 在降),│
│ 立刻停止——你已经在过拟合了。 │
│ │
└─────────────────────────────────────────────────────────────┘3.5 学习率的选择——为什么这么小
┌─────────────────────────────────────────────────────────────┐
│ 学习率的选择:从 3e-4 降到 1e-5 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 典型的学习率对比: │
│ │
│ Pre-Training: 3e-4(GPT-3 用这个) │
│ SFT: 1e-5 ~ 5e-6 │
│ 差异:30-60 倍 │
│ │
│ 为什么 SFT 需要这么小的学习率? │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Pre-Training 时,权重是随机的,远离最优解。 │ │
│ │ 大 LR = 大步走向最优解。 │ │
│ │ │ │
│ │ SFT 时,权重已经在一个"还不错"的位置(Pre-Trained),│ │
│ │ 大 LR = 大步跨出这个区域 → 破坏已学到的知识。 │ │
│ │ 小 LR = 小步微调 → 保持 Pre-Training 的底子, │ │
│ │ 只做表面调整。 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 还有一个经验法则:如果你用 LoRA 做 SFT(常见做法), │
│ LR 可以稍大一些(1e-4),因为 LoRA 只改少量的新增参数, │
│ 对原始权重的干扰本来就小。 │
│ │
│ 但全量微调(Full Fine-Tuning)时,LR 一定要保守。 │
│ │
└─────────────────────────────────────────────────────────────┘3.6 Packing——提升训练效率的技巧
SFT 数据的一个特点是长度差别极大——有的 prompt-response 只有几十个 token,有的多轮对话上千 token。
┌─────────────────────────────────────────────────────────────┐
│ 序列 Packing:节省 GPU 算力 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 不用 Packing: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Batch 中每个样本 pad 到 max_length: │ │
│ │ │ │
│ │ Sample 1: [tok][tok][tok][PAD][PAD][PAD][PAD][PAD] │ │
│ │ Sample 2: [tok][tok][tok][tok][tok][tok][tok][PAD] │ │
│ │ Sample 3: [tok][tok][tok][tok][tok][tok][tok][tok] │ │
│ │ Sample 4: [tok][tok][PAD][PAD][PAD][PAD][PAD][PAD] │ │
│ │ │ │
│ │ PAD 位置的计算都浪费了。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 使用 Packing: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 把多个短样本拼接成一个长序列,中间用 EOS 分隔: │ │
│ │ │ │
│ │ [Sample1][EOS][Sample2][EOS][Sample3] │ │
│ │ │ │
│ │ 注意:不同 sample 的 loss 不能跨 EOS │ │
│ │ → 位置 0 不能 attend 到位置 150(那是另一个 sample) │ │
│ │ → 需要用特殊的 attention mask 阻断 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 实践中,很多框架(如 HuggingFace TRL、Axolotl) │
│ 已经内置了 packing 支持。 │
│ │
└─────────────────────────────────────────────────────────────┘3.7 完整训练循环(伪代码)
# SFT 训练的完整流程(简化版)
from transformers import AutoModelForCausalLM, AutoTokenizer
from torch.utils.data import DataLoader
import torch
# 1. 加载 Base Model(已经 Pre-Trained 好的权重)
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3-8B")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3-8B")
# 2. 加载 SFT 数据并应用 chat template
def format_conversation(messages):
"""把 messages 列表转成 tokenized 序列 + loss mask"""
# 应用 chat template
text = tokenizer.apply_chat_template(messages, tokenize=False)
tokens = tokenizer(text, return_tensors="pt")
# 构建 loss mask:只 mask assistant 部分
# 这一步的具体实现取决于 chat template 的特殊 token
# 核心逻辑:找到每个 assistant 块的起止位置,标记为 1
return tokens, loss_mask
# 3. 训练循环
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5) # 小 LR
model.train()
for epoch in range(3): # 最多 3 个 epoch
for batch in dataloader:
input_ids = batch["input_ids"].to(device)
attention_mask = batch["attention_mask"].to(device)
labels = batch["labels"].to(device) # 已经应用了 loss mask
# 前向传播 —— 和 Pre-Training 完全一样
outputs = model(
input_ids=input_ids,
attention_mask=attention_mask,
labels=labels # CrossEntropyLoss(ignore_index=-100)
)
loss = outputs.loss
# 反向传播
loss.backward()
optimizer.step()
optimizer.zero_grad()
# 每个 epoch 结束后在 validation set 上评估
val_loss = evaluate(model, val_dataloader)
if val_loss > best_val_loss * 1.01: # val loss 开始上升
print(f"Early stopping at epoch {epoch}")
break第4部分:SFT 的局限性——为什么 SFT 不是终点
4.1 三大局限全景
┌─────────────────────────────────────────────────────────────┐
│ SFT 的三大根本局限 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ① 灾难性遗忘(Catastrophic Forgetting) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 训练 SFT 时,模型逐渐"忘记"Pre-Training 学到的知识。 │ │
│ │ 训得越多,忘得越多。 │ │
│ │ │ │
│ │ 尤其危险的是"对齐税"(Alignment Tax): │ │
│ │ 让模型更 helpful + harmless → 它在某些 benchmark │ │
│ │ 上的表现下降了。这是几乎所有 SFT 模型都有的现象。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ② 数据瓶颈(Data Bottleneck) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 高质量 SFT 数据需要人类标注员逐条手写。 │ │
│ │ 一个标注员一天只能写 20-50 条。 │ │
│ │ │ │
│ │ 如果你需要 10 万条高质量数据: │ │
│ │ 10万 ÷ 30条/天 ÷ 250工作日/年 = 13 人年 │ │
│ │ │ │
│ │ 而且高质量标注员很难找(需要语言能力+领域知识+耐心)。│ │
│ │ │ │
│ │ 蒸馏可以缓解但引入新问题:Teacher 的 bias、法律风险、 │ │
│ │ 多样性下降(所有模型越来越像 GPT-4)。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ③ 无法学习"判断"——这是最根本的局限 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ SFT 学的是 "给定 prompt,输出什么" │ │
│ │ 但它学不到 "这两个回答哪个更好" │ │
│ │ │ │
│ │ 具体来说: │ │
│ │ │ │
│ │ SFT 数据里只有一种"正确答案",模型学会了模仿它。 │ │
│ │ │ │
│ │ 但在真实世界中: │ │
│ │ • 很多问题没有"唯一正确答案" │ │
│ │ • "好"和"更好"之间是连续的,不是二元的 │ │
│ │ • 我们常常不知道"完美的回答"是什么, │ │
│ │ 但我们知道"A 比 B 好" │ │
│ │ │ │
│ │ 这就是为什么 RLHF/DPO 是 SFT 的下一步—— │ │
│ │ 它们用"偏好数据"(preference data)训练模型, │ │
│ │ 让模型学会"什么是更好的回答"。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘4.2 灾难性遗忘的深层原因
┌─────────────────────────────────────────────────────────────┐
│ 为什么 SFT 会导致灾难性遗忘? │
├─────────────────────────────────────────────────────────────┤
│ │
│ 从优化的角度理解: │
│ │
│ Pre-Trained 权重 W_pt 是以下优化问题的解: │
│ min L_pt(W) (最小化 next-token-prediction loss │
│ W 在互联网数据上) │
│ │
│ SFT 权重 W_sft 是以下优化问题的解: │
│ min L_sft(W) (最小化 next-token-prediction loss │
│ W 在 SFT 数据上) │
│ │
│ 问题:L_sft 的最小值点 ≠ L_pt 的最小值点 │
│ │
│ 从 W_pt 开始,沿着 L_sft 的梯度方向走: │
│ │
│ L_sft 在下降 ↓ │
│ 但同时 L_pt 在上升 ↑(因为你在远离 W_pt) │
│ │
│ 数学上:∇L_sft 和 ∇L_pt 的方向不完全一致—— │
│ 某些方向对 L_sft 很重要但对 L_pt 有害。 │
│ │
│ ───────────────────────────────────────────────── │
│ │
│ 缓解手段: │
│ │
│ 1. KL 正则化:在 SFT loss 上加一个惩罚项 │
│ L_total = L_sft + β · KL(P_sft || P_pt) │
│ 不要让新模型的输出分布离原始模型太远。 │
│ │
│ 2. 数据混合(Data Mixing): │
│ 把一些 Pre-Training 数据混入 SFT 数据一起训练。 │
│ 相当于告诉模型:"别忘了你原来学的东西"。 │
│ L_total = L_sft + α · L_pt(同时计算两个 loss) │
│ │
│ 3. LoRA / 参数高效微调: │
│ 只修改少量新增参数,冻结原始权重。 │
│ 天然防止遗忘(因为原始权重根本没变)。 │
│ 但代价是"对齐效果"可能不够深。 │
│ │
└─────────────────────────────────────────────────────────────┘4.3 为什么"不能学判断"是致命的
这是理解整个 Post-Training 链条最关键的一点。
┌─────────────────────────────────────────────────────────────┐
│ 一个例子说明 SFT 为什么"学不到判断" │
├─────────────────────────────────────────────────────────────┤
│ │
│ 假设你有一段高质量的 SFT 数据: │
│ │
│ Prompt: "解释什么是相对论" │
│ Response: "相对论是爱因斯坦在1905年和1915年提出的..." │
│ │
│ SFT 训练后,模型学会了: │
│ 看到 "解释什么是X" → 给出定义 + 背景 + 细节 │
│ │
│ 这很好。但考虑这个 prompt: │
│ │
│ Prompt: "解释什么是相对论(用一句话)" │
│ Response: "相对论是描述时空和引力关系的物理理论。" │
│ │
│ 现在模型需要知道: │
│ 同样的问题,不同的约束 → 不同长度、不同深度的回答 │
│ │
│ SFT 可以学到 "用一句话" → 简短的输出(如果数据里有)。 │
│ 但这只是 pattern matching。 │
│ │
│ ───────────────────────────────────────────────── │
│ │
│ SFT 真正学不会的场景: │
│ │
│ Prompt: "写一首关于秋天的五言绝句" │
│ │
│ 假设模型生成了两首诗: │
│ 候选 A: "秋风扫落叶,霜降百花残,孤雁南飞去,游子衣正单" │
│ 候选 B: "秋天来了树叶黄了天气凉了我们穿上了厚衣服" │
│ │
│ 哪首更好?候选 A 显然是更好的诗(押韵、意象、结构), │
│ 但 SFT 数据中只有"一首好诗"作为正确答案—— │
│ 模型从未被告知"为什么 A 比 B 好"或"B 为什么不好"。 │
│ │
│ 它没见过"差"的回答长什么样,更没学过怎么区分好坏。 │
│ │
│ ───────────────────────────────────────────────── │
│ │
│ 这就是 Preference Learning(偏好学习)要解决的问题: │
│ │
│ SFT 数据: │
│ {prompt, good_response} │
│ → 只能告诉模型 "这个好,照着学" │
│ │
│ Preference 数据(RLHF/DPO 用): │
│ {prompt, chosen_response, rejected_response} │
│ → 告诉模型 "这个比那个好,往这个方向走" │
│ → 模型学会的不是"正确答案长什么样", │
│ 而是"什么特质让一个回答更好" │
│ │
│ ───────────────────────────────────────────────── │
│ │
│ 为什么这很重要? │
│ │
│ 1. 大多数真实问题没有唯一的"正确回答" │
│ 2. 我们关心的不是"模型能不能输出某个特定答案" │
│ 而是"模型能不能一直输出高质量的回答" │
│ 3. Preference data 比 SFT data 更容易获取: │
│ 写一个好回答很难 → 判断两个回答哪个更好相对容易 │
│ │
└─────────────────────────────────────────────────────────────┘4.4 从 SFT 到 RLHF/DPO 的桥梁
┌─────────────────────────────────────────────────────────────┐
│ Post-Training 的完整链条 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Base Model(Pre-Trained) │
│ │ │
│ │ 问题:只会续写,不会回答问题 │
│ │ │
│ ▼ │
│ SFT(本章) │
│ │ 输入:{prompt, response} 对 │
│ │ 输出:会对话的模型 │
│ │ 仍然不会:知道"什么回答更好" │
│ │ │
│ ▼ │
│ Reward Modeling(训练打分模型) │
│ │ 输入:{prompt, chosen, rejected} 偏好对 │
│ │ 输出:一个"打分器"——给任意回答打分 │
│ │ │
│ ▼ │
│ RLHF / DPO(偏好对齐) │
│ │ 用 Reward Model 的分数或偏好对来优化 SFT 模型 │
│ │ 输出:对齐后的模型(helpful, harmless, honest) │
│ │ │
│ ▼ │
│ Aligned Model(最终可部署的 Chat Model) │
│ │
│ SFT 是第一步——必须的,但不够的。 │
│ 没有 SFT,RLHF/DPO 不知道从哪开始优化; │
│ 没有 RLHF/DPO,SFT 只能做一个"会说话的复读机"。 │
│ │
└─────────────────────────────────────────────────────────────┘核心总结
总结1:SFT 解决什么问题
Base Model 只会"续写文本",不会"遵循指令"。你问它一句话,它给你续写一段话——而不是回答你的问题。SFT 通过监督学习把"续写模式"扭转为"对话模式"。
总结2:SFT 的数据
SFT 数据本质上是 {Prompt, Response} 对或 Multi-turn Conversation。关键格式要求:
- 用特殊的 role token 区分 system/user/assistant
- 不同模型有不同的 chat template(ChatML、Llama3、Mistral 等)
- 数据来源:人工标注 > 模型蒸馏 > 合成数据 > 众包 > 用户共享 > NLP 数据集改造
- 质量远大于数量:LIMA 用 1000 条高质量数据打败了 52000 条机器生成数据
总结3:SFT 的训练
SFT 的训练方法和 Pre-Training 一模一样:
- 同样的 Transformer 架构(加载 Pre-Trained 权重)
- 同样的 Next-Token Prediction + Cross-Entropy Loss
- 唯一的区别:Loss 只在 Assistant 的 token 上计算(mask 掉 system 和 user 的部分)
- 训练 1-3 epochs,学习率降到 1e-5 ~ 5e-6(比 Pre-Training 小 30-60 倍)
- 关键超参:batch size、LR、epochs、warmup ratio、weight decay
总结4:SFT 的三大局限
- 灾难性遗忘:训得越多,Pre-Training 的知识丢得越多(对齐税)
- 数据瓶颈:高质量标注数据极其昂贵,难以规模化
- 无法学"判断":SFT 只学"输出什么",学不会"什么更好"——这正是 RLHF/DPO 存在的理由
总结5:SFT 在整个 Post-Training 中的位置
SFT 是 Post-Training 的第一步,也是最基础的一步。它把 Base Model 变成一个"会对话"的模型,但它不能让模型学会"什么是高质量的对话"。这是后续 RLHF 或 DPO 要做的事。
Base Model ──SFT──→ SFT Model (会对话) ──RLHF/DPO──→ Aligned Model (高质量对话)章节测试
测试1:Base Model 的行为
给 Base Model 输入"解释一下光合作用",它最可能输出什么?为什么?
测试2:SFT 数据格式
Instruction-Response 对和 Multi-turn Conversation 在训练数据格式上有什么区别?它们各自的优缺点是什么?
测试3:Loss Masking
为什么 SFT 训练时要 mask 掉 system 和 user token 的 Loss?如果把整个序列的所有 token 都参与 Loss 训练会有什么后果?
测试4:数据质量
LIMA 论文用了多少条数据做到了什么效果?这一发现对 SFT 实践有什么指导意义?
测试5:训练超参
SFT 的学习率为什么比 Pre-Training 低 30-60 倍?如果 SFT 也用 Pre-Training 的 LR 会发生什么?
测试6:灾难性遗忘
什么是"对齐税"(Alignment Tax)?有哪些技术手段可以缓解它?
测试7:SFT 的根本局限
为什么 SFT 无法让模型学到"什么是好的回答"?这导致了 Post-Training 链条中哪个后续步骤的出现?
参考答案
测试1答案
Base Model 最可能输出类似于"解释一下光合作用的意义和过程..."这样的文本——它会续写 prompt,像一个维基百科条目的开头,而不是直接回答"光合作用是...(定义)"。
原因:Base Model 的唯一技能是 next-token prediction。它看到"解释一下光合作用"这个开头,最自然的续写就是继续展开(定义、背景、过程)。它没有被训练过"看到问题 → 直接给出答案"的行为模式。在 Pre-Training 数据中,大多数"X 是什么"的文本后面跟着的是展开阐述,而不是精炼的直接回答。
测试2答案
格式区别:
- Instruction-Response 对:单轮结构,一条 instruction + 一条 output,适合训练模型执行单步任务。
- Multi-turn Conversation:多轮结构,包含 system/user/assistant 三种角色的交替对话,适合训练模型的多轮交互能力。
各自的优缺点:
- Instruction-Response 优点:结构简单,生成成本低,单轮任务表现好;缺点:无法训练多轮对话能力、追问、纠错、角色一致性等。
- Multi-turn Conversation 优点:更接近真实使用场景,能训练追根溯源、纠错、记忆上下文等能力;缺点:标注成本更高(要设计多轮交互),数据更难获取和验证。
测试3答案
如果不用 loss mask,让所有 token(包括 system prompt 和 user 提问)参与训练,会出现以下问题:
- 模型会学会"自我对话":模型学会了生成 system prompt 然后自己提问然后自己回答——这在推理时是灾难性的行为。
- 不稳定输出:模型可能在回答完一个问题后自动生成下一个 user 问题,陷入"自问自答"的循环。
- 学习目标错误:我们想让模型学的是 P(assistant_reply | dialogue_context),而不是 P(system_prompt | ...) 或 P(user_question | ...)。
本质原因:推理时,system prompt 和 user message 由外部输入提供,模型只需要生成 assistant 的回复。训练时的目标应该和推理时的任务完全一致。
测试4答案
LIMA(Meta AI, 2023)用 1000 条精心挑选的高质量指令数据训练 LLaMA 65B,在与 Alpaca(52000 条机器生成数据)的对比中取得相当甚至更好的效果。
指导意义:
- SFT 的核心是"格式对齐",不是"知识注入"——模型的知识已经在 Pre-Training 阶段学好了。
- 质量远比数量重要。与其用 5 万条噪声数据,不如花精力精选 1000 条高质量数据。
- 数据筛选和清洗可能是 SFT 中最重要的工作——比训练超参调整更重要。
- 注意 LIMA 用的是 65B 大模型,小模型可能仍然需要更多数据来弥补能力差距。
测试5答案
SFT 的学习率比 Pre-Training 低 30-60 倍的原因:
目标不同:
- Pre-Training:权重是随机的,离最优解很远,需要大步前进(大 LR)。
- SFT:权重已经在 Pre-Training 的"好位置"上了,只需要微调,大步走容易脱离这个好位置。
如果 SFT 用 Pre-Training 的 LR:
- 模型会迅速远离 Pre-Trained 权重,导致灾难性遗忘——在标准 benchmark 上的表现急剧下降。
- 可能无法收敛(loss 震荡),因为在曲面"较平坦"的区域使用过大的 LR 会跳来跳去。
- 极端情况下,模型可能退化成只能输出 SFT 数据中的回答,而完全丧失泛化能力。
测试6答案
对齐税(Alignment Tax):为了让模型更 helpful、harmless、honest 而进行 SFT/RLHF 后,模型在某些能力维度(如知识问答 benchmark、代码生成、推理能力)上出现性能下降。这种"为了对齐而付出的能力代价"被称为对齐税。
缓解手段:
- 数据混合(Data Mixing):在 SFT 数据中混入一部分 Pre-Training 数据,帮助模型保持原有能力。
- KL 正则化:在 SFT 损失函数中加入 KL 散度惩罚项,约束模型不要偏离 Pre-Trained 分布太远。
- 参数高效微调(如 LoRA):只修改少量新增参数,冻结原始权重,天然防止遗忘(但对齐深度可能受限)。
- 适度训练:1-2 个 epoch 就停,不要贪多。
- Replay / Experience Replay:在训练过程中周期性地在 Pre-Training 数据上做少量步骤。
测试7答案
SFT 无法让模型学到"什么是好的回答",因为 SFT 数据的格式是 {prompt, single_good_response}——它只告诉模型"这是一个好回答,照着学",但从不告诉模型"为什么这个好"或"那个为什么不好"。
更具体地说:
- SFT 假设存在一个"正确答案",模型的目标就是输出这个答案。
- 但大多数真实问题没有唯一的"正确答案"——有很多不同程度的"好"回答。
- 模型从未见过"差"的回答,所以无法学会区分好坏的边界。
这导致了:RLHF(基于人类反馈的强化学习)和 DPO(直接偏好优化)的出现。这两种方法使用偏好数据 {prompt, chosen, rejected}——不是告诉模型"输出这个",而是告诉模型"这个比那个更好"。模型学会了"什么特质让一个回答更好",而不仅仅是"输出什么"。
这也是为什么 SFT 是必须的第一步(否则模型连基本对话都不会),但不能是最后一步(否则模型只是一个"会说话的复读机")。
相关笔记
- [[11-training-primer]] — 训练基础概念(Loss、梯度、Optimizer、LR Schedule)
- [[14-post-training-overview]] — 后训练全景图(SFT → RM → RLHF/DPO)
- [[07-llm-evolution]] — 从 Word2Vec 到 GPT,了解模型架构演化
- [[09-decoder-only-llm]] — 为什么现代 LLM 只用 Decoder
- [[10-training-vs-inference]] — 训练和推理的完整对比(Teacher Forcing vs Auto-Regressive)
下一步学习
- [ ] 阅读 [[14-post-training-overview]],建立从 SFT 到 RLHF/DPO 的完整后训练链条认知
- [ ] 动手做:用 HuggingFace TRL 的
SFTTrainer在一个小模型上跑一次 SFT(推荐 SmolLM-135M,几十 MB,CPU 都能跑) - [ ] 理解 Reward Model(奖励模型)的设计——它是 RLHF 的核心组件,就是 SFT 的"升级版"损失函数
- [ ] 对比 RLHF(PPO)和 DPO 的核心区别——同样是"教模型判断好坏",两种完全不同的路径
- [ ] 阅读 LIMA 论文原文("LIMA: Less Is More for Alignment"),理解为什么"小数据+高质量"比"大数据+低质量"更有效
学习状态:🟡 开始学习