量化感知训练(QAT):原理与实战

上一章我们聊了PTQ(训练后量化),说白了就是模型训练完了再硬转。但有个问题——精度掉得厉害,尤其低比特场景。这时候就得请出QAT了。

量化感知训练,核心思想就一句话:让模型在训练阶段就「体验」量化带来的误差。等它学会了适应,推理时再真正量化,精度损失就小得多。

QAT的核心原理

为什么PTQ会掉精度?因为权重和激活值的分布,在训练时是连续的浮点数。你突然一刀切量化成int8,相当于把原本平滑的分布硬生生「砍」成阶梯状。模型没见过这种输入,自然就懵了。

QAT的做法很聪明——在训练的前向过程中,插入伪量化节点(FakeQuant)。这些节点模拟量化的舍入误差,但反向传播时仍然用浮点梯度更新。这样模型就学会了在「带噪声」的环境下保持精度。

关键公式:伪量化操作可以表示为

Q(x) = round(clamp(x / scale + zero_point, qmin, qmax))
DQ(x) = (Q(x) - zero_point) * scale

前向走量化-反量化流程,反向梯度直接跳过这些节点(直通估计器STE)。

说白了,就是让模型「戴着镣铐跳舞」。我刚开始接触QAT时也觉得这方法有点绕,但后来发现——它确实管用。

伪量化节点怎么插?

伪量化节点不是随便乱插的。通常插在三个位置:

  • 权重之后:模拟权重量化
  • 激活值之前:模拟激活值量化
  • 某些特殊层前后:比如concat、add操作

以PyTorch为例,官方实现里用torch.quantization.FakeQuantize模块。我习惯这样配置:

import torch.nn as nn
from torch.quantization import FakeQuantize, MovingAverageMinMaxObserver

# 配置伪量化参数
fake_quant = FakeQuantize(
    observer=MovingAverageMinMaxObserver,
    quant_min=-128,
    quant_max=127,
    dtype=torch.qint8,
    qscheme=torch.per_tensor_symmetric
)

嗯,这里要注意:observer负责统计数据的min/max,FakeQuantize负责执行量化-反量化。两者配合才能正常工作。

训练策略:怎么训才不崩?

QAT不是简单地把模型扔进去继续训练。我踩过不少坑,总结出几条经验:

我的建议:QAT训练策略可以归纳为「三阶段」:

  1. 预热阶段(约10%的epoch):先用小学习率让模型适应量化噪声
  2. 正式训练阶段(约70%的epoch):正常学习率,重点优化量化敏感层
  3. 微调阶段(约20%的epoch):学习率衰减,冻结部分层,只调量化参数

我曾经在一个项目里直接拿完整训练集跑QAT,结果模型直接发散。后来发现——学习率太大。QAT的学习率通常要比原始训练小一个数量级,我一般用原始学习率的0.1到0.01倍。

调参技巧:那些容易忽略的细节

QAT的调参,说白了就是跟量化误差「斗智斗勇」。我整理了几个关键参数:

参数 推荐值 说明
学习率 原始LR × 0.01~0.1 太大容易震荡,太小收敛慢
batch size 与原始训练一致 改变batch size会影响BN统计量
量化起始epoch 第1个epoch就开始 越早让模型适应越好
observer更新频率 每batch更新 快速跟踪数据分布变化
权重衰减 适当减小 量化对权重范围敏感,过强正则化不利

避坑指南:我曾经在QAT训练时忘记冻结BN层的running statistics,结果推理时精度暴跌。记住——QAT训练完成后,一定要把BN层融合到卷积层中,否则量化后的模型行为会不一致。

QAT的整体流程

下面这张图展示了QAT从训练到部署的完整流程:

量化感知训练(QAT)完整流程 预训练浮点模型 插入FakeQuant节点 权重+激活值 QAT微调训练 小学习率+STE BN融合到Conv 消除训练/推理差异 导出量化模型 int8权重+量化参数 推理引擎部署 TensorRT / ONNX Runtime 精度不达标?调整参数重新训练 关键点: • 前向模拟量化误差 • 反向直通估计器(STE) • 训练/推理行为一致

实战中的几个坑

QAT看起来简单,但实际落地时问题不少。我挑几个常见的说说:

  • 梯度消失:STE的梯度是近似的,层数太深时梯度可能消失。我习惯在关键层加梯度裁剪。
  • 量化参数震荡:scale和zero_point在训练后期可能来回跳。解决办法是冻结observer,只更新权重。
  • 精度反而下降:有时候QAT训完精度还不如PTQ。别慌,检查一下是不是学习率没调对,或者量化位宽选得太低。

我的经验:如果你发现QAT训完精度不理想,先别急着调参。试试逐层量化——先量化前几层,看看精度变化,再逐步扩大量化范围。这样能快速定位「问题层」。

QAT的核心价值,就是让模型在训练阶段就「学会」量化。虽然训练成本比PTQ高,但精度优势明显。尤其在做4bit、2bit这种极端量化时,QAT几乎是唯一选择。