第二节:SFT基础回顾——监督微调的核心原理
聊RLHF之前,咱们得先把SFT这块地基打牢。说实话,我见过不少同学一上来就冲RLHF,结果连SFT的loss怎么算的都说不清楚——这就像没学会走路就想跑马拉松。
监督微调(Supervised Fine-Tuning,SFT),说白了就是让预训练模型学会“说人话”。预训练阶段模型学的是语言规律,但不知道该怎么回答用户的问题。SFT就是给它喂一堆高质量的“问答对”,让它学会指令遵循。
2.1 指令微调的数据构建
数据是SFT的灵魂。我踩过最大的坑,就是以为数据越多越好。结果模型学了一堆垃圾,反而变笨了。
指令微调的数据格式,通常长这样:
{
"instruction": "请解释什么是反向传播?",
"input": "",
"output": "反向传播是一种计算梯度的算法..."
}
有些数据集还会加一个system prompt字段,用来设定角色。比如:
{
"system": "你是一个资深AI算法工程师",
"instruction": "解释一下Transformer的注意力机制",
"output": "注意力机制的核心是Query、Key、Value..."
}
数据构建的几个关键点:
- 多样性:指令类型要覆盖问答、写作、翻译、代码生成等。我有个项目只用了单一类型的数据,结果模型在其他任务上直接崩了。
- 质量优先:宁缺毋滥。一条高质量数据胜过一百条垃圾数据。我曾经用GPT-4生成了一批数据,但没做人工审核,结果模型学会了胡编乱造。
- 难度递进:先简单后复杂。就像教小孩,先教1+1,再教微积分。
2.2 损失函数与训练流程
SFT用的损失函数,其实就是标准的交叉熵损失(Cross-Entropy Loss)。但这里有个细节——我们只计算输出部分的loss,不计算输入部分的。
为什么?你想想看,输入是用户给的指令,模型不需要去预测指令本身。它只需要学会怎么生成正确的回答。
具体来说,对于每个样本,我们只对output部分的token计算loss:
import torch.nn.functional as F
def compute_sft_loss(logits, labels, attention_mask):
# logits: [batch, seq_len, vocab_size]
# labels: [batch, seq_len]
# attention_mask: [batch, seq_len]
shift_logits = logits[..., :-1, :].contiguous()
shift_labels = labels[..., 1:].contiguous()
shift_mask = attention_mask[..., 1:].contiguous()
loss = F.cross_entropy(
shift_logits.view(-1, shift_logits.size(-1)),
shift_labels.view(-1),
reduction='none'
)
# 只保留output部分的loss
loss = loss * shift_mask.view(-1)
return loss.sum() / shift_mask.sum()
这段代码里,我用了shift操作,把logits和labels对齐。这是做自回归语言模型的标准做法。
2.3 训练流程详解
SFT的训练流程,其实就三步:
- 数据准备:把指令-回答对拼接成模型能理解的格式,加上特殊token(如
<|im_start|>、<|im_end|>)。 - 前向传播:模型根据输入生成logits,计算loss。
- 反向传播:更新模型参数。
听起来简单吧?但实际做的时候,有几个坑要注意:
- 学习率:SFT的学习率通常比预训练小很多,一般在1e-5到5e-5之间。我习惯用cosine衰减,效果比较稳定。
- Batch size:能大就大。但受限于显存,我一般用梯度累积来模拟大batch。
- Epoch数:2-3个epoch就够了。多了容易过拟合,模型会死记硬背训练数据。
2.4 SFT的核心逻辑图
下面这张图,是我自己总结的SFT核心流程。每次做项目前,我都会看一眼,确保没漏掉什么:
这张图里,我特意把“只计算output部分的loss”标红了。这是SFT和预训练最大的区别,也是新手最容易忽略的地方。
2.5 实战中的一些经验
最后,分享几个我在实际项目中积累的经验:
- 数据清洗比模型调参更重要:我花在数据清洗上的时间,通常是模型训练时间的3倍。一条有问题的数据,可能让模型学歪。
- 先小规模验证:别一上来就全量数据训练。我会先用1000条数据跑一个epoch,看看loss曲线是否正常,生成效果是否合理。
- 混合数据策略:如果任务类型多样,我会按比例混合数据。比如60%的通用问答,20%的代码生成,20%的翻译。这样模型不会偏科。
嗯,SFT的基础就这些。说白了,它就是让模型学会“模仿”高质量的回答。但光靠模仿还不够,RLHF才是让模型真正“理解”用户意图的关键。不过那是后面章节的内容了,咱们先把SFT吃透。