QAT伪量化
QAT伪量化为什么可行?你的理解和QAT真实逻辑差异
你的直觉:真实硬件推理流程
输入浮点 → 量化转 int8
权重浮点 → 量化转 int8
int8 × int8 整数卷积/矩阵乘,得到int32中间结果
反量化/偏移缩放 → 输出int8
全程算子内部全部整数运算,没有浮点数。
QAT训练里伪量化(FakeQuant):全程张量都是float,只是模拟量化舍入截断,没有真正转int。
你疑惑点:明明硬件跑整数,训练却用浮点数模拟,为什么能收敛、PTQ/QAT之后导出onnx给NPU还能跑对?
1. 先把量化公式写清楚
对称量化公式:
xint=round(xfloatscale),xq=clip(xint,−128,127)x_{int}=round\left(\frac{x_{float}}{scale}\right),\quad x_{q}=clip(x_{int},-128,127)xint=round(scalexfloat),xq=clip(xint,−128,127)
反量化:
x^float=xq⋅scale\hat{x}_{float}=x_q \cdot scalex^float=xq⋅scale
伪量化 = quant + dequant 的组合:x^=Dequant(Quant(x))\hat x = Dequant(Quant(x))x^=Dequant(Quant(x))
输入是float,输出还是float。相当于:把数值压到离散的量化格点上,但数据类型不变。
硬件真实推理(int8)
float_in → quant → int8 → conv(int8*int8) → int32 → requant → int8_out → dequant → float_outQAT伪量化前向(PyTorch)
float_in → FakeQuant(quant+dequant) → float(已经落在量化格点)→ conv(float * float) → FakeQuant → float_out👉 前向传播:数值行为尽量贴近硬件;但数据类型依旧float,没有真正int8。
2. 反向传播梯度怎么处理?round是不可导!
这是伪量化存在的最核心原因。
round()函数导数处处为0,直接求导梯度直接死掉,网络无法训练。
QAT用直通估计器 STE(Straight‑Through Estimator):
前向:执行round+clip,模拟量化噪声
反向:绕过round,直接把梯度原样回传,相当于梯度穿过FakeQuant节点
∂x^∂x≈1\frac{\partial \hat x}{\partial x} \approx 1∂x∂x^≈1
如果训练时真的把张量转成int8类型:
PyTorch整数张量无法计算梯度,autograd直接失效,网络完全不能更新权重。
这就是训练阶段不能真转整数的根本约束。
所以QAT不能真正做int8计算,只能用浮点数模拟量化效果。
3. 那为什么伪量化训练完,导出给NPU跑真实int8可以对齐?
关键点:QAT学习的是scale/zero_point,以及权重在量化格点上的最优浮点位置。
QAT训练阶段,FakeQuant强制:权重、激活只能取
k * scale的离散浮点值;训练结束后,把权重直接round到对应整数格点:
Wint8=round(Wfloat/scalew)W_{int8}=round(W_{float}/scale_w)Wint8=round(Wfloat/scalew)
保存int8权重和scale;
- NPU推理时:硬件复现完全一样的scale,做真正int8整数乘加。
只要训练时的scale、clip范围、round行为和硬件推理严格对齐,伪量化训练得到的浮点权重,round之后得到的int8权重就是最优的那一组整数权重。
简单比喻:
伪量化训练:在浮点数世界,强迫参数只能站在整数格子对应的坐标点上训练;
训练结束:直接把每个点取格子编号,就是硬件要用的int8。
4. 你的设想:训练就全部用int8计算,有没有这种东西?
有,叫完全整数训练(Integer‑only training),但工程上很难落地:
整数的梯度、优化器(Adam)都需要浮点数;优化器状态是float,无法全部int;
整数动态范围窄,梯度很容易溢出饱和;
目前NPU/地平线工具链主流只支持QAT伪量化流程。
所以工业界不做训练时int8计算,只做前向模拟量化噪声,反向STE梯度近似。
5. 容易踩坑:伪量化与硬件不一致导致精度掉点
很多人QAT做完精度崩,就是FakeQuant和硬件行为对不上:
FakeQuant的clip范围、对称/非对称模式,和NPU硬件的量化模式不一致;
requant重量化逻辑(卷积输出int32如何缩放到int8),QAT仿真和硬件算子实现有差异;
round模式:PyTorch round是银行家舍入,部分硬件是四舍五入;
激活的scale是逐tensor / per‑channel,两边配置必须一模一样。
地平线J6的QAT插件,本质就是重写FakeQuant节点,仿真J6硬件真实的量化舍入、截断、重量化行为,保证导出ONNX之后,NPU真实int8推理数值尽可能贴近QAT仿真输出。
简短总结
硬件推理:确实是输入、权重转int8,整数运算。
QAT训练不能真转int8:整数张量无法求导,round不可导。
伪量化FakeQuant:
quant(round+clip)+dequant,float模拟量化离散效应,反向用STE直通估计器传梯度。训练收敛后,权重/激活用训练得到scale做round,得到真正int8,交给硬件执行整数运算。
QAT的全部价值:让网络适应量化噪声,学到在离散整数格点上最优的参数,而不是训练阶段就跑整数计算。
如果你需要,我可以写一小段极简PyTorch伪量化+STE的示例代码,直观看到前向round、反向直通的行为。