1. SFT 是什么?
SFT,全称 Supervised Fine-Tuning ,即监督微调。
在大语言模型训练流程中,SFT 通常位于预训练之后,用于让基础模型学会按照人类指令、对话格式、业务规范或特定任务要求进行输出。
可以简单理解为:
预训练让模型"会语言、懂知识",SFT 让模型"会按要求回答"。
SFT 的输入通常是:
text
指令 / 问题 / 多轮对话 / 上下文
输出是:
text
标准答案 / 人工示范回答 / 结构化结果
模型通过学习大量"输入 → 标准输出"的样本,逐渐掌握目标任务的回答方式。
2. SFT 的核心目标
对于自回归语言模型,SFT 本质上仍然是 next-token prediction,即预测下一个 token。
假设输入为:
x x x
标准答案为:
y=(y1,y2,...,yT) y = (y_1, y_2, \dots, y_T) y=(y1,y2,...,yT)
模型参数为:
θ \theta θ
则 SFT 的优化目标是最大化标准答案在模型下的条件概率:
maxθ∑t=1Tlogπθ(yt∣x,y<t) \max_{\theta} \sum_{t=1}^{T} \log \pi_{\theta}(y_t \mid x, y_{<t}) θmaxt=1∑Tlogπθ(yt∣x,y<t)
等价于最小化负对数似然损失:
LSFT(θ)=−∑t=1Tlogπθ(yt∣x,y<t) \mathcal{L}{\mathrm{SFT}}(\theta)= -\sum{t=1}^{T} \log \pi_{\theta}(y_t \mid x, y_{<t}) LSFT(θ)=−t=1∑Tlogπθ(yt∣x,y<t)
如果数据集中有多个样本:
D={(xi,yi)}i=1N \mathcal{D}= \{(x_i, y_i)\}_{i=1}^{N} D={(xi,yi)}i=1N
整体训练目标为:
LSFT(θ)=−1N∑i=1N∑t=1Tilogπθ(yi,t∣xi,yi,<t) \mathcal{L}{\mathrm{SFT}}(\theta)= -\frac{1}{N} \sum{i=1}^{N} \sum_{t=1}^{T_i} \log \pi_{\theta}(y_{i,t} \mid x_i, y_{i,<t}) LSFT(θ)=−N1i=1∑Nt=1∑Tilogπθ(yi,t∣xi,yi,<t)
其中:
- xix_ixi 表示第 iii 个样本的输入;
- yiy_iyi 表示第 iii 个样本的标准答案;
- yi,ty_{i,t}yi,t 表示第 iii 个答案中的第 ttt 个 token;
- πθ\pi_{\theta}πθ 表示当前模型策略;
- TiT_iTi 表示第 iii 个答案的长度。
3. SFT 与预训练的区别
| 对比项 | 预训练 | SFT |
|---|---|---|
| 数据形式 | 大规模无标注文本 | 指令-答案、对话、结构化样本 |
| 训练目标 | 学习语言建模能力 | 学习任务执行与指令跟随 |
| 数据规模 | 通常非常大 | 相对较小但质量要求高 |
| 关注重点 | 知识、语法、语言规律 | 格式、风格、流程、任务能力 |
| 输出特征 | 自然续写 | 按用户要求回答 |
| 典型阶段 | Foundation Model 训练 | Instruction Model 训练 |
预训练更像是"读大量书",SFT 更像是"看标准答案学怎么答题"。
4. SFT 的典型数据格式
4.1 单轮指令格式
json
{
"instruction": "解释 SFT 和 DPO 的区别",
"input": "",
"output": "SFT 是基于标准答案的监督微调,DPO 是基于偏好对的直接偏好优化。"
}
4.2 多轮对话格式
json
{
"messages": [
{
"role": "system",
"content": "你是一个专业 AI 工程助手。"
},
{
"role": "user",
"content": "请解释 SFT 的训练目标。"
},
{
"role": "assistant",
"content": "SFT 的训练目标是最大化标准答案在模型条件分布下的概率。"
}
]
}
4.3 结构化输出格式
json
{
"messages": [
{
"role": "user",
"content": "从 OCR 结果中抽取左比分、右比分和比赛时间。"
},
{
"role": "assistant",
"content": "{\"left_score\":33,\"right_score\":35,\"game_clock\":\"02:15\"}"
}
]
}
5. SFT 的完整流程
5.1 明确训练目标
在开始 SFT 前,必须明确模型要学什么。
常见目标包括:
| 目标 | 说明 |
|---|---|
| 指令跟随 | 让模型理解用户指令并正确执行 |
| 多轮对话 | 保持上下文一致性 |
| 领域问答 | 适配医疗、法律、工业、机器人、OCR 等领域 |
| 代码生成 | 提升代码生成、补全、解释和调试能力 |
| 结构化输出 | 输出 JSON、SQL、Markdown、XML 等格式 |
| 工具调用 | 学会函数调用、API 调用或 Agent 动作规划 |
| 风格对齐 | 固定回答语气、长度、格式和专业程度 |
| 安全对齐 | 学会拒绝违规请求或给出安全替代方案 |
对于工程型模型,目标应该更加具体,例如:
text
让模型能够根据 OCR 部署需求生成设计文档
让模型能够根据 C++ 编译错误定位问题
让模型能够根据检测结果输出结构化 JSON
让模型能够解释 YOLO、ONNX、MNN、TensorRT 部署流程
5.2 选择基座模型
选择基座模型时需要考虑以下因素:
| 因素 | 建议 |
|---|---|
| 语言能力 | 中文任务优先选择中文能力强的模型 |
| 模型规模 | 7B/8B 适合低成本训练,14B/32B 效果更强 |
| 上下文长度 | 长文档和多轮任务需要长上下文 |
| 推理成本 | 边缘部署需要小模型或量化模型 |
| 是否已有 Instruct 版本 | 通用助手任务优先从 Instruct 模型继续微调 |
| 许可证 | 商业项目必须检查模型 License |
一般建议:
text
通用助手任务:选择 Instruct 模型继续 SFT
领域适配任务:可选择 Base 模型或 Instruct 模型
结构化输出任务:优先选择格式跟随能力强的模型
边缘部署任务:优先选择小模型 + LoRA/QLoRA + 量化
5.3 数据收集
SFT 数据质量通常比数量更重要。高质量数据能够明显改善模型的指令跟随和回答风格。
常见数据来源:
| 数据来源 | 说明 |
|---|---|
| 人工标注数据 | 质量最高,但成本高 |
| 真实业务日志 | 最贴近真实需求 |
| 专家问答 | 适合垂直领域模型 |
| 文档转问答 | 适合技术文档、产品手册、规范文档 |
| 强模型合成 | 可快速扩充数据,但需要清洗 |
| 开源指令数据 | 可作为通用能力补充 |
| 错误案例修正 | 对提升模型鲁棒性非常有价值 |
5.4 数据清洗
数据清洗是 SFT 中最重要的步骤之一。
需要清理的数据包括:
text
空样本
乱码样本
重复样本
答非所问样本
事实错误样本
格式错误样本
过短无信息样本
过长低质量样本
包含无关广告或噪声的样本
包含错误角色标记的样本
推荐检查项:
| 检查项 | 说明 |
|---|---|
| 空值检查 | instruction、input、output 是否为空 |
| 长度检查 | 是否超过 max_seq_len |
| 格式检查 | JSON、Markdown、代码块是否闭合 |
| 角色检查 | 是否只包含 system、user、assistant |
| 重复检查 | 完全重复和近似重复 |
| 安全检查 | 是否包含敏感或违规内容 |
| 质量检查 | 是否存在明显错误或胡编内容 |
5.5 数据去重
重复数据会导致模型过拟合固定句式或模板。
常见去重方式:
text
精确去重
基于文本 hash 的去重
基于 SimHash / MinHash 的近似去重
基于 embedding 相似度的语义去重
推荐流程:
text
先做 exact dedup
再做 near dedup
最后人工抽查高频模板样本
5.6 构造训练模板
SFT 训练模板必须与推理模板一致。
例如 ChatML 风格:
text
<|system|>
你是一个专业 AI 工程助手。
<|user|>
请解释 SFT 的训练过程。
<|assistant|>
SFT 的训练过程包括数据准备、模板构造、loss mask、模型训练、评估和部署。
如果训练时使用一种模板,推理时使用另一种模板,模型可能出现:
text
角色混乱
不按格式回答
重复用户问题
无法正常停止
输出特殊 token
多轮对话上下文异常
核心原则:
训练时怎么拼接 prompt,推理时就应该怎么拼接 prompt。
6. Loss Mask 设计
SFT 中非常关键的一点是:通常只对 assistant 的回答部分计算 loss。
6.1 普通对话样本
对于如下样本:
text
system: 你是一个专业助手。
user: 请解释 SFT。
assistant: SFT 是监督微调,用于让模型学习指令跟随能力。
训练时通常只计算 assistant 部分的 loss:
text
system 部分:mask
user 部分:mask
assistant 部分:计算 loss
6.2 Loss Mask 公式
设完整 token 序列为:
z=(z1,z2,...,zL) z = (z_1, z_2, \dots, z_L) z=(z1,z2,...,zL)
mask 为:
mt∈{0,1} m_t \in \{0, 1\} mt∈{0,1}
其中:
- mt=1m_t = 1mt=1 表示该 token 参与 loss;
- mt=0m_t = 0mt=0 表示该 token 不参与 loss。
则带 mask 的 SFT 损失为:
LSFT(θ)=−∑t=1Lmtlogπθ(zt∣z<t)∑t=1Lmt \mathcal{L}{\mathrm{SFT}}(\theta)=- \frac{ \sum{t=1}^{L} m_t \log \pi_{\theta}(z_t \mid z_{<t}) }{ \sum_{t=1}^{L} m_t } LSFT(θ)=−∑t=1Lmt∑t=1Lmtlogπθ(zt∣z<t)
对于常见的对话 SFT:
mt={1,zt∈assistant response0,zt∈system or user prompt m_t = \begin{cases} 1, & z_t \in \text{assistant response} \\ 0, & z_t \in \text{system or user prompt} \end{cases} mt={1,0,zt∈assistant responsezt∈system or user prompt
6.3 为什么不训练 user 和 system?
如果 user 和 system 部分也参与 loss,模型可能学到:
text
复述用户问题
生成 system prompt
自问自答
角色错乱
输出训练模板
因此工程中通常会将 prompt 部分 label 设置为:
python
-100
在 PyTorch 的 CrossEntropyLoss 中,ignore_index=-100 表示该位置不参与损失计算。
7. EOS Token 的重要性
EOS token 表示回答结束。
如果训练数据中没有正确加入 EOS,或者 EOS 没有参与 loss,模型可能出现:
text
停不下来
重复输出
无限续写
多轮对话边界混乱
推荐做法:
text
每个 assistant 回复末尾加入 EOS
EOS 参与 loss
推理时配置正确的 stop token
训练模板和推理模板保持一致
8. SFT 的训练方式
8.1 全参数微调
全参数微调会更新模型的全部参数。
优点:
text
效果上限高
适合大规模领域迁移
适合有大量高质量数据和充足算力的场景
缺点:
text
显存占用高
训练成本高
容易灾难性遗忘
保存和部署成本高
适合场景:
text
数据量较大
领域差异明显
模型需要深度适配
企业有充足算力
8.2 LoRA 微调
LoRA 通过在模型部分线性层旁边加入低秩矩阵,仅训练新增的低秩参数,冻结原模型参数。
对于原始权重矩阵:
W0∈Rd×k W_0 \in \mathbb{R}^{d \times k} W0∈Rd×k
LoRA 不直接更新 W0W_0W0,而是学习一个低秩增量:
ΔW=BA \Delta W = BA ΔW=BA
其中:
B∈Rd×r,A∈Rr×k,r≪min(d,k) B \in \mathbb{R}^{d \times r}, \quad A \in \mathbb{R}^{r \times k}, \quad r \ll \min(d, k) B∈Rd×r,A∈Rr×k,r≪min(d,k)
最终权重为:
W=W0+ΔW=W0+BA W = W_0 + \Delta W = W_0 + BA W=W0+ΔW=W0+BA
通常还会加入缩放因子:
W=W0+αrBA W = W_0 + \frac{\alpha}{r} BA W=W0+rαBA
其中:
- rrr 是 LoRA rank;
- α\alphaα 是 LoRA scaling 系数;
- AAA 和 BBB 是可训练低秩矩阵;
- W0W_0W0 是冻结的原始模型权重。
LoRA 优点:
text
显存占用低
训练速度快
可插拔
适合多任务 adapter 管理
适合单卡或低成本训练
LoRA 缺点:
text
效果上限可能低于全参数微调
rank 设置不合理会影响效果
不同任务之间 adapter 管理复杂
8.3 QLoRA 微调
QLoRA 是 LoRA 的低显存版本。它通常将基座模型量化到 4-bit,然后冻结量化后的基座模型,只训练 LoRA adapter。
典型特点:
text
4-bit 量化加载基座模型
冻结基座模型参数
只训练 LoRA 参数
显著降低显存占用
适合单卡训练较大模型
常见配置:
yaml
load_in_4bit: true
bnb_4bit_quant_type: nf4
bnb_4bit_compute_dtype: bfloat16
lora_r: 16
lora_alpha: 32
lora_dropout: 0.05
9. 常用超参数建议
9.1 LoRA / QLoRA 常用参数
| 参数 | 推荐范围 |
|---|---|
| learning_rate | 1e-5 ~ 2e-4 |
| epochs | 1 ~ 3 |
| max_seq_length | 2048 / 4096 / 8192 |
| batch_size | 根据显存设置 |
| gradient_accumulation_steps | 4 ~ 32 |
| warmup_ratio | 0.03 ~ 0.1 |
| weight_decay | 0 ~ 0.1 |
| lora_r | 8 / 16 / 32 / 64 |
| lora_alpha | 16 / 32 / 64 / 128 |
| lora_dropout | 0.05 ~ 0.1 |
| precision | bf16 优先,fp16 次之 |
| lr_scheduler | cosine / linear |
| optimizer | AdamW / paged_adamw_8bit |
9.2 7B/8B 模型起步配置
yaml
learning_rate: 2e-4
num_train_epochs: 2
max_seq_length: 4096
per_device_train_batch_size: 1
gradient_accumulation_steps: 8
warmup_ratio: 0.03
lora_r: 16
lora_alpha: 32
lora_dropout: 0.05
bf16: true
gradient_checkpointing: true
9.3 14B 模型 QLoRA 配置
yaml
learning_rate: 1e-4
num_train_epochs: 2
max_seq_length: 4096
per_device_train_batch_size: 1
gradient_accumulation_steps: 16
warmup_ratio: 0.03
load_in_4bit: true
bnb_4bit_quant_type: nf4
lora_r: 32
lora_alpha: 64
lora_dropout: 0.05
bf16: true
gradient_checkpointing: true
9.4 小数据集配置
如果只有几百到几千条高质量数据:
yaml
learning_rate: 5e-5
num_train_epochs: 2
lora_r: 8
lora_alpha: 16
lora_dropout: 0.1
小数据集不建议使用过高学习率或过多 epoch,否则容易过拟合。
10. 数据配比技巧
SFT 数据不应简单混合,而应按照任务目标设计配比。
例如通用 AI 工程助手:
| 数据类型 | 推荐占比 |
|---|---|
| 通用指令跟随 | 20% |
| 技术解释 | 25% |
| 代码生成与调试 | 20% |
| 工程方案设计 | 20% |
| 结构化输出 | 10% |
| 安全边界样本 | 5% |
如果是 OCR / 目标检测 / 机器人 / 多模态部署方向,可以设计为:
| 数据类型 | 推荐占比 |
|---|---|
| OCR 部署与识别问答 | 20% |
| 目标检测与跟踪方案 | 20% |
| 多模态模型部署 | 15% |
| C++ / ONNX / MNN / TensorRT 排错 | 20% |
| 机器人感知与 SLAM | 15% |
| 技术文档生成 | 5% |
| 结构化 JSON 输出 | 5% |
技巧:
text
不要让单一类型数据占比过高
不要让长答案样本压倒短答案样本
不要让 JSON 样本过多影响普通问答
保留一定比例的通用能力样本
保留真实错误修正样本
11. 长度控制技巧
SFT 会明显影响模型输出长度。
如果训练数据中大量答案都很长,模型会倾向于长篇输出。
如果训练数据中大量答案都很短,模型可能回答不充分。
建议按任务区分长度:
| 任务类型 | 推荐答案长度 |
|---|---|
| 简单问答 | 短 |
| 概念解释 | 中等 |
| 工程方案 | 较长 |
| 技术文档 | 长 |
| JSON 抽取 | 极短 |
| 代码生成 | 根据需求变化 |
| 报错排查 | 先结论,后解释 |
可以在 instruction 中加入明确约束:
text
请用三句话回答
请输出 Markdown
请只输出 JSON
请先给结论,再给原因
请给出表格和代码示例
12. 结构化输出技巧
如果目标是让模型稳定输出 JSON、SQL、XML 或 Markdown,需要专门设计格式数据。
12.1 JSON 输出样本
推荐:
json
{
"task": "extract_scoreboard",
"input": "左侧比分 33,右侧比分 35,时间 02:15",
"output": {
"left_score": 33,
"right_score": 35,
"game_clock": "02:15"
}
}
不推荐:
text
左边比分是33,右边比分是35,大概还有2分15秒。
12.2 强制格式约束
训练时应增加以下类型样本:
text
只输出 JSON
不要添加解释
字段缺失时输出 null
无法判断时输出 unknown
数组为空时输出 []
数字字段保持数字类型
12.3 自动校验
结构化任务推荐加入自动校验:
text
JSON parse valid rate
字段完整率
字段类型正确率
枚举值合法率
多余字段比例
13. 防止过拟合
过拟合表现:
text
固定开头,例如"好的,下面我将详细解释"
固定结尾,例如"总之,SFT 是非常重要的"
回答结构高度模板化
训练集问题回答很好,测试集明显变差
输出训练数据中的具体样本内容
通用能力下降
解决方法:
text
减少 epoch
降低 learning rate
降低 LoRA rank
提高 dropout
增强数据多样性
去重
加入验证集 early stopping
混入通用指令数据
14. 防止灾难性遗忘
灾难性遗忘指模型经过 SFT 后,虽然目标任务变强,但原有通用能力下降。
常见原因:
text
领域数据占比过高
训练轮次过多
学习率过大
全参数微调过强
数据风格过于单一
缓解方法:
text
使用 LoRA / QLoRA
降低学习率
减少 epoch
混入 10% ~ 30% 通用指令数据
保留数学、代码、常识、推理样本
使用验证集做回归评估
15. 评估方法
不要只看训练 loss。SFT 的最终目标是提升实际任务表现。
15.1 自动评估指标
| 指标 | 适用场景 |
|---|---|
| Exact Match | 分类、抽取、固定答案 |
| F1 | 实体抽取、信息抽取 |
| JSON Valid Rate | 结构化输出 |
| Field Accuracy | 表单抽取、OCR 结构化 |
| Pass@k | 代码生成 |
| Win Rate | 与旧模型或基座模型对比 |
| Refusal Accuracy | 安全拒答 |
| Hallucination Rate | 事实一致性 |
15.2 人工评估维度
| 维度 | 说明 |
|---|---|
| 正确性 | 内容是否正确 |
| 完整性 | 是否覆盖关键点 |
| 格式符合度 | 是否满足输出格式 |
| 专业性 | 是否符合领域表达 |
| 可执行性 | 工程建议是否能落地 |
| 简洁性 | 是否没有无意义废话 |
| 稳定性 | 多次生成是否一致 |
| 安全性 | 是否遵守安全边界 |
15.3 测试集设计
建议至少准备:
text
常规样本
困难样本
边界样本
反例样本
真实业务样本
模型历史错误样本
16. 常见问题与修复
16.1 模型复述用户问题
原因:
text
user 部分参与了 loss
loss mask 设置错误
训练模板混乱
修复:
text
只对 assistant 部分计算 loss
检查 labels 中 user token 是否为 -100
统一训练和推理 chat template
16.2 模型停不下来
原因:
text
EOS token 未加入训练
EOS token 没有参与 loss
推理 stop token 设置错误
修复:
text
assistant 回复末尾加入 EOS
EOS 参与 loss
推理时配置正确 eos_token_id 和 stop token
16.3 输出格式不稳定
原因:
text
格式数据太少
结构化样本不规范
同一任务混入多种输出风格
修复:
text
增加结构化样本
自动校验 JSON / SQL / XML
统一字段名和字段类型
加入格式错误修正样本
16.4 模型变啰嗦
原因:
text
训练数据中过长回答比例太高
所有样本都要求"详细解释"
修复:
text
加入简洁回答样本
控制答案长度分布
在 instruction 中明确回答长度
16.5 模型领域能力强但通用能力下降
原因:
text
领域数据占比过高
训练轮次过多
学习率过大
修复:
text
混入通用数据
降低学习率
减少 epoch
使用 LoRA / QLoRA
17. SFT 与 CPT、DPO、RLHF 的关系
| 方法 | 数据形式 | 目标 | 适合场景 |
|---|---|---|---|
| CPT | 大量无标注领域文本 | 注入领域知识和语言分布 | 行业语料、代码库、文档 |
| SFT | 指令 + 标准答案 | 学会如何回答 | 指令跟随、格式、任务流程 |
| DPO | chosen / rejected 偏好对 | 学会偏好更好的回答 | 风格优化、偏好对齐 |
| RLHF / PPO | 奖励模型 + 强化学习 | 最大化人类偏好奖励 | 高成本复杂对齐 |
推荐训练顺序:
text
预训练模型
→ CPT,可选,用于领域知识继续预训练
→ SFT,用于学习任务和标准回答方式
→ DPO,用于偏好优化
→ RLHF/PPO,可选,用于更复杂的人类反馈对齐
18. SFT 与 DPO 的区别
SFT 学的是:
标准答案是什么。
DPO 学的是:
两个答案中哪个更好。
SFT 数据形式:
text
输入 x
标准答案 y
DPO 数据形式:
text
输入 x
更优答案 y_w
较差答案 y_l
DPO 的常见损失函数为:
$$
\mathcal{L}_{\mathrm{DPO}}(\theta)
\log
\sigma
\left(
\beta
\left[
\log
\frac{
\pi_{\theta}(y_w \mid x)
}{
\pi_{\mathrm{ref}}(y_w \mid x)
}
\log
\frac{
\pi_{\theta}(y_l \mid x)
}{
\pi_{\mathrm{ref}}(y_l \mid x)
}
\right]
\right)
$$
其中:
- ywy_wyw 表示 preferred / chosen response;
- yly_lyl 表示 rejected response;
- πθ\pi_{\theta}πθ 表示当前训练模型;
- πref\pi_{\mathrm{ref}}πref 表示参考模型;
- β\betaβ 控制偏好优化强度;
- σ(⋅)\sigma(\cdot)σ(⋅) 是 sigmoid 函数。
对比:
| 对比项 | SFT | DPO |
|---|---|---|
| 数据 | 标准答案 | 偏好对 |
| 学习目标 | 模仿标准答案 | 偏向更优答案 |
| 适合阶段 | 对齐初期 | SFT 之后 |
| 优点 | 简单稳定 | 能优化回答偏好 |
| 缺点 | 依赖标准答案质量 | 依赖偏好对质量 |
19. 工程实践路线
阶段一:小规模试训
目标:
text
验证数据格式
验证 chat template
验证 loss mask
验证训练脚本
验证输出是否正常
推荐数据量:
text
500 ~ 2000 条
推荐训练:
text
LoRA / QLoRA
1 ~ 2 epoch
阶段二:领域 SFT
目标:
text
提升领域问答能力
提升工程方案生成能力
提升代码解释和排错能力
提升结构化输出能力
推荐数据量:
text
5K ~ 50K 条
推荐训练:
text
LoRA / QLoRA
2 ~ 3 epoch
阶段三:困难样本增强
收集模型错误样本,例如:
text
答非所问样本
格式错误样本
幻觉样本
拒答错误样本
边界条件失败样本
多轮上下文失败样本
将这些样本修正后加入训练集,进行二次 SFT 或 DPO。
阶段四:偏好优化
当模型已经具备基本能力后,可以构造偏好对进行 DPO。
例如:
text
同一个问题下:
A 答案:结构清晰、准确、可执行
B 答案:啰嗦、遗漏关键步骤
将 A 作为 chosen,将 B 作为 rejected。
阶段五:部署和回归测试
部署前需要检查:
text
推理速度
显存占用
量化后效果
多轮稳定性
结构化输出合法率
长上下文表现
安全拒答能力
历史测试集回归结果
20. 推荐项目目录结构
text
sft_project/
├── data/
│ ├── raw/
│ ├── cleaned/
│ ├── train.jsonl
│ ├── val.jsonl
│ └── test.jsonl
├── configs/
│ ├── qlora_sft.yaml
│ └── lora_sft.yaml
├── scripts/
│ ├── clean_data.py
│ ├── check_format.py
│ ├── build_dataset.py
│ ├── train_sft.py
│ ├── eval_model.py
│ └── merge_lora.py
├── outputs/
│ ├── checkpoints/
│ ├── logs/
│ └── merged_model/
└── README.md
21. SFT 数据检查脚本示例
python
import json
from pathlib import Path
def check_jsonl(path):
bad_lines = []
with open(path, "r", encoding="utf-8") as f:
for idx, line in enumerate(f, 1):
try:
obj = json.loads(line)
except Exception as e:
bad_lines.append((idx, f"json_error: {e}"))
continue
if "messages" not in obj:
bad_lines.append((idx, "missing messages"))
continue
messages = obj["messages"]
if not isinstance(messages, list) or len(messages) == 0:
bad_lines.append((idx, "empty messages"))
continue
roles = [m.get("role") for m in messages]
if "assistant" not in roles:
bad_lines.append((idx, "missing assistant"))
for m in messages:
if m.get("role") not in {"system", "user", "assistant"}:
bad_lines.append((idx, f"invalid role: {m.get('role')}"))
if not str(m.get("content", "")).strip():
bad_lines.append((idx, "empty content"))
return bad_lines
if __name__ == "__main__":
path = Path("train.jsonl")
errors = check_jsonl(path)
print(f"bad samples: {len(errors)}")
for e in errors[:20]:
print(e)
22. 最佳实践总结
SFT 的关键经验可以总结为以下几点:
- 数据质量优先于数量。
- 训练模板必须和推理模板一致。
- 通常只对 assistant 部分计算 loss。
- EOS token 必须正确加入并参与训练。
- 小数据集不要训练太多 epoch。
- LoRA / QLoRA 是大多数工程场景的首选方案。
- 结构化输出任务必须做格式校验。
- 训练集要包含真实失败案例和边界案例。
- 混入一定比例通用数据,避免灾难性遗忘。
- 不要只看 loss,要看任务成功率和人工评估结果。
- 先小规模试训,再扩大数据和训练规模。
- SFT 解决"怎么答",CPT/RAG 解决"知道什么",DPO 解决"哪个答法更好"。
23. 总结
SFT 的本质是:
用高质量示范数据告诉模型:面对某类输入时,应该以什么角色、什么格式、什么逻辑和什么风格输出答案。
它不是简单地"灌知识",而是让模型学会更稳定、更符合业务需求地执行任务。