第 34 章 SFT:从续写到遵循指令
34.1 续写得通,不一定答得对路
Section titled “34.1 续写得通,不一定答得对路”“法国的首都是”留出了一个续写位置。“请说出法国首都,简短回答”则提出了要求。模型可能在第二句话后接着写另一条指令、一段对话引文,或者一篇旅行介绍。文字通顺,并不能证明它会按要求作答。
第 22 章区分了训练目标与模型架构。这里仍然使用因果解码器,仍然预测下一个 Token。我们改变的是训练材料:给出一条请求,再给出希望助手作出的回答。用这些示范继续训练,就是监督微调(supervised fine-tuning,SFT)。“监督”指训练记录里有期望输出,不是说每次参数更新都有人在旁边盯着。
这件事有两层验收。首先,训练记录必须在正确的对话前缀下,对真正想教的回答 Token 计算损失。其次,适配后的模型要能应对示范之外的请求,并保留原先需要的行为。数据格式正确,只能说明第一层做对了。KA-1 已经给出一个具体区别:英文输出 750. 可以正确表达现行限额,但用同一句话回答历史限额,就错了。
我们先把一条记录算清楚。第 18 章讲过错开一位的预测目标,第 6 章讲过负对数似然。选做 CPU 实践时会用到已有的 Python 入门。正文不要求 GPU 或模型账号。
34.2 对话最终要排成一串 Token
Section titled “34.2 对话最终要排成一串 Token”例 34.1:展开一个简短回答。 示范只有三条消息:系统消息 brief,用户消息 France?,助手消息 Paris .。前两条分别简写“简短回答”和“法国首都是哪里”。这些写法及下表的分词规则都是作者给定的教学设定,与 MG-2 或真实模型的聊天格式无关。
角色(role)标明消息由谁提供。对话模板(chat template)规定怎样把消息转换成 Token 序列。本例先写一个起始标记,再依次写每条消息的角色标记、内容和回合结束标记。标记是约定了用途的 Token,并不是独立执行权限检查的程序。
表 34.1:完整的教学词表。 数组顺序决定 ID。ID 是标签,数值大小没有语义。
| Token | ID | 用途 |
|---|---|---|
<bos> | 0 | 序列开头,只写一次 |
<system> | 1 | 系统消息开始 |
brief | 2 | 系统消息内容 |
<eot> | 3 | 一条消息结束 |
<user> | 4 | 用户消息开始 |
France? | 5 | 用户消息内容 |
<assistant> | 6 | 回答开始 |
Paris | 7 | 回答的第一个 Token |
. | 8 | 回答的第二个 Token |
<pad> | 9 | 本例未使用的填充标签 |
应用模板后得到 11 个 Token。下面的空格只是为了把它们分开显示,不额外占用 ID。
<bos> <system> brief <eot> <user> France? <eot> <assistant> Paris . <eot>0 1 2 3 4 5 3 6 7 8 3让这个教学模型开始回答时,我们提供的前缀一直到 <assistant> 为止。训练记录里还放着正确答案,这样预测后面的 Token 时,就可以用前面已经给出的正确答案 Token 作条件。这与此前预测下一个 Token 时采用的教师强制方式相同。因果注意力掩码仍会阻止模型在预测 Paris 时提前读取这个目标或后面的句号。
真实模板支持的角色、分隔符和结束方式各不相同。有的助手前缀包含多个 Token,有的格式不需要单独的助手前缀。应当把模板、分词器和学过这套格式的检查点配套使用。官方模板文档还说明了重复添加特殊 Token 的问题。本例给出的是一套完整的局部规则,不能把它直接套在别的模型上。
34.3 哪一次预测要为错误付出代价
Section titled “34.3 哪一次预测要为错误付出代价”模型读到某个位置,用此前的前缀预测下一个 Token。这里选择只对助手内容及其结束标记 <eot> 计分,不要求模型通过这条练习学习生成系统消息和用户消息。损失掩码(loss mask)是一串 0 和 1,用来选择哪些预测计入损失。它与注意力掩码不同:某个提示 Token 不计分,仍然可以为回答提供信息。
表 34.2:例 34.1 的逐位置对照。 位置从 0 开始。目标是序列中右边一位的 Token;横线表示没有目标。
| 输入位置 | 输入 Token / ID | 下一个目标 / ID | 损失掩码 | 原因 |
|---|---|---|---|---|
| 0 | <bos> / 0 | <system> / 1 | 0 | 角色标记 |
| 1 | <system> / 1 | brief / 2 | 0 | 系统消息内容 |
| 2 | brief / 2 | <eot> / 3 | 0 | 系统消息结束 |
| 3 | <eot> / 3 | <user> / 4 | 0 | 角色标记 |
| 4 | <user> / 4 | France? / 5 | 0 | 用户消息内容 |
| 5 | France? / 5 | <eot> / 3 | 0 | 用户消息结束 |
| 6 | <eot> / 3 | <assistant> / 6 | 0 | 助手开头由输入提供 |
| 7 | <assistant> / 6 | Paris / 7 | 1 | 学习回答的第一个 Token |
| 8 | Paris / 7 | . / 8 | 1 | 学习继续回答 |
| 9 | . / 8 | <eot> / 3 | 1 | 学习结束当前回合 |
| 10 | <eot> / 3 | — | — | 没有后续 Token |
仔细看位置 7。输入虽然是角色标记,预测的却是回答的第一个 Token。如果按输入 Token 属于哪个角色来决定是否计分,就会漏掉这一项。掩码应当跟着被预测的目标走。十次预测对应的掩码是 0 0 0 0 0 0 0 1 1 1。
例 34.2:计算回答损失。 依次给三个正确目标指定概率 0.5、0.25、0.5。它们各自以不同前缀为条件,不是同一个分布里的三个备选项。分别取负自然对数,得到约 0.693147、1.386294、0.693147 nat。把代价相加,再除以计分的三次预测:
分母是 3,不是存储的 11 个 Token,也不是可作出的 10 次预测。句号这一项的代价是另外任一项的两倍。如果把它的指定概率提高到 0.5,平均损失就降到 0.693147。这说明了给定概率下的算术关系,并不是一次真实训练带来的改善。
把记录一般地写成 。用 表示读到位置 后作出的预测是否计分;用 表示参数为 时,模型给正确下一个 Token 的概率。所选目标的平均损失为:
本例 ,只有 为 1,代入后就是例 34.2。如果一条记录没有任何计分目标,分母就是零,应当拒收,或者明确排除后再求平均。把它的损失记成零,会把“没有训练信号”说成“预测完全正确”。合并一个批次时,要先分别累加损失和计分 Token 数,再相除。若先算各条记录的平均损失,再对记录求平均,就变成了每条记录权重相同的另一种目标,必须明确说明。
- 文本片段前缀:… France? <eot> <assistant>
- 处理步骤输入位置 7 → 目标 Paris;m₇ = 1
- 处理步骤−ln(1/2) = 0.693147 nat
助手开头由输入提供,但在它之后预测的回答要计分。
- 处理步骤Paris、句号、结束标记:3 个计分目标
- 处理步骤ln(16) / 3 = 0.924196
- 模型保存的数值共享参数可以改变;提示不计分不等于参数被冻结
选择损失项与选择可训练参数,是两件不同的事。
图 34.1 按这套损失掩码,把前缀、第一个计分目标和共享参数连在一起。损失位为零,不会冻结处理那个位置时使用的参数。回答损失仍可能沿着用到 France? 的计算向后传播。优化器究竟允许改哪些参数,是另一个选择。损失掩码也不意味着可以读取未来的答案 Token;因果掩码仍然有效。
34.4 记录看似合格,也可能教错任务
Section titled “34.4 记录看似合格,也可能教错任务”现在把模型运行时前缀里的 <assistant> 去掉。末尾就变成用户消息的 <eot>,不再是训练时预测 Paris 所依据的助手开头。模型面对的是另一个条件下的分布。在正确模板上多训练,并不能修好运行时的序列化错配。首先应当核对两条路径实际产生的 ID。
多轮对话要逐段检查助手回答及其结束标记。“从第一个助手标记开始全部计分”还会把后续用户消息算进去。增加两个填充位置,也不会增加两个回答目标:填充对应的损失位仍为零,而且不应作为有用上下文。截断长记录时,可能把回答或结束标记切掉;默默接受残片,就改变了教学内容。把多条对话拼接到一起,也需要明确边界与注意力规则。
本节的小检查程序先把结构化消息展开,再按规则核对序列和错开一位的掩码。固定输入放在 data/part-vi/sft-v1.json,实现放在 code/part-vi/core.py。结果在下面直接可读。
表 34.3:按教学规则检查格式。 这些确定性结果不是模型质量分数。
| 记录 | 结果 | 原因 |
|---|---|---|
| 完整的三条消息 | 接受 | 11 个 Token,10 次预测,3 个计分目标 |
把用户角色改成 customer | 拒绝 | 不支持这种角色序列 |
| 助手内容为空 | 拒绝 | 必须提供示范回答 |
删除末尾 <eot> | 拒绝 | 序列不完整 |
| 掩码整体向右错一位 | 拒绝 | 漏掉第一个回答目标 |
| 内容中插入控制标记 | 拒绝 | 内容冒充了模板边界 |
真实分词器可能把形似标记的普通文字编码成另一种形式。本练习选择拒收,没有实现转义协议。这个范围很窄的检查器无法认证生产环境的格式。它真正检查的是:记录、模板、分词器、目标偏移与掩码是否一致。
34.5 格式正确之后,还要检查示范本身
Section titled “34.5 格式正确之后,还要检查示范本身”假设住宿例题提供的是新版 750 元限额,回答却写“现行限额为 600 元”。文件可以通过前面所有格式检查,训练却会奖励过时答案。又如只拿到订单状态,就回答“已取消”,这教给模型的是没有依据的操作声明,助手角色和结束标记再正确也无济于事。
因此要用示范附带的证据核对内容,不能只看语气是否讨喜。把现行和历史问题配对,避免“永远答 750”成为捷径;纳入信息缺失、尚未批准、中英文请求,以及对回答长短的不同要求。相互冲突的标签需要处理,数据来源和使用权也要留下记录。SFT 同样可以用解题过程作示范,但过程再长,答案错了也不会自动成为好样本。
参数更新可能改善新行为,同时损害旧行为。这里的遗忘(forgetting)指适配后测得的既有能力下降,不意味着旧事实都被抹掉了。数据过于集中,可能反复把共享参数推向某一种格式或领域。在选择训练配置之前,应固定一套保留能力的评测,例如法国首都、红蓝钥匙配对线索、新旧限额和审批边界,并分类型、分语言查看结果。减少更新次数、调整数据配比或缩小更新范围,都只是需要检验的办法,不能保证不会退化。
InstructGPT 展示了先学示范、再进行偏好训练的做法,也讨论了评测与取舍。Ouyang 等人的论文提醒我们直接检查行为,不能只凭训练损失选择模型。
SFT 说明用什么示范、按什么目标训练;全量更新和 LoRA 说明怎样改变参数。因此一次训练可以是“用 LoRA 做 SFT”,也可以是“全量更新参数做 SFT”。第 39 章会算清这个区别。在此之前,还得回答一个问题:两个看上去都有道理的回答,究竟该更偏好哪一个?
34.6 本章小结
Section titled “34.6 本章小结”因果模型可以在继续预测下一个 Token 的同时,学习遵循指令。模板提供对话结构,错开一位的损失掩码选择回答目标。第一个回答 Token、结束标记和计分分母都要逐项核对。格式、示范质量与旧能力保留是三件不同的事。SFT 既没有规定可训练参数的数量,也没有保证回答真实。
34.7 习题
Section titled “34.7 习题”- 保持例 34.2 前两个概率不变,把结束标记的概率从
0.5提高到1。计算平均损失。这能说明首都答案变得更容易生成了吗? - 第二条记录只有一个计分目标,代价为
1.386294nat。按计分 Token 求平均,将它与例 34.2 合并,再与“先按记录求平均”的结果比较。 - 程序只对输入 Token 属于助手内容的位置计分。表 34.2 中哪次预测会漏掉?这样会不会阻止模型读取用户问题?
- 训练集里只有回答 750 的现行限额问题。设计两个测试,区分模型是在使用版本证据,还是只会重复 750,并写出期望答案。
- 一项训练冻结基座矩阵,用期望回答训练低秩因子。这是 SFT、LoRA、两者都是,还是两者都不是?需要什么证据才能认定双语助手得到改善?
34.8 参考解答
Section titled “34.8 参考解答”- 结束标记的代价变成零。平均损失为 。改变的只有结束概率,
Paris的概率仍为0.5。总损失下降不代表每一项都改善。 - 先把 与 相加,再除以 ,得到 。按记录求平均则为 ,相当于让只有一个目标的记录与有三个目标的记录权重相同。
- 位置 7 后预测
Paris的这一项会消失,因为该位置的输入是角色标记。句号和结束标记可能仍会计分,相当于训练了怎样收尾,却漏掉回答的第一个内容目标。选择损失项并不会把用户 Token 从上下文中删除。 - 先给出旧限额 600、现行限额 750,询问历史限额,要求答 600;再构造一个明确的新版本,现行限额 800、旧限额 750,要求答 800。两种语言都要保留清楚的证据与问题范围。这些对照仍不能替代新文档族上的评测。
- 两者都是:SFT 指示范与目标,LoRA 指训练哪些因子。应按原契约比较适配前后,包括中文、历史范围、未知信息、审批和保留能力。训练损失下降或适配器文件很小,都不能证明这些项目得到改善。
34.9 参考文献
Section titled “34.9 参考文献”- Ouyang 等,Training language models to follow instructions with human feedback,2022,第 3.1、3.5、5 节。支持 InstructGPT 中示范训练、后续偏好阶段及其评测和限制的说明。
- Hugging Face,Chat templates,Transformers v5.17.0。解释序列化、生成前缀与重复添加特殊 Token 的问题;核对日期为 2026-09-13,本章无外部依赖的教学实现不调用该框架。