第 15 章 · 从基础模型到助手

解释基础模型如何变成有用的助手——指令微调、偏好微调和对话模板——而架构本身不做任何改动。

前面讲的一切都是基础模型:纯粹的自动补全。但你实际使用的 LLM 会回答问题、听从指令。中间一定有什么把两者连了起来。

8 分钟
术语表 · 7 个术语
pretraining
在数万亿 token 的通用文本上做下一个 token 预测——模型的知识来自这里。
instruction tuning (SFT)
在精心整理的(指令,回复)对上做有监督微调;教会模型以助手的角色作答。
RLHF
Reinforcement Learning from Human Feedback(基于人类反馈的强化学习)——用一个基于人类偏好比较训练出的奖励模型来训练主模型。
DPO
Direct Preference Optimization(直接偏好优化)——直接在"偏好 vs 拒绝"的回复对上优化,省去单独的奖励模型。
chat template
把多轮对话转换成模型实际读取的单一 token 字符串的格式约定。
special token
一种保留 token(如 <|im_start|>、<|im_end|>),用来标记结构——轮次边界、角色、回合结束——而不是字面文本。
tool calling
对话模板可以在 <tools>…</tools> 系统块中列出可用工具;模型随后输出结构化的 <tool_call><function=NAME><parameter=NAME>VALUE</parameter></function></tool_call> 而不是普通文本,应用执行它并把结果喂回去。在线对话中它就是 "tools" 开关。

从基础模型到助手

这里有一个让几乎所有人都措手不及的事实:训练一章描述的那个模型——一个纯粹的下一个 token 预测器——并不会表现得像 ChatGPT。把 "What is 2 + 2?" 输进一个原始的基础模型,它可能接着写出另一个问题、一串家庭作业题,或者任何在网络上有可能跟在这串文字后面的东西。它不会回答,因为从来没有任何东西教过它:问题后面应该跟一个有用的回复。三个后训练阶段解决了这个问题——而且没有一个会动架构。先把这道鸿沟摆在眼前,左右对照:

同一个提示词,指令微调前后
基础模型(自动补全)接着写下去——并不回答
What is 2 + 2?
经过 SFT(助手)把问题当成要回答的东西
What is 2 + 2?

两条通道在同一个提示词上运行同一套架构。基础模型只是预测网络上最可能跟在这串文字后面的内容—— 而在网络上,一道家庭作业题后面通常跟着更多作业。指令微调改变了什么算“合理”:在见过精心整理的(问题,答案) 对之后,一个问题最可能的续写就是它的答案。

脚本化示意——这里并没有真的加载基础模型(本站附带的 checkpoint 已经过指令微调)。

基础模型 → 助手,三个阶段
预训练基础模型
数据:数万亿 token 的原始网页文本、书籍、代码
学到:语言与世界知识
朴素的下一个 token 交叉熵。几乎所有 GPU 时数都花在这里。
指令微调SFT
数据:约 10K–1M 条精心整理的(指令,回复)对
学到:以助手的角色听从指令
同样的下一个 token 损失——只是数据集换了。
偏好微调RLHF / DPO
数据:人类对候选回复的比较
学到:变得有帮助、无害且诚实
一个新目标——优化偏好/奖励,而不是朴素的交叉熵。

只有第一个阶段需要互联网规模的语料。两个后训练阶段相对都很小——它们与其说在教模型新的事实,不如说在塑造它如何使用预训练已经给它的东西。

指令微调(SFT)

第一步修复最简单。拿到预训练好的模型继续训练——同样的前向传播、同样的下一个 token 交叉熵——但数据换成一小批由人编写或审核过的(指令,回复)对。经过几千到几百万条示例之后,模型学会了这个任务的形状:当眼前的文本是一条用户指令时,最可能的续写是一段以助手口吻给出的有用回复。这就是有监督微调(supervised fine-tuning),简称 SFT。它相对预训练而言微不足道——几乎不增加新知识;它教的是模型如何使用已有的知识。

关于损失的一个细节:用户的 token 不计损失——它们被掩掉了,和训练一章里不计分的最后一个位置一模一样——只有助手回复的 token 参与计分。否则模型也会被训练去模仿用户,而不是学会回答用户。

预训练 vs SFT——数据规模
预训练(下一个 token 预测)≈ 3T tokens
指令微调(SFT)≈ 1M examples

为了在线性刻度下保持可见,SFT 条已被垫高——它在这里的真实宽度约为预训练条的 0.00003%。

≈ 3T tokens vs ≈ 1M examples——原始数量相差约 10^6×。(仅用于示意规模:两者单位不同——一个是普通文本的 token,另一个是精心整理的示例——所以这不是严格的同类比较。)

预训练消化数万亿 token 来构建模型的知识;SFT 只需要几千到几百万条精心整理的示例就能把行为重塑成一个有用的助手。在线性刻度下,SFT 的数据几乎消失不见——这正是重点。切换到对数刻度,才能看清这道相差多个数量级的鸿沟。SFT 在预训练面前微不足道,这正是后训练相对便宜、快速的原因。

示意性的数量级——真实语料差异很大。token(预训练)和示例(SFT)是不同的单位,所以这个比值只用来读规模,不是字面意义上的同类比较。

偏好微调(RLHF / DPO)

SFT 让模型听从指令;偏好微调让它听得更好——更有帮助、更诚实、更不容易给出有害或敷衍的回答。人类比较候选回复("A 比 B 好"),模型则被训练去偏向人们偏好的回复。先快速给个定义:强化学习(RL)= 尝试各种输出、给每个输出打分、让高分行为更可能出现——从试错中学习,而不是从带标注的目标中学习。RLHF 用一个独立的奖励模型来做这件事——那是第二个模型,在人类比较数据上训练,用来预测人们会有多喜欢某个回复,主模型再被优化去在它上面拿高分——并配合强化学习;DPO 则直接优化偏好本身,跳过奖励模型。DPO 不是完整的 RL,也不是下一个 token 交叉熵:它是一个作用在(偏好,拒绝)回复对上的偏好(分类式)损失,单纯地提高模型对偏好回复相对于被拒回复的相对似然。所以这个阶段确实偏离了你一路看到的朴素下一个 token 损失——但就 DPO 而言,它仍是固定数据集上的有监督损失,而不是经典 RL 那种试错式 rollout。

选出人类更偏好的那条回复,连按几次更新,看看“在偏好上训练”到底做了什么。给个尺度感:典型的偏好数据集约有 10K–1M 个比较对——与 SFT 同一个数量级,和预训练的数万亿 token 完全不是一回事。

偏好微调——选出赢家,轻推一下
人类偏好其中一条回复;更新会提高 P(chosen)
提示词:向一个 5 岁小孩解释递归。
回复 A👤 偏好

它就像一个小机器人:要完成一件大事,就复制一个更小的自己去做更小的一块——直到那块小到可以直接做完。

回复 B

递归就是一个函数是递归的。这是个标准概念;你以后总会懂的。

人类偏好:
P(A)——模型给回复 A 的概率偏好50%
P(B)——模型给回复 B 的概率50%
0 次更新
P(偏好) — A
50%
P(拒绝) — B
50%
点击“应用偏好更新”,把被选中的回复(A)往上推、把被拒绝的(B)往下压。

人类偏好回复 A。模型给 A 的概率为 50%,给 B 的为 50%,已应用 0 次偏好更新。方法:RLHF。

更新是如何计算的
提示词 + 回复对A vs B,人类选了 A奖励模型为回复打分模型更新RL 更新

RLHF 先用人类比较数据训练一个独立的奖励模型,再用强化学习去优化主模型在这个奖励上的得分。

示意——这是一幅卡通。真实的偏好数据集有成千上万个回复对,而这里的“轻推”对应偏好损失上的一步梯度(DPO) 或一步由奖励模型引导的 RL 更新(RLHF);这些概率是手工设定的,并非来自模型。

对话模板:多轮对话如何接线

在这一切过程中,模型看到的仍然只是一个扁平的 token 字符串——它天生没有“消息”或“角色”的概念。对话模板就是把多轮对话压平成那个字符串的约定,用special token标记每一轮从哪里开始、是谁在说话,以及一轮在哪里结束。

对话模板
推理(“think” 开关)
<|im_start|>system You are a helpful assistant. Be concise.<|im_end|>
<|im_start|>user What is 2 + 2?<|im_end|>
<|im_start|>assistant 4<|im_end|>
<|im_start|>assistant <think> ▮ 模型在这里推理,然后闭合 </think> 并给出答案

这个“助手”和其他每一章里的下一个 token 预测器是同一个——它只是被喂进了一个用角色标记包裹起来的字符串。 special token <|im_start|> / <|im_end|> 告诉它现在轮到谁说话、一轮在哪里结束。正是指令微调教会了它在结尾的 <|im_start|>assistant 之后接上一段有用的回答,而不是(比如)编出第三个用户问题。真实的 Qwen3.5 模板总会在那个标记之后立刻注入一个 <think> 推理块——在线对话的 think 开关开启时是未闭合的(<think>\n),关闭时是预先闭合的(<think>\n\n</think>\n\n)——也就是上面那个子开关。 (少数更罕见的标记,比如下方的工具调用块,为了可读性仍被省略;应用会替你补全它们。)

值得留意的是,“聊天机器人”的那种感觉,有相当一部分来自这种框定(framing),而非来自训练。基础模型只会自动补全。但只要你给它——哪怕是一个纯基础模型——一个铺设场景的 system prompt,比如"the following is a conversation between a curious user and a knowledgeable, helpful assistant",然后开始用户这一轮,最合理的续写就已经是一段助手口吻的回复了——纯粹因为周围的文字暗示了这一点。2020 年早期的 GPT-3 演示正是这样工作的:GPT-3 是一个基础模型,人们仅凭写一段生动的前言加一个塑造得当的提示,就能从它身上诱导出类似助手的行为;让它变得可靠的有监督微调和偏好微调是后来才加上的。你在上面小组件里看到的 Qwen system 块很简短——只是一句简短的指令,比如"You are a helpful assistant"——所以它只完成了那种框定工作的 一部分,剩下的交给 SFT 和偏好微调。框定能让你拿到第一句合理的回复;而后训练才是让助手每次都准时出现、而不是只在场景恰好铺设妥当时才出现的关键。

当你在这个应用里和模型聊天时,它会替你把对话包装成同样的格式,在末尾追加一个 <|im_start|>assistant,然后让模型生成回答——模型一输出 <|im_end|> 就立刻停止。正是 SFT 教会了模型主动输出这个回合结束 token,而不是编造一条假的下一轮用户消息。

同一个模板还接入了另外两样东西,你会在在线对话里看到它们以开关(pill)的形式出现。think 开关控制推理块:在 assistant 标记之后,模板会注入 <think>\n(思考开启——模型先推理,再在给出答案前闭合 </think>),或一个预先闭合的 <think>\n\n</think>\n\n(思考关闭)。tools 开关则添加一个 <tools>…</tools> 系统块,描述模型可以调用的函数;模型随后输出的不是普通文本,而是一个结构化调用—— <tool_call><function=get_weather><parameter=city>Paris</parameter></function></tool_call> —— 应用执行它,再把结果作为新的一轮喂回去。两者都纯粹是格式约定:网络没有任何改动;是 SFT 教会了模型遵守它们。

它仍然是同一个模型

要抓牢的一点是:这一切都没有改动网络。同样的 24 层,同样的注意力和 MLP 块,同样的前向传播为下一个 token 产出 logits。后训练只是轻推了权重,让这个函数的输出长成我们想要的样子,再给输入裹上一层对话格式。你对话的那个“助手”,就是其他每一章里的那个基础模型——披着一件对话模板,权重被温和地引向“乐于助人”。

工程要点
  • 基础模型只会接着写下去;是指令微调让它以助手的角色回应指令。
  • 后训练(先 SFT,再偏好微调)复用同一个模型,损失函数也大体相同——它塑造的是行为,并不增加新架构。
  • 一段聊天对话其实就是一个格式化的字符串:special token 标记轮到谁说话;模型在结尾的 assistant 标记之后开始生成,输出回合结束 token 即停止。
动手练习

把对话模板小组件切换到“模型实际看到的原始文本”。模型开始生成之前紧挨着的是哪个 special token?又是哪个 token 告诉它该停下?

随堂测验
1. 如果你向一个基础(仅预训练的)模型输入一个问题,它会做什么?
2. 指令微调(SFT)与预训练的区别是什么?
3. 在对话模板中,<|im_start|> 和 <|im_end|> 这类 special token 的作用是什么?