模型蒸馏与微调结合:小模型意图识别落地的完整实践
模型蒸馏和微调这几年在模型落地圈子里几乎成了固定搭配。单看蒸馏它是把大模型的推理能力压缩到小模型里单看微调它是让模型去适配特定业务的输出格式和分布。但真正把两者结合好不是先跑一遍蒸馏再跑一遍微调那么简单。这篇文章想聊的就是我在一个多分类意图识别项目里把模型蒸馏与微调结合应用的完整过程包括技术路线怎么选、参数怎么调、坑怎么踩。适合正在做大模型轻量化部署的团队或者刚接触知识蒸馏的算法工程师参考。去年年底我们团队接了一个边缘部署的客服意图识别项目场景要求模型的单条推理延迟压在100毫秒以内、模型体积控制在两三百兆以内常规的7B级模型根本没戏。于是我们把蒸馏和微调两条技术路线串起来用最后用一个三层的小Transformer模型接住了业务。整个过程走下来最大的感受是这两个动作的边界感很重要——什么时候该继承什么时候该适配想清楚了效果自然就上来了。这篇文章不堆理论把我踩过的、验证过的部分都写出来。1. 先搞清楚蒸馏和微调各自在解决什么问题1.1 蒸馏把大模型的手感传给小模型知识蒸馏的核心逻辑不是让模型背答案而是让模型学怎么想。它用一个已经训练好的大模型当教师用教师输出的软标签去指导学生模型。我们平时训练分类模型用的是硬标签也就是0和1这种离散的类别指示软标签则是一组概率分布比如这个句子有0.6的概率是查天气、0.3的概率是问温度、0.1的概率是其他。硬标签只告诉你哪个答案是对的软标签则把类与类之间的模糊关系也暴露出来了。我习惯用一个类比来解释硬标签相当于把菜谱背下来软标签是跟着老厨师学火候。一份红烧肉配方你按步骤做味道可能及格但老厨师手抖一下、多焖两分钟的手感你是从配方里学不到的。软标签里携带的正是这种手感——大模型在大量数据上学到的类间相似性、边界过渡、甚至是某种语言习惯。学生模型通过最小化和教师输出之间的分布差异相当于把大模型的知识压缩进了自己的参数里。具体到公式层面蒸馏通常引入一个温度参数T教师输出先除以T再做softmax让概率分布变得更平滑。学生模型往两个方向学一是和教师的软标签尽量接近二是和真实硬标签尽量接近两个loss按权重配比叠加。这就是蒸馏的骨架后面我会讲具体怎么调。1.2 微调让模型贴合业务数据的方言微调的本质是领域适配。预训练模型学会的是通用语法、常识和世界知识但它不知道你的业务里转人工和投诉是不是同一个意图也不知道你们的话术体系里咨询和查询哪个词更常用。微调就是拿一批业务数据让模型把通用的能力转化成特定任务的输出。这个过程好比一个人普通话很好但进了地方单位得学会当地方言和内部黑话。微调的量不需要很大几千条高质量标注数据往往就够了关键是数据分布要贴近线上真实场景。它解决的问题是模型在你的数据上说话像不像那么回事包括输出格式、边界情况、历史遗留的特殊说法等等。很多团队把微调当万能药不管什么问题都先微调一把。但如果模型本身容量很小直接微调很容易出现过拟合或欠拟合。小模型没有足够的参数去记忆大模型几百亿参数里沉淀的那些深层规律你给它一万条业务数据它背下来了换个说法就懵了。这就是为什么蒸馏微调要组合在一起的原因。1.3 为什么偏要组合在一起单独用蒸馏学生模型虽然继承了教师的知识但它学的是通用分布不是你的业务分布。教师模型的软标签在查天气和问温度之间可能划分得很细但你的业务里这两个意图压根合并成了一个叫天气咨询的类蒸馏出来的模型不知道这个合并规则。单独用微调小模型从通用底座出发直接拿业务数据训练因为没有经过大模型知识蒸馏这一步模型只能学到业务数据里显式出现的内容语文理解、常识推理这些底层能力完全是靠底座模型自带的上限有限。把两者串起来效果是互补的。蒸馏负责继承让学生模型站在大模型的肩膀上微调负责落地让继承来的知识贴合业务。我在项目里做过对比同样一个三层小模型从预训练权重直接拿5000条业务数据微调准确率大概82%先做蒸馏再做微调准确率能到90%以上。差的那8个点就是大模型知识带来的价值。下面我把整个技术路线选型展开讲。2. 结合方案的路线选型三选一怎么挑2.1 两阶段式先蒸馏后微调流程最清晰两阶段式的流程很简单第一步用训练好的教师模型对海量无标注数据生成软标签学生模型通过蒸馏学会模仿教师第二步把蒸馏完成的学生模型拿出来用少量业务标注数据做微调。两个阶段目标明确、互不干扰出问题时也容易定位。它的优点是工程实现最简单。蒸馏阶段不涉及业务数据可以放在离线大集群里跑微调阶段数据量小一张消费级显卡就能搞定。缺点是存在目标不一致的问题蒸馏阶段优化的是和教师更像微调阶段优化的是和真实标签更匹配如果微调步子迈得太大很容易把蒸馏学到的分布给覆盖掉导致最后的效果还不如不蒸馏。我在项目里踩过这个坑后面会专门讲怎么规避。总体来说两阶段式是大多数团队的第一选择因为它可控性最强。适合标注数据量在几百到几千条的中小项目这也是最常见的落地场景。2.2 联合训练式蒸馏和微调在同一条loss里优化联合训练的方式是让教师模型和学生模型同时前向传播学生模型同时计算两个loss一个是和教师软标签之间的KL散度一个是和真实标签之间的交叉熵两个loss加权求和一起反向传播更新学生参数。这个方案的理论优势很明显模型在每一步更新时既向教师学习分布知识又向真实标签学习任务知识两个目标互相约束不容易出现先学后忘的情况。训练过程也更平滑alpha权重成了关键超参数调好了可以兼顾准确度和泛化性。代价是显存开销大。教师和学生要同时加载到显存里如果教师是7B级别光这一项就够喝一壶的。我在项目里的处理办法是教师模型用半精度加载并固定住只让学生的参数参与梯度更新。即使这样训练速度也比两阶段式慢不少。适合数据量中等、大概几万条标注数据的项目以及团队里有足够GPU资源、愿意花时间调alpha的场景。2.3 迭代蒸馏式微调后的模型再回去当教师迭代蒸馏是我后来才尝试的方案。思路是先按两阶段式蒸馏出一个学生A然后用业务数据微调A得到A再把A当作新的教师重新去蒸馏一个新的学生B。理论上每一轮迭代教师都比上一轮更强学生也跟着水涨船高。实测下来这个方式确实能提升一点效果但提升幅度随着迭代次数递减。第二轮迭代大概能涨零点几个点第三轮基本就饱和了。代价是训练时间成倍增加而且每一轮都可能把教师模型在微调阶段学到的一些噪声也蒸馏给学生相当于把错误也放大了。我的建议是只有对效果上限要求极高且推理成本不敏感的团队才考虑这个方案普通项目没必要。2.4 场景选型判断表我做了一个简单的选型表可以直接对照用项目情况推荐方案理由标注数据少几百到几千条两阶段式蒸馏学通用知识微调学业务知识各司其职标注数据中等几万条左右联合训练式两个目标同步优化正则效果好不易遗忘效果要求极高、算力充足迭代蒸馏式上限最高但耗时和算力开销明显学生模型非常小3层以内两阶段式联合训练容易让学生模型优化目标打架线上数据分布经常变化两阶段式每次微调只需重跑小阶段迭代快选择的关键还是回到继承和适配的边界上模型容量越小越要优先保证知识继承的稳定性这时候两阶段更可控模型容量够大数据量也够多联合训练才能发挥出它的优势。3. 实操过程在一个意图识别系统上跑通全流程3.1 项目背景与数据准备我们的项目对标的是客服对话中的意图识别模块共15个意图类别包括查天气、问温度、咨询订单状态、转人工、投诉建议等。标注数据一共5000条质量还不错但因为业务更新快新话术不断出现所以非常依赖模型的泛化能力。蒸馏阶段需要海量的无标注数据。我们攒了约20万条脱敏后的客服对话文本用教师模型自动打上软标签。这一步有个细节要注意不是所有数据都适合进蒸馏集。我们会先让教师模型预测一遍只保留预测置信度高于0.85的样本置信度太低的直接丢掉。教师自己都没把握的样本教出来的学生也是糊涂的。数据清洗还包括去重和去短。去重是去掉完全重复的对话文本避免某类意图被过度重复学习去短是过滤掉字符数少于5的片段因为这类几乎没有语义信息只会给训练带来噪声。清洗下来大概剩17万条够用了。3.2 教师模型的选择与训练教师模型我们选了一个7B级别的开源底座模型。选择它不是因为参数越大越好而是它在通用语义理解上的表现已经被社区大量验证过且部署在我们内部的GPU集群上跑一次全量推理时间可控。教师模型不是直接用底座而是先用那5000条标注数据做了一轮微调。这一步很多人会跳过觉得大模型直接用就行但实测下来微调过的教师模型在业务数据上的准确率能从89%提到96%而蒸馏学生的效果上限直接取决于教师的上限所以这一轮微调非常值得。教师微调的配置比较常规学习率2e-5batch size 32训练3个epoch加了权重衰减。唯一要注意的是教师模型也不能微调过头否则它对训练数据过分自信输出的软标签分布过于尖锐反而不利于学生学习。我们通过验证集监控发现第3个epoch后验证loss不再下降就停了。3.3 蒸馏阶段的关键细节蒸馏阶段我们采用了两阶段式里的前半部分。学生模型是一个3层的小Transformerhidden size 256参数量大概1200万目标是要部署在CPU环境。教师模型和学生模型之间差了快600倍参数量这种差距下蒸馏温度必须调得比常规更高一些。温度T我们试了2、3、4、6四个档位最终定在3。T太小时软标签接近one-hot小模型学不到类间关系T太大时所有类别概率被拉平学生学不到判别性信息。蒸馏用的loss是KL散度配上0.7的alpha权重也就是70%的权重放在学习教师软标签上30%放在学习真实硬标签上防止学生完全偏离正轨。用PyTorch实现的核心loss代码如下import torch import torch.nn.functional as F def distillation_loss(student_logits, teacher_logits, labels, T3.0, alpha0.7): # 软化教师输出 teacher_soft F.softmax(teacher_logits / T, dim-1) # 软化学生输出并取log用于KL散度 student_log_soft F.log_softmax(student_logits / T, dim-1) # KL散度衡量两个分布的距离 kd_loss F.kl_div(student_log_soft, teacher_soft, reductionbatchmean) # 温度缩放补偿因为softmax被除以T梯度也缩放了所以要乘回T*T kd_loss kd_loss * (T * T) # 和真实标签的交叉熵保证学生不错得太离谱 ce_loss F.cross_entropy(student_logits, labels) return alpha * kd_loss (1 - alpha) * ce_loss这句kd_loss * (T * T)很多人会漏掉。因为我们在计算log_softmax之前把logits除以了T这相当于把梯度也缩小了T倍若不做补偿温度越高学生学习的力度越弱。乘回T的平方才能在任意温度下保持训练步伐一致。这个小细节网上不少资料都没写清楚。蒸馏训练了2个epoch学习率3e-5。这里有个反直觉的经验蒸馏训练轮数不宜过多。第一轮结束学生基本能跟住教师第二轮结束时达到最优再往后学生就开始死记硬背教师输出里的噪声了验证集上反而掉点。我们保存了每个epoch的模型做评估确认了第2轮的效果最佳。3.4 微调阶段的实操配置蒸馏完成之后进入微调阶段。这里最关键的决策是微调时动哪些参数。全量微调一个1200万参数的小模型理论上可行但实测全量微调会把蒸馏学到的分布很快覆盖掉。我们的做法是冻结底层和前几层只放开最后一层分类层同时在外面的线性层上挂了LoRA适配器。LoRA的配置比较保守rank8alpha16dropout0.1。LoRA是一种低秩适配方法它给原有权重加一个低秩的增量矩阵训练时只更新这些增量参数而不是更新整个权重。好处是参数改动幅度小不容易破坏蒸馏阶段学到的分布。微调的学习率用的是1e-4比全参数微调常用的2e-5要高因为可训练参数量少需要更大步长才能有效学习。微调数据用那5000条标注数据按8比1比1切分成训练集、验证集和测试集。训练了5个epoch用了早停策略验证集F1连续2个epoch不涨就停止最终停在第三个epoch。验证集上准确率90.6%F1 89.4%相对纯蒸馏不微调的88.7%又涨了近两个点。虽然涨幅不大但意图识别这个场景里两个点的准确率意味着每天少接几十通错误路由的客服电话质量差异是实打实的。3.5 整体的效果对比完整实验对比在这里模型方案准确率F1CPU单条推理耗时模型体积7B教师模型96.2%95.4%780ms约14GB小模型直接微调82.1%80.3%45ms约110MB小模型蒸馏后不微调88.7%87.5%45ms约110MB小模型先蒸馏后微调90.6%89.4%45ms约110MB小模型联合训练90.1%89.0%45ms约110MB看数据就能发现蒸馏和微调不是二选一而是各自抬升了一段水位。蒸馏先把小模型基线从82%拉到88.7%微调再往上拉1.9个点。联合训练的效果跟两阶段式接近但训练耗时更长最终我们选择了两阶段式的产物上线。4. 关键参数的一点点深度解读4.1 温度T软标签的锐利度怎么定温度T本质上是控制概率分布平滑程度的旋钮。T趋近于0时softmax退化成one-hot软标签等于硬标签蒸馏退化成普通训练T增大时分布变得越来越平坦各类别之间的差异被拉平学生能看到更多非标注类别的信息。我调参的经验是T的取值和两个因素强相关一是类别数量二是类间相似度。意图识别这类任务15个类别之间有不少语义重叠比如查天气和问温度在语料里经常混用这样的任务适合用偏高的温度T在3到4之间表现好如果是像猫狗分类这种类间差异很大的任务T在1到2之间就够用温度太高反而把判别性信息磨没了。判断T是否合适有个快速方法看学生模型和教师模型在无标注数据上的logit一致性。如果学生的预测分布与教师的KL距离在前期快速下降、后期平稳收敛说明温度合适如果出现振荡大概率是温度过低导致类别关系没被充分平滑。4.2 alpha权重软硬标签的配比alpha控制的是学生模型中学教师和学真实答案的比例。alpha越高学生越像教师的影子alpha越低学生越倾向直接学真实标签。理论上真实标签是正确答案的强信号教师软标签是知识分布的弱信号两者都不可偏废。我的默认起点是alpha0.7。如果业务数据存在标签噪声比如人工标注时不小心标错了一部分我会把alpha抬到0.8以上让教师的一致性约束来稀释标签噪声的影响。反过来如果数据质量非常高alpha可以压到0.6左右让真实标签主导训练方向。我在另一个项目里试过alpha0.9的极端配置学生模型输出分布会变得过于保守所有类别概率都趋近推理时明明应该判转人工的句子被判成其他因为教师本身在这类样本上的置信度就不高学生跟着学歪了。4.3 冻结策略与LoRA配置微调阶段最容易被忽视的是动多少参数这个问题。很多人拿到蒸馏好的模型直接全量微调结果学得快也忘得快。我在项目里的经验是蒸馏完成的小模型底层特征是通用语义的沉淀顶多只允许最后两层和分类头参与微调。LoRA的rank决定了适配器的表达能力。rank太大可训练参数多模型容易过拟合那几千条业务数据rank太小学不到业务特有的模式。bulk场景下rank8是性价比很高的起点。LoRA的alpha一般设置成rank的2倍左右取值16正好dropout设0.1防止适配器学得过于激进。还有一个不起眼但影响很大的细节优化器的权重衰减不要加在LoRA参数上。LoRA的初衷是让增量矩阵在小范围内调整加了参数正则反而会把它压得太死我实测DFO对比去掉LoRA上的权重衰减后微调F1能高0.5到1个点。4.4 评估指标别只盯着准确率蒸馏加微调这种复合流程单看准确率很容易被表象迷惑。我建议拆成三个层面看一是教师模型本身的准确率和F1这是蒸馏的天花板二是蒸馏阶段的学生模型和教师模型的分布一致性用KL距离衡量它直接反映继承的质量三是微调后模型在业务验证集上的F1它才是最终交付指标。在意图识别这类类别不平衡的任务里准确率会掩盖长尾类别的崩溃。比如投诉建议类只占整体数据的3%准确率即使掉了10个点整体准确率可能只波动零点几个百分点但业务上这类意图恰恰最关键。所以我一直强调按类别分别看F1尤其是那些低频但重要的意图类都需要单独盯住。5. 踩坑记录与排查思路5.1 坑一蒸馏loss降不下去但模型F1还行第一次跑蒸馏我发现KL loss始终在一个较高的位置震荡降不下来心里很慌。后来检查了温度设置发现T开到了6导致教师软标签几乎成了均匀分布学生学不到任何判别信号。把T降到3之后loss在半个epoch内就开始明显下降。这个坑提醒我loss降不下去时先回头检查温度而不是急着调学习率或加batch size。温度太高会让软标签失去信息量类似教师把答案说得模棱两可学生当然无从学起。另外也要看教师有没有开eval模式教师开了dropout输出的软标签每次前向都不一样学生会被这种随机性干扰得无所适从。5.2 坑二先蒸馏后微调效果反而向下掉我在一次实验中先蒸馏再微调最终F1比不蒸馏直接微调还低当时差点推翻整个方案。逐层排查后发现微调阶段的学习率设成了3e-4相对蒸馏阶段大了整整一个数量级第一轮就把底层参数冲得七零八落。微调阶段最要紧的口诀是小步慢走。小模型的可训练参数本来就不多学习率超过2e-4就很容易覆盖蒸馏学到的知识。我把学习率降到1e-4后微调效果立刻回来了。另外还要给微调加早停不设上限地跑epoch模型必然开始死记硬背训练集把蒸馏阶段换来的泛化能力全部吐回去。5.3 坑三教师自动打标的数据里有毒样本蒸馏数据的软标签是由教师自动生成的教师也会犯错。我检查了一批低置信度样本发现其中有相当比例是领域术语混用和含糊的省略表达比如明天呢这种话教师给了三个意图各28%的概率等于什么都没教。处理方法是设置信度阈值过滤掉教师预测概率低于0.85的样本。过滤之后数据少了约15%但蒸馏效果反而更稳了。软标签错误的危害比硬标签错误更大因为软标签传递的是分布信息一旦错学生学到的是一种扭曲的类间关系。5.4 排查思路速查表现象可能原因处理方式蒸馏loss降不下去温度过高软标签失去判别信息把T降到2到4之间蒸馏loss降了但F1低alpha太低真实标签压过软标签提高alpha到0.7以上微调后面临效果下滑学习率太大或训练轮数过多小学习率加早停冻结底层微调后泛化变差可训练参数过多LoRA rank太大rank降到4卡住顶层教师自动打标出现乱标签置信度阈值过低过滤掉低置信度样本训练显存不足教师和学生同时加载教师半精度推理或先算好软标签存盘5.5 一个省内存的实操技巧蒸馏阶段需要在海量数据上跑教师模型输出软标签并缓存下来。直接用pickle存Tensor会占用大量磁盘空间而且加载慢。我的做法是转成numpy用float16存盘每条样本只存一个1乘15的向量17万条数据总共占不到200MB加载时再转回torch tensor。这个细节在数据量大时能省下大量等待时间。另外一个经验是教师模型的推理脚本里记得设置torch.inference_mode()而不是普通的torch.no_grad()。前者会跳过某些梯度相关的记录逻辑在纯推理场景下能再挤出5%到10%的速度提升。这点从日志上看不明显但大规模跑蒸馏数据时省出来的时间可以多跑一轮实验。我个人在实际操作中体会最深的一点模型蒸馏和微调结合真正难的并不是某个公式或某个参数而是你搞不清楚当前模型知识是从哪个管道流进来的。蒸馏阶段磕磕碰碰微调阶段又覆盖掉前面的成果这种反复最磨人。最后再分享一个小技巧上线前可以顺带做一层4bit量化把110MB的模型再压到一半以下CPU推理速度还能再提一节。这个方向后续还能跟剪枝、结构化稀疏组合着做每一步增益虽然不大叠在一起效果就很可观了。