4、训练后量化(PTQ)技巧—— 不用重新训练,如何用校准数据集把模型压到INT8?

说实话,很多同学一听到「量化」两个字,第一反应就是:「完了,又要重新训模型了?」

其实不是的。训练后量化(Post-Training Quantization,简称 PTQ)就是专门解决这个问题的——你手里已经有一个训练好的 FP32 模型,不需要动它的权重,只需要做一点「校准」工作,就能把它压到 INT8,推理速度翻倍,显存减半。

我在项目中遇到过好几次这种情况:模型已经上线了,但延迟太高,客户催着优化。重新训练?时间不够。换模型?成本太高。最后就是用 PTQ 搞定的。嗯,今天我就把这里面的门道给你讲透。

4.1 PTQ 的核心思想:用「校准」代替「训练」

PTQ 的原理其实很简单。你想想看,FP32 转 INT8,本质上就是给每个权重和激活值找一个「缩放因子」(scale)和一个「零点偏移」(zero point)。

这个缩放因子怎么找?最笨的办法就是直接看权重的最大值和最小值,然后线性映射过去。但这样做有个问题——权重分布往往不是均匀的,直接硬映射会丢失大量信息。

所以 PTQ 引入了「校准数据集」的概念。你拿一小批有代表性的数据(通常几百到几千张图片或几十条文本),让模型跑一遍前向推理,同时统计每一层的激活值分布。然后根据这个分布,去计算最优的缩放因子。

关键点:校准数据集不需要标签,只需要输入数据。因为 PTQ 只关心激活值的分布,不关心预测结果对不对。

4.2 校准数据集怎么选?我踩过的坑

校准数据集的选择,直接决定了量化后的精度。我刚开始做 PTQ 时,图省事,随便拿了 100 张训练集图片做校准。结果量化后模型精度掉了 5 个点,查了半天才发现问题。

后来我总结了几条经验:

  • 数据分布要对齐:校准数据集必须和实际部署场景的数据分布一致。比如你做的是人脸检测,校准集里就不能全是风景图。
  • 数量不用太多:我个人习惯用 200-500 条数据就够了。再多,边际收益递减;再少,统计不稳定。
  • 多样性要够:不要只挑「好识别」的样本。极端情况、边缘案例也要包含进去,否则量化后遇到这些情况会崩。
  • 避免重复:校准集里不要有大量重复或高度相似的样本,否则统计出来的分布会偏。

小技巧:如果你实在不知道怎么选校准集,可以试试「随机采样 + 人工筛选」的组合。先随机抽 500 条,然后快速扫一遍,去掉明显异常的样本。

4.3 量化参数的计算方法:对称 vs 非对称

量化参数的计算,说白了就是找 scale 和 zero point。常见的方法有两种:

方法 原理 适用场景 精度表现
对称量化 scale = max(|min|, |max|) / 127,zero point = 0 权重量化(权重分布通常对称) 一般
非对称量化 scale = (max - min) / 255,zero point = round(-min / scale) 激活值量化(分布可能偏斜) 较好

我在实际项目中,权重一般用对称量化,激活值用非对称量化。为什么?因为权重的分布通常以 0 为中心,对称量化不会浪费表示范围;而激活值经过 ReLU 后全是非负的,非对称量化能更好地利用 INT8 的 256 个刻度。

4.4 校准算法:不止是 min-max

校准算法决定了你怎么从激活值分布中算出 scale 和 zero point。常见的算法有几种:

  • Min-Max 校准:直接取激活值的最小值和最大值。简单粗暴,但对 outlier 非常敏感。我遇到过激活值里有一个 100 的 outlier,其他值都在 0-10 之间,结果量化后大部分值都被压缩到了 0-10 的范围内,精度直接崩了。
  • Percentile 校准:取某个百分位(比如 99.9%)的值作为最大值,忽略极端 outlier。这个方法比 min-max 稳定很多,我个人比较推荐。
  • KL 散度校准:通过最小化 FP32 和 INT8 分布的 KL 散度,找到最优的截断阈值。这是 NVIDIA TensorRT 默认使用的方法,精度通常最好,但计算量也最大。
  • MSE 校准:最小化量化前后的均方误差。效果和 KL 散度接近,但实现更简单。

注意:校准算法没有绝对的好坏,取决于你的模型和数据。我建议你在项目中至少试两种方法(比如 Percentile 和 KL 散度),对比一下精度再决定。

4.5 实战:用 PyTorch 做 PTQ

说了这么多理论,咱们来点实际的。下面是一个用 PyTorch 做 PTQ 的完整流程:

import torch
import torch.quantization as quant

# 1. 加载预训练模型(FP32)
model = torch.load('model_fp32.pth')
model.eval()

# 2. 准备校准数据集(假设是 DataLoader)
calib_loader = get_calib_dataloader(batch_size=32, num_samples=200)

# 3. 配置量化参数
model.qconfig = quant.get_default_qconfig('fbgemm')  # 用于 x86 CPU
# 或者用:quant.get_default_qconfig('qnnpack')  # 用于 ARM CPU

# 4. 插入量化/反量化节点
model_prepared = quant.prepare(model, inplace=False)

# 5. 用校准数据集跑一遍前向
with torch.no_grad():
    for data, _ in calib_loader:
        model_prepared(data)

# 6. 转换为 INT8 模型
model_int8 = quant.convert(model_prepared, inplace=False)

# 7. 保存量化后的模型
torch.save(model_int8.state_dict(), 'model_int8.pth')

这段代码看起来简单,但有几个细节要注意:

  • 校准阶段不要 backward:只需要 forward,不需要计算梯度。所以记得用 torch.no_grad()
  • 校准数据要 shuffle:虽然不需要标签,但数据顺序会影响统计结果。我习惯把校准集 shuffle 一下。
  • 校准完成后要 convert:prepare 只是插入了量化节点,真正把权重转成 INT8 是在 convert 这一步。

4.6 精度回退:当量化后精度不够怎么办?

有时候,PTQ 做完后精度下降得比较厉害。别慌,有几个补救措施:

  • 检查校准数据集:是不是数据分布不对?样本数量够不够?这是最常见的原因。
  • 尝试不同的校准算法:从 min-max 换成 KL 散度,或者试试 percentile。
  • 部分层回退到 FP16:有些层对量化特别敏感(比如 attention 的 softmax 层),可以单独把这些层保留为 FP16 或 FP32。
  • 使用量化感知训练(QAT):如果 PTQ 实在搞不定,那就只能上 QAT 了。不过那是下一章的内容。

我的经验:80% 的 PTQ 精度问题,都是校准数据集没选好。先花时间把校准集搞对,比调算法参数有效得多。

4.7 本章知识体系

下面这张图总结了 PTQ 的核心流程和关键决策点:

PTQ 核心流程 FP32 预训练模型 准备校准数据集(200-500条) 选择校准算法(Min-Max / Percentile / KL / MSE) 前向推理,统计激活值分布 INT8 量化模型 权重对称量化 激活非对称量化 推荐:KL散度 备选:Percentile 精度不够? 尝试部分层回退

这张图把 PTQ 的五个核心步骤串起来了。你从 FP32 模型出发,准备好校准集,选好算法,跑一遍前向,最后得到 INT8 模型。每一步都有坑,但每一步也都有对应的解决方案。

4.8 总结

PTQ 是大模型部署中最实用的技术之一。它不需要重新训练,只需要一个校准数据集和几百行代码,就能把模型压到 INT8,推理速度提升 2-4 倍,显存占用减半。

我个人觉得,PTQ 是每个做模型部署的工程师必须掌握的技能。它不复杂,但细节很多。校准数据集怎么选、校准算法怎么配、精度不够怎么回退——这些经验都是在项目中一点点积累出来的。

嗯,今天的内容就到这里。记住一句话:PTQ 不是万能的,但 80% 的场景下它都够用。剩下的 20%,咱们下一章用 QAT 来解决。

核心要点回顾:

  • PTQ 不需要重新训练,只需要校准数据集
  • 校准数据集要分布对齐、数量适中、多样性够
  • 权重用对称量化,激活用非对称量化
  • 校准算法推荐 KL 散度或 Percentile
  • 精度不够时,先检查校准集,再尝试部分层回退

专注资料整理