4. 量化精度调优
量化,说白了就是把模型从浮点数世界拽到整数世界。这个过程一定会丢精度,但怎么丢得少、丢得可控,就是咱们今天要聊的核心。
我刚开始做端侧部署时,觉得量化就是个除法加取整。结果第一次把 MobileNet 量化到 INT8 后,精度直接掉了 5 个点,模型在手机上跑得飞快,但识别结果完全没法用。嗯,从那以后我才真正开始研究量化校准这件事。
4.1 校准数据与校准流程
量化校准的第一步,是收集激活值的分布。你需要一小部分有代表性的数据,跑一遍前向推理,把每一层的激活值统计下来。
- 校准集大小:我个人习惯用 500~2000 张图片,太少分布不准,太多浪费时间。
- 数据代表性:必须覆盖真实场景。我见过有人用 ImageNet 校准集去量化一个工业缺陷检测模型,结果部署后精度崩了——因为分布完全不一样。
- 逐层统计:每一层的激活值范围差异很大,不能混在一起算。
4.2 KL 散度校准:最小化信息损失
KL 散度校准是目前用得最广的方法,TensorRT 和很多 NPU 工具链都在用。它的核心思想很简单:找一个阈值,把原始浮点分布截断后量化,让量化后的分布和原始分布尽可能相似。
为什么不是直接取 min/max?因为浮点分布往往有长尾。直接取 min/max 会把整个动态范围拉得很大,导致有效数值区间被压缩,精度损失反而更大。
KL 散度的计算过程大致如下:
- 统计激活值的直方图(通常 2048 个 bin)。
- 从某个候选阈值开始,把直方图截断并量化到 256 个 bin。
- 计算量化后分布与原始分布的 KL 散度。
- 遍历所有候选阈值,选 KL 散度最小的那个。
# 伪代码示意
def kl_calibrate(hist, bin_edges, num_bins=2048):
best_kl = float('inf')
best_threshold = 0
for threshold_idx in range(128, num_bins):
# 截断直方图
clipped_hist = hist[:threshold_idx].copy()
# 量化到 256 个 bin
quantized_hist = quantize(clipped_hist, 256)
# 计算 KL 散度
kl = compute_kl(clipped_hist, quantized_hist)
if kl < best_kl:
best_kl = kl
best_threshold = bin_edges[threshold_idx]
return best_threshold
4.3 MSE 最小化校准:直接优化数值误差
KL 散度关注的是分布相似性,而 MSE 最小化更直接——它追求量化前后的数值误差最小。
我个人更喜欢 MSE 校准,尤其是在回归任务或对数值精度敏感的模型上。它的计算更直观:
def mse_calibrate(tensor, num_bits=8):
best_mse = float('inf')
best_scale = 0
min_val, max_val = tensor.min(), tensor.max()
# 在 min/max 之间搜索最优 scale
for scale in np.linspace(0.1, max_val, 1000):
quantized = quantize(tensor, scale, num_bits)
mse = np.mean((tensor - quantized) ** 2)
if mse < best_mse:
best_mse = mse
best_scale = scale
return best_scale
MSE 校准的优点是稳定,缺点是计算量比 KL 散度大。不过现在硬件快了,这点开销可以接受。
| 校准方法 | 优化目标 | 适用场景 | 计算开销 |
|---|---|---|---|
| KL 散度 | 分布相似性 | 分类任务、分布长尾明显 | 中等 |
| MSE 最小化 | 数值误差 | 回归任务、检测任务 | 较高 |
| Min-Max | 无 | 简单基线 | 低 |
| 百分比截断 | 去除极端值 | 分布有异常值 | 低 |
4.4 每通道量化 vs 每层量化
这是量化精度调优里最容易忽略但又影响巨大的选择。
- 每层量化(Per-Layer):整层共用一个 scale 和 zero_point。优点是计算简单,硬件实现容易。缺点是如果层内不同通道的数值范围差异大,精度损失会很明显。
- 每通道量化(Per-Channel):每个通道独立计算 scale 和 zero_point。精度更高,但计算量也更大。
我做过一个对比实验:在 MobileNetV2 上,每层量化掉 3 个点,每通道量化只掉 0.5 个点。差距非常明显。
4.5 量化误差分析与补偿
量化误差不是均匀分布的。有些层对量化敏感,有些层几乎无感。你需要找到那些敏感层,单独处理。
我常用的分析方法:
- 逐层对比浮点和量化后的输出,计算 SNR(信噪比)或 MSE。
- 把 SNR 最低的 top 5 层标记为敏感层。
- 对敏感层做特殊处理:比如保留 FP16、使用更大的校准集、或者做量化感知训练。
补偿手段主要有三种:
- 偏置校正:量化误差会导致输出偏置漂移,统计后减去这个偏置。
- 权重均衡:调整相邻层的权重分布,让量化更均匀。这个技巧在 Depthwise 卷积层特别有效。
- 混合精度:敏感层用 FP16,其他层用 INT8。我有个项目就是靠这个把精度从掉 2% 拉回到只掉 0.3%。
4.6 量化精度调优流程总览
下面这张图是我在实际项目中总结的量化精度调优流程,你可以把它当作一个检查清单:
这个流程我用了很多项目,基本能覆盖 90% 的量化精度问题。记住一个原则:先校准,再分析,最后补偿。不要一上来就上混合精度,那是最后的手段。