2. 量化类型

聊到量化,你最先要搞清楚的,就是「量化成什么样子」。

我刚开始接触模型量化时,以为就是把 float32 转成 int8 就完事了。结果一上手,发现里面门道不少。同样是 int8,有的是对称的,有的是非对称的;有的只量化权重,有的连激活值一起动。

这一节,咱们就把这两组概念彻底掰扯清楚。

对称量化 vs 非对称量化

先看一个最基础的问题:量化后的整数范围,怎么映射到原始的浮点数范围?

对称量化,说白了就是让量化后的零点(zero-point)等于 0。原始浮点数的 0,量化后还是 0。正负范围是对称的。

举个例子,int8 对称量化,范围是 [-127, 127](有的实现用 [-128, 127])。原始浮点数的最大值和最小值,对称地映射到这个范围的两端。

对称量化的核心公式:

scale = max(|x_max|, |x_min|) / 127
x_quant = round(x / scale)

零点固定为 0,不需要存储。

非对称量化就不一样了。它的零点可以不是 0。原始浮点数的 0,量化后可能对应一个非零的整数。

非对称量化的范围是 [0, 255](uint8)。它能把浮点数的整个范围「塞」进这个区间,不管正负比例如何。

非对称量化的核心公式:

scale = (x_max - x_min) / 255
zero_point = round(-x_min / scale)
x_quant = round(x / scale) + zero_point

需要存储 scale 和 zero_point 两个参数。

你可能会问:那到底用哪个?

我个人习惯这样判断:

  • 如果数据分布接近对称(比如权重经过训练后,正负值差不多),用对称量化。计算简单,硬件友好。
  • 如果数据分布明显偏斜(比如 ReLU 后的激活值,全是非负的),用非对称量化。能充分利用量化范围,精度损失更小。

我在项目中遇到过一件事:有个模型,激活值全是正数,我一开始用了对称量化,结果精度掉了 2 个点。换成非对称量化后,精度只掉了 0.3 个点。差别就这么大。

我的建议:

权重一般用对称量化,激活值一般用非对称量化。这不是绝对的,但 80% 的场景都适用。

权重量化 vs 激活量化

接下来是第二个维度:量化对象。

权重量化,量化的是模型里那些固定的参数。权重在推理时是不变的,所以你可以离线算好 scale 和 zero_point,推理时直接用。

激活量化,量化的是每一层的输出(也就是下一层的输入)。激活值在推理时是动态变化的,你得在线统计它的范围,或者提前用校准数据集跑一遍,算出统计值。

这两者的区别,我用一个表格说清楚:

对比维度 权重量化 激活量化
数据特性 静态,推理时不变 动态,随输入变化
量化时机 离线,一次搞定 需校准集或在线统计
精度影响 相对较小 相对较大
实现难度 低 高
硬件加速收益 中等 高(减少访存)

你想想看,只量化权重,不量化激活值,那计算时还是 float32 的乘法,加速效果有限。只有把激活值也量化了,才能用上 int8 的矩阵乘,速度才能起飞。

但激活量化有个坑:分布不稳定。同一个模型,输入不同的图片,某一层的激活值范围可能差好几倍。如果你用固定的 scale,遇到 outlier 就会截断,精度直接崩掉。

我曾经踩过的坑:

有个检测模型,激活量化后精度掉了 5 个点。排查了半天,发现是某一层的激活值偶尔会出现一个很大的值(比校准集里的最大值大 10 倍)。校准集没覆盖到这种情况,scale 设小了,大量正常值被截断。

后来我改用 per-tensor + 动态范围裁剪,才把精度拉回来。

四种组合,一张图看懂

对称/非对称、权重/激活,两两组合,一共四种情况。我画了张图,帮你理清关系:

量化类型四象限 量化类型 权重量化 激活量化 对称权重量化 常用,硬件友好 非对称权重量化 少用,分布偏斜时 对称激活量化 少用,精度损失大 非对称激活量化 常用,精度最优 实际部署中最常见的组合 权重:对称量化 | 激活:非对称量化 兼顾精度和硬件效率,是工业界的默认选择

实际部署时怎么选?

说了这么多,你肯定想知道:真正干活的时候,到底怎么配?

我直接给你一个参考配置:

# 常见的量化配置(以 TensorRT 为例)
量化策略:
  - 权重:对称量化,per-channel
  - 激活:非对称量化,per-tensor
  - 校准方法:熵校准(Entropy Calibration)
  
# 为什么这么配?
# 1. 权重分布通常对称,对称量化省掉 zero_point,计算更快
# 2. 激活值多为非负,非对称量化能更好利用 uint8 范围
# 3. per-channel 对权重更精细,减少精度损失
# 4. 熵校准能自动找到最优截断阈值

当然,这不是银弹。我遇到过一些特殊情况:

  • 某些轻量模型(如 MobileNet),激活值分布很窄,对称量化反而更好。因为非对称量化的 zero_point 会引入额外的计算开销。
  • 某些 NLP 模型,权重和激活的分布都很奇怪,需要逐个 layer 调试,不能一刀切。

我的调试流程:

  1. 先用默认配置(权重对称 + 激活非对称)跑一遍
  2. 看精度损失,如果 < 0.5%,直接收工
  3. 如果损失大,逐层分析,看哪层是瓶颈
  4. 对瓶颈层单独调整量化方式或校准方法

80% 的情况,第一步就够了。

小结一下

对称量化和非对称量化,本质是「零点要不要偏移」的问题。权重量化和激活量化,本质是「量化谁」的问题。

这两组概念组合起来,就是你在实际部署中要做的选择。没有绝对的对错,只有适合不适合。

嗯,记住一句话:权重用对称,激活用非对称,先跑起来再说。