2-SFT 监督微调

1. SFT 的本质:从”会续写”到”会说话”

预训练模型的核心能力是续写:给一段文字,它学会预测下一个 token。输入是 “用户说了一段话”,输出是 “模型继续往下写”——模型并不理解什么是”回答”,什么是”指令”,什么是”角色”。

监督微调(Supervised Fine-Tuning, SFT)的目标,就是把这种续写能力重新定向为对话能力。具体来说,让模型学会:

  • 把用户的指令理解为”需要回答”,而不是”需要续写”
  • 按照指定格式输出回复(而不是续写用户的输入)
  • 遵循不同的任务类型(问答、摘要、写作、代码等)

从训练角度,SFT 和预训练本质上都在做语言建模。但关键区别在于:训练数据的构造方式完全不同。预训练是让模型从海量互联网文本中自监督学习语言规律,SFT 则是用人工标注或模型生成的问答对,让模型学习”在给定指令条件下输出特定回答”。

经过 SFT 的模型,通常还需要经过 RLHF(DPO/PPO)阶段来进一步对齐人类偏好,但 SFT 是构建基础对话能力的必经步骤


2. 数学形式:条件语言建模

2.1 目标函数

SFT 的本质仍是条件语言建模(Conditional Language Modeling)。给定一段上下文 ,模型预测下一个 token

展开来说,对于一条训练样本,我们将其拼接为如下序列:

[instruction] + [input] + [output]

模型对这个完整序列做自回归预测,loss 只在 [output] 部分(即 assistant 的回复)计算。

2.2 Token-level Cross Entropy

具体到每个 token,loss 的计算方式为:

这与预训练的语言模型 loss 形式完全一致。区别在于:

预训练SFT
训练文本来源互联网文档(自动采集)人工标注/模型生成的问答数据
监督信号位置所有 token仅 assistant 回复部分
数据格式连续文本流结构化 instruction-output 对
典型数据量数千亿~万亿 tokens数十万~数百万条样本

2.3 Loss Mask 的作用

为什么只对 assistant 部分计算 loss?

SFT 训练中,输入部分(system prompt、user instruction、input)都是模型不该”学习回答”的内容。如果把这些位置也计入 loss,模型会学到”在 instruction 后面生成指定 output”——但我们真正想让模型学会的是在给定 instruction 后生成 output,而不是”在 instruction 之后生成特定文字”。

更具体地,假设一条训练数据是:

<|system|>: 你是一个有帮助的助手。
<|user|>: 请问中国的首都是哪里?
<|assistant|>: 中国的首都是北京。

在计算 loss 时:

  • <|system|><|user|> 部分的 token:mask 掉,不计算梯度
  • <|assistant|> 部分的 token:计算交叉熵 loss

这确保了模型学到的是”根据 instruction 和 input 生成 output”,而不是”在输入文本后面原样复现 output”。

Loss Mask 的实现:在实践中,通常用一个 binary mask 数组标记哪些位置需要计算 loss:

loss_mask = [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, ...]
final_loss = (loss_mask * token_log_probs).sum() / loss_mask.sum()

3. 数据形式与构造规范

3.1 基础数据格式

SFT 训练数据的核心字段:

字段含义示例
instruction任务指令”请把以下英文翻译成中文”
input输入内容(可为空)“Hello, world!”
output期望输出”你好,世界!”

对于纯开放式问答,instruction 可以直接是问题本身,input 为空。

3.2 Chat Template

在实际训练中,数据需要转换为模型的输入格式。不同的模型家族使用不同的 chat template:

LLaMA / Qwen 格式示例

<|begin_of_text|><|start_header_id|>system<|end_header_id|>
你是我的助手。<|eot_id|><|start_header_id|>user<|end_header_id|>
今天天气怎么样?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
今天天气晴朗,适合出行。<|eot_id|>

ChatML 格式(Qwen2 等)

<|im_start|>system
你是我的助手<|im_end|>
<|im_start|>user
今天天气怎么样?<|im_end|>
<|im_start|>assistant
今天天气晴朗,适合出行。<|im_end|>

统一要求

  1. 模板一致性:同一训练任务中,所有数据必须使用完全相同的 template。不同 template 混用会导致模型学到”在不同格式下输出不同内容”,严重影响训练稳定性。
  2. 特殊 token 清晰:system/user/assistant 的 role token 必须和模型预训练时一致,否则模型会对奇怪的角色标记感到困惑。

3.3 多轮对话

多轮对话数据的构造相对复杂。关键原则是:将完整的多轮对话历史作为上下文,模型只预测下一个 assistant 回复

示例(3 轮对话):

<|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|>

在训练时,loss 只在最后一个 <|im_start|>assistant 之后的 token 上计算,前面的所有 user/assistant 轮次都作为 context。

多轮对话的训练策略

  • 方案一:每个 turn 单独作为一条样本,上下文截断到固定长度
  • 方案二:将完整多轮对话作为一个样本训练,loss 只在最后 assistant 回合计算
  • 方案三:随机选择一个 turn 作为监督目标,前面的所有内容作为 context(类似 DPO 的做法)

4. SFT 与相关概念的关系

4.1 SFT 与 Instruction Tuning

Instruction Tuning 是一个更宽泛的概念,指的是让模型学会遵循指令的能力。它的训练方式可以是 SFT(用指令-响应对数据),也可以是 RLHF(通过奖励模型训练)。

通常情况下,人们说”做 Instruction Tuning”时,指的就是”做 SFT”。但严格来说:

  • Instruction Tuning 是目标:让模型学会遵循指令
  • SFT 是方法:用监督学习(交叉熵)来实现这个目标

两者常被混用,不必过于纠结概念区分。

4.2 SFT 与 Pretraining 的核心差异

维度PretrainingSFT
数据来源互联网海量文本人工标注/高质量问答
数据量级万亿 tokens数十万~数百万条
监督方式自监督(next token prediction)有监督(指令-响应对)
Loss 覆盖所有 token通常只覆盖 output 部分
训练目标学习语言知识、常识、世界知识学习指令跟随、任务完成
典型过拟合现象较少常见(见第 7 节)

SFT 不是预训练的替代,而是预训练的后续微调阶段。预训练赋予模型语言能力和知识储备,SFT 则将这些能力引导到”按照指令完成任务”的轨道上。


5. 工程流程

5.1 数据预处理流水线

Step 1:数据清洗

原始数据 → 格式校验 → 质量过滤 → 去重 → 长度截断 → 格式转换 → 保存
  • 格式校验:检查必填字段是否存在,role token 是否完整,特殊字符是否转义
  • 质量过滤:移除有毒内容(色情、暴力、政治敏感)、过长/过短样本、重复内容过多的样本
  • 去重:使用 MinHash 或 SimHash 对文本做近似去重,去除完全重复或高度相似的样本对

Step 2:样本构造

  • 格式化:根据选定 chat template,将每条数据转换为模型输入格式
  • 长度分析:统计样本长度分布,确定合理的 max_length(通常 2048~8192,根据模型和硬件决定)
  • 长度截断策略
    • 方案 A:截断 input 保留 output(output 往往更重要)
    • 方案 B:截断中间部分保留首尾(head + tail truncation)
    • 方案 C:直接丢弃超过 max_length 的样本

Step 3:数据分区

建议的划分比例(可根据数据量调整):

  • 训练集:95%
  • 验证集:3%
  • 测试集:2%

注意:验证集和训练集必须来自同分布,不能用 Pretraining 数据混入验证集。

5.2 Chat Template 设计规范

设计一个 SFT 可用的 chat template,需要满足以下要求:

  1. 覆盖所有角色:system、user、assistant 是必须的,有些场景还需要 tool、observation 等
  2. 区分明显:不同 role 的 token 在语义上不能有歧义
  3. 兼容预训练:template 中的 special token 应该在模型词表中存在
  4. 支持多轮:模板必须能处理任意轮次的对话

不推荐的做法

  • 在模板中使用模型词表中不存在的 token
  • 不同数据使用不同的模板
  • 在 assistant 回复中混入其他 role 的 token

5.3 多轮对话的监督信号构造

多轮对话训练时,一个常见问题是如何决定哪些 token 要计算 loss

推荐策略

  • 最后一个回合监督:只对最后一轮 assistant 的回复计算 loss。优点是简单、训练稳定。
  • 所有回合监督(不推荐):对所有 assistant 回合计算 loss。但容易导致模型过度关注中间轮次,忽略最后一轮的质量。
  • 随机回合监督:每个样本随机选择一个回合的 assistant 作为监督目标。折中方案,训练效果通常优于仅最后一轮监督。

注意:user 的回复永远不计算 loss。

5.4 训练超参数设置

以下是 SFT 中相对稳定的超参数配置(以 7B~70B 模型为例):

参数推荐范围说明
学习率1e-5 ~ 5e-5大模型用更小的学习率,LLaMA3 8B 建议 2e-5
Batch Size4~16(per device)配合 gradient accumulation 达到 global batch 32~128
Epoch1~3SFT 数据量有限,过拟合风险高,通常不超过 3 epoch
Warmup3~10% 总步数学习率从 10% 线性上升到峰值
Weight Decay0.1 ~ 0.01大模型通常用 0.1
Max Length2048 / 4096 / 8192根据显存和典型样本长度选择
LR Schedulecosine / constantSFT 训练步数有限,cosine 衰减效果不明显

学习率选择建议

  • 数据量 < 100K 条:学习率 1e-5 ~ 2e-5
  • 数据量 > 100K 条:学习率 2e-5 ~ 5e-5
  • 二次预训练(续训):学习率 5e-6 ~ 1e-5

6. 训练稳定性与常见问题

6.1 训练崩溃与梯度问题

梯度爆炸:SFT 训练的后期,loss 可能突然上升,导致模型崩溃。

  • 解决思路:使用 gradient clipping(通常 1.0),监控梯度的 norm 变化
  • 检查学习率是否过高,SFT 后期建议使用更小的学习率

Loss 爆发(Loss Spike):训练到某一时刻 loss 突然升高。

  • 原因:可能是学习率太高,或某些异常样本导致
  • 解决:降低学习率,使用 early stopping 保存最佳 checkpoint

6.2 过拟合

SFT 数据量相对预训练非常小,模型很容易过拟合。表现为:

  • 在训练集上 loss 持续下降,但验证集 loss 反而上升
  • 模型开始逐字记忆训练数据中的回复

缓解方法

  • 增加数据量和数据多样性
  • 使用 dropout(如果框架支持)
  • 降低 epoch 数,保存验证集 loss 最低的 checkpoint
  • 使用 larger batch size(等价于更多的 step)
  • 添加适当的 weight decay

6.3 模板化回答(回复缺乏多样性)

模型在某些指令上反复给出格式化的回答,例如:

  • “作为一个 AI 模型,我…”
  • “根据我的分析,…”
  • “以下是几个要点:1. … 2. … 3. …”

原因:训练数据中类似回答出现频率过高。

缓解方法

  • 在数据构造阶段,控制模板化回复的比例
  • 增加数据的多样性,让模型学到更多样的表达方式
  • 使用 RLHF/DPO 阶段进一步提升回复多样性

6.4 拒答倾向

模型在面对某些类型的问题时,倾向于说”我不知道”或给出安全但无用的回答。

原因:训练数据中包含大量拒答样本,或 RLHF 阶段过度强调安全性。

缓解方法

  • 在 SFT 数据中减少拒答样本的比例
  • 在 RLHF 阶段调整 reward model 的权重
  • 使用 DPO 训练,增加正负样本的对比

6.5 胡编乱造(Hallucination)

模型在给出事实性陈述时编造假信息。

原因:训练数据中的事实信息不准确,或模型缺乏相关领域的知识。

缓解方法

  • 在 SFT 数据中确保事实性输出的准确性
  • 在预训练阶段增加相关领域的数据
  • 使用 RAG(检索增强)架构在推理阶段引入外部知识

7. 中间评测与样本抽检

SFT 是一个数据驱动的过程,数据质量直接决定模型上限。因此,训练过程中的评测至关重要。

7.1 评测指标设计

指标类型具体指标评测方式
语言能力PPL / token accuracy在验证集上计算
任务能力Pass@K (code) / ROUGE (summarization)任务特定评测
对话能力人工评估 / LLM 评估用 GPT-4 评估模型回复质量
安全性拒答率 / 毒性率自动 + 人工

7.2 中间评测策略

  • 每 N 步评测一次:建议每 500~1000 步在验证集上跑一次完整评测
  • 保存多个 checkpoint:不要只保存最后一个 checkpoint,SFT 容易过拟合,训练过程中会存在一个最优 checkpoint
  • Early Stopping:监控验证集 loss,当连续 3 次评测验证集 loss 上升时,停止训练

7.3 样本抽检

自动化指标只能反映整体趋势,具体问题需要通过人工抽检来发现。

抽检流程:

  1. 从当前 checkpoint 生成的样本中随机抽取 20~50 条
  2. 逐条检查:是否遵循指令、是否事实正确、是否通顺自然
  3. 记录问题类型:格式错误、事实错误、表达不当、创意不足等
  4. 根据问题分布,决定是否需要补充/清洗数据

抽检频率:建议每 epoch 结束进行一次。


8. SFT 数据构造模板

# 数据字段规范
fields:
  - instruction: str        # 任务指令,必填
  - input: str             # 输入内容,可为空字符串
  - output: str            # 期望输出,必填
  - system: str            # 系统提示,可选,默认"你是一个有帮助的助手"
  - category: str          # 任务类别,用于分层采样
  - source: str            # 数据来源,便于追溯
 
# Chat Template(以 Qwen 格式为例)
chat_template: |
  {% for message in messages %}
    {% if loop.first and messages[0]['role'] != 'system' %}
      <|im_start|>system
      你是一个有帮助的助手。<|im_end|>
    {% endif %}
    <|im_start|>{{ message['role'] }}
    {{ message['content'] }}<|im_end|>
  {% endfor %}
  {% if add_generation_prompt %}
    <|im_start|>assistant
  {% endif %}
 
# 数据质量过滤规则
quality_filters:
  - 长度约束:
      instruction_min: 5
      instruction_max: 512
      output_min: 10
      output_max: 4096
      total_max_length: 8192
 
  - 内容过滤:
      exclude_patterns:
        - "xxx"
        - "测试"
      min_instruction_complexity: 0.3
 
  - 去重规则:
      method: simhash
      threshold: 0.9

9. SFT 训练排错清单

训练不收敛

检查项解决方案
学习率是否过低尝试 1e-5 ~ 5e-5
数据格式是否与 chat template 一致检查 special token 是否匹配
数据是否存在大量重复执行去重步骤
Batch size 是否太小增加 per_device_batch_size

Loss 爆炸

检查项解决方案
学习率是否过高降低到 1e-5
是否使用 gradient clipping设置 max_grad_norm = 1.0
是否存在异常样本检查数据中是否有超长/损坏的样本

过拟合

检查项解决方案
Epoch 是否过多减少到 1~2 epoch
数据量是否不足增加数据多样性或数据量
学习率是否过高降低学习率
是否保存了最佳 checkpoint使用验证集 loss 最低的 checkpoint

回复质量差(模板化、拒答、胡编)

检查项解决方案
数据质量是否不足重新清洗数据,确保高质量
数据多样性是否不足增加不同来源的样本
模板化数据是否过多减少格式化回复的比例
拒答样本是否过多减少”我不知道”类样本
事实性错误是否多核实数据中的事实信息

评测指标与实际效果不一致

检查项解决方案
评测集与训练集分布是否一致确保同分布
自动化指标是否与人类偏好一致引入人工抽检
是否存在数据泄露检查评测集是否混入训练集

10. 总结

SFT 是将预训练模型转化为可用对话模型的关键步骤。其核心是:

  1. 数据为王:SFT 的效果上限由数据质量决定。数据格式统一、模板一致、去重清洗、事实准确是基本要求。
  2. Loss Mask 是关键:只对 assistant 回复计算 loss,避免模型学习”复述输入”。
  3. 多轮对话构造:将完整对话历史作为上下文,随机或固定选择监督回合。
  4. 训练稳定性:学习率不宜过高,梯度裁剪必须开启,checkpoint 得多保存。
  5. 持续评测:自动化指标 + 人工抽检双轨并行,及时发现数据问题和模型问题。

SFT 不是终点,而是起点。经过 SFT 的模型,通常还需要经过 RLHF(DPO/PPO)阶段来进一步对齐人类偏好,才能成为真正可用的大语言模型。