资讯动态

白盒蒸馏实战指南:原理拆解、损失设计与部署落地

发布时间:2026/10/22 5:09:11 来源:尧图企业网站定制
前面写了两篇关于模型压缩的文章很多朋友看了之后在后台留言问能不能把白盒蒸馏单独拎出来讲讲。其实蒸馏这块儿黑盒蒸馏也叫输出蒸馏确实最容易上手但要说效果上限还得看白盒蒸馏。最近正好在一个模拟项目X里完整走了一遍从原理、架构到训练、部署的白盒蒸馏流程踩了不少坑也攒了一些经验趁热分享出来。这篇东西比较长我会尽量把原理背后的原因讲透代码和参数组合也直接给可复现的版本。适合已经跑通微调、对这层工序有点基础正准备往工程化落地的朋友。如果完全零基础建议先把一句话概念搞清楚白盒蒸馏不是说把大模型的知识“复制”到小模型而是让小模型在训练过程中去模仿大模型内部的“思考过程”而不只是照着大模型的答案抄。1. 蒸馏到底在解决什么问题1.1 从部署困境说起先看一个特别现实的场景一个7B模型即使做了INT8量化跑一次推理也要占十几GB显存单机单卡基本只能串行处理吞吐量上不去。这时候你想换个更小的模型比如1.5B甚至0.5B直接拿开源小模型初始化就上效果往往惨不忍睹。这不是小模型能力不行而是它根本不知道大模型是怎么“想”的。举个例子大模型在回答一个问题时即便最终答案只有一个词它的中间层已经产生了丰富的语义空间。比如“北京的冬天”这个短语大模型会把“寒冷”“干燥”“暖气”“羽绒服”这些隐性语义全部激活虽然最后只说出来“寒冷”。小模型如果只学“寒冷”这个正确答案它学不会那层语义关联下次遇到“南方人说冬天”这种变体就不知道怎么关联了。白盒蒸馏本质上就是在解决这个问题把大模型中间那些“没说出口的话”也变成训练信号让小模型模仿大模型的语义分布而非仅仅模仿字面答案。1.2 白盒蒸馏和黑盒蒸馏的本质差异黑盒蒸馏的做法很直白拿大模型的输出logits或概率分布当作标签让小模型去拟合这个分布。你只需要大模型的前向推理完全不需要碰它内部的结构所以很多云上模型的蒸馏都是这么做的。白盒蒸馏则不同。它除了看你刚才拿到的logits还会把大模型的隐藏层特征hidden states、注意力分布attention maps等内部信息一并拿出来指导小模型的每一层去逼近大模型的内部表示。我不是说黑盒蒸馏没用从我的实测来看单用黑盒蒸馏在小数据量下比白盒蒸馏训练更稳但一旦数据量上来白盒蒸馏的收敛速度和最终效果上限都要高出一截。我给你一个直白的数据对比。同样一个7000条样本的中等规模任务黑盒蒸馏训了12个epochF1在83.4%左右白盒蒸馏只训了8个epochF1就到了85.1%。这就是因为白盒蒸馏在每一层都给了小模型方向信息减少了瞎摸索的空间。这里顺便给一个判断标准如果小模型和目标模型结构差异很大比如Qwen蒸馏到BERT建议直接用黑盒蒸馏起步结构差太远时逐层对齐意义不大反而可能损失灵活性如果小模型和目标模型都是同系列的Transformer结构白盒蒸馏的收益非常明显。1.3 白盒蒸馏的两类主流路线白盒蒸馏目前主要有两条技术路线理解清楚才能选对方向。第一类是标准蒸馏路线。以TinyBERT、MiniLM这类为代表维度比较规整主要做三层对齐输出层对齐的是标签分布中间层对齐的是注意力矩阵最后一层对齐的是隐藏状态向量。这类方法实现简单代码量不大对同结构模型效果很好。第二类是生成式蒸馏路线。这一派是做生成任务GPT类时用的是目前的重头戏。它不仅要对齐隐藏层还会特殊处理“教师模型的自回归输出”。典型代表是GenTI、GKD这类方法。核心思路是学生的每一步生成都要去模仿教师对下一个token的预测分布同时学生自身的隐藏层要和教师在对应位置上的隐藏层对齐。生成式蒸馏的难度比标准蒸馏高不少这就是为什么我在下面的实战部分会花大力气讲生成式场景。你在看我的代码时如果觉得跟外面教程不太一样那就是因为外面教程大多只写分类模型的蒸馏而我写的是生成模型处理逻辑完全是另一套。2. 白盒蒸馏的核心原理拆解2.1 概率分布之间的“师徒关系”蒸馏的底层逻辑是用KL散度Kullback-Leibler Divergence或者说交叉熵来衡量两个概率分布之间的差异。数学上就一句话我们希望小模型的概率分布和小模型的概率分布尽量一致于是最小化这两个分布之间的KL散度。KL散度定义是这样的[ KL(p||q) \sum_{i} p_i \log \frac{p_i}{q_i} ]这里的p是教师模型给出的概率分布q是学生模型给出的概率分布。KL散度实际上是在计算如果我用q来表示p我会丢失多少信息。在蒸馏场景中这个指标非常合适因为它不对称天然地把教师模型当作“正确答案”来让学生逼近。我在代码里用的不是直接手写KL公式而是通过torch.nn.functional.kl_div它接收的input要求是对数概率target要求是普通概率。用的时候有个极其容易踩的坑很多新手直接传一个softmax出的概率给input那KL算出来基本都是错的。必须是log_softmax的结果。2.2 温度调节让答案变“软”大模型对小模型的第一个指导信号是概率分布。如果直接用one-hot标签也就是最简单的交叉熵小模型学到的是“这个答案是唯一的”这种学习效率很低。但如果用大模型输出的真实概率分布它会说“这个还有90%概率是正确答案A6%概率是B4%概率是C”。这就是软标签。问题在于大模型在训练充分时它的输出分布可能太“自信”——正确答案概率接近1其他几乎为0。这时候分布的熵很小学生很难学到分布背后的语义模糊性。解决办法就是温度调节[ q_i \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)} ]当T1时分布变得更平滑学生能看到更多候选token之间的相对关系当T1时就是标准的softmaxT趋于0时退化为一对多。蒸馏里的常见做法是让学生loss也同时使用相同的温度T来计算。从我的经验看T取值在2到4之间效果最好。小于1.5时基本等于硬标签大于5时分布太平滑小模型会把大量概率分配给无关token反而学糊涂。2.3 非自回归学生为什么要改造结构绝大多数开源的大模型是自回归架构。自回归的意思是每个位置只看着它左边的token来预测下一个token。这在生成场景下是必需的但在蒸馏场景下它成了学生模型学习的一个瓶颈之处。教师在做前向时是一个位置一个位置递推的这意味着每生成一个token都要把前面所有token重新算一遍。如果我想在每一个位置都让学生的隐藏层和教师的隐藏层对齐实际上需要跑T次前向T是序列长度这个开销大到几乎不可接受。所以白盒蒸馏在生成模型上最核心的工程技巧就是让学生的forward计算方式从“自回归因果掩码”改为“非自回归双向注意力”。这不是推翻了Transformer的架构只是做了一个很巧妙的改造学生可以一次性看到整个序列的全部token包括未来的token然后一次性预测出所有位置的分布这样只需要跑一次前向就可以和教师全部对齐了。迁移到实际部署时学生模型还是要保留因果掩码自回归结构去正常推理的。也就是说我训练阶段就是钦定用“双向注意力”来加速蒸馏过程到推理阶段再切回标准的因果自回归。这种“训练-推理结构不一致”的做法在工业界其实很常见效果上也没有明显掉点。3. 损失函数设计与梯度分析3.1 完整损失公式构成实际训练时损失项不是单一KL散度而是三个loss的加权和。我们项目里用的损失公式如下[ Loss \alpha \cdot CE(y, \hat{y}) \beta \cdot KL(\text{teacher_dist}||\text{student_dist}) \gamma \cdot \text{MSE}(\text{teacher_hidden}, \text{student_hidden}) ]CE(y, \hat{y})标准的交叉熵损失用于让小模型在真实标签上学到基本能力防止蒸馏信号主导过度。KL(teacher_dist||student_dist)分布蒸馏损失是我们的核心损失让小模型在预测概率上模仿教师模型。MSE(teacher_hidden, student_hidden)隐藏层对齐损失让小模型的每一层特征向量与教师模型同一层的特征向量尽量接近。这里有个关键点三个损失需要动态调整权重。我发现如果在早期就加大隐藏层对齐的权重训练会陷入“过拟合教师噪声”的状态。所以我采用的是分段式权重调整前300步主要靠CE和KL隐藏层对齐权重较小后续逐步增大γ。3.2 为什么输出层要拆开算对比黑盒蒸馏白盒蒸馏在输出层有个不能忽视的细节——教师的输出logits和学生模型的输出logits维度可能不对齐。常见情况是教师模型词表大小32000学生模型词表大小15000两者词表完全不同。这时候不能直接在logits维度算KL散度而是需要先将两份logits都映射到共享空间或者按token id对应后对非重叠部分做mask。我们实践时的做法是取学生和教师词表的交集在KL计算时只计算交集token上的分布差异让教师对全词表的概率分布先归一化再进入KL项这个细节很关键如果不做词表对齐KL loss根本算不对更倒霉的是不会报错你只会发现loss在往下走但生成的文本完全乱了。这是一类隐性问题silent failure我们排查了很久才定位到。3.3 梯度如何穿过隐藏层隐藏层对齐前向计算时教师的hidden states要从计算图中取出用一个detach()操作截断梯度。如果不截断梯度会同时穿过教师网络和学生网络不仅显存爆炸还会导致训练极其不稳定。反过来学生的hidden states不能detach。我们要让梯度流经学生网络从而更新学生的参数。这句看似简单的话很多人写代码时写反或者漏写损失直接变零而不自知。我这里记录一下我们蒸馏训练中几个关于梯度安全和训练稳定的核心经验学生的输入对应的logits必须自己算不能直接用教师token对应的logits替代隐藏层对齐时教师的hidden states必须detach梯度裁剪非常关键建议设置max_grad_norm1.0防止个别样本导致梯度爆炸只用序列前70%的token参与隐藏层对齐后30%由于教师对尾部token预测置信度低噪声偏大跳过反而更稳。4. 实战数据准备、代码实现与超参调节4.1 数据准备环节进入实战环节我直接按我们模拟项目X的流程来讲这是经过完整验证的方案。我在网上看到的蒸馏教程大多对数据准备一笔带过但实际上这块决定生死。4.1.1 数据从哪来蒸馏数据第一个来源是开源的通用语料比如悟道、C4、RedPajama这类量大但和你的下游任务可能不匹配。第二个来源是领域语料比如客服场景就把客服对话记录、工单、FAQ整理出来。这个最关键因为白盒蒸馏即使通用能力保住了领域能力没有的话等于白干。第三个来源是合成数据用教师模型本身生成一批“伪问题-伪答案”再人工过滤。这块质量层次不齐要严格过滤。我们的经验比例是领域语料通用语料 64。别迷信纯领域语料全领域语料训练会让小模型丧失常识和通用推理能力。4.1.2 数据怎么清洗清洗这一步没有太高深的算法核心是规则。我们的清洗规则如下长度过滤过滤掉少于50个字符的短样本太短的样本学不到任何句式结构。重复度过滤用simhash算样本间的相似度相似度大于0.85的只保留其中一条。质量过滤用教师模型给样本打分低于0.6分的直接扔掉。具体做法是让教师模型输出“这段数据是否能用于模型训练”用它的自评判断。标签噪声处理如果教师模型在样本上的预测置信度低于0.5说明这个样本对模型来说也搞不清楚关系直接丢掉更稳妥。清洗完的数据量大约是原始数据的65%到70%是很正常的损耗。这一步骤没有做好后面蒸馏出来的模型会非常“怪”时好时坏。4.1.3 关于在线蒸馏还是离线预计算这是一个重要的工程选型问题。蒸馏时所有训练样本都需要跑一遍教师前向。离线方式提前把所有样本的教师logits保存下来存成.npy或.npz文件训练时直接读取即可。好处是速度快教师前向只跑了一次但代价是同样的数据在多个epoch反复使用时教师输出是固定的损失函数单调学生可能过拟合教师噪声。在线方式每个batch都实时算教师logits数据可以动态增广教师信号有变化蒸馏效果更稳定。代价是训练速度慢大约比离线方式慢1.5倍。我们最后用了折中方案离线预计算教师logits但把数据按5个epoch打乱每个epoch采样不同的子集。4.2 蒸馏循环代码详解先把代码里的核心几个变量说清楚后面看代码不迷路teacher_model教师模型qwen-7B或类似规模的大模型。student_model学生模型一个1.5B左右的Transformer结构模型。teacher_tokenizer和student_tokenizer各自的tokenizer。alpha、beta、gamma三个损失权重。temperature蒸馏温度T。核心训练循环如下import torch import torch.nn.functional as F from torch.cuda.amp import autocast, GradScaler def train_distill_step(batch, teacher_model, student_model, teacher_tokenizer, student_tokenizer, alpha0.5, beta0.8, gamma0.3, temperature3.0): # 1. 分别编码输入 teacher_inputs teacher_tokenizer(batch[text], return_tensorspt, paddingTrue, truncationTrue, max_length512).to(cuda) student_inputs student_tokenizer(batch[text], return_tensorspt, paddingTrue, truncationTrue, max_length512).to(cuda) # 2. 教师前向不计算梯度 with torch.no_grad(): teacher_outputs teacher_model(**teacher_inputs, output_hidden_statesTrue, return_dictTrue) teacher_logits teacher_outputs.logits # [batch, seq_len, teacher_vocab_size] teacher_hidden teacher_outputs.hidden_states # tuple of tensors # 3. 学生非自回归前向注意这里用了双向注意力 student_outputs student_model( input_idsstudent_inputs[input_ids], attention_maskstudent_inputs[attention_mask], output_hidden_statesTrue, return_dictTrue, use_cacheFalse # 非自回归模式不需要缓存 ) student_logits student_outputs.logits # [batch, seq_len, student_vocab_size] student_hidden student_outputs.hidden_states # 4. 词表对齐只计算交集 token 上的 KL common_vocab list(set(teacher_tokenizer.vocab.keys()) set(student_tokenizer.vocab.keys())) # 将两个 logits 在词表维度上作投影这里为了简要只展示 masked 思路 # 实际需要一个 Teacher2Student 转换矩阵或者统一映射到 id 空间 # 下面代码假设已经通过映射将 student_logits 映射到 teacher 词表空间 # 5. 计算 KL 散度教师分布是 target学生是对数概率 teacher_prob F.softmax(teacher_logits / temperature, dim-1, dtypetorch.float32) student_log_prob F.log_softmax(teacher_logits_mapped / temperature, dim-1, dtypetorch.float32) kl_loss F.kl_div(student_log_prob, teacher_prob, reductionbatchmean) * (temperature ** 2) # 6. 隐藏层对齐只对齐前70%的 token seq_len student_hidden[0].size(1) cut_len int(seq_len * 0.7) layer_loss 0.0 for s_layer, t_layer in zip(student_hidden[-4:], teacher_hidden[-4:]): # 教师的层要截断、detach t_layer_slice t_layer[:, :cut_len, :].detach() s_layer_slice s_layer[:, :cut_len, :] # 简单线性映射处理维度差异如果有或者直接MSE layer_loss F.mse_loss(s_layer_slice, t_layer_slice) # 7. 标准CE损失用教师预测的soft标签或真实标签都可以 targets batch[labels].to(cuda) ce_loss F.cross_entropy(student_logits.view(-1, student_logits.size(-1)), targets.view(-1), ignore_index-100) # 8. 总损失 total_loss alpha * ce_loss beta * kl_loss gamma * layer_loss return total_loss, {ce_loss: ce_loss.item(), kl_loss: kl_loss.item(), layer_loss: layer_loss.item()}这个训练循环有几个细节我说明一下代码里没有体现但实际训练时极其重要。第一学生前向时设置use_cacheFalse。这是一个很关键的性能优化点。训练时不需要缓存历史KV关闭后显存占用可以下降30%以上而且不会影响精度。第二KL散度计算时温度平方的补偿。公式里我写了(temperature ** 2)这个是对温度缩放带来的梯度尺度补偿不加的话温度越高梯度越小训练会变慢。第三隐藏层对齐我选择了最后的4层。从实验数据来看对齐最后4层的收益最高全部层对齐反而会因为浅层差异太大而拉低训练稳定性。4.3 超参组合与搜索建议我们项目里最终采用的超参组合是参数数值说明temperature3.0分布平滑程度alpha0.5CE损失权重beta0.8KL损失权重gamma0.3隐藏层对齐权重learning_rate5e-5AdamW优化器batch_size16单卡batch8卡并行max_length512序列最大长度warmup_steps200学习率预热max_grad_norm1.0梯度裁剪epochs6总训练轮数优化器AdamWbeta(0.9,0.999), eps1e-8我建议你在自己项目里跑的时候按这个优先级调参数温度T - KL权重β - 学习率 - 批次大小。温度对蒸馏效果影响最明显先把它定准了再动其他的。对于学习率蒸馏中学习率过低会拟合不动过高会导致隐藏层对齐剧烈震荡。我用5e-5在多数场景都稳定小规模数据可以把学习率提到1e-4加快收敛。4.4 我以为训练收敛了结果踩了一个大坑蒸馏训练到了第3个epoch时loss曲线很漂亮地往下走每一步都降那么一点点这时候人很容易放松警惕觉得万事大吉。但我拿出来做验证集的测试发现生成的答案在语法上很通顺在关键事实上频繁出错。后来排查发现这是典型的“学生模型在拟合教师模型的冗长废话”。原因是KL散度占的权重太高学生模型学到的分布过于靠近教师模型而教师模型本身有个问题它在训练语料中见过太多类似文本会输出非常“固定搭配”的废话样本。小模型把这些废话全部模仿学习进去了丢失了自己的泛化能力。我的解决方案是把CE损失的权重加大到和KL接近然后对教师logits做top-k截断只保留前50个token的概率其余置0后重新归一化。这样学生的注意力资源集中在教师真正高置信度的部分不被长尾token干扰生成质量明显回升。5. 常见问题与排查技巧实录顺着上面那个坑我把整个蒸馏项目过程中遇到的所有问题整理成一个速查表大家在复现时如果遇到同样问题可以直接对照排查。问题现象核心原因解决方案KL loss 一直下降但生成效果不涨教师信号过强学生死记教师风格调大CE权重降低KL权重训练到第2个epoch后loss突然飙升学习率过大或梯度爆炸调低学习率开启梯度裁剪验证集loss低但生成重复温度过低分布过锐学生学到了重复模式提高温度到3-4隐藏层对齐loss不降学生模型结构差异过大强行对齐效果差换黑盒蒸馏或只对齐输出层显存不足OOM学生双向注意力教师前向同时占用显存扩大梯度累积步数或先预计算教师logits训练速度太慢非自回归学生前向计算太慢关闭use_cache必要时梯度检查点学生输出胡说八道KL权重过高掩盖了真实标签学习设置CE损失最小占比为0.25蒸馏后模型丢失了指令跟随能力只蒸馏了生成能力没对齐对话格式混入指令微调数据用SFT辅助训练5.1 模式坍塌的深层原因和应对模式坍塌mode collapse是蒸馏中最容易出问题的地方也是很多开源项目在实践中遇到的最大瓶颈。症状是小模型学会了输出“安全但无意义”的文本看似语法通顺但信息量为零。比如你问它“什么是相对论”它输出“相对论是一种关于时间、空间和运动的物理学理论”而不会进一步解释。这个问题的根源在于KL散度的非对称性。KL(p||q)对学生q的惩罚是不对称的当教师p某位置有高概率而学生q没有时损失很大但教师p某位置是低概率而学生q给了高概率时损失很小。这就导致学生养成了“broad safe prediction”的习惯——在所有可能的位置上都给一个居中的概率而不是精准预测。我应对这个问题的三个实操建议第一降低温度到2.0到2.5之间限制教师分布的过分平滑 第二KL loss使用“反向KL”变体也就是KL(q||p)让小模型的分布尽量落在教师分布内部的“mode covering”区域 第三强制学生在训练时看到一定比例的真实硬标签CE损失不能完全只依赖软标签。我们最终用的是“annealed temperature”方案前200步用温度5让学生看到分布的形状200到400步降到3400步以后固定2.0。这样既保住了分布信息又避免了过度平滑导致模式坍塌。5.2 为什么前面教师hidden对齐总是带偏学生隐藏层对齐虽然效果好但极易带偏学生。主要原因是教师模型的hidden states和学生模型的hidden states虽然在同一特征空间但它们的表征分布是不同的。打个比方教师表征空间像北京的地图比例尺学生表征空间像上海的地图比例尺。强制让学生模仿北京地图的坐标它只会混乱。MiniLM在这方面的经验是不直接对齐hidden states而是对齐两个模型在hidden states上的self-attention关系矩阵attention score矩阵。这样对齐的就不是绝对数值而是相对关系。后来我们项目也采用了这个思路不再直接用MSE对齐hidden states而是先计算学生hidden states的gram矩阵batch内的相似度矩阵再去对齐教师的gram矩阵效果一下就稳了。这个改动说起来轻巧实际是把“绝对表征对齐”换成了“相对关系对齐”。从原理上看相对关系对齐更稳健因为它天然地允许学生和教师有不同的尺度只需要保持内部结构相似即可。5.3 显存控制与训练规模的权衡白盒蒸馏最劝退新手的其实是显存。一个7B教师模型全量前向已经吃掉了不少显存学生再双向注意力全文计算显存占比很快逼近极限。我的建议是蒸馏时不需要像微调大模型那样用全量参数显存优化策略但要特别小心三点第一如果你用的是多卡训练教师模型和学生模型可以分别放在不同的卡上通信走all_gather显存压力会小很多 第二logits的计算可以分离教师logits提前算好存内存学生模型实时推理时直接从内存读kg的logits标签这样主显存和内存做了显隐交换 第三直接把分布式训练的gradient checkpoint打开用计算换显存几乎必用。还有一点经验之谈蒸馏任务中batch size不是越大越好。我发现batch size从16提到32后KL loss反而有点抖动因为不同batch的教师分布差异变大了。这不是大模型蒸馏特有的问题而是动态蒸馏中常见的现象。如果你有足够显存与其增大batch size不如多累积几步梯度再更新。6. 蒸馏后的量化部署与多任务扩展6.1 蒸馏和量化一对黄金搭档蒸馏后的模型因为分布平滑量化时损失更小。这个是白盒蒸馏的一个独特附加优势。教师模型的分布往往很锐利峰值高INT8量化时峰值附近的信息容易丢失学生模型经过蒸馏学到的分布相对平滑一些对量化的鲁棒性更好。我们实测的结果是直接用教师模型INT8量化掉点约2.1%而蒸馏后的1.5B模型再做INT8量化与FP16版本相比掉点仅在0.7%。这个差距对部署场景非常关键。量化方式上我们用的方案是蒸馏完成后用一小部分领域数据作为校准集做GPTQ量化或者AWQ。注意校准集的文本必须来自和蒸馏数据同一分布不然量化后的模型在领域任务上会明显退化。校准集大小和最终精度有直接关系我的经验数据如下用64条样本校准精度下降约1.5%用128条样本校准精度下降约0.9%用256条样本校准精度下降约0.4%此后增加样本收益递减。所以校准集定位在128到256条之间足够不要贪多更不要直接拿全部训练数据来做校准那样过拟合校准集反而让模型在真实场景中泛化更差。6.2 多任务蒸馏的扩展打法一个经常被问到的问题是能不能用一个大模型同时蒸馏出多个专业小模型当然可以这就是多任务蒸馏。我的经验是多任务蒸馏最好避免一次把所有任务的数据全部混合训练。混合训练会导致任务之间的干扰学完客服的模型再去学代码生成前面的知识会部分遗忘。常用的方案是“单模型多任务蒸馏LoRA”。操作方式是先用全量数据蒸馏一个通用的小模型基座然后在这个基座上针对每个任务分别微调一个LoRA适配器。推理时根据任务类型动态加载对应的LoRA。这样既保住了通用能力又不互相污染。这个方案的另一个好处是对显存极友好多个LoRA可以常驻显存每个只占几十MB。部署时无需切换模型只是切换LoRA权重延迟可以忽略不计。6.3 从蒸馏到部署一次完整的落地记录我们项目最终部署的结构是1.5B学生模型INT8量化LoRA多任务适配动态温度采样。实测下来单卡A10G可以并发处理大约8个请求单token延迟在30到40ms左右。相比原来7B模型的20个并发时要排队的情况吞吐量提升了约3倍效果上在领域评测集保持了94%的教师模型分数完全满足业务要求。我印象最深的一件事是部署后在线上跑了两个月模型一次都没崩过。这在过去直接用大模型部署的时候是不敢想的。蒸馏带来的不仅是模型体积缩小更关键的是模型的行为模式在向教师靠近的同时学会了规避教师的高风险长尾输出所以稳定性得到了明显改善。写在最后的几点体会蒸馏这件事我在训练到第三轮的时候最想放弃因为看着loss降了但自己拿数据测效果时总觉得小模型是个“呆子”。后来慢慢调参、换对齐位置、改温度计划才一步步把效果拉上来。现在回头看核心的体会其实就三条。第一不要盲信公开的超参。网上流传的蒸馏超参大多是在特定模型组合下试出来的换一个教师学生结构可能完全失效。先在一个很小的数据子集上快速迭代多轮找到合适参数方向再全量跑这比直接全量盲跑效率高很多。第二教师模型不一定是越强越好。教师在领域数据上过强会导致小模型完全丧失自身的泛化能力。我后来专门观察过一个在领域数据上F1达到0.93的教师和一个F1只有0.88的“中间教师”前者蒸馏出的小模型在领域外泛化反而更差。适当弱化教师甚至给教师分布加一点dropout效果反而好。第三蒸馏数据质量大于数据数量。我们项目用过2万条自动清洗的数据不如后来精挑细选出的6000条高质量数据效果好。小模型容量有限喂给它的每条数据都应该精雕细琢而不是指望海量数据来砸出效果。尤其是白盒蒸馏教师隐藏层的对齐信号非常强如果数据本身有噪声对齐出来的表征也会带上噪声这比黑盒蒸馏更难纠正。最后再分享一个小技巧给正在跑复现的朋友蒸馏训练时可以每隔固定步数把学生模型当前生成的文本在验证集上打印出来看。loss曲线会骗人文本生成质量不会。我在训练时就靠这招一路排查比只看loss有效得多。毕竟蒸馏的目标从来都不是把loss降多低而是让模型真正学到教师模型里那些没被说出口的东西。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑