4. 训练后量化(PTQ)

训练后量化,简称 PTQ。这是目前工业界落地大模型最常用的手段。说白了,就是模型训练完了,我们直接拿它做量化,不需要再动训练过程。

我刚开始接触 PTQ 时,觉得这玩意儿挺简单的——不就是把 FP16 的权重转成 INT8 吗?后来踩了不少坑才发现,里面的门道比想象中多得多。今天咱们就把 PTQ 的流程、校准集怎么选、以及逐层和逐通道量化的区别,一次性讲清楚。

PTQ 的基本流程

PTQ 的流程其实不复杂。我习惯把它拆成四个步骤:

  1. 加载预训练模型——把训练好的 FP16 或 FP32 模型读进来。
  2. 准备校准数据集——选一小部分有代表性的数据,跑一遍前向推理,收集激活值的分布信息。
  3. 计算量化参数——根据收集到的分布,算出每个张量的 scale 和 zero_point。
  4. 转换并保存量化模型——把原始权重和激活值映射到 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 流程 加载预训练模型 准备校准数据集 计算量化参数 转换并保存模型 校准数据集选择 代表性 接近真实数据分布 多样性 覆盖多种输入模式 适量性 100~500条足够 量化方式对比 逐层量化 一组参数,速度快,精度损失大 逐通道量化 多组参数,精度高,硬件要求高

这张图把 PTQ 的三个核心模块串起来了。左边是流程,中间是校准集的选择原则,右边是两种量化方式的对比。你可以把它当作一个快速参考。

我的建议:如果你刚接触 PTQ,先从逐层量化开始,跑通流程再说。等你对量化有了感觉,再尝试逐通道量化。一口吃不成胖子,量化这事儿也一样。

好了,PTQ 的核心内容就这些。校准集选好,量化方式选对,你的模型就能在几乎不掉精度的情况下,获得 2~4 倍的推理加速。嗯,这就是 PTQ 的魅力所在。