3. 训练后量化(PTQ):流程、校准与实战踩坑
训练后量化,也就是 PTQ,是模型压缩里最「轻量」的一招。你不需要重新训练模型,拿一个训好的浮点模型,跑几步校准数据,就能把它变成 INT8 甚至 INT4 的版本。说白了,就是给模型「减重」的同时,尽量不让它「失忆」。
我在实际项目中,PTQ 用得比 QAT 多得多。为什么?因为快。一个百兆模型,QAT 可能要训几天,PTQ 几十分钟就搞定了。但快不代表简单,坑也不少。今天我就把 PTQ 的完整流程、校准数据怎么选,以及我踩过的几个大坑,一次性讲清楚。
3.1 PTQ 的核心流程
PTQ 的流程其实不复杂,我习惯把它拆成 5 步。你想想看,每一步都对应一个「量化误差」的来源,搞懂了这些,你就能对症下药。
- 加载浮点模型:把训练好的 FP32 模型读进来。这一步没什么好说的,注意模型结构要跟训练时完全一致。
- 插入量化节点:在模型的权重和激活值后面,插入伪量化节点(FakeQuant)。这些节点会模拟量化-反量化的过程,但不真的改变模型结构。
- 运行校准数据:喂一批校准数据给模型,跑前向推理。这一步的目的是收集每一层激活值的统计分布(比如 min/max、直方图)。
- 计算量化参数:根据收集到的统计信息,计算每一层的 scale 和 zero_point。不同的校准方法(如 MinMax、KL 散度、MSE)会算出不同的参数。
- 导出量化模型:把浮点权重替换成 INT8 整数,同时保存 scale 和 zero_point。最终得到一个可以直接在推理引擎上跑的量化模型。
关键点:PTQ 的精度损失,主要来自第 3 步和第 4 步。校准数据选得不好,或者量化参数算得不准,模型精度就会崩。
下面这张图,是我自己画的一个 PTQ 流程框架图,你可以对照着看。
3.2 校准数据集的选择
校准数据集,是 PTQ 里最容易被忽视、但影响最大的环节。我见过不少团队,随便拿几百张训练集图片就跑校准,结果模型上线后精度惨不忍睹。
为什么会这样?因为校准数据决定了量化参数的「视野」。如果校准数据跟实际部署数据分布不一致,量化参数就会偏,模型精度自然就掉。
我总结了几条选校准数据的经验:
- 数量不是越多越好:我个人习惯用 200-500 张。太少,统计信息不稳定;太多,校准时间变长,而且可能引入冗余信息。
- 要覆盖「边界情况」:比如目标检测模型,校准数据里要有不同大小、不同光照、不同角度的目标。如果全是正样本,量化参数对背景的表示就会很差。
- 避免「数据泄露」:校准数据不能跟测试集重叠。否则你测出来的精度是假的,上线就现原形。
- 优先用验证集:如果验证集分布跟部署场景一致,直接用验证集做校准是最稳妥的。
小技巧:如果你不确定校准数据选得好不好,可以跑一个「量化敏感性分析」。逐层对比量化前后的激活值分布,哪一层偏差大,就重点调整那一层的校准方法。
3.3 校准方法对比
不同的校准方法,本质上是不同的「统计策略」。我整理了一个表格,方便你对比。
| 校准方法 | 原理 | 适用场景 | 精度表现 |
|---|---|---|---|
| MinMax | 直接取激活值的全局 min/max | 分布均匀、无异常值的层 | 一般,易受 outlier 影响 |
| KL 散度 | 最小化量化前后分布的 KL 散度 | 大部分卷积层、全连接层 | 较好,NVIDIA 常用 |
| MSE | 最小化量化前后的均方误差 | 对精度敏感的层(如分类头) | 较好,但计算稍慢 |
| Percentile | 取指定百分位(如 99.9%)作为 max | 存在少量 outlier 的层 | 稳健,适合工业场景 |
我在项目中,通常先用 KL 散度跑一遍,然后对精度掉得多的层,单独换成 MSE 或 Percentile 再试。说白了,没有万能的方法,得根据模型和数据的特性来调。
3.4 我在实际项目中踩过的坑
这部分是我最想跟你分享的。PTQ 看起来简单,但坑都在细节里。我挑几个印象深刻的说说。
坑一:校准数据跟训练数据分布不一致
我曾经做一个人脸识别模型,校准数据用的是公开数据集(LFW),但实际部署场景是安防摄像头。结果量化后模型精度掉了 5 个点。后来我把校准数据换成安防场景的 300 张图片,精度只掉了 0.3 个点。教训:校准数据一定要贴近真实部署场景。
坑二:某些层对量化特别敏感
有一次量化一个语义分割模型,其他层都好好的,就最后一层卷积精度崩了。排查了半天,发现是这层的激活值分布特别窄(集中在 0-0.1 之间),MinMax 量化后信息全丢了。后来我把这层单独改成 FP16 推理,问题解决。嗯,这里要注意:混合精度量化有时候比纯 INT8 更实用。
坑三:校准数据数量太少
我刚开始做 PTQ 时,图省事,只用了 50 张图片做校准。结果量化模型在测试集上精度波动很大,每次校准结果都不一样。后来我把校准数据增加到 300 张,波动就消失了。建议:校准数据至少 100 张,200-500 张是比较稳妥的范围。
坑四:忽略 BN 层的融合
这个坑比较隐蔽。有些框架在量化前没有把 BN 层融合到卷积层里,导致量化参数计算错误。我建议你在做 PTQ 之前,先确认模型是否已经做了 BN 融合。如果没做,量化精度会莫名其妙地差。
3.5 一个简单的 PTQ 代码示例
下面我用 PyTorch 的 torch.quantization 库,给你演示一个完整的 PTQ 流程。代码不长,但每一步都有注释。
import torch
import torch.quantization
# 1. 加载预训练浮点模型
model = torch.load('model_fp32.pth')
model.eval()
# 2. 设置量化配置(使用最常用的 MinMax + per_tensor)
model.qconfig = torch.quantization.get_default_qconfig('fbgemm')
# 3. 插入 FakeQuant 节点
model = torch.quantization.prepare(model, inplace=False)
# 4. 运行校准数据(假设 calib_loader 是 DataLoader)
with torch.no_grad():
for images, _ in calib_loader:
model(images)
# 5. 计算量化参数并转换
model = torch.quantization.convert(model, inplace=False)
# 6. 保存量化模型
torch.save(model.state_dict(), 'model_int8.pth')
print("PTQ 完成,量化模型已保存。")
这段代码跑起来很快,但你要注意:calib_loader 里的数据,一定要是你精心挑选过的校准集。别随便拿训练集的前 100 张就跑了。
3.6 总结与建议
PTQ 是模型压缩的「第一站」,也是性价比最高的方法。你不需要改模型结构,不需要重新训练,只要花几十分钟跑一遍校准,就能拿到一个 INT8 模型。
但 PTQ 不是万能的。如果你的模型对精度要求极高(比如医疗影像、自动驾驶),或者模型本身很小(比如 MobileNet),PTQ 的精度损失可能不可接受。这时候,你就得考虑 QAT 了。
最后,给你三个实操建议:
- 先跑 PTQ,再决定要不要 QAT:PTQ 成本低,先看看精度损失能不能接受。能接受,就省了 QAT 的功夫。
- 校准数据要「精」不要「多」:200 张精心挑选的图片,比 2000 张随便选的图片效果好得多。
- 逐层检查量化误差:如果精度掉得厉害,别慌。逐层对比量化前后的激活值分布,找到「问题层」,单独处理。
PTQ 的坑,我踩过不少,但每次踩完都学到新东西。希望今天的分享,能帮你少走一些弯路。