4. 模型轻量化技术
模型轻量化,说白了就是给模型「减肥」。你想想看,一个几百兆的 ResNet-152 在 GPU 上跑得飞快,但放到边缘端的 Jetson Nano 或者树莓派上,帧率可能直接掉到个位数。我刚开始做工业部署时,就吃过这个亏——模型精度再高,跑不起来等于零。
工业缺陷检测对实时性要求很苛刻。一条产线每分钟要检测上百个工件,留给算法的推理时间往往只有几十毫秒。所以,轻量化不是可选项,而是必选项。这一章,我把常用的四板斧——剪枝、蒸馏、量化、ONNX 优化——挨个讲透。
核心目标:在精度损失 ≤1% 的前提下,将模型体积压缩 4~8 倍,推理速度提升 3~10 倍。
4.1 模型剪枝:砍掉冗余参数
模型剪枝,就是去掉那些不重要的权重或通道。一个训练好的模型,很多参数其实接近零,对最终输出贡献很小。把它们砍掉,模型变小了,推理速度也上去了。
我个人习惯把剪枝分为两类:
| 类型 | 粒度 | 硬件加速 | 典型场景 |
|---|---|---|---|
| 非结构化剪枝 | 单个权重 | 差(稀疏矩阵加速有限) | 学术研究、模型压缩 |
| 结构化剪枝 | 通道 / 卷积核 | 好(直接减少计算量) | 工业部署、边缘端 |
我在项目中遇到过一个问题:非结构化剪枝后模型体积确实小了,但推理速度几乎没变。为什么?因为边缘端的 CPU/GPU 对稀疏矩阵的支持很弱,你剪掉的权重还是占着内存位置。所以,工业部署我强烈建议用结构化剪枝。
我的经验:结构化剪枝的剪枝率建议从 30% 开始,每次增加 10%,观察精度变化。一旦精度掉到 1% 以上,就回退到上一档。我常用的工具是 PyTorch 的 torch.nn.utils.prune 和 NVIDIA 的 TensorRT 剪枝 API。
4.2 知识蒸馏:大模型教小模型
知识蒸馏的思路很巧妙——用一个大的教师模型去指导一个小学生模型训练。教师模型输出的是「软标签」,包含了类别之间的相似度信息。比如,一张图片是猫还是狗,教师模型可能输出「猫 0.7,狗 0.3」,这比硬标签「猫 1.0,狗 0.0」信息量大多了。
蒸馏的损失函数一般长这样:
loss = α * KL(教师软标签 || 学生软标签) + (1-α) * CE(学生输出, 硬标签)
α 是平衡系数,我一般设 0.7。温度 T 控制软标签的平滑程度,T 越大,类别分布越均匀。工业缺陷检测中,T 取 4~8 效果比较好。
避坑指南:我曾经在钢材表面缺陷数据集上做蒸馏,发现学生模型学到的特征不够细粒度,漏检了一些微小裂纹。后来我把教师模型的中间层特征也加入蒸馏目标——这叫特征蒸馏,效果立竿见影。所以,如果你的任务对细节敏感,别只蒸馏最后的 logits。
4.3 量化:INT8 / FP16
量化,就是把模型参数从 FP32 降到 FP16 甚至 INT8。FP16 几乎无损,推理速度能翻倍;INT8 精度会掉一点,但速度能提升 3~4 倍。
两种主流量化方式:
- PTQ(训练后量化):训练完直接转,简单粗暴。我用过 TensorRT 的 PTQ,校准集选 500 张代表性图片就够了。
- QAT(量化感知训练):在训练过程中模拟量化误差,精度更高,但需要重新训练。我一般只在 PTQ 精度掉超过 2% 时才用 QAT。
FP16 和 INT8 怎么选?我列个对比:
| 指标 | FP16 | INT8 |
|---|---|---|
| 精度损失 | 几乎为 0 | 0.5% ~ 2% |
| 推理加速 | 1.5x ~ 2x | 3x ~ 4x |
| 硬件要求 | 大部分 GPU 支持 | 需要 Tensor Core / DPU |
| 部署难度 | 低 | 中 |
嗯,这里要注意:INT8 量化对激活值的分布很敏感。如果激活值集中在很小的范围,量化后的精度会崩。我习惯先跑一遍校准集,看看激活值的直方图,再决定量化策略。
4.4 ONNX 导出与优化
ONNX 是模型部署的「通用语言」。不管你是 PyTorch、TensorFlow 还是 PaddlePaddle 训练的模型,都能转成 ONNX,然后在不同推理引擎上跑。
导出 ONNX 的代码很简单:
import torch
import torch.onnx
model = torch.load('model.pth')
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(
model,
dummy_input,
'model.onnx',
opset_version=11,
input_names=['input'],
output_names=['output'],
dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}}
)
导出后,我一般会用 ONNX Runtime 或 TensorRT 做优化:
- 算子融合:把 Conv + BN + ReLU 合并成一个算子,减少内存读写。
- 常量折叠:把计算图中固定不变的部分提前算好,省掉推理时的计算。
- 动态形状支持:工业场景中 batch size 经常变化,ONNX 的动态轴配置必须开。
我的习惯:导出 ONNX 后,先用 onnxruntime.quantization.quantize_dynamic 做一次动态量化,再转 TensorRT 引擎。这样两步走,精度和速度都能兼顾。另外,opset_version 别用太新的,11 或 13 最稳,太新的版本有些边缘端推理引擎不支持。
4.5 组合策略:我的推荐流程
四种技术单独用效果有限,组合起来才是王道。我总结了一套流程,在多个工业项目中验证过:
- 结构化剪枝:剪掉 40%~50% 的通道,精度掉 0.5% 以内。
- 知识蒸馏:用原始大模型做教师,训练剪枝后的学生模型,把精度拉回到接近原始水平。
- INT8 量化:对蒸馏后的模型做 PTQ,精度再掉 0.5%~1%。
- ONNX + TensorRT 优化:导出 ONNX,转 TensorRT 引擎,做算子融合和内存优化。
这套流程走下来,模型体积能压缩到原来的 1/8,推理速度提升 5~8 倍,精度损失控制在 1% 以内。我在一个 PCB 缺陷检测项目里就是这么干的,最终在 Jetson Xavier NX 上跑到了 120 FPS,完全满足产线需求。
最后提醒一句:轻量化不是一锤子买卖。每次调整参数后,一定要用真实的产线数据做验证。我见过有人用公开数据集调好了量化参数,一到现场就崩——因为现场的光照、噪声分布跟训练集不一样。所以,校准集最好从产线现场采集。