目录

端侧模型知识蒸馏实战:把大模型的本事教给手机小模型

上次我们用 INT8 量化把 MobileNetV2 从 14 MB 压到 3.5 MB(见端侧模型 INT8 量化实战 )。但量化是事后减肥:模型该多大还多大,该多笨还多笨。今天聊另一种压缩思路——知识蒸馏(Knowledge Distillation),它发生在训练阶段:让小模型拜大模型为师,直接把大模型脑子里的判断力继承下来。

一句话概括:量化管体积,蒸馏管智商。两者不冲突,先蒸馏再量化,是端侧部署的标准组合拳。

蒸馏在学什么:软标签与温度

2015 年 Hinton 提出蒸馏时发现一个反直觉现象:训练好的大模型(教师)除了输出"这是猫"的硬结论,其 softmax 概率分布里还藏着"猫和老虎像、猫和卡车不像"这类暗知识。普通训练只让模型看硬标签(0 或 1),把这类信息全扔了。

蒸馏的做法是:把教师模型的输出除以一个温度 T 再做 softmax,得到"放软"的概率分布,作为学生的训练目标。T 越大分布越平,暗知识暴露得越多;T=1 时就退化回普通输出。损失函数是软目标 KL 散度与硬标签交叉熵的加权和,权重 alpha 控制两者比例。

实战:ResNet18 当老师,MobileNetV2 当学生

CIFAR-10 完整可跑代码(PyTorch):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torchvision.transforms as T

# 教师:ResNet18,约 11.7M 参数,FP32 约 47 MB
teacher = torchvision.models.resnet18(weights="DEFAULT")
teacher.fc = nn.Linear(512, 10)            # 换成 CIFAR-10 的 10 类
# 注意:教师要先在目标任务上微调好,软标签才准

# 学生:MobileNetV2,约 3.4M 参数,FP32 约 14 MB
student = torchvision.models.mobilenet_v2(weights=None)
student.classifier[1] = nn.Linear(1280, 10)

def kd_loss(s_logits, t_logits, labels, T=4.0, alpha=0.7):
    s_soft = F.log_softmax(s_logits / T, dim=1)
    t_soft = F.softmax(t_logits / T, dim=1)
    kl = F.kl_div(s_soft, t_soft, reduction="batchmean") * T * T
    ce = F.cross_entropy(s_logits, labels)
    return alpha * kl + (1 - alpha) * ce

transform = T.Compose([T.RandomCrop(32, padding=4),
                       T.RandomHorizontalFlip(),
                       T.ToTensor(),
                       T.Normalize((0.4914, 0.4822, 0.4465),
                                   (0.2470, 0.2435, 0.2616))])
loader = torch.utils.data.DataLoader(
    torchvision.datasets.CIFAR10(root="./data", train=True,
                                 download=True, transform=transform),
    batch_size=128, shuffle=True)

teacher.eval()
opt = torch.optim.Adam(student.parameters(), lr=1e-3)
for images, labels in loader:
    with torch.no_grad():
        t_logits = teacher(images)         # 教师软标签,一次前向搞定
    loss = kd_loss(student(images), t_logits, labels)
    opt.zero_grad()
    loss.backward()
    opt.step()

两个细节值得注意:KL 散度是在除以 T 之后的 logits 上算的,梯度会缩水 T 倍,所以要乘回 T * T 恢复梯度量级;教师全程 no_grad,只推理不出梯度,显存占用很低,手机显存不够也跑得动。

效果与组合拳

以 CIFAR-10 为例(参考量级,具体数值取决于你的数据):从零硬训的 MobileNetV2 约 85%-90% 准确率,跟蒸馏后的学生通常能再涨 1-3 个点,逼近 ResNet18 的水平,而体积只有教师的四分之一、推理更快。数据越少、任务越难,蒸馏收益越明显——这正是端侧小模型偏爱蒸馏的原因。

蒸馏完再走一遍 INT8 量化流程 ,14 MB 的学生模型压到 3.5 MB,且因为学生输出分布更平滑,量化掉点通常比直接量化教师更小。

大模型场景同样适用:DistilBERT 用蒸馏把 BERT-base 从 1.1 亿参数减到 6600 万,保留约 97% 的性能(官方数据)。注意它的输入是文本,说明蒸馏对模态不挑,图像、语音、文本流程通用。

开源工具箱

项目 GitHub 适用场景
torchdistill yoshitomo-matsubara/torchdistill 通用蒸馏框架,KD、FitNets、attention transfer 等论文方法现成可用
timm huggingface/pytorch-image-models 训练脚本原生支持 --teacher-model,一行参数开启蒸馏
MMRazor open-mmlab/mmrazor OpenMMLab 出品,剪枝加蒸馏一体化,适合结构压缩一起做
Transformers huggingface/transformers DistilBERT 官方训练脚本,文本模型蒸馏的样板
TF Model Optimization tensorflow/model-optimization TF 侧量化感知训练加蒸馏的官方方案

选型建议:PyTorch 图像任务先试 timm,够简单;要复现论文方法用 torchdistill;检测、分割这类复杂任务,MMRazor 覆盖面最全。

踩坑清单

  1. 教师别太强:教师与学生能力差距过大时学生学不动,梯度里全是噪声。CIFAR-10 这种小任务,ResNet18 教 MobileNetV2 刚好,没必要上 ResNet152。
  2. T 和 alpha 要一起调:常用区间 T=3-8、alpha=0.5-0.9。T 太大分布太平,暗知识稀释成噪声;太小退化回硬标签训练。
  3. 教师必须先微调好:直接用 ImageNet 预训练教师换任务,前几轮软标签不准,收敛慢。
  4. 学生小 batch 注意 BN:学生网络浅、BN 统计不稳时,攒大 batch 或用累积梯度。
  5. 蒸馏本质是免费监督信号:软标签相当于额外告诉学生"类别之间存在相似性",和 dropout、数据增强不冲突,可以叠加。

端侧部署的压缩三件套——蒸馏(训练期)、剪枝(结构)、量化(存储)——蒸馏是唯一能提升小模型精度的环节。先拜师,再瘦身,最后上设备,这条路比单吊量化稳健得多。