4、训练后量化(PTQ):原理、校准与实战策略

训练后量化,英文叫 Post-Training Quantization,简称 PTQ。说白了,就是模型训练完了,我们直接把它从 FP32 转成 INT8 甚至更低比特。不需要重新训练,成本低,见效快。

我刚开始接触 PTQ 的时候,总觉得这玩意儿有点「玄学」。明明模型精度好好的,一量化就掉点。后来踩的坑多了,才慢慢摸清楚门道。今天我把这些经验掰开揉碎讲给你听。

PTQ 的原理:到底在做什么?

PTQ 的核心就一句话:找到 FP32 数值到 INT8 数值的最佳映射关系。

FP32 的取值范围大约是 ±3.4×10³⁸,而 INT8 只有 -128 到 127。要把一个大范围压缩到小范围,必然有精度损失。关键是怎么让损失最小。

数学上,量化可以表示为:

Q = round( (R / S) + Z )

其中:

  • R 是原始的 FP32 值
  • S 是缩放因子(Scale)
  • Z 是零点偏移(Zero Point)
  • Q 是量化后的 INT8 值

反量化就是逆过程:

R ≈ (Q - Z) × S

嗯,这里要注意:S 和 Z 怎么选,直接决定了量化精度。选大了,数值范围覆盖全了但精度粗;选小了,精度细但容易截断。

核心矛盾:量化误差 = 截断误差 + 舍入误差。截断误差来自超出范围的数值被强行截断,舍入误差来自浮点转整数的精度丢失。两者此消彼长,需要找到平衡点。

校准数据集的选择:别随便拿数据

PTQ 需要一个「校准数据集」来统计每层激活值的分布。这个数据集选不好,量化效果直接崩。

我在项目中遇到过一件事:用 ImageNet 的验证集做校准,模型在公开测试集上表现不错。但一部署到客户的实际场景,精度直接掉了 5 个点。为什么?因为校准数据分布和实际数据分布不一致。

选校准数据集,我总结了三条原则:

  1. 代表性:校准数据必须来自真实部署场景。别偷懒用公开数据集凑数。
  2. 多样性:覆盖各种边缘情况。比如自动驾驶场景,晴天、雨天、夜晚的数据都要有。
  3. 适量性:不是越多越好。我一般用 500-2000 张图片就够了。太多反而让校准过程变慢,收益却很小。

我的小技巧:如果场景数据不好收集,可以用训练集的一个子集,但一定要做数据增强。随机裁剪、颜色抖动、加噪声,让校准数据「看起来」更像真实场景。

PTQ 的常见策略:四种主流方法

PTQ 不是只有一种做法。根据你的精度要求和硬件限制,可以选择不同的策略。我按推荐程度从低到高给你排个序。

策略 原理 精度损失 适用场景
Min-Max 量化 直接取激活值的 min 和 max 作为量化范围 较大 对精度不敏感的任务
Percentile 量化 去掉一定比例的极端值,用剩余范围量化 中等 有少量离群值的模型
KL 散度量化 最小化 FP32 和 INT8 分布的 KL 散度 较小 大多数通用场景
逐通道量化 每个通道独立计算 S 和 Z 最小 对精度要求高的场景

1. Min-Max 量化

最简单粗暴。直接拿校准数据里每层的最大值和最小值作为量化边界。优点是快,缺点是容易被离群值带偏。我曾经试过一个模型,激活值里有个别像素特别大,结果 Min-Max 量化后大部分数值都挤在 INT8 的 0 附近,精度直接崩了。

2. Percentile 量化

改进版。去掉前 0.1% 或 1% 的极端值,用剩下的范围量化。这样能避免离群值的干扰。我一般先用 Percentile 99.9% 试试,如果精度不够再调低。

3. KL 散度量化

这是目前最主流的方法。NVIDIA TensorRT 默认用的就是它。原理是:把 FP32 的分布和 INT8 的分布做对比,找到让两者 KL 散度最小的阈值。说白了,就是让量化后的分布「看起来最像」原始分布。

# 伪代码示意
def find_optimal_threshold(fp32_distribution):
    best_threshold = None
    min_kl_divergence = float('inf')
    
    for threshold in candidate_thresholds:
        # 截断 FP32 分布
        truncated = clip(fp32_distribution, -threshold, threshold)
        # 量化到 INT8
        quantized = quantize(truncated)
        # 反量化回来
        dequantized = dequantize(quantized)
        # 计算 KL 散度
        kl = kl_divergence(fp32_distribution, dequantized)
        
        if kl < min_kl_divergence:
            min_kl_divergence = kl
            best_threshold = threshold
    
    return best_threshold

4. 逐通道量化

精度最高的方法。每个通道独立计算缩放因子和零点偏移。代价是计算量大了,而且有些硬件不支持。我一般在部署到 GPU 时用这个,CPU 上慎用。

曾经踩过的坑:逐通道量化在卷积层效果很好,但在全连接层上反而可能掉精度。因为全连接层的每个输出通道和所有输入通道相关,独立量化会破坏这种关联性。遇到这种情况,我建议全连接层用逐层量化,卷积层用逐通道量化。

实战中的避坑指南

说了这么多理论,最后分享几个我实际踩过的坑:

  • 校准数据量太少:少于 100 张图片,统计出来的分布不可靠。我至少用 500 张。
  • 忽略 BN 层的融合:量化前一定要把 BN 层融合到卷积层里。否则 BN 的参数会干扰量化统计。
  • 一次性量化所有层:我建议逐层量化,每量化一层就验证一次精度。这样能快速定位是哪一层出了问题。
  • 忽视量化后的微调:如果 PTQ 后精度还是不够,可以尝试 QAT(量化感知训练)。但那是另一个话题了。

你想想看,PTQ 其实就是在精度和速度之间做 trade-off。没有银弹,只有最适合你场景的方案。我个人的习惯是:先用 KL 散度量化跑一遍,精度损失在 1% 以内就收工。如果超过 1%,再尝试逐通道量化或者调整校准数据集。

PTQ 量化策略选择流程图 开始 PTQ 量化 选择校准数据集(500-2000样本) 精度要求高? (损失 < 1%) 是 逐通道量化 + KL 散度 否 Percentile 量化 或 Min-Max 量化 验证量化后精度

这张图帮你快速决策:精度要求高就走右边,要求不高就走左边。但不管走哪条路,最后一定要验证。我见过太多人量化完就跑,结果部署上去才发现精度不对。

好了,PTQ 的核心内容就这些。记住一句话:校准数据选得好,量化成功一半。另一半靠策略选择和反复验证。

最后一个小建议:如果你用的是 PyTorch,可以试试 torch.ao.quantization 里的 PTQ 工具。TensorRT 的话,直接用 trtexec 的 --int8 选项,它会自动帮你做校准。但不管用哪个工具,理解原理才能用好它。

可可精品资料库