模型轻量化基础:量化(PTQ/QAT)、剪枝、蒸馏的基本原理

各位同学,咱们今天聊点实在的。BEV模型要往MCU上搬,第一个拦路虎就是模型太大。你想想看,一个BEV模型动辄几十上百兆,而MCU的Flash通常就几兆,RAM更是以KB计。这差距,好比让大象钻进蚂蚁窝。

所以,模型轻量化是必修课。我这些年做嵌入式AI,踩过最多的坑就在这。今天咱们把量化、剪枝、蒸馏这三个基本功讲透。说白了,就是给模型「减肥」的三种不同思路。

一、量化:把高精度数字塞进小口袋

量化是什么?我打个比方。你有一张照片,原本是24位真彩色,现在要存成256色的GIF图。颜色少了,但大体轮廓还在。量化就是这个道理。

模型训练时,权重和激活值通常用FP32(32位浮点数)表示。量化就是把这些值用更少的位数来表示,比如INT8(8位整数)。一个参数从32位变成8位,体积直接缩到四分之一。

这里有个关键点:量化不是简单的截断。它有一套完整的数学映射关系。

量化核心公式:

r = S * (q - Z)

其中:

  • r 是真实的浮点数值
  • q 是量化后的整数值
  • S 是缩放因子(scale)
  • Z 是零点偏移(zero point)

嗯,这个公式你得记住。我刚开始做量化时,就是没搞懂Z的作用,结果模型精度直接崩了。后来才发现,零点偏移是为了让浮点0能精确映射到整数0,这对很多激活函数(比如ReLU)至关重要。

1.1 训练后量化(PTQ)

PTQ,全称Post-Training Quantization。顾名思义,模型训练完了,我再做量化。这是最省事的方法。

具体怎么做?你拿一小部分校准数据,跑一遍推理,统计出每一层激活值的分布范围。然后根据这个范围,算出S和Z。整个过程不需要反向传播,不需要重新训练。

优点:快,省事,不需要原始训练数据也能做。

缺点:精度损失可能比较大。尤其是BEV模型里那些对数值敏感的结构,比如注意力机制,PTQ一上去精度可能掉3-5个点。

我的经验:PTQ适合那些已经训得比较「结实」的模型。如果模型本身过拟合严重,PTQ反而可能把过拟合的部分给「抹平」了,精度不降反升。我在一个车道线检测项目里就遇到过这种情况。

1.2 量化感知训练(QAT)

QAT,Quantization-Aware Training。说白了,就是在训练过程中就模拟量化效果,让模型学会适应低精度。

做法是这样的:在训练的前向传播中,插入伪量化节点(fake quantization node)。这些节点会把浮点值先量化再反量化,模拟出量化后的精度损失。反向传播时,梯度还是用浮点计算。这样训练出来的模型,对量化操作就「免疫」了。

# 伪代码示例:QAT中的前向传播
def forward(x):
    # 模拟量化
    q = quantize(x, scale, zero_point)  # 浮点 -> 整数
    x_hat = dequantize(q, scale, zero_point)  # 整数 -> 浮点
    # 注意:x_hat 和 x 之间已经有误差了
    return layer(x_hat)

你想想看,这就像让一个人戴着沙袋跑步训练,等比赛时把沙袋摘了,他跑得比谁都快。QAT就是这个道理。

优点:精度损失极小,通常能控制在0.5%以内。

缺点:需要重新训练,耗时。而且需要原始训练数据和完整的训练流程。

注意:QAT不是万能的。我曾经在一个BEV深度估计模型上试QAT,结果精度反而比PTQ还差。后来排查发现,是因为那个模型用了大量SiLU激活函数,QAT的伪量化节点对SiLU的梯度估计不准。所以,具体问题具体分析。

二、剪枝:砍掉不重要的连接

剪枝的思路更直接。神经网络里有很多参数,其实贡献很小。把这些参数砍掉,模型照样能工作。就像一棵树,剪掉枯枝烂叶,主干反而长得更好。

剪枝分两种:结构化剪枝和非结构化剪枝。

类型 做法 对硬件的影响
非结构化剪枝 把权重矩阵中绝对值小的元素置零 产生稀疏矩阵,通用硬件加速困难
结构化剪枝 直接砍掉整个通道或卷积核 模型结构变窄,硬件友好

我个人习惯用结构化剪枝。为什么?因为MCU上的推理库,对稀疏矩阵的支持普遍很差。你费了半天劲把参数置零了,结果推理时还是按稠密矩阵算,白忙活。

剪枝的流程一般是:

  1. 训练一个完整的大模型
  2. 评估每个通道的重要性(比如用L1范数)
  3. 砍掉不重要的通道
  4. 微调(fine-tune)恢复精度
  5. 重复2-4步,直到达到目标压缩率

避坑指南:我曾经一次性砍掉50%的通道,结果模型直接崩了。后来学乖了,每次只砍5%-10%,然后微调几个epoch。这叫「慢剪枝」,效果比一次性砍要好得多。

三、蒸馏:让学生向老师学习

蒸馏的思路最有意思。你有一个大模型(老师),精度很高,但跑不动。你想训练一个小模型(学生),让它学到大模型的「知识」。

怎么学?不是直接学老师的输出结果,而是学老师的「软标签」。什么意思呢?

比如分类任务,老师模型对一张图片的输出可能是:猫0.7,狗0.2,鸟0.1。这个分布里包含了老师对各类别相似度的理解。学生模型不仅要学「这是猫」,还要学「猫和狗有点像,和鸟不太像」。

# 蒸馏损失函数示意
loss = alpha * hard_loss(y_student, y_true) + 
       (1 - alpha) * soft_loss(y_student, y_teacher / T)

这里的T是温度系数。T越大,软标签的分布越平滑,学生能学到更多「暗知识」。

蒸馏对BEV模型特别有用。因为BEV模型通常有一个很重的backbone(比如ResNet-50或Swin-T),你可以用蒸馏把backbone换成轻量级的MobileNet或ShuffleNet,然后用老师模型去指导它。

我推荐的做法:

  • 用BEVFormer或BEVDet作为老师
  • 用轻量级backbone + 简化版transformer作为学生
  • 在BEV特征空间做蒸馏,而不是在输出层做

这样学生模型能学到BEV空间的结构化知识,效果比单纯在输出层蒸馏好得多。

四、三种方法的组合策略

在实际项目中,这三种方法不是互斥的。我通常按这个顺序来:

  1. 先蒸馏:用大模型指导小模型训练,得到一个精度尚可的轻量模型
  2. 再剪枝:对蒸馏后的模型做结构化剪枝,进一步压缩
  3. 最后量化:用QAT做量化感知训练,把模型压到INT8

你想想看,这个流程是有逻辑的。蒸馏先让模型变「聪明」,剪枝再让它变「苗条」,量化最后让它变「轻便」。三步走下来,模型体积能压缩到原来的十分之一甚至二十分之一,而精度损失控制在2%以内。

注意:这个顺序不是绝对的。如果你的模型本身已经很小了,直接上QAT可能就够了。我见过有人把剪枝放在蒸馏前面,效果也不错。关键是要根据你的模型和硬件特点来调整。

好了,今天的内容就到这里。量化、剪枝、蒸馏,这三个基本功你掌握了,BEV模型上MCU就有了底气。下一节咱们聊具体的移植工具链,到时候我会分享一些我在STM32和ESP32上踩过的坑。