Day09|SFT 监督微调——把“接龙机器“变成“对话助手“
苦猿的大模型日记 · Day09 · SFT 监督微调到底在改什么-帮普通人把AI学进简历系列前言base model 直接拿来聊天有多灾难我先给你看一个让我第一次见的时候笑出声的画面。你拿一个预训练好的 base 模型比如 Qwen2-7B-Base直接问它帮我写一封请假信你以为它会老老实实给你写一封信。结果它接的是——帮我写一封请假信 帮我写一封辞职信 帮我写一封感谢信 帮我写一封道歉信 ……它在接龙不在回答。它根本没意识到你在问它。它只是看见帮我写一封请假信这串字按照语言的概率分布接着往下续——而互联网上帮我写一封XX信这种列表格式太多了它续得理直气壮。这就是 Day08 讲完预训练之后留给你的一个尴尬现实——预训练产出的是一个会接龙的怪物不是一个会聊天的助手。它肚子里有整个互联网的知识会写代码、会做数学题、会引经据典——但你需要先用一种特定的方式教它用户提问的时候你该回答而不是续写。这种教就是SFTSupervised Fine-Tuning监督微调。读完 Day09你能——搞懂 SFT 到底在改什么——不是知识是行为模式看懂 SFT 的 loss 为什么只算答案段——一个-100背后的工程巧思理解 LoRA 凭啥让普通人也玩得起微调——把显存门槛砍掉两个数量级明白 QLoRA 是怎么把 7B 模型塞进 6G 显存的——这是个人玩家入场的命脉四个概念是 LLM 工程师面试时聊微调的地基——少一块都站不稳。PART 01SFT 的本质——教模型从接龙切换到回答要理解 SFT先得把整个 LLM 训练的全景图摆出来。业界主流的大模型训练分三段——预训练 → SFT → RLHF/DPO 接龙 教回答对齐人类偏好预训练喂几万亿 token学会语言长什么样——产出 base modelSFT喂几万条问-答对学会被问的时候怎么答——产出 chat modelRLHF/DPO再用人类偏好打磨让回答不那么啰嗦、不那么有害Day08 讲完了第一段。今天讲第二段——SFT。同样的权重为啥 base 接龙、chat 回答?这里有个反直觉点必须先讲清楚——base 模型和 chat 模型权重结构完全一样。Qwen2-7B-Base 和 Qwen2-7B-Instruct参数量一样、架构一样、注意力机制一样。区别不在模型在训练数据的格式。预训练数据互联网抓的纯文本一段一段往下续今天天气真好我们去公园SFT 数据人工构造的问题-答案对帮我写请假信 → 尊敬的领导……同样一个模型喂纯文本它就学接龙喂问答对它就学回答。SFT 没让模型变聪明只是让模型换了种存在方式。SFT 的数据长啥样业界最经典的格式叫Alpaca 格式斯坦福那批人定的——{ instruction: 把下面这句话翻译成英文, input: 今天天气真好, output: The weather is really nice today }三段instruction指令input可选的输入output标准答案。就这个格式几万条高质量数据喂给 base 模型训练一遍——它就脱胎换骨了。国内对应的开源数据集有 Belle、Firefly、COIG国外的有 Alpaca、Dolly、OpenOrca。质量比数量重要得多——这个反直觉点 PART 02 会展开。SFT 教会了模型什么一个词——指令跟随Instruction Following。预训练的模型看见帮我写一封请假信它续写帮我写一封辞职信。SFT 之后的模型看见帮我写一封请假信它知道——哦这是指令我该输出请假信的内容而不是接着列指令。这个知道不是模型突然变聪明了是 SFT 数据告诉它当输入长成指令这个样子时输出应该长成答案那个样子。它学的不是知识是输入和输出之间的角色对应关系。跟 Day07 BERT 微调的本质差异Day07 我们做过 BERT 微调——在 BERT 上加个分类头训它做情感分类。那也是微调但跟 SFT 是两码事——维度BERT 微调Day07SFTDay09改什么只改顶部的分类头改整个模型的输出行为数据几千条带标签文本几千到几万条问答对目标学一个特定任务学会听指令这个通用能力产出一个分类器一个对话助手BERT 微调是给模型装个新技能SFT 是给模型换个灵魂。金句——预训练教模型世界是什么样的SFT 教模型用户问问题时你该怎么站。PART 02SFT 的 loss——只算答案那段不算问题PART 01 讲了 SFT 在改什么。这一 PART 讲它怎么改。乍一看SFT 跟预训练没啥区别——都是 next-token prediction都用 Day08 讲过的交叉熵 loss。但有一个关键不同新手 100% 会踩——错误做法整段都算 loss假设你有一条 SFT 数据用户帮我写一封请假信 助手尊敬的领导我今天身体不适……如果你把它整段拼起来丢给模型做 next-token prediction对每一个 token 都算交叉熵——你犯错了。因为你让模型在用户帮我写一封请假信这段也算了 loss——也就是说你让模型花力气去学怎么提问。但模型需要学怎么提问吗不需要。用户会自己提问模型只负责答。更糟的是——互联网上问题的写法千奇百怪、鱼龙混杂让模型学怎么提问等于把噪声塞进它的容量里反而拖累回答质量。正确做法mask 掉问题只算答案工程上的标准做法是——把 prompt问题部分的 label 设为-100。PyTorch 的CrossEntropyLoss看见-100就自动忽略这个位置不算 loss、不反传梯度。也就是说——问题部分的 token模型照样 forward 算它们的概率分布因为答案例子的预测需要看到问题但不参与 loss答案部分的 token才算 loss反传梯度更新参数模型只在答案这段被纠正——它学的就是在给定问题的前提下答案该怎么写。代码长这样# input_ids: 整段 token问题 答案 # labels: 跟 input_ids 同形状但问题部分设为 -100 input_ids tokenize(用户帮我写请假信\n助手尊敬的领导……) labels input_ids.clone() # 找到助手之后的位置 answer_start find_answer_start(input_ids) # 问题部分全部 mask 掉 labels[:answer_start] -100 # 只有答案部分参与 loss loss model(input_ids, labelslabels).loss这一行labels[:answer_start] -100就是 SFT 区别于预训练的核心。一个反直觉点SFT 数据要精不要多既然 SFT 只在答案段算 loss那意味着——每条数据的答案质量决定了模型学到什么。一万条精心标注的问答对效果好过十万条粗制滥造的。业界有个共识LIMA 论文Meta 2023只用 1000 条高质量数据就微调出了一个能打的对话模型。为啥?因为底子预训练已经打好了。SFT 不是在教模型新知识是在教它在什么场合用什么已有知识——这种角色感几百到几千条高质量样本就够教明白了。数据脏、答案水再多万条也没用——模型只会学成在什么场合都说废话。横向连接 Day08 PART 02——交叉熵还是那套数学变的不是公式是算哪些位。这就是 SFT 的工程巧思。金句——SFT 的 loss 只盯着答案段——因为模型该练的不是怎么提问是怎么回答。PART 03LoRA——普通人玩得起 SFT 的命脉PART 01、02 讲清了 SFT 在改什么、怎么改。但还差一个致命问题——全参数 SFT普通人玩不起。全参数微调的代价假设你要 SFT 一个 7B 模型70 亿参数动它的所有参数。显存账这么算——模型权重FP167B × 2 字节 14 GB梯度FP1614 GBAdam 优化器状态m vFP327B × 8 字节 56 GB加上激活值、临时张量……加起来轻松80 GB。一张 A10080G才勉强能跑。一张 A100 一小时租金十几块跑一次 SFT 几百块起步——普通人玩个锤子。这就是为啥 2023 年之前微调大模型是大厂的专利——门槛是显存显存就是钱。LoRA冻结基模挂个低秩小矩阵2021 年微软发了篇论文叫LoRALow-Rank Adaptation把这个门槛砍掉两个数量级。核心思想一句话——冻结原模型所有权重只在每一层旁边挂一个低秩矩阵作为旁路只训这个旁路。具体说原模型某一层的权重矩阵是W比如 4096×4096。LoRA 不动W而是在它旁边挂两个小矩阵——W W A × B 其中 W冻结不训4096 × 4096 A可训4096 × r B可训r × 4096 r秩通常 8 或 16r是个小数字8、16、32所以A和B加起来的参数量比原矩阵W小好几个数量级。训练时只更新A和BW一动不动。参数量对比降了三个数量级7B 模型全参数微调70 亿可训参数。挂 LoRArank8可训参数量大概几百万到一两千万降了三个数量级。这意味着——显存里不用存 70 亿参数的梯度和优化器状态只存几百万参数的那点东西几个 GB 就够一张消费级显卡3090、4090就能跑LoRA 把 SFT 的硬件门槛从租机房打到开个人电脑。为啥这么小的参数量还能有效?这是 LoRA 最反直觉、也最精彩的地方。我分三层讲——第一层低秩分解是个数学老把式。线性代数里有个事实——任何一个大矩阵都可以被近似成两个小矩阵相乘。W是 4096×4096总共 1678 万个数。但如果我们假设它的有效信息只分布在 8 个方向上秩为 8那它可以被近似成——W ≈ A × B (4096×8) × (8×4096)A 和 B 加起来只有65536 个数——比原矩阵少了三个数量级但近似出来的结果几乎一样。这就是低秩分解。它不是 LoRA 发明的是数学里早就有的降维工具——推荐系统里的 SVD、PCA 用的都是这套。LoRA 的洞察在于——把这个工具用在了权重的更新量上而不是权重本身。第二层为啥权重的更新量是低秩的?LoRA 论文做了一个对比实验——把一个微调后的模型权重W_after减去微调前的W_before得到更新矩阵ΔW。然后他们发现——ΔW的有效秩远小于W本身。也就是说,虽然ΔW跟W一样大,但它真正承载信息的维度很少,绝大多数行/列是高度冗余的。直觉上为啥会这样?因为微调不是从零学新东西,是在已有知识上做小幅方向调整——这种调整天然是低自由度的。打个比方:你想把一辆车的方向从往北调成往北偏东 5 度——不需要换整个方向盘,只需要轻轻转一点点。微调就是那个轻轻转一点点,它的自由度远小于从零造一辆车。微软 LoRA 论文实证——用 rank8 就能逼近全参数微调的效果,就是因为微调的本征秩就这么低。第三层:A 和 B 的初始化有个巧妙设计。LoRA 训练开始时,A 用随机高斯初始化,B 初始化为全零。这意味着——训练第一步,A × B 0,所以W W 0 W。也就是说,训练起点上,LoRA 模型的输出跟原模型一模一样。然后随着训练,A 和 B 逐渐长出非零值,模型的行为才慢慢改变。这个设计很关键——它保证了 LoRA不会在训练一开始就把原模型搞坏。如果 A 和 B 都随机初始化,第一步A × B就是一个乱七八糟的噪声矩阵,叠到W上会瞬间摧毁预训练学到的知识。一个零初始化,锁住了训练起点 原模型这条命脉。穷是穷,但穷得有数学道理。代码长这样HuggingFace 的peft库10 行代码挂上 LoRA——from peft import LoraConfig, get_peft_model config LoraConfig( r8, # 秩常用 8/16/32 lora_alpha16, # 缩放系数 target_modules[q_proj, k_proj, v_proj, o_proj], # 挂哪些层 lora_dropout0.05, task_typeCAUSAL_LM ) model get_peft_model(base_model, config) model.print_trainable_parameters() # 输出trainable params: 4,194,304 || all params: 7,042,713,600 || trainable%: 0.06%最后一行print_trainable_parameters()会告诉你——只有 0.06% 的参数在训剩下 99.94% 全冻着。三个原理层面的认知点详细训练坑留到 Day10这里只讲三个原理层面必须知道的——1. rank 不是越大越好。新手爱把r8调到r64以为效果会更好。多数任务上 r8 和 r64 效果几乎没差但显存翻几倍。为啥?因为前面说了微调的本征秩就那么低r8已经够覆盖了再大是浪费。2. target_modules 必须挂对地方。target_modules[q_proj, k_proj, v_proj, o_proj]——这四个是 attention 的投影矩阵。如果你只挂了q_proj漏了v_projLoRA 形同虚设——因为 attention 的关键计算分布在 q/k/v 上漏一层就少一块儿。经验法则全挂比漏挂强宁可多挂几个gate_proj/up_proj也别漏v_proj。3. 学习率要比预训练小一个数量级。预训练 lr 通常是1e-4~5e-4量级。LoRA 的 lr 通常是1e-4~2e-4——听起来差不多但 LoRA 只动 0.06% 的参数每个参数承受的更新密度高得多lr 一大就训崩。具体调参坑 Day10 展开。金句——LoRA 不是偷工减料是数学告诉我们微调本来就不需要动那么多参数——穷是穷但穷得有道理。PART 04QLoRA——LoRA 再砍一刀6G 显存微调 7BLoRA 把可训参数砍了三个数量级已经很猛了。但还有个问题——LoRA 砍的是训的参数没砍加载的参数。LoRA 的剩余瓶颈基模还是 FP16LoRA 冻结了原模型权重但冻结 ≠ 不占显存。7B 模型的权重是 FP16加载进显存还是要 14 GB。加上 LoRA 自己的梯度、优化器状态、激活值LoRA 微调 7B 模型实测要 16-20 GB 显存。一张 309024G能跑但紧巴巴一张 306012G直接 OOM。LoRA 把门槛从 A100 砍到 3090但还没砍到普通人真正人手一张的显卡。QLoRA基模 4bit 量化 LoRA2023 年华盛顿大学发了QLoRA再砍一刀。核心两招——第一刀NF4 量化。把基模权重从 FP16 量化到4bit。但这里有个学问——4bit 只能表达 16 个不同的值2 的 4 次方。这 16 个值怎么选,决定了量化误差有多大。朴素的办法是均匀量化:在权重的最小值和最大值之间,等距切 16 段,每段取一个代表值。但均匀量化有个问题——它假设权重是均匀分布的。可大模型的权重根本不是均匀分布,它们像一口钟,集中在 0 附近,两边越来越少(近似正态分布)。权重出现的频率 ▲ │ ██ │ ██ ██ │ ██ ██ │ ██ ██ │ ██ ██ └──────────────► 权重值 -0.1 0 0.1均匀量化在中间这个权重最密集的区域只分了几个格子,精度浪费在了两边没人用的尾部。QLoRA 提出的NF4Normal Float 4——专门为正态分布设计的 16 个值。它在权重密集的 0 附近多放格子,在稀疏的尾部少放格子,让这 16 个格子刚好覆盖权重最可能出现的位置。数学上,NF4 是这么算的:先把正态分布的概率区间分成 16 等份(每份 1/16 概率),然后取每份的中位数作为代表值。这样每个代表值都恰好覆盖等量的权重——误差被均匀分摊,而不是集中在中间。结果就是——同样 4bit,NF4 比均匀量化的误差小一个量级。7B 模型:14 GB →4 GB。第二刀:可训部分仍是 LoRA,保持高精度。QLoRA 不量化 LoRA 的A和B——它们仍然是 FP16/BF16,反传梯度时是高精度计算。也就是说——前向传播时,4bit 权重会被临时反量化成 FP16 参与计算(算完就丢,不占常驻显存);反向传播更新 A 和 B 时,完全是 FP16 精度。读权重时用 4bit(省显存),训参数时用 16bit(保精度)。两刀一叠加:基模 4G LoRA 那点东西 激活值,7B 模型 QLoRA 微调,6-8 GB 显存就能跑。一个反直觉点:QLoRA 几乎不掉精度直觉上,FP16 → 4bit,精度掉得稀里哗啦。但实测几乎不掉。QLoRA 论文里,4bit 量化后微调效果跟 FP16 全参微调几乎打平。为啥能这么神奇?三个原因叠在一起——NF4 的格子分配本来就对路(刚才讲的,正态分布优化)量化的是冻结的基模权重,不是梯度——基模权重是固定的静态值,量化一次就不用再动,误差不累积梯度反传走的是 LoRA 的高精度通道——模型真正学习的那部分,从头到尾没被量化碰过也就是说,QLoRA 牺牲的只是前向传播时基模权重少了几位精度这点几乎不可见的误差,换来了 70% 的显存节省。这个反直觉点很重要——它意味着省钱几乎不付出代价。QLoRA 还藏了两个工程招除了 NF4,QLoRA 论文里还有两个让低显存能跑的关键工程细节,经常被科普文漏掉——1. 双重量化(Double Quantization)。量化本身要存一些缩放参数(每个权重组一个 scale)。这些 scale 也是数字,QLoRA 把它们也量化了一遍(8bit),又省了零点几个 GB。听起来微不足道,但 7B 模型上有几百个 scale,省下来的显存刚好够 LoRA 多挂两层。2. Paged Optimizer(分页优化器)。训练时显存不是恒定的——某些瞬间(比如长序列突然进来)会瞬时飙升,触发 OOM。QLoRA 用了 NVIDIA 的 unified memory 机制——显存不够时,自动把优化器状态临时挪到 CPU 内存,用完再挪回来。像操作系统的虚拟内存一样。这一招解决了训练到一半突然 OOM这个最让人崩溃的场景。显存对比表7B 模型 SFT三种方案的显存和硬件门槛——方案基模精度可训参数显存占用硬件门槛全参数 SFTFP1670 亿80 GBA100 80GLoRAFP16~千万16-20 GB3090 / 4090QLoRA4bit~千万6-8 GB3060 / 4060最后一行——一张 306012G甚至 40608G就能微调 7B 模型。这是真正的个人玩家入场券。QLoRA 为啥是普通人入场的关键我反复说普通人——这个词在 Day09 里出现好几次不是凑字数。LLM 这门手艺光看不练是学不会的。你可以背一百篇博客讲 SFT 是什么但只要没亲手跑过一次微调面试时聊起来就是飘的——你做过 SFT 吗?理论上懂但没实际训过。——这句话一出口offer 就飞了一半。QLoRA 把亲手微调一次的门槛从租机房降到开自己电脑——这是普通人从用模型跨到改模型的命脉。Day10 我会带你实际跑通一次 QLoRA 微调——从数据准备到合并权重所有坑一次踩明白。金句——QLoRA 让微调从租机房变成开个人电脑——这是普通人能动手的第一站。结尾Day09 给地图Day10 给铲子Day09 到这就讲完了。回头看我们用一篇的篇幅把 SFT 的为什么 是什么 原理全清了——为什么需要 SFT预训练产出的是接龙机器不是对话助手SFT 在改什么不是知识是被问的时候该回答这种行为模式SFT 的 loss 怎么算mask 掉问题段只在答案段算交叉熵LoRA 凭啥救场冻结基模挂低秩矩阵可训参数砍三个数量级QLoRA 再砍一刀基模 4bit 量化6G 显存就能微调 7B这五个点是面试聊微调时的地基——少一块都站不稳。简历视角面试高频题——预训练和微调的区别是什么?三层答案Day09 全覆盖——数据格式预训练喂纯文本SFT 喂问答对loss 算法预训练整段算SFT 只在答案段算mask 问题段目标行为预训练教接龙SFT 教回答答出这三层面试官就知道你不是看科普文背的——是真懂工程。原理懂了但真正动手跑通一次 SFT还有一堆坑在等着——数据集 chat template 拼错模型学了个寂寞LoRA target_modules 漏挂训练 loss 正常但效果为零epochs 跑太多过拟合模型把训练集背下来丢了泛化合并权重时 dtype 没对齐推理全是乱码OOM 时不知道是 batch 太大还是 max_seq_len 太长这些坑光看 Day09 是踩不到的必须亲手跑一遍。Day10 我带你跑通一次完整 QLoRA 微调——从零到合并权重出可推理模型所有坑一次踩明白。再埋后面一篇的钩子SFT 之后模型会回答了。但回答得可能很糟——问它今天天气怎么样它给你写一千字啰嗦问它怎么制作危险品它真给你写有害同一个问题它两次答得完全不一样不一致这些问题SFT 解决不了。需要靠人类偏好对齐——那就是RLHF / DPO的地盘。不过这个不急——先把 SFT 训练实操吃透再聊对齐。Day10、Day11 我会连着把训练这一摊讲透RLHF/DPO 留到训练讲完之后。互动时间你实际微调过哪个开源模型?Qwen、Llama、DeepSeek、还是 ChatGLM?最想用 LoRA 还是 QLoRA 上手?卡在了哪一步?评论区告诉我——Day10 我会把高频坑写进训练实操你提的坑我专门拆给你看。点赞够多的话我把主流开源模型 适配的 LoRA 配置整理成一张表发出来。下一篇预告Day10 | SFT 训练实操——从零跑通一次 QLoRA 微调— END —苦猿 · 陪你踩完每一个坑