3、训练后量化(PTQ):PTQ流程详解,校准数据集的选择,静态量化与动态量化

训练后量化,简称 PTQ,是模型轻量化里最实用的技术之一。说白了,就是模型训练好之后,我们直接把它从 FP32 精度转成 INT8 甚至更低精度。不需要重新训练,成本低、见效快。

我在项目里遇到过不少团队,一上来就想搞 QAT(量化感知训练),结果折腾两周效果还不如 PTQ。其实对于大部分推理场景,PTQ 完全够用。你想想看,部署到边缘设备上,谁有耐心等你重新训一遍?

3.1 PTQ 的核心流程

PTQ 的流程并不复杂,但每一步都有坑。我习惯把它拆成四个阶段:

  1. 模型准备:加载训练好的 FP32 模型,确保它处于 eval 模式。
  2. 校准:用一小批数据跑一遍前向推理,收集每层的激活值分布。
  3. 量化参数计算:根据收集到的分布,算出每层的 scale 和 zero_point。
  4. 量化转换:把 FP32 权重和激活值映射到 INT8 范围,生成量化模型。

关键点:校准这一步决定了量化精度。校准数据集选不好,模型精度可能直接崩掉。

下面这张图是我自己整理的 PTQ 流程,你可以对照着看:

训练后量化(PTQ)核心流程 FP32 预训练模型 校准数据集 前向推理收集分布 计算 scale/zero_point 量化权重 + 激活值 INT8 量化模型 校准数据集通常只需要几百到几千张图片,不需要完整训练集 收集激活分布时,建议使用 100-500 个 batch 的数据

3.2 校准数据集的选择

校准数据集是 PTQ 里最容易翻车的地方。我曾经在一个图像分类项目里,随手拿了 100 张验证集图片做校准,结果量化后精度掉了 5 个点。后来换成训练集里随机抽的 200 张,精度只掉了 0.3%。

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

  • 代表性:数据分布要和真实场景一致。别拿猫的图片去校准一个做人脸识别的模型。
  • 多样性:覆盖尽可能多的类别和边缘情况。比如自动驾驶模型,晴天、雨天、夜晚都得有。
  • 适量性:不是越多越好。500 张图片通常就够了,再多反而可能引入噪声。

我的习惯:先拿 100 张跑一遍,看精度变化。如果掉点超过 1%,就加到 300 张。一般 200-500 张之间能稳定下来。

不同任务对校准数据量的敏感度也不一样。我整理了一个表格:

任务类型 推荐校准样本数 常见问题
图像分类 100-500 张 类别不平衡导致某些类精度骤降
目标检测 200-1000 张 小目标容易丢失,需要多包含小目标样本
语义分割 50-200 张 边缘像素量化误差大,建议选边缘清晰的图片
NLP 文本分类 500-2000 条 长文本截断后分布偏移,注意序列长度

3.3 静态量化 vs 动态量化

这两个概念很多人搞混。我简单解释一下:

动态量化:推理时实时计算激活值的 scale 和 zero_point。权重是提前量化好的,但激活值每跑一个 batch 都要重新算一遍。好处是精度损失小,坏处是推理速度提升有限。

静态量化:校准阶段就把激活值的量化参数算好,推理时直接用。权重和激活值都是提前量化好的。速度提升明显,但精度损失比动态量化大一点。

嗯,这里要注意:静态量化需要校准数据集,动态量化不需要。所以如果你手头没有合适的校准数据,动态量化是更稳妥的选择。

我曾经踩过的坑:在一个语音识别模型上,我直接用了静态量化,结果 WER(词错误率)从 8% 飙到了 15%。后来换成动态量化,WER 只涨到 9.2%。所以对于精度敏感的任务,优先试动态量化。

下面这个表格可以帮你快速决策:

对比维度 静态量化 动态量化
权重量化 提前量化 提前量化
激活值量化 提前计算,推理时固定 推理时实时计算
是否需要校准集 是 否
推理速度提升 高(2-4 倍) 中(1.5-2 倍)
精度损失 较小 极小
适用场景 对延迟要求高的场景 精度优先的场景

3.4 实战:用 PyTorch 做 PTQ

光说不练假把式。我拿 PyTorch 的 torch.quantization 举个例子。下面这段代码演示了静态量化的完整流程:

import torch
import torch.quantization

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

# 2. 配置量化后端
model.qconfig = torch.quantization.get_default_qconfig('fbgemm')

# 3. 准备量化(插入观察器)
model_prepared = torch.quantization.prepare(model)

# 4. 校准:跑 200 张图片收集分布
with torch.no_grad():
    for images, _ in calibration_loader:
        model_prepared(images)

# 5. 转换为量化模型
model_quantized = torch.quantization.convert(model_prepared)

# 6. 保存
torch.save(model_quantized.state_dict(), 'model_int8.pth')

动态量化更简单,一行代码搞定:

model_dynamic = torch.quantization.quantize_dynamic(
    model, {torch.nn.Linear}, dtype=torch.qint8
)

小技巧:如果你用的是 Transformer 模型,动态量化只量化 Linear 层就够了。Attention 层的激活值分布太复杂,量化后容易掉精度。

最后说一句,PTQ 不是万能的。如果模型本身很小(比如小于 1MB),量化带来的收益有限。但对于大模型,尤其是部署在手机或嵌入式设备上的模型,PTQ 几乎是必选项。你想想看,一个 100MB 的模型量化到 25MB,加载时间从 2 秒降到 0.5 秒,用户体验完全不一样。