Skip to content
阅读进度0%
Training待复核

SFT 监督微调

Supervised Fine-Tuning - 让模型学会遵循指令

我的学习与文章检索 →
学习记录仅保存在当前浏览器,可随时取消标记。

SFT 监督微调 ​

从知识储备到任务执行的关键一步

🎯 核心概念 ​

来源:从“扩充书库”到“教授技能” | RLHF之PPO、DPO详解

LLM训练三阶段LLM 训练流程:预训练 → SFT → RLHF

什么是SFT? ​

定义

SFT(Supervised Fine-Tuning) 是在预训练模型基础上,使用高质量结构化标签数据(指令-输入-输出对)教导模型遵循特定任务行为和输出格式的训练方法。

底层原理:训练模型将输入(Prompt/Instruction)映射到期望输出(Completion),实现行为对齐:让模型从“仅会预测下一个词”的基座模型,转变为“能理解并执行指令”的聊天助手或任务解决者。

领域大模型定制的两大目标 ​

目标说明实现方式
领域适配增强领域语言与任务知识,不能保证事实记忆准确CPT / SFT,动态事实配合 RAG
行为对齐教会模型按照用户特定指令和格式输出SFT(监督微调)

SFT的作用 ​

阶段模型能力训练目标
预训练后知识储备丰富,但不会对话预测下一个 Token
SFT后理解指令,按要求回答生成符合指令的响应
RLHF后输出符合人类偏好最大化奖励信号
一种训练路线:Base → SFT → 偏好优化;Instruct/Chat 是产品命名,不对应强制独立阶段

SFT vs CPT vs RAG ​

策略改变什么适用条件主要验收点
CPT继续优化语言建模分布大量领域语料、基础领域表征不足领域困惑度与下游任务,通用能力回归
SFT提高目标回答的条件概率有稳定任务规范与高质量示范任务成功率、格式正确率、未见问题泛化
RAG为当前请求提供外部证据内容常变、需要引用或权限过滤召回覆盖、引用准确性、答案忠实度

它们可组合使用。SFT 也能学习知识,CPT 也会改变行为;区分的是训练目标与输入数据,而不是互斥的能力。成本由模型规模、token 数、硬件、标注和服务流量决定,不使用无预算口径的美元区间比较。

SFT vs 强化学习 ​

SFT 用 teacher forcing 逐 token 拟合示范;RL 通过奖励与采样优化行为。SFT 可以学习多轮对话和多种正确表达,RL 也会被错误奖励诱导产生幻觉,两者都没有天然真实性保证。InstructGPT

若任务“好答案长什么样”容易示范,先用 SFT;若多种输出难写唯一标准答案、但能可靠比较或验证结果,再评估偏好优化。用 DPO 或 RLHF前,先确认 SFT 基线和独立评估足够稳定。


📊 SFT四种模式 ​

SFT并非单一流程,数据选择与训练目标决定模型最终形态:

模式对比 ​

模式数据来源目标优点缺点
通用SFT开源通用数据集建立基础指令遵循能力广泛能力、多任务领域表现一般
领域SFT领域专用数据适配领域任务需求专业性强可能遗忘通用能力
混合SFT通用+领域混合平衡专业性与通用性CF防御标准实践数据配比需调优
持续SFT增量领域数据适应新任务灵活扩展需要防遗忘策略

A. 通用SFT(指令微调) ​

通用 SFT 通常被称为指令微调(Instruction Tuning),是 LLM 后训练的初始阶段:

  • 目标:建立模型基础指令遵循能力和多任务处理能力
  • 数据:覆盖翻译、摘要、问答等场景的广泛、多样化指令数据集
  • 结果:模型从 Base Model 转化为 Instruct Model 或 Chat Model

B. 领域SFT ​

领域 SFT 是“在特定领域数据集上训练模型,适配领域任务需求”:

  • 数据:领域专业术语、特定格式、复杂任务规则标注(如医疗本体库、药物相互作用规则)
  • 挑战:灾难性遗忘——领域数据通常高度集中、范围狭窄

C. 混合SFT(推荐) ​

标准实践

混合通用样本是一种回放策略。是否混合、混多少应由通用回归集与领域目标共同决定,不能把某个比例作为所有任务的必选项。

核心原理:在领域数据集训练过程中,混合通用指令数据,为通用任务持续提供梯度信号,降低遗忘风险,但不保证所有通用能力保留。

D. 模型起点选择 ​

SFT 前的核心决策——选择 Base Model 还是 Instruct Model 作为起点:

模型类型核心优势适用场景
Base Model灵活性最高,可开发全新专业化对话格式需高度定制化输出格式/任务
Instruct Model已具备对话结构、“助手”人设、多轮上下文理解领域专家聊天机器人,需连贯聊天体验

灾难性遗忘问题 ​

核心挑战

领域SFT可能导致模型"忘记"预训练阶段学到的通用知识,这被称为灾难性遗忘。

解决方案:

方法原理效果
混合数据通过消融确定通用数据占比简单有效
EWC 正则化保护重要参数不变理论扎实
LoRA 微调限制更新参数仍需通用能力回归,不能保证不遗忘
Replay 机制重放历史数据计算成本高

SFT 数据质量要求 ​

关键洞察

LIMA展示了少量精选示范在特定基座和任务上的价值;不能把它解释为固定的“1,000 胜过 100,000”定律。质量、覆盖和难度需要一起看。

维度要求
准确性响应内容正确无误
相关性响应切题,符合指令
多样性覆盖多种任务类型和表达方式
一致性风格、格式保持统一
安全性不含有害、偏见内容

🔧 SFT实现 ​

使用Transformers ​

下面展示单轮 Alpaca 样本的显式 labels 构造,重点是回答监督与 padding。为了让输入与输出拼接边界可核对,示例采用自定义模板;已有聊天基座应使用它自己的 chat template,不能直接换成 Alpaca。

python
from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments
from datasets import load_dataset

model_name = "meta-llama/Llama-2-7b-hf"
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(model_name)
max_length = 2048

def tokenize(sample):
    prompt = (f"### 指令:\n{sample['instruction']}\n\n"
              f"### 输入:\n{sample.get('input', '')}\n\n### 回答:\n")
    prompt_ids = tokenizer(prompt, add_special_tokens=True)["input_ids"]
    answer_ids = tokenizer(sample["output"], add_special_tokens=False)["input_ids"]
    answer_ids += [tokenizer.eos_token_id]
    # 超长样本交给上游拆分或拒绝,避免静默截掉全部回答
    if len(prompt_ids) + len(answer_ids) > max_length:
        raise ValueError("样本超长:请先统计并制定截断策略")
    ids = prompt_ids + answer_ids
    pad = max_length - len(ids)
    return {
        "input_ids": ids + [tokenizer.pad_token_id] * pad,
        "attention_mask": [1] * len(ids) + [0] * pad,
        "labels": [-100] * len(prompt_ids) + answer_ids + [-100] * pad,
    }

raw = load_dataset("json", data_files="train.json", split="train")
data = raw.map(tokenize, remove_columns=raw.column_names)
trainer = Trainer(
    model=model,
    args=TrainingArguments(output_dir="./sft_output", max_steps=10,
                           per_device_train_batch_size=1, learning_rate=2e-5),
    train_dataset=data,
)
# 先检查一条 labels 的非 -100 部分恰为回答与终止符,再启动短跑
trainer.train()

这只是全量训练的教学短跑;7B 模型的完整训练状态通常超出单张消费卡容量,资源受限时按 LoRA改造。需要 PyTorch、Transformers、Datasets 与模型访问权限,未进行 GPU 实测。

使用TRL SFTTrainer ​

对于包含 prompt/completion 的对话数据,使用模型内置模板可减少手写分隔符错误。以下假定 sft_model 为本地聊天基座,JSONL 的两列均为 messages 数组。

python
from trl import SFTTrainer, SFTConfig
from transformers import AutoTokenizer
from datasets import load_dataset

tokenizer = AutoTokenizer.from_pretrained("sft_model")
train = load_dataset("json", data_files="train.jsonl", split="train")
trainer = SFTTrainer(
    model="sft_model",
    processing_class=tokenizer,
    args=SFTConfig(
        output_dir="./sft_output", max_length=2048,
        completion_only_loss=True, packing=False,
        per_device_train_batch_size=1, num_train_epochs=1,
    ),
    train_dataset=train,
)
trainer.train()

按 TRL SFT 文档复核接口;项目应固定依赖版本。先不启用 packing,核对角色、EOS、有效 labels 与截断统计后再比较吞吐。assistant_only_loss=True 是另一种 messages 监督选择,要求模板提供 assistant mask,不能对任意模板盲开。


📝 模板设计 ​

常见模板格式 ​

Alpaca模板 ​

Below is an instruction that describes a task. Write a response that appropriately completes the request.

### Instruction:
{instruction}

### Input:
{input}

### Response:
{output}

ChatML模板 ​

<|im_start|>system
{system_message}<|im_end|>
<|im_start|>user
{user_message}<|im_end|>
<|im_start|>assistant
{assistant_message}<|im_end|>

Llama2模板 ​

<s>[INST] <<SYS>>
{system_message}
<</SYS>>

{user_message} [/INST] {assistant_message} </s>

损失掩码 ​

重要

指令跟随任务通常只监督 completion 或 assistant 消息;全文语言建模也有使用场景。关键是明确选择并验证 mask。padding 必须忽略,不能因为 pad 与 eos 共用 ID 就把真正的 EOS 标签一并屏蔽。

python
def create_labels_with_mask(input_ids, response_start_idx):
    """创建带掩码的标签"""
    labels = input_ids.clone()
    # 将指令部分的标签设为-100(忽略)
    labels[..., :response_start_idx] = -100
    return labels

⚙️ 超参数调优 ​

关键超参数 ​

参数推荐值说明
learning_rate1e-5 ~ 5e-5学习率,太高易过拟合
batch_size根据显存调整有效batch=每卡batch×累积步数×数据并行卡数;packing 时另报有效 token 数
epochs1-3SFT通常不需要太多轮次
warmup_ratio0.03-0.1预热比例
max_length按业务长度分布确定当前 TRL SFTConfig 的长度上限;记录截断率

学习率调度 ​

python
from transformers import get_cosine_schedule_with_warmup

# 余弦退火调度器
scheduler = get_cosine_schedule_with_warmup(
    optimizer,
    num_warmup_steps=100,
    num_training_steps=1000
)

📈 评估方法 ​

自动评估指标 ​

以下函数只接收独立 generate() 产生的 token ID 和对应标签;普通 Trainer 默认返回的是 logits,不能直接 batch_decode。BLEU/ROUGE 适合作为翻译或摘要的辅助指标,不是开放问答正确性的验收指标。

python
from evaluate import load

# 加载评估指标
bleu = load("bleu")
rouge = load("rouge")

def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
    labels = [[tokenizer.pad_token_id if t == -100 else t for t in row] for row in labels]
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
    
    # BLEU分数
    bleu_score = bleu.compute(
        predictions=decoded_preds, 
        references=[[l] for l in decoded_labels]
    )
    
    # ROUGE分数
    rouge_score = rouge.compute(
        predictions=decoded_preds, 
        references=decoded_labels
    )
    
    return {
        "bleu": bleu_score["bleu"],
        "rouge-l": rouge_score["rougeL"]
    }

人工评估维度 ​

维度评估内容
相关性回答是否切题
准确性内容是否正确
流畅性表达是否自然
完整性是否覆盖要点
安全性是否有害内容

排障顺序与发布门槛 ​

  • loss 很低但不回答:先解码有效 labels,看是否只学了输入、padding,或回答全被截断。
  • 复述用户、无法停止:比较训练和推理模板、assistant 起始标记、EOS 与停止配置。
  • 领域集变好但真实问题变差:按来源/用户/时间检查泄漏,检查示范是否只有单一模板。
  • 通用能力退化:比较小学习率、早停与混合数据;用独立回归集决定比例。

一次合格实验要保留未微调基线、按来源分组的留出集、模板和 tokenizer revision;按任务成功率、格式通过率与通用回归选 checkpoint,不能只选最低训练 loss。

🔗 相关阅读 ​

相关文章:

外部资源:

基于 VitePress 构建