量化感知训练(QAT):让模型学会“低精度生存”

各位同学,今天我们来聊聊量化感知训练。说实话,这是我在实际项目中用得最多的量化方法。为什么?因为PTQ虽然简单,但精度损失有时候真的让人头疼。而QAT,说白了就是让模型在训练阶段就学会“如何在低精度下好好工作”。

为什么需要QAT?

先说说我的亲身经历。有一次做智能音箱的唤醒词模型,用PTQ直接量化到INT8,精度掉了将近3个点。产品经理当场就急了——这唤醒率下降,用户不得骂娘?后来换成QAT,精度只掉了0.3%,问题迎刃而解。

为什么会这样?你想想看,PTQ是训练完后再去量化,模型根本没“见过”量化后的世界。就像一个人突然被扔到异国他乡,语言不通,肯定各种不适应。QAT就不一样了,它让模型在训练时就“假装”自己已经被量化了,提前适应低精度的环境。

核心思想:QAT通过在训练过程中模拟量化操作,让模型权重和激活值学会适应低精度表示,从而减少量化后的精度损失。

模拟量化:一场精心设计的“骗局”

模拟量化,英文叫Simulated Quantization。说白了就是——模型以为自己被量化了,但实际上还是在用浮点数计算。

具体怎么做呢?在前向传播时,我们把浮点数值先量化成INT8,再反量化回浮点数。这样模型看到的虽然是浮点数,但这些浮点数已经被“阉割”过了——它们只能取量化后的那些离散值。

# 模拟量化的伪代码
def simulated_quantize(x, scale, zero_point):
    # 量化:浮点 → 整型
    x_int = round(x / scale) + zero_point
    # 限制在INT8范围内
    x_int = clamp(x_int, -128, 127)
    # 反量化:整型 → 浮点(但精度已经损失了)
    x_fp = (x_int - zero_point) * scale
    return x_fp

嗯,这里要注意:反向传播的时候,量化操作是不可导的。怎么办?我们用直通估计器(STE),直接把梯度“绕过”量化操作传回去。虽然不精确,但实践中效果出奇的好。

伪量化节点:模型里的“卧底”

伪量化节点(FakeQuantize Node)就是上面说的模拟量化操作在神经网络里的具体实现。我习惯把它想象成安插在模型里的“卧底”——平时跟着模型一起训练,关键时刻(推理时)就变成真正的量化节点。

伪量化节点一般插在哪里?

  • 权重之后:模拟权重量化
  • 激活函数之前:模拟激活值量化
  • 卷积/全连接层之后:模拟输出量化

我在项目中遇到过一个问题:伪量化节点插得太多,训练速度慢得离谱。后来发现,其实不需要每个层都插,只在关键层(比如第一个卷积层、最后一个全连接层)插入就够了。这算是个小技巧吧。

我的经验:伪量化节点的插入位置很关键。一般来说,对精度敏感的层(如分类头)建议保留浮点精度,只量化计算密集的层(如卷积层)。

QAT训练流程:三步走

QAT的训练流程其实不复杂,我总结成三步:

  1. 预训练浮点模型:先用常规方法训练一个高精度的FP32模型。这一步不能省,底子打好了后面才稳。
  2. 插入伪量化节点:在模型的关键位置插入FakeQuantize节点。我建议先用少量数据跑一遍校准,看看各层的数值范围,再决定量化参数。
  3. 微调训练:用较小的学习率(通常是原来的1/10到1/100)继续训练几个epoch。注意,这里不是从头训练,是微调。

我曾经犯过一个错误:微调时用了太大的学习率,结果模型直接崩了,精度比PTQ还差。后来学乖了,学习率设成原来的1/50,慢慢来,效果反而更好。

避坑指南:我曾经在微调时忘记冻结BN层的参数,结果训练不稳定,精度波动很大。建议在QAT微调时冻结BN层的均值和方差,只更新权重。

QAT vs PTQ:怎么选?

这个问题我经常被问到。直接上对比表:

对比维度 PTQ(训练后量化) QAT(量化感知训练)
精度 大模型(>100M)精度损失小 小模型精度损失更小
训练成本 无需训练,只需校准数据 需要微调,额外训练时间
适用场景 模型较大、精度要求不高 模型较小、精度要求苛刻
实现复杂度 低,工具链支持好 中等,需要修改训练代码
我的推荐 IoT设备上模型>50M时优先 IoT设备上模型<10M时必选

我个人习惯是:先试PTQ,如果精度损失在可接受范围内,就用PTQ。如果不行,再上QAT。毕竟QAT要多花时间训练,能省则省。

QAT的核心逻辑图

下面这张图展示了QAT的完整流程,我特意用SVG画出来,方便大家理解:

QAT量化感知训练核心流程 ① 预训练FP32模型 ② 插入FakeQuantize节点 权重+激活值模拟量化 ③ 微调训练 反向传播(STE直通估计器) 导出INT8模型 关键细节: • 前向传播:量化→反量化,模拟INT8计算 • 反向传播:STE绕过量化操作,直接传递梯度 • 学习率:建议设为原学习率的1/50~1/10 • BN层:建议冻结均值和方差,只更新权重 • 校准数据:通常需要500~2000张图片

实际项目中的QAT调参心得

最后分享几个我在实际项目中总结的经验:

  • 校准数据集:不要用训练集做校准,用验证集。我试过用训练集校准,结果过拟合了,推理时精度反而下降。
  • 量化粒度:逐层量化(per-layer)比逐通道量化(per-channel)简单,但精度差一些。IoT设备上如果算力够,建议用per-channel。
  • 混合精度:不是所有层都需要量化到INT8。敏感层(如注意力机制的softmax)保持FP16,计算密集层用INT8,效果更好。
  • 训练时长:一般微调5-10个epoch就够了。我见过有人微调50个epoch,结果过拟合了,得不偿失。

小技巧:如果你用的是PyTorch,可以试试torch.ao.quantization里面的QAT工具。TensorFlow的话,TF Lite的量化工具也支持QAT。别自己手写,容易踩坑。

好了,关于QAT的内容就讲到这里。记住一句话:QAT不是万能的,但没有QAT是万万不能的——尤其是在IoT这种资源受限的设备上。下次遇到精度损失问题,先别急着调模型结构,试试QAT,说不定有惊喜。


公众号:蓝海资料掘金营,微信deep3321