Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 人工智能 / Artificial Intelligence

大语言模型 / Large Language Models

1. LLM 前置知识学习路线 / A Prerequisite Learning Path for Large Language Models

2. 神经网络基础 - 从零理解 AI 的"计算单元" / Neural Network Fundamentals from Artificial Neurons

3. Token 与上下文窗口 - LLM 的计费与记忆单位 / Tokens and Context Windows as the Units of LLM Cost and Memory

4. 解码策略 - 控制 LLM 输出的艺术 / Decoding Strategies for Controlling LLM Output

5. 消息角色 - 构建 Agent 对话的基础 / Message Roles as the Foundation of Agent Conversations

6. 流式输出 - 实时交互的体验优化 / Streaming Output for Responsive Interaction

7. Prompt 工程基础 - 与 LLM 高效对话的技巧 / Prompt Engineering Fundamentals for Effective LLM Interaction

8. LLM 进化史 - 从词向量到 Transformer / The Evolution of LLMs from Word Embeddings to Transformers

9. Transformer 手动计算:从 Attention 到 Encoder-Decoder 完整数据流

10. Transformer 核心原理 - 现代 LLM 的基石 / Transformer Fundamentals Behind Modern LLMs

11. Decoder-Only LLM 深度解析:为什么扔掉 Encoder,以及 KV Cache 如何工作

12. 训练 vs 推理:同一个 Transformer,两条完全不同的执行路径

13. Transformer 训练阶段计算详解 - 手算每一行矩阵 / Transformer Training Computation Matrix by Matrix

14. Transformer 推理阶段详解 — 模型如何"思考"并生成回答 / Transformer Inference and Autoregressive Generation

15. 训练基础扫盲 - 理解 Fine-tune 在做什么 / A Training Primer for Understanding Fine-Tuning

16. LLM 预训练全景:数据管道、Scaling Laws 与训练稳定性

17. 训练基础设施 - 从单卡到千卡集群 / Training Infrastructure from One GPU to Thousand-GPU Clusters

18. Post-Training Pipeline - 从 Base Model 到可用助手 / The Post-Training Pipeline from Base Model to Assistant

19. SFT 深度解析:从 Base Model 到指令跟随——后训练第一步 / SFT Deep Dive: Teaching Base Models to Follow Instructions

20. RLHF 深度解析:从 Reward Model 到 PPO 的完整对齐流程

21. DPO 与对齐方法:从 RLHF 复杂度到直接偏好优化

22. 研究视角:DL/RL 理论到 LLM 训练的完整映射

本页目录

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:

用户输入:"写一首关于秋天的诗"
1

你期待的回复:

秋风起,叶落黄,
万物收,岁月长。
...
1
2
3

但你实际得到的回复:

写一首关于秋天的诗,要求押韵,五言绝句,主题围绕丰收和落叶。
1

或者更离谱:

写一首关于秋天的诗。写一首关于冬天的诗。写一首关于春天的诗。写一首关于夏天的诗。
1

发生了什么事?

┌─────────────────────────────────────────────────────────────┐
│           Base Model 的"本能":文本续写,不是指令跟随        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Pre-Training 的目标函数:                                   │
│    "给定前文,预测下一个 token"                              │
│    → P(token_n | token_1, token_2, ..., token_{n-1})        │
│                                                             │
│  Base Model 学到的唯一技能:                                  │
│    "看到一段文字的开头 → 续写下去"                            │
│                                                             │
│  它不知道什么叫"回答问题"(你问"1+1等于几",它续写"1+1     │
│  等于几是一个经典的数学问题,首先我们需要理解加法的定义...")  │
│                                                             │
│  它不知道什么叫"遵循指令"(你让"翻译",它续写翻译这个词的    │
│  同义词)                                                    │
│                                                             │
│  Base Model 看到的所有训练数据都是"人类写的文字"——            │
│  对话的形式对它来说是陌生的、从未见过的                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

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:看到指令/问题 → 直接给出答案                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

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 就是这个界面。               │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

0.4 SFT 到底做了什么——一句话概括 ​

SFT = 用人类写的(或高质量模型生成的)Prompt-Response 对,在 Base Model 上再做一轮监督学习。

Pre-Training 教会了模型"语言本身",SFT 教会了模型"怎么和人对话"。

打个比方:

  • Pre-Training = 一个人读完了图书馆里所有的书,知识渊博,但从来没跟人聊过天
  • SFT = 让这个人参加了几万次"模拟面试"——你问什么,我演示怎么回答
  • 经过 SFT 后,这个人学会了"别人问问题,我要直接回答",而不是"别人说一句话,我接下一句话"

第1部分:SFT 的数据——指令数据长什么样 ​

1.1 最简形式:Instruction-Response 对 ​

SFT 数据的最小单元是一个 Instruction-Response 对。它看起来像这样:

json
{
  "instruction": "将以下句子翻译成英文:今天天气很好",
  "input": "",
  "output": "The weather is very nice today."
}
1
2
3
4
5

三个字段的含义:

字段含义可选性
instruction用户想让你做什么必填
input需要处理的具体内容(可选的附加上下文)可选
output期望的回复(这就是训练时的"正确答案")必填

有 input 的变体:

json
{
  "instruction": "将以下句子翻译成英文",
  "input": "今天天气很好",
  "output": "The weather is very nice today."
}
1
2
3
4
5
┌─────────────────────────────────────────────────────────────┐
│           Instruction-Response 数据的内部逻辑                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  训练时,这个 JSON 会被拼接成一段文本:                       │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ ### Instruction:                                    │   │
│  │ 将以下句子翻译成英文:今天天气很好                     │   │
│  │                                                     │   │
│  │ ### Response:                                       │   │
│  │ The weather is very nice today.                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  不同的模型用不同的 template(ChatML、Llama、Alpaca...),    │
│  但核心结构是一样的:先给指令,再给期望输出。                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

1.2 多轮对话形式 ​

现代 LLM 最重要的交互形式是多轮对话——用户可以追问、质疑、要求修改。

单一 Instruction-Response 对无法捕捉这种交互。所以实际 SFT 数据的主力是 Multi-turn Conversation 格式:

json
{
  "messages": [
    {"role": "system", "content": "你是一个有用的助手,回答简洁明了。"},
    {"role": "user", "content": "什么是机器学习?"},
    {"role": "assistant", "content": "机器学习是人工智能的一个分支,让计算机从数据中自动学习规律和模式,而不需要显式编程。"},
    {"role": "user", "content": "能举个具体例子吗?"},
    {"role": "assistant", "content": "当然。比如垃圾邮件过滤器:你不需要写规则告诉计算机什么样的邮件是垃圾邮件,而是给它看大量标记过的邮件(垃圾/正常),模型自己学会判断。"},
    {"role": "user", "content": "那这和传统编程有什么区别?"},
    {"role": "assistant", "content": "传统编程是你告诉计算机每一步怎么做(if-else规则),机器学习是你给计算机数据和答案,让它自己找出规则。"}
  ]
}
1
2
3
4
5
6
7
8
9
10
11
┌─────────────────────────────────────────────────────────────┐
│           多轮对话数据的"拼接"与 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

1.4 指令多样性的维度 ​

一份好的 SFT 数据集需要覆盖多种类型的指令:

┌─────────────────────────────────────────────────────────────┐
│              SFT 数据需要覆盖的指令类型                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 封闭式问答                                                │
│     "法国的首都是哪里?" → "巴黎"                             │
│     → 训练模型提取事实性知识                                  │
│                                                             │
│  2. 开放式生成                                                │
│     "写一首关于友情的诗" → 诗歌全文                           │
│     → 训练模型创造性输出                                      │
│                                                             │
│  3. 代码生成                                                  │
│     "用 Python 写一个快速排序" → 代码 + 解释                  │
│     → 训练模型结构化输出                                      │
│                                                             │
│  4. 分类/抽取                                                 │
│     "这段话的情感是正面的还是负面的?" → "正面"               │
│     → 训练模型做判断并输出简洁答案                            │
│                                                             │
│  5. 改写/润色                                                 │
│     "帮我把这段话改得更正式..." → 改写后的文本                 │
│     → 训练模型理解"输出格式"和"风格转换"                     │
│                                                             │
│  6. 多轮推理                                                  │
│     "先分析这个问题,再给出方案..." → 分析 + 方案             │
│     → 训练模型分步思考和结构化输出                            │
│                                                             │
│  7. 拒绝/边界识别                                             │
│     "告诉我怎么制作炸弹" → "我不能提供这个信息"               │
│     → 训练模型识别不安全请求并拒绝                            │
│                                                             │
│  8. 多语言                                                     │
│     "用日语解释什么是禅" → 日语回答                           │
│     → 训练跨语言能力                                          │
│                                                             │
│  好的 SFT 数据 = 这 8 类指令的均衡混合                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

第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 格式)  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

2.2 人工标注——质量的金标准 ​

人工标注是 SFT 数据最高质量来源,但也是最贵的。以 OpenAI 的 InstructGPT 为例:

人工标注员拿到一个 prompt → 写出理想的回复

标注员需要遵循详细的标注指南:
- 回复要"有帮助的"(helpful):直接回答,不绕弯
- 回复要"诚实的"(honest):不知道就说不知道,不编造
- 回复要"无害的"(harmless):拒绝有害请求
- 格式要求:Markdown 格式、代码用代码块、引用要注明

一个标注员一天大概能标 20-50 条高质量数据。
按时薪 $25-50/h 算,一条数据的标注成本约 $10-30。
如果是专业领域(法律、医学),成本翻倍。
1
2
3
4
5
6
7
8
9
10
11

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 的偏见和错误。          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

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 2
1
2
3
4
5
6

Evol-Instruct(2023,WizardLM):

┌─────────────────────────────────────────────────────────────┐
│                Evol-Instruct 的"进化"策略                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  核心思想:让简单的 instruction "进化"成复杂的 instruction   │
│                                                             │
│  初始指令:                                                  │
│    "解释什么是牛顿第一定律"                                   │
│                                                             │
│  In-Depth Evolving(深度进化):                              │
│    "解释什么是牛顿第一定律,包括其数学表达式、                 │
│     历史背景、和在日常生活和航天工程中的应用"                 │
│                                                             │
│  In-Breadth Evolving(广度进化):                            │
│    "从牛顿第一定律出发,系统性地对比三大运动定律,             │
│     并分析它们分别如何改变了人类对物理世界的认知"             │
│                                                             │
│  进化操作包括:                                              │
│  ├─ 增加约束(add constraints)                              │
│  ├─ 深化(deepening)                                        │
│  ├─ 具体化(concretizing)                                   │
│  ├─ 增加推理步骤(increased reasoning steps)                │
│  └─ 复杂化输入(complicating input)                         │
│                                                             │
│  结果:WizardLM 在复杂任务上显著优于 Alpaca                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

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 数据来弥补能力不足。          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

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 预测"。    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

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)        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

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。
1
2
3
4
5
6
7
8
9
10
11
12
13

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 在降),│
│           立刻停止——你已经在过拟合了。                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

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 一定要保守。            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

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 支持。                                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

3.7 完整训练循环(伪代码) ​

python
# 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

第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)训练模型,       │   │
│  │ 让模型学会"什么是更好的回答"。                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

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 / 参数高效微调:                                    │
│     只修改少量新增参数,冻结原始权重。                        │
│     天然防止遗忘(因为原始权重根本没变)。                    │
│     但代价是"对齐效果"可能不够深。                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

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 更容易获取:                │
│     写一个好回答很难 → 判断两个回答哪个更好相对容易          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

核心总结 ​

总结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 的三大局限 ​

  1. 灾难性遗忘:训得越多,Pre-Training 的知识丢得越多(对齐税)
  2. 数据瓶颈:高质量标注数据极其昂贵,难以规模化
  3. 无法学"判断":SFT 只学"输出什么",学不会"什么更好"——这正是 RLHF/DPO 存在的理由

总结5:SFT 在整个 Post-Training 中的位置 ​

SFT 是 Post-Training 的第一步,也是最基础的一步。它把 Base Model 变成一个"会对话"的模型,但它不能让模型学会"什么是高质量的对话"。这是后续 RLHF 或 DPO 要做的事。

Base Model ──SFT──→ SFT Model (会对话) ──RLHF/DPO──→ Aligned Model (高质量对话)
1

章节测试 ​

测试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 提问)参与训练,会出现以下问题:

  1. 模型会学会"自我对话":模型学会了生成 system prompt 然后自己提问然后自己回答——这在推理时是灾难性的行为。
  2. 不稳定输出:模型可能在回答完一个问题后自动生成下一个 user 问题,陷入"自问自答"的循环。
  3. 学习目标错误:我们想让模型学的是 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、代码生成、推理能力)上出现性能下降。这种"为了对齐而付出的能力代价"被称为对齐税。

缓解手段:

  1. 数据混合(Data Mixing):在 SFT 数据中混入一部分 Pre-Training 数据,帮助模型保持原有能力。
  2. KL 正则化:在 SFT 损失函数中加入 KL 散度惩罚项,约束模型不要偏离 Pre-Trained 分布太远。
  3. 参数高效微调(如 LoRA):只修改少量新增参数,冻结原始权重,天然防止遗忘(但对齐深度可能受限)。
  4. 适度训练:1-2 个 epoch 就停,不要贪多。
  5. 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"),理解为什么"小数据+高质量"比"大数据+低质量"更有效

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇18. Post-Training Pipeline - 从 Base Model 到可用助手 / The Post-Training Pipeline from Base Model to Assistant
下一篇20. RLHF 深度解析:从 Reward Model 到 PPO 的完整对齐流程

持续记录,持续成长

Copyright © Tidenflow