news 2026/10/26 1:55:54

指令微调 SFT:从“会说话“到“会干活“

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
指令微调 SFT:从“会说话“到“会干活“

指令微调 SFT:从"会说话"到"会干活"

你给 Base 模型一段话:“请把下面这句话翻译成英文:今天天气不错。”

它可能会接着写:“请把下面这句话翻译成英文:今天天气不错。请把下面这句话翻译成英文:今天天气不错……”

你当场沉默。它不是不会翻译,它是没搞懂你现在是在提问,不是在给它一段要续写的文本。


本文的关系图:

Base 模型:会续写,不会听指令 ↓ 准备数据:指令 + 回答(带角色模板) 一条条 (prompt, response) 样本 ↓ 前向 → 只对回答算 loss → 反向 学会:看到「问题」就输出「回答」,而不是继续编问题 ↓ Instruct / Chat 模型:会听话,但知识还是原来那些

一、Base 模型缺的不是知识,是"上班经验"

先把一个常见的误会掰正:Base 模型不是笨,它只是没上过班。

它读过海量文本,知道语法、事实、代码、翻译,甚至知道很多冷门知识。但它学到的唯一技能是"接着往下写"。你给它一个问句,它最自然的反应不是回答,而是把"问句后面通常会跟什么"继续写出来。

所以在它眼里:

请介绍一下杭州

不是"一个问题",而是"一段文章的开头"。它可能接着写:

请介绍一下杭州,并说明它有哪些著名景点、适合几月份去、人均消费多少……

它以为自己在帮你完善提纲,其实你只是想让它说两句话。

SFT(Supervised Fine-Tuning,监督微调)干的事,就是给这个读完了整座图书馆的人做岗前培训。不是重新上学,是教他:

  • 客户问什么,你先回答问题,别接着替客户提问;
  • 先给结论,再给理由;
  • 不知道就说不知道,别硬编;
  • 回答完就停,别没完没了。

知识还是那些知识,变的是"什么时候开口、怎么开口"。

顺便说一句:严格意义上的"指令微调"(instruction tuning)是 SFT 最常见的一种用法;日常语境里两者基本混着叫,本文也不抠这个字眼。


二、SFT 到底在训什么

SFT 的数据长这样:

指令:请把下面这句话翻译成英文:今天天气不错。 回答:The weather is nice today.

一条一条,都是"输入 + 标准答案"。训练目标和预训练一模一样——还是猜下一个 Token。区别在于:

只对"回答"那部分算 loss,指令部分不参与。

这里有个容易望文生义的地方:prompt 当然要喂给模型,不然它根本不知道在回答什么。mask 掉的只是"对 prompt 的预测误差",不是 prompt 本身。它看得见问题,只是不用为"复述问题"负责。

这就是 SFT 最核心、也最容易被写错的一个细节。

为什么要这样?因为你要教的是:

P(回答 | 指令) 要高 P(继续编问题 | 指令) 要低

如果你把指令也拿来算 loss,模型就会花很多力气去学"怎么把问题复述一遍"——那不是你要的。你要的是一个客服,不是一个复读机。

所以 SFT 的本质不是"往脑子里灌知识",而是把条件概率的形状掰过来:同样一个开头,以前它倾向于继续提问,现在它倾向于给出答案。

用一张表对比一下:

维度预训练SFT
数据无标注文本指令 + 回答对
数据量几万亿 Token几万到几百万条
目标猜下一个 Token同样,但只算回答部分
主要学到语言、知识、推理种子格式、任务、语气、边界
成本极高,一次低几个数量级
产出Base 模型Instruct / Chat 模型

一句话:预训练给它"能力",SFT 教它"把能力用出来"。


三、一条训练样本长什么样

真实训练时,一条样本不是一个字符串,而是一串带角色的 Token:

<|system|> 你是助手。 <|user|> 中国的首都是哪里? <|assistant|> 北京。 <|end|>

四个特殊标记,各干各的活:

  • <|system|>:设定人设和约束,比如"你是简洁的助手"“只回答与客服相关的问题”;
  • <|user|>:用户的指令;
  • <|assistant|>:轮到模型说话了,这个头本身不参与学习,它只是一个发令枪;
  • <|end|>:回答结束。没有它,模型不知道什么时候该停,可能一直说到天荒地老。

下面这段代码把一条样本拼出来,并标出哪些 Token 参与 loss:

importmathfromcollectionsimportdefaultdict SYSTEM="你是助手。"SAMPLES=[("中国的首都是哪里?","北京。"),("日本的首都是哪里?","东京。"),("法国的首都是哪里?","巴黎。"),("德国的首都是哪里?","柏林。"),("1 加 1 等于几?","2。"),("天空为什么是蓝色的?","因为瑞利散射。"),]defbuild(system,user,answer):tokens=["<|system|>"]+list(system)tokens+=["<|user|>"]+list(user)tokens.append("<|assistant|>")start=len(tokens)# 从这里开始才是要学的部分tokens+=list(answer)+["<|end|>"]returntokens,start ALL=[build(SYSTEM,u,a)foru,ainSAMPLES]seq,resp_start=ALL[0]print("① 一条训练样本")print(" 不学习:"+" ".join(seq[:resp_start]))print(" 要学习:"+" ".join(seq[resp_start:]))print(f" prompt{resp_start}个 Token,回答{len(seq)-resp_start}个 Token")N,K,BOS=2,0.01,"<|bos|>"V=len({tfors,_inALLfortins}|{BOS})defpairs(tokens,n):p=[BOS]*(n-1)+tokensreturn[(tuple(p[i-n+1:i]),p[i])foriinrange(n-1,len(p))]deftrain(sequences,n):m=defaultdict(lambda:defaultdict(int))fors,_insequences:forctx,nxtinpairs(s,n):m[ctx][nxt]+=1returnmdefnll(m,ctx,tok):c=m.get(ctx,{})return-math.log((c.get(tok,0)+K)/(sum(c.values())+K*V))m=train(ALL,N)losses=[nll(m,ctx,tok)forctx,tokinpairs(seq,N)]p_loss=sum(losses[:resp_start])r_loss=sum(losses[resp_start:])p_n,r_n=resp_start,len(seq)-resp_start total=p_loss+r_lossprint("② loss 被谁贡献")print(f" prompt:{p_n}个 Token,loss{p_loss:.2f},占{p_loss/total:.0%}")print(f" 回答:{r_n}个 Token,loss{r_loss:.2f},占{r_loss/total:.0%}")print(f" prompt 平均 loss:{p_loss/p_n:.2f}")print(f" 回答平均 loss:{r_loss/r_n:.2f}")print(f" 不 mask 平均 loss:{total/(p_n+r_n):.2f}")print(f" mask 后平均 loss:{r_loss/r_n:.2f}")

真实运行结果(本机实测,完整脚本见code/09-sft-demo.py):

① 一条训练样本 不学习:<|system|> 你 是 助 手 。 <|user|> 中 国 的 首 都 是 哪 里 ? <|assistant|> 要学习:北 京 。 <|end|> prompt 17 个 Token,回答 4 个 Token ② loss 被谁贡献 prompt:17 个 Token,loss 6.02,占 65% 回答: 4 个 Token,loss 3.18,占 35% prompt 平均 loss:0.35 回答平均 loss:0.80 不 mask 平均 loss:0.44 mask 后平均 loss:0.80

这里的 n-gram 只是把 mask 的账算清楚的小玩具;真实 SFT 用的是 Transformer,但 loss 的定义和 mask 的逻辑一模一样。


四、为什么必须 mask:65% 的 loss 在教它复述问题

看 ② 那段输出,这是整篇最值得盯住的数字。

在这条样本里,prompt 有 17 个 Token,回答只有 4 个。但如果不 mask,prompt 贡献了 65% 的 loss。真实对话里,system prompt 和多轮历史更长,这个比例只会更夸张。

注意,这不是因为 prompt 难——prompt 的平均 loss 只有 0.35,回答是 0.80。prompt 的单个 Token 更容易,但它人多。17 个人一起交作业,总分自然被它拉走。

后果是什么?

模型会把大部分梯度花在"怎么复述问题"上,而不是"怎么回答问题"。你训了半天,它学会的是:看到"中国的首都是哪里?"就把它再写一遍;看到"请翻译这句话"就把它再抄一遍。它成了一个非常擅长重复你说话的模型。

mask 掉之后,梯度只从回答那 4 个 Token 来。loss 数字看起来变高了(0.44 → 0.80),这不是变差,是分母换成了真正要学的部分。就像考试只算大题分,平均分当然会变,但那才是你关心的。

实现上,常见做法是把 prompt 部分的 label 设成-100(PyTorch 的ignore_index),或者维护一个 0/1 的 loss mask。另外两个坑:

  • padding 不能被算进 loss,否则模型会努力学习"怎么生成空白";
  • packing 时要注意 attention mask,别让后一条样本偷看前一条样本的内容。

很多 SFT 翻车,不是模型笨,是 mask 写错了。


五、数据从哪来:SFT 的胜负手在数据,不在模型

SFT 的模型结构通常不变,超参也不复杂。真正决定效果的是数据。

来源优点风险
人工编写质量高、意图清楚、覆盖可控贵、慢、规模有限
强模型合成便宜、量大、风格统一同质化、错误被放大、许可证问题
真实用户日志分布最真实、覆盖长尾隐私 / PII、噪声大、质量参差
公开指令数据集起步快、可对比风格杂、领域不匹配、可能被污染

人工数据是天花板,但请人写一万条高质量指令-回答,成本不低。合成数据是现在的主流:用强模型批量生成,再用规则或另一个模型筛。它能把量做起来,但有两个副作用:风格越来越像同一个模子,以及错误会被继承和放大——老师错了,学生跟着错。

真实日志最宝贵,因为它就是用户真正会问的东西。但里面有手机号、地址、内部项目名,清洗不干净就是事故。

还有一个反直觉的结论:SFT 数据不是越多越好。

Meta 的 LIMA 实验用一千条左右精心筛选的指令数据,就训出了相当不错的指令跟随能力。这说明质量和多样性,比堆量更重要。当然,生产环境要覆盖的任务多,几千条通常不够;但"十万条低质数据"往往打不过"一万条高质量、覆盖广的数据"。

数据配比也要讲究:

  • 任务要杂:问答、改写、总结、抽取、分类、写代码、拒答、多轮对话;
  • 长度要杂:一句话的问题和长文档摘要都得有;
  • 要教它说"不知道":不然它遇到不会的也会硬编;
  • 混一点预训练数据:防止它在窄领域里训过头,把通用能力忘了;
  • 工具调用要单独教:JSON 格式、参数名、什么时候该调工具,这些不会凭空出现。

一句话:SFT 数据不是"越多越好",是"越像你希望它面对的真实请求越好"。你拿十万条写诗数据训它,它写诗确实厉害;你问它报销流程,它可能给你写一首诗。


六、训练细节:低学习率、少轮次、别训忘了

SFT 是"微调",不是"重训",所以有几个和预训练相反的习惯:

学习率要低。预训练可能用 1e-4 量级,SFT 常用 1e-5 到 2e-5(LoRA 可以到 1e-4)。你是在给一个已经会走路的人纠正姿势,不是在教婴儿站立。步子太大,它会把原来会的东西也踩坏。

轮次要少。通常 1 到 3 个 epoch。同一批指令反复训太多遍,模型会开始背答案,甚至在无关问题上也套同一个模板。

小心灾难性遗忘。如果你只用"客服话术"训它,它可能连写代码、做数学的能力都退化。常见做法是混入一部分预训练数据,或者用 LoRA 这类参数高效方法,只动一小部分权重。

LoRA / QLoRA值得单独说一句:它们不更新全部参数,只训练一小块低秩适配器。好处是显存小、成本低、不容易把底座训坏,而且一个底座可以挂多个 LoRA 切换不同任务。代价是上限通常略低于全量微调——但对大多数团队,这个性价比是压倒性的。

模板必须一致。训练时用<|user|>、<|assistant|>,推理时也得用同一套。训练时一套模板、推理时另一套,等于教它普通话、考它粤语。特殊 Token 还要在分词器里正确注册,别让它们被拆成普通字符。

评测别只看 loss。SFT 之后要看的至少有三类:

  • 指令跟随:该答的答了没有,格式对不对;
  • 事实性:有没有为了"像答案"而编造;
  • 回归:原来会的任务,现在还会不会。

人工评测最可靠,但慢;LLM-as-judge 便宜,但有偏见;任务专用测试集最客观,但覆盖有限。三样搭配着用,别拿一个 loss 数字当结论。


七、SFT 能给什么,不能给什么

能力SFT 能给吗说明
听指令✅这是 SFT 的主业
输出格式✅JSON、Markdown、固定字段,都能教
语气 / 人设✅客服、老师、编程助手,都能演
工具调用格式✅前提是数据里教过
拒答 / 边界⚠️能教例子,但不保证稳
新知识⚠️能灌一点,但不可靠,容易变成自信的胡说
实时信息❌训练数据没有就是没有
复杂推理⚠️能教"解题格式",教不了"解题能力"
安全对齐⚠️SFT 是起点,不是全部

最值得记住的是"新知识"这一行。

你当然可以用 SFT 教它一条新事实:“我们公司的报销上限是 5000 元。” 它会记住,至少在训练集上。但这种方式不可靠:数据一多、问题一绕、上下文一变,它可能就答错,而且错得很自信。

事实性知识更适合放在模型外面,用检索的方式在推理时喂给它。SFT 更适合教"怎么回答",而不是"答案是什么"。

这就是那句话:

SFT 是把"能力"翻译成"行为",不是把"知识"灌进"脑子"。

它能让模型"会按格式回答",但不能让它"知道答案"。一个是演技,一个是台词。


八、五个常见误区

误区一:SFT 就是给模型灌知识。

不是。SFT 主要改的是行为:什么时候答、怎么答、答多长、遇到不会的怎么办。知识的上限在预训练就定得差不多了。SFT 能让它"像个专家",但不能让它"真的是专家"。

误区二:数据越多越好。

十万条低质数据,可能打不过一万条高质量、覆盖广的数据。重复、同质、错误的指令数据,会让模型学会一套固定的废话。先想清楚要它面对什么请求,再决定收什么数据。

误区三:loss 越低,模型越好。

SFT 的 loss 只算回答部分,低 loss 可能只是"背下了训练集"。要看留出集、看人工评测、看原来会的任务有没有退化。loss 是体温计,不是体检报告。

误区四:随便套个模板就行。

模板里的角色标记、特殊 Token、结束符,都是模型学习"轮到谁说话"的信号。训练和推理不一致,效果会莫名其妙地崩。模板不是格式问题,是语义问题。

误区五:SFT 能替代 RLHF。

SFT 教的是"模仿标准答案",RLHF / DPO 优化的是"在两个回答里更喜欢哪个"。前者教"像不像",后者教"好不好"。SFT 让模型会答题,RLHF 让它答得更符合人类偏好——两件事,不能互相替代。


九、把这件事放回地图上

把大模型的概念按依赖关系从下往上摞成几层——最底下是算力和数据,往上依次是文字的表示、模型结构、训练、调用,最顶上才是增强和智能体:

L6 智能体层 Agent / Function Calling / ReAct / 记忆 / MCP L5 增强层 RAG / 向量检索 / Rerank / GraphRAG / 微调 L4 调用层 Prompt / Few-shot / CoT / 温度 / Top-p L3 训练层 预训练 / ★ SFT ★ / RLHF / DPO / LoRA / Scaling Law L2 结构层 Transformer / 注意力 / 参数量 / 上下文窗口 / 开源闭源 L1 表示层 Token / 分词 / 词表 / Embedding L0 底座 算力 / 数据 / 成本

SFT 在 L3 的中间:预训练之后,偏好对齐之前。

顺便澄清一个容易混的点:L5 里的"微调"指的是面向业务的领域微调,和 L3 的 SFT 不在一个层面——SFT 是训练流程的一步,业务微调是"拿模型去适配场景"的用法。

往下看:预训练给了它知识和语言能力,SFT 只是把这些能力"接"到指令格式上。底座不行,SFT 数据再好也救不回来;底座行了,SFT 才能四两拨千斤。

往上看:RLHF / DPO 在 SFT 的基础上继续调偏好;RAG 在推理时补事实;Agent 用的工具调用格式,很多时候就是 SFT 教出来的。SFT 是"从模型到产品"的第一道加工,也是性价比最高的一道。

它的边界也很清楚:SFT 能让模型"会干活",但改不了它"能干多大的活"。想让模型知道新事实,靠检索;想让模型更符合偏好,靠对齐;想让模型更强,只能回到预训练。


三条可以带走的结论

  1. SFT 教的是"接口"和"行为",不是"知识"。它把 Base 模型的续写能力,翻译成听指令的能力。
  2. 关键是 mask:只对回答算 loss。不 mask,大部分梯度会花在教模型复述问题上。
  3. 数据质量和多样性 > 数量。模板一致性、防遗忘、评测,比调参更能决定 SFT 的成败。

《大模型概念地图》专栏 · 第 9 篇

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 1:54:48

华为发布昇腾 AI 全栈软件平台:CANN 3.0、MindStudio、MindX

IT之家在8月10日的这一天发布了消息, 今天的时候, 在一个名叫“昇腾AI新品全球发布会”的场合上, 这个场合的英语缩写是, 并且后面还跟着<2020>这个数字, 地点是在深圳那里举行的, 华为这家公司在那个会上发布了关于昇腾AI的全栈式的软件平台, 这个平台里面包含了异构计算…

作者头像 李华
网站建设 2026/10/26 1:54:06

和小马一起学Python(五)——字符串编码转换、字符串运算符、字符串常见内建函数

01 字符串编码转换字符串编码的本质可以看作是二进制数据与语言文字之间的一种映射或对应关系。它是将字符(不仅仅是语言文字&#xff0c;还包括标点符号、控制字符等)转换成一种标准化的二进制表示形式&#xff0c;以便计算机能够存储、处理和传输这些信息。1.1 编码格式&…

作者头像 李华
网站建设 2026/10/26 1:54:06

微信小程序学习笔记|案例 3.10 文件引用 include 标签

一、案例简介 本案例学习小程序 include 文件引入语法&#xff0c;把页面拆分成多个独立 wxml 片段&#xff08;头部标题、正文、页脚、拓展阅读&#xff09;&#xff0c;实现代码复用。 课后扩展需求&#xff1a;增加两个按钮&#xff0c;控制拓展阅读内容的显示与隐藏。 二、…

作者头像 李华