4. 训练后量化(PTQ)
训练后量化,简称 PTQ。这是目前工业界落地大模型最常用的手段。说白了,就是模型训练完了,我们直接拿它做量化,不需要再动训练过程。
我刚开始接触 PTQ 时,觉得这玩意儿挺简单的——不就是把 FP16 的权重转成 INT8 吗?后来踩了不少坑才发现,里面的门道比想象中多得多。今天咱们就把 PTQ 的流程、校准集怎么选、以及逐层和逐通道量化的区别,一次性讲清楚。
PTQ 的基本流程
PTQ 的流程其实不复杂。我习惯把它拆成四个步骤:
- 加载预训练模型——把训练好的 FP16 或 FP32 模型读进来。
- 准备校准数据集——选一小部分有代表性的数据,跑一遍前向推理,收集激活值的分布信息。
- 计算量化参数——根据收集到的分布,算出每个张量的 scale 和 zero_point。
- 转换并保存量化模型——把原始权重和激活值映射到 INT8 范围,保存成量化后的格式。
嗯,这里要注意:第二步和第三步是 PTQ 的核心。校准集选得不好,量化参数算得不准,模型精度就会崩。我在项目中遇到过,有人随便拿了 100 张图片做校准,结果模型直接没法用。
核心要点:PTQ 不需要反向传播,也不需要训练数据。只需要一小部分校准数据,就能完成量化。这也是它为什么这么受欢迎的原因——成本低、速度快。
校准数据集的选择
校准数据集,说白了就是用来「摸清」模型激活值分布的一组数据。你想想看,量化本质上是在做数值映射。如果你不知道原始数据的分布范围,你怎么确定映射的边界?
我个人习惯,校准集的选择遵循三个原则:
- 代表性——校准集的数据分布,要尽量接近真实推理时的数据分布。比如你做的是对话模型,就别拿一堆代码文本去校准。
- 多样性——覆盖尽可能多的输入模式。单一模式会导致量化参数偏向某个方向。
- 适量性——不是越多越好。我一般用 100~500 条数据就够了。太多了反而增加校准时间,收益却很小。
我曾经犯过一个错误:用训练集的子集做校准。结果量化后的模型在验证集上表现很好,但一上线就崩了。后来发现,训练集和真实场景的数据分布有偏差。从那以后,我坚持用验证集或真实场景采样来做校准。
小技巧:如果你不确定校准集选多少合适,可以先用 100 条试试,然后逐步增加,观察精度变化。当精度不再明显提升时,就是最佳数量。
逐层量化 vs 逐通道量化
这两个概念,很多人容易搞混。我简单解释一下。
逐层量化,就是给整个权重张量算一个 scale 和 zero_point。比如一个卷积层,权重形状是 [64, 3, 3, 3],逐层量化只算一组量化参数,作用于所有 64 个输出通道。
逐通道量化,则是给每个输出通道单独算一组量化参数。还是那个卷积层,64 个通道,每个通道都有自己的 scale 和 zero_point。
你可能会问:为什么要分这么细?
原因很简单。不同通道的数值分布可能差异很大。比如某个通道的权重范围是 [-1.0, 1.0],另一个通道是 [-0.1, 0.1]。如果逐层量化,为了覆盖所有通道,scale 会被拉大,导致小数值通道的量化精度严重损失。逐通道量化就能避免这个问题。
我在项目中做过对比实验:
| 量化方式 | 精度损失(相对 FP16) | 推理速度提升 | 模型体积减小 |
|---|---|---|---|
| 逐层量化 | 约 2%~5% | 约 2x | 约 4x |
| 逐通道量化 | 约 0.5%~1% | 约 1.8x | 约 4x |
可以看到,逐通道量化在精度上明显优于逐层量化,但推理速度略慢一点点。为什么?因为逐通道量化需要存储多组 scale,计算时也要做额外的缩放操作。
注意:逐通道量化虽然精度更好,但不是所有硬件都支持。部署前一定要确认目标硬件的算子库是否支持逐通道量化。我曾经在某个边缘设备上踩过这个坑,量化模型跑不起来,最后只能退回到逐层量化。
PTQ 的完整代码示例
下面我用 PyTorch 演示一个简单的 PTQ 流程。这里用的是 torch.quantization 的 API。
import torch
import torch.quantization as quant
# 1. 加载预训练模型
model = torch.load('model_fp16.pth')
model.eval()
# 2. 准备校准数据集(假设是 DataLoader)
calib_loader = get_calib_dataloader(batch_size=32, num_samples=200)
# 3. 配置量化方式
model.qconfig = quant.get_default_qconfig('fbgemm')
# 或者使用逐通道量化:
# model.qconfig = quant.get_default_qconfig('fbgemm_per_channel')
# 4. 准备量化(插入观察器)
model_prepared = quant.prepare(model, inplace=False)
# 5. 校准:跑一遍校准数据,收集激活值分布
with torch.no_grad():
for data in calib_loader:
model_prepared(data)
# 6. 转换:根据收集到的分布计算量化参数,并转换权重
model_quantized = quant.convert(model_prepared, inplace=False)
# 7. 保存量化模型
torch.save(model_quantized.state_dict(), 'model_int8.pth')
这段代码看着简单,但实际项目中要注意的点不少。比如校准时的 batch size 要和推理时保持一致,否则激活值分布会有偏差。
知识体系图
下面这张图,是我梳理的 PTQ 核心知识结构。你可以把它当作一个快速参考。
这张图把 PTQ 的三个核心模块串起来了。左边是流程,中间是校准集的选择原则,右边是两种量化方式的对比。你可以把它当作一个快速参考。
我的建议:如果你刚接触 PTQ,先从逐层量化开始,跑通流程再说。等你对量化有了感觉,再尝试逐通道量化。一口吃不成胖子,量化这事儿也一样。
好了,PTQ 的核心内容就这些。校准集选好,量化方式选对,你的模型就能在几乎不掉精度的情况下,获得 2~4 倍的推理加速。嗯,这就是 PTQ 的魅力所在。