4. 模型量化技术

模型量化,说白了就是给模型“减减肥、降降压”。

FP32 的模型跑在边缘设备上,又慢又吃内存。我刚开始做嵌入式部署时,一个 100MB 的模型塞进开发板,直接 OOM(内存溢出)。后来才意识到——不量化,根本没法落地。

4.1 为什么要量化?——从 FP32 到 INT8 的跨越

FP32 每个数占 4 字节,INT8 只占 1 字节。理论上,模型体积直接缩到 1/4,推理速度也能翻倍(取决于硬件对 INT8 的加速支持)。

但代价呢?精度损失。嗯,这里要注意——不是所有模型都能无损量化。我踩过的坑是:轻量模型(如 MobileNet)量化后精度掉得比大模型还厉害。

核心收益:
  • 模型体积减少 75%
  • 推理速度提升 2~4 倍(边缘端 NPU 加持下更明显)
  • 内存带宽占用降低,多路并发更稳

4.2 量化原理:映射与校准

量化本质就是做映射:把 FP32 的浮点范围“压缩”到 INT8 的整数范围。

公式其实不复杂:

scale = (max_val - min_val) / 255
zero_point = round(-min_val / scale)

# 量化
q = round(x / scale) + zero_point

# 反量化
x_hat = (q - zero_point) * scale

这里有两个关键参数:scale(缩放因子)和 zero_point(零点偏移)。

我个人习惯用 对称量化(zero_point=0),因为计算更简单,硬件实现也友好。但遇到 ReLU 输出全是正数的情况,非对称量化反而能保留更多信息。

避坑指南: 我曾经在量化一个检测模型时,发现某个通道的激活值范围特别窄(0.01~0.05),scale 算出来极小,导致量化后全是 0。后来加了 per-channel 量化,每个通道独立算 scale,问题才解决。

4.3 三种量化路线:PTQ、QAT、混合精度

目前业界主流就三条路。我按落地难度排个序:

方法 需要重训? 精度损失 部署难度 适用场景
后训练量化 (PTQ) 否 中~高 低 快速验证、大模型
量化感知训练 (QAT) 是 低 中 精度敏感场景
混合精度量化 可选 可控 高 关键层保护

4.3.1 后训练量化(PTQ)

PTQ 是最省事的方法。模型训练完,拿一小批校准数据跑一遍,统计出每层的激活值范围,然后直接转 INT8。

步骤很简单:

  1. 准备 100~500 张校准图片(别用训练集,用验证集)
  2. 跑一遍前向推理,记录每层的 min/max
  3. 计算 scale 和 zero_point
  4. 转换权重和激活值为 INT8

但 PTQ 有个致命问题—— outlier(离群值)。如果某层有个别值特别大,整个量化范围被拉宽,大部分值反而精度受损。

我曾经踩过的坑: 用 PTQ 量化一个 YOLOv5 模型,mAP 从 0.72 掉到 0.51。排查后发现是某个检测头的输出层有极端值。后来改用 KL 散度校准(不是简单的 min/max),mAP 回升到 0.68。

4.3.2 量化感知训练(QAT)

QAT 的思路是:在训练过程中就模拟量化误差,让模型自己学会“抗噪”。

具体做法是在前向传播时插入 伪量化节点(FakeQuant):

# 伪量化操作(PyTorch 风格)
def fake_quantize(x, scale, zero_point, qmin=-128, qmax=127):
    x = torch.clamp(x, min_val, max_val)
    x = torch.round(x / scale) + zero_point
    x = torch.clamp(x, qmin, qmax)
    x = (x - zero_point) * scale
    return x

反向传播时,round 操作不可导,怎么办?用 直通估计器(STE)——直接把梯度传过去,忽略 round 的梯度。

QAT 的精度通常比 PTQ 高 1~3 个点。代价是训练时间增加 20%~50%。

我的建议: 如果模型对精度要求极高(比如医疗影像、自动驾驶),别省 QAT 这一步。我做过一个分割模型,PTQ 掉 5 个点,QAT 只掉 0.5 个点——这 4.5 个点的差距,可能就是“能用”和“不能用”的区别。

4.3.3 混合精度量化

不是所有层都适合 INT8。有些层对精度极其敏感——比如第一层(输入层)、最后一层(输出层)、注意力机制中的 softmax。

混合精度量化的思路就是:敏感层保留 FP16 或 FP32,其他层用 INT8。

怎么判断哪些层敏感?我一般用 逐层精度损失分析:

  1. 先全量量化,记录每层输出的 SNR(信噪比)
  2. SNR 低于阈值的层,标记为“敏感层”
  3. 这些层回退到 FP16 或 FP32

这样做的好处是:模型体积只增加 5%~10%,但精度几乎无损。

4.4 量化工具链与实战对比

不同框架的量化支持差异很大。我整理了一张对比表:

框架 PTQ QAT 混合精度 边缘端支持
TensorRT ✅ 成熟 ✅ 需插件 ✅ 自动 Jetson 系列
ONNX Runtime ✅ 成熟 ⚠️ 实验性 ✅ 手动 通用
TFLite ✅ 成熟 ✅ 成熟 ⚠️ 有限 Android/ARM
OpenVINO ✅ 成熟 ⚠️ 实验性 ✅ 自动 Intel 系列
NCNN ✅ 成熟 ❌ 不支持 ⚠️ 手动 ARM/手机

我个人在 Jetson 上用得最多的是 TensorRT。它的 INT8 校准器 支持三种模式:

  • EntropyCalibratorV2:基于 KL 散度,通用性好
  • MinMaxCalibrator:简单粗暴,适合分布均匀的层
  • LegacyCalibrator:老版本兼容,不推荐
实战经验: 我一般先用 EntropyCalibratorV2 跑一遍,如果精度掉得厉害,再切到 MinMax 试试。有时候 MinMax 反而更好——因为 KL 散度对长尾分布不敏感。

4.5 量化全流程框架图

下面这张图总结了从 FP32 模型到 INT8 部署的完整流程:

模型量化全流程框架图 模型量化全流程框架图 FP32 预训练模型 选择量化路线 后训练量化 (PTQ) 校准数据 + 统计 min/max 量化感知训练 (QAT) 插入 FakeQuant + STE 混合精度量化 敏感层 FP16/FP32 校准 + 精度验证 对比 FP32 基线,检查精度损失 精度是否达标? 是 部署 INT8 模型 否 回退/换路线 重新选择路线 说明:PTQ 最快但精度损失最大;QAT 精度最高但需要重训;混合精度是折中方案

4.6 量化后的验证与调优

量化完别急着部署。我一般会做三件事:

  1. 逐层对比输出:跑 10 张图,对比 FP32 和 INT8 每层的输出余弦相似度。低于 0.99 的层重点关注。
  2. 端到端精度测试:在完整验证集上跑一遍,看 mAP/ACC 掉了多少。
  3. 性能基准测试:测延迟、吞吐量、内存占用。有时候 INT8 反而比 FP32 慢——因为某些硬件对 INT8 支持不好。
重要提醒: 量化后的模型一定要在目标硬件上测,不要在 PC 上测完就以为万事大吉。我遇到过 TensorRT 在 Jetson Xavier 上 INT8 加速 3 倍,但在 Orin 上只加速 1.5 倍——硬件差异很大。

4.7 总结:量化选型建议

最后给个实用建议:

  • 快速原型验证:PTQ + EntropyCalibratorV2
  • 精度敏感场景:QAT + 混合精度(保护输入输出层)
  • 资源极度受限:全 INT8 + 对称量化(计算更简单)
  • 多平台部署:ONNX Runtime + 混合精度(兼容性最好)

量化不是银弹,但它是边缘端部署的必经之路。你想想看,一个 100MB 的模型量化到 25MB,推理速度翻倍——这笔账怎么算都划算。