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左右。具体调参看任务,图像分类和语音识别的敏感度不一样。

我的经验: 蒸馏时Teacher模型最好用FP32精度推理,不要量化。Teacher的精度损失会直接传导给学生。我曾经踩过这个坑,Teacher用了INT8,学生怎么训都上不去,后来换成FP32才解决。

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虽然省得更多,但需要做校准集、处理量化敏感层,工作量翻倍。

避坑指南: 我曾经在STM32H743上部署一个分类模型,FP16跑得好好的,换成INT8后精度掉了5%。排查了两天,发现是BatchNorm层量化后统计量偏移。解决方案:把BN层融合进卷积再量化。

4.3 混合精度训练:又快又准的折中方案

混合精度训练不是部署技术,是训练技术。它的思路是:前向传播和反向传播用FP16加速,权重更新用FP32保精度。

为什么会这样?因为梯度更新时,FP16的精度不够,小梯度直接变0了。我见过一个模型,全FP16训练直接不收敛,换成混合精度就好了。

4.3.1 混合精度训练流程

  1. 复制一份FP32的权重副本(Master Weights)
  2. 前向传播时,把权重转成FP16,计算输出
  3. 反向传播计算FP16梯度
  4. 把梯度转回FP32,更新Master Weights
  5. 重复

PyTorch里用torch.cuda.amp就能实现。但MCU场景下,我们通常是在PC上做混合精度训练,然后导出INT8/FP16模型部署到MCU。

关键点: 混合精度训练产出的模型,直接转INT8往往比全FP32训练后转INT8精度更高。因为训练过程中模型已经适应了低精度噪声。

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个百分点,推理时间不变。这就是知识蒸馏的价值——白捡的精度。

我的建议: 蒸馏后的模型再做INT8量化,精度还能再保一保。上面这个TinyNet蒸馏后量化到INT8,精度只掉了0.7%,最终达到67.6%,完全满足项目需求。

4.5 本章知识体系

下面这张图把本章的核心逻辑串起来了。你可以看到,知识蒸馏、量化、混合精度训练三者不是孤立的,而是可以组合使用的流水线。

模型轻量化技术(下)知识体系 知识蒸馏 量化技术 混合精度训练 Teacher-Student 软标签损失 INT8量化 FP16量化 FP16前向 FP32更新 组合使用:蒸馏 → 量化 → 部署 实战流水线建议 1. 训练Teacher模型(FP32) 2. 蒸馏得到Student模型 3. 量化Student模型到INT8/FP16 → 部署到MCU