3. PTQ与QAT:两种量化方案的实战抉择
聊到模型量化,圈子里最常争论的就是PTQ和QAT谁更好。说实话,这问题没有标准答案。我做了几年端侧部署,两种方案都踩过坑,今天就把我的经验掰开揉碎讲给你听。
3.1 Post-Training Quantization(PTQ)原理
PTQ,全称是训练后量化。说白了,就是模型训练完了,我再动手脚。你训练好的浮点模型,我直接拿过来,通过一些统计手段,把权重和激活值从FP32映射到INT8。
它的核心流程其实就三步:
- 收集统计量:跑一小批校准数据,观察每一层激活值的分布范围(min/max或者百分位)
- 计算缩放因子:根据统计到的范围,算出scale和zero_point
- 执行量化:把浮点数值映射到定点整数空间
这里有个关键点——校准数据集的选择。我在项目中遇到过,有人随便拿了几张图就跑校准,结果模型精度直接崩了。校准数据最好能覆盖真实场景的分布,数量不用多,几百张就够了,但一定要有代表性。
PTQ的数学本质:
量化过程可以理解为:q = round(r / scale) + zero_point
其中r是浮点值,q是量化后的整数,scale和zero_point就是我们要找的参数。
3.2 PTQ的优缺点
PTQ最大的优点就是快。真的快。我试过对一个ResNet50做PTQ,从准备校准数据到导出量化模型,前后不到半小时。不需要重新训练,不需要改网络结构,甚至不需要原始训练代码。
但缺点也很明显——精度损失不可控。尤其是对小型模型或者敏感层,PTQ可能会带来1%-3%的精度下降。我曾经在一个语音唤醒模型上试过PTQ,结果唤醒率直接掉了5个点,排查了半天才发现是某个卷积层的激活值分布太宽,量化后信息丢失严重。
| 维度 | PTQ表现 |
|---|---|
| 部署速度 | 极快,几小时搞定 |
| 精度损失 | 中等,大模型损失小,小模型损失大 |
| 需要的数据 | 少量校准数据(无标签也可) |
| 工程复杂度 | 低,几乎不需要改代码 |
我的建议:如果你的模型比较大(比如超过100M参数),或者对精度要求不那么苛刻,优先尝试PTQ。它是最省力的方案。
3.3 Quantization-Aware Training(QAT)原理
QAT就不一样了。它是在训练过程中就模拟量化效果,让模型自己去适应量化带来的误差。你想想看,模型在训练时就知道自己将来要被量化,它就会主动调整权重分布,让量化后的损失最小化。
QAT的核心是伪量化节点(Fake Quantize)。这些节点插入在网络的每一层前后,在前向传播时模拟量化和反量化的过程,但反向传播时仍然使用浮点梯度更新权重。
# 伪代码示例:QAT中的前向传播
def forward(x):
# 模拟量化
x_q = quantize(x, scale, zero_point)
# 模拟反量化(保持浮点计算)
x_fake = dequantize(x_q, scale, zero_point)
# 后续计算
y = conv(x_fake)
return y
这里有个细节很多人会忽略——直通估计器(STE)。因为量化操作不可导,QAT用STE来近似梯度:前向传播走量化路径,反向传播时直接把梯度传过去,忽略量化带来的梯度截断。嗯,这招虽然粗糙,但实践中效果出奇的好。
注意:QAT训练时,学习率要调小。我一般设置为原始训练学习率的1/10到1/100。学习率太大,模型容易在量化边界附近震荡,收敛不了。
3.4 QAT的优缺点
QAT的优点就是精度高。我做过对比,同样一个语音识别模型,PTQ后WER(词错误率)上升了2.3%,而QAT只上升了0.4%。对于生产环境来说,这个差距可能就是能不能上线的问题。
但代价也很明显——训练成本高。你需要完整的训练流程,需要带标签的数据,需要调参。而且训练时间会变长,因为伪量化节点增加了计算量。我记得有一次做QAT,一个模型训了3天才收敛,而PTQ只用了2小时。
| 维度 | QAT表现 |
|---|---|
| 部署速度 | 慢,需要完整训练流程 |
| 精度损失 | 极小,通常<1% |
| 需要的数据 | 大量带标签训练数据 |
| 工程复杂度 | 高,需要修改训练代码 |
3.5 两种方法的优劣对比
好了,咱们把PTQ和QAT放在一起比比看。我直接说结论:
- 精度优先:选QAT。尤其是语音模型、小模型、对精度敏感的模型。
- 速度优先:选PTQ。快速验证、原型开发、大模型场景。
- 折中方案:先用PTQ快速评估,如果精度不达标,再针对关键层做QAT微调。
我曾经在一个项目里就是这么干的。先跑PTQ,发现某个注意力层的量化误差特别大,然后只对这一层做QAT微调,其他层保持PTQ结果。最后精度恢复了1.8%,而训练时间只增加了半天。这种混合方案,我个人觉得是工程上最实用的。
避坑指南:
我曾经犯过一个错误——在QAT训练时忘了冻结BN层的统计量。结果模型在验证集上精度很高,但部署到端侧后完全跑偏。后来排查才发现,BN层的running_mean和running_var在QAT训练时被更新了,导致推理时的分布和训练时不一致。
所以记住:QAT训练时,BN层要设置为eval模式,或者冻结其统计量。
3.6 实战选择建议
最后给个实操建议。如果你刚接触量化,我建议你:
- 先跑PTQ:花半天时间,看看精度损失多少。如果损失在可接受范围内,直接上线。
- 如果精度不够:分析哪些层量化误差大(可以用余弦相似度或者SNR来评估),对这些层做QAT微调。
- 如果还不行:考虑换模型结构,或者用更宽的位宽(比如INT16混合精度)。
记住,量化不是银弹。有时候模型本身设计就不适合量化,比如用了太多激活值分布不均匀的激活函数(像Swish、GELU)。这种情况下,再好的量化方案也救不了。嗯,这就是另一个话题了。
一句话总结:PTQ是快刀斩乱麻,QAT是慢工出细活。根据你的场景选,别盲目跟风。