3、训练后量化(PTQ):动态量化、静态量化、Calibration数据集的选择与构建
各位同学,咱们今天聊聊训练后量化(PTQ)。
说实话,在模型压缩这个领域,PTQ 是我个人用得最多的技术。为什么?因为它省事。你想想看,一个训练好的大模型,动辄几十上百 GB,要重新训一遍?成本太高了。PTQ 就像给模型做一次「瘦身手术」,不动筋骨,只减脂肪。
3.1 动态量化 vs 静态量化:到底选哪个?
先搞清楚概念。量化,说白了就是把模型里的 FP32 参数,映射到 INT8 甚至更低精度。但「怎么映射」这件事,分成了两派。
3.1.1 动态量化
动态量化,我习惯叫它「懒人量化」。为什么?因为它只在推理时,才把权重从 FP32 转成 INT8。激活值呢?还是 FP32,每次计算前临时转一下。
优点很明显:
- 实现简单——几行代码就能跑起来
- 无需数据——不需要额外的校准数据集
- 对精度影响小——因为激活值保留了高精度
缺点也直接:
- 推理速度提升有限——毕竟每次都要做类型转换
- 不适合计算密集型场景——比如 Transformer 的注意力层
我的经验:动态量化最适合那些「权重占大头,激活值计算量小」的模型。比如一些简单的 MLP 或者小规模的 LSTM。我在做语音唤醒词检测时,就用动态量化把模型从 50MB 压到 12MB,精度只掉了 0.3%。
3.1.2 静态量化
静态量化就「硬核」多了。它不光量化权重,连激活值也一起量化了。但这里有个关键问题:激活值的范围是动态变化的,你怎么知道它最大是多少?
这就需要用到 Calibration(校准)了。我们拿一批有代表性的数据,跑一遍推理,统计出每一层激活值的 min/max 或者分布,然后「固定」下来。之后推理时,就用这个固定的范围做量化。
优点:
- 推理速度更快——所有计算都在 INT8 下完成
- 内存占用更低——激活值也压缩了
缺点:
- 需要校准数据——而且数据质量直接影响精度
- 实现复杂——要处理各种边界情况
注意:静态量化不是万能的。我曾经在一个 NLP 模型上试过,校准集选得不好,结果精度直接掉了 5% 以上。后来换了更贴近真实分布的校准数据,才把精度拉回来。
3.2 Calibration 数据集:选对了,事半功倍
说到校准数据集,这是 PTQ 里最容易踩坑的地方。我见过太多人随便拿几百张图片或者几段文本就去校准了,结果模型上线后表现一塌糊涂。
为什么会这样?
因为校准数据集决定了量化时每一层的「缩放因子」和「零点」。如果校准数据不能代表真实推理时的数据分布,那量化后的模型就会「偏科」——对校准数据表现好,对真实数据表现差。
3.2.1 选择原则
我个人总结了三条原则:
- 代表性——校准数据必须覆盖真实场景中的各种情况。比如做文本分类,不能只拿新闻语料,还得有社交媒体、技术文档等。
- 多样性——不要只用「好样本」,也要用「坏样本」。比如图像分类,模糊的、光照不足的、遮挡的图片都要有。
- 适量性——不是越多越好。我一般用 500-2000 条数据就够了。太多了反而可能引入噪声。
小技巧:如果你不确定校准集够不够好,可以做一个「交叉验证」——用不同的校准集分别量化模型,然后在同一个测试集上对比精度。选精度最高的那个校准方案。
3.2.2 构建方法
构建校准数据集,我通常分三步走:
第一步:收集原始数据
从训练集中随机抽取一部分。但注意,不要只抽前 10% 的数据,要打乱后均匀抽取。我习惯用 random.shuffle 后再取。
第二步:数据增强
对抽取的数据做简单的增强。比如文本数据可以加一些拼写错误、同义词替换;图像数据可以做随机裁剪、旋转。这样能让校准集更「皮实」。
第三步:验证分布
跑一遍推理,统计每一层激活值的分布。如果发现某些层的分布和训练时差异很大,说明校准集可能有问题。这时候我会手动检查一下数据。
# 一个简单的校准集构建示例(PyTorch 风格)
import torch
from torch.utils.data import DataLoader, Subset
# 假设 train_dataset 是原始训练集
# 随机抽取 1000 条数据作为校准集
indices = torch.randperm(len(train_dataset))[:1000]
calib_dataset = Subset(train_dataset, indices)
calib_loader = DataLoader(calib_dataset, batch_size=32, shuffle=True)
# 跑一遍推理,收集激活值分布
def collect_activations(model, loader):
model.eval()
activations = {}
with torch.no_grad():
for batch in loader:
# 前向传播,同时注册 hook 收集中间层输出
# ...(具体实现略)
pass
return activations
3.3 实战中的坑与避坑指南
嗯,这里我要多说几句。PTQ 看着简单,但实际做起来,坑不少。
坑一:校准集和测试集分布不一致
我曾经做过一个情感分析模型,校准集用的是电影评论,结果上线后用户输入的是产品评价。模型直接「懵」了,准确率从 92% 掉到 78%。后来我重新用产品评价数据做了校准,才恢复正常。
坑二:校准数据量太少
有些人觉得 100 条数据就够了。其实不然。我测试过,100 条和 1000 条校准数据,量化后的模型精度能差 1-2 个百分点。尤其是对于深层网络,数据量太少会导致某些层的激活值范围估计不准。
坑三:忽略了异常值
校准数据里如果混入了异常值(比如全黑图片、全零文本),会导致量化范围被「拉宽」,从而降低精度。我建议在收集校准数据后,先做一轮异常值检测,把明显偏离分布的数据剔除掉。
避坑指南:我曾经在一个 BERT 模型上做 PTQ,校准集选了 500 条新闻文本。结果发现模型在「科技类」新闻上表现很好,但在「娱乐类」上差得离谱。后来一查,原来校准集里 80% 都是科技新闻。所以,一定要保证校准集的类别分布和真实场景一致。
3.4 总结一下
PTQ 的核心就三件事:
- 选对量化方式——动态量化省事但效果有限,静态量化效果好但需要校准
- 建好校准集——代表性、多样性、适量性,缺一不可
- 验证再验证——量化后一定要在真实测试集上跑一遍,别偷懒
最后说一句,PTQ 不是银弹。如果你的模型对精度极其敏感(比如医疗诊断、金融风控),那还是老老实实做 QAT(量化感知训练)吧。但如果你只是想快速部署一个 demo 或者对精度要求不那么苛刻,PTQ 绝对是最优解。
好了,这一章就到这里。下一章我们聊聊 QAT,那个更「硬核」的量化方法。