4. 模型轻量化技术(下):知识蒸馏原理、量化技术(INT8/FP16)、混合精度训练、模型蒸馏实战案例
上一讲我们聊了剪枝和低秩分解,今天继续把轻量化剩下的硬骨头啃完。知识蒸馏、量化、混合精度训练——这三板斧在端侧部署里几乎必用。我自己的经验是,光靠剪枝往往压不到理想体积,必须组合使用。
4.1 知识蒸馏:大模型当老师,小模型当学生
知识蒸馏的核心思想很简单:用一个已经训练好的大模型(Teacher)去指导一个小模型(Student)学习。小模型不光看真实标签,还要模仿大模型的“思考过程”。
为什么有效?因为大模型的输出分布里藏着“暗知识”。比如分类猫和狗,大模型对“猫”输出0.9,对“狗”输出0.08,对“老虎”输出0.02——这0.02就告诉学生:猫和老虎有点像。真实标签(one-hot)可不会告诉你这个。
4.1.1 蒸馏的损失函数
蒸馏损失 = 软标签损失 + 硬标签损失。公式长这样:
L = α * KL(softmax(Teacher/T), softmax(Student/T)) + (1-α) * CE(Student, hard_label)
这里的T是温度系数。T越大,软标签分布越平滑,暗知识越明显。我习惯T取3~5,α取0.7左右。具体调参看任务,图像分类和语音识别的敏感度不一样。
4.1.2 蒸馏的几种模式
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 离线蒸馏 | Teacher提前算好logits,学生离线训练 | 大部分场景,简单稳定 |
| 在线蒸馏 | Teacher和学生同时训练,互相学习 | Teacher不够强时,或数据量极大 |
| 自蒸馏 | 自己当自己的老师,不同epoch互教 | 没有大模型可用时,聊胜于无 |
我个人最常用离线蒸馏。省事,稳定,效果可预期。在线蒸馏虽然理论上更优,但训练成本翻倍,MCU场景下不划算。
4.2 量化技术:从FP32到INT8/FP16
量化说白了就是把模型参数的精度降低。FP32的权重占4字节,INT8只要1字节,直接压缩到1/4。但代价是精度会掉。
4.2.1 量化原理
量化的数学本质是映射:
q = round(r / S) + Z
r是浮点值,S是缩放因子,Z是零点偏移。反量化就是:
r = (q - Z) * S
嗯,这里要注意:S和Z的选择直接影响精度。我见过有人直接用min-max映射,结果离群点把整个量化范围撑大了,小数值全被截断。建议用百分位法,去掉前后0.1%的极端值。
4.2.2 INT8 vs FP16
| 类型 | 存储大小 | 精度损失 | 硬件支持 |
|---|---|---|---|
| FP32 | 4字节 | 基准 | 所有平台 |
| FP16 | 2字节 | 极小(约0.1%) | 部分MCU带FPU |
| INT8 | 1字节 | 中等(1~3%) | 需专用指令或查表 |
MCU上我建议优先试FP16。很多Cortex-M7/M33系列支持半精度浮点,几乎无痛迁移。INT8虽然省得更多,但需要做校准集、处理量化敏感层,工作量翻倍。
4.3 混合精度训练:又快又准的折中方案
混合精度训练不是部署技术,是训练技术。它的思路是:前向传播和反向传播用FP16加速,权重更新用FP32保精度。
为什么会这样?因为梯度更新时,FP16的精度不够,小梯度直接变0了。我见过一个模型,全FP16训练直接不收敛,换成混合精度就好了。
4.3.1 混合精度训练流程
- 复制一份FP32的权重副本(Master Weights)
- 前向传播时,把权重转成FP16,计算输出
- 反向传播计算FP16梯度
- 把梯度转回FP32,更新Master Weights
- 重复
PyTorch里用torch.cuda.amp就能实现。但MCU场景下,我们通常是在PC上做混合精度训练,然后导出INT8/FP16模型部署到MCU。
4.4 模型蒸馏实战案例:MobileNetV2蒸馏到TinyNet
光说不练假把式。我拿一个实际项目举例:把MobileNetV2(参数量3.5M)蒸馏到一个TinyNet(参数量0.5M),部署在Cortex-M4上。
4.4.1 实验配置
| 项目 | 值 |
|---|---|
| Teacher模型 | MobileNetV2 (FP32) |
| Student模型 | TinyNet (自定义,0.5M参数) |
| 数据集 | CIFAR-100 |
| 温度T | 4 |
| α | 0.7 |
| 优化器 | Adam, lr=1e-3 |
4.4.2 核心代码片段
# 蒸馏训练核心逻辑
def distillation_loss(student_output, teacher_output, labels, T=4, alpha=0.7):
# 软标签损失
soft_loss = KL_div(
F.log_softmax(student_output / T, dim=1),
F.softmax(teacher_output / T, dim=1)
) * (T * T) # 温度补偿
# 硬标签损失
hard_loss = F.cross_entropy(student_output, labels)
return alpha * soft_loss + (1 - alpha) * hard_loss
# 训练循环
for epoch in range(100):
for images, labels in dataloader:
teacher_logits = teacher_model(images) # 提前算好,离线蒸馏
student_logits = student_model(images)
loss = distillation_loss(student_logits, teacher_logits, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
4.4.3 结果对比
| 模型 | 精度(CIFAR-100) | 参数量 | 推理时间(Cortex-M4, 100MHz) |
|---|---|---|---|
| MobileNetV2 (Teacher) | 78.2% | 3.5M | 无法部署(内存超限) |
| TinyNet (直接训练) | 62.5% | 0.5M | 45ms |
| TinyNet (蒸馏后) | 68.3% | 0.5M | 45ms |
蒸馏后精度提升了5.8个百分点,推理时间不变。这就是知识蒸馏的价值——白捡的精度。
4.5 本章知识体系
下面这张图把本章的核心逻辑串起来了。你可以看到,知识蒸馏、量化、混合精度训练三者不是孤立的,而是可以组合使用的流水线。