2、模型量化基础(上):量化的数学原理、对称量化与非对称量化、量化参数(scale/zero-point)的计算

好,咱们正式开始聊模型量化。

说实话,量化这块内容,我当年刚接触的时候也觉得有点绕。一堆数学公式,什么 scale、zero-point,看着就头疼。但后来我在一个嵌入式视觉项目里,硬是把一个 200MB 的模型压到了 50MB,精度只掉了 0.3%。那一刻我才真正体会到——量化不是玄学,是实实在在的工程利器。

今天这一讲,咱们先把量化的数学底子打牢。你搞懂了原理,后面实操才不会慌。

2.1 为什么要量化?—— 从浮点到整数的“降维打击”

模型在训练的时候,参数和激活值默认都是 FP32(32位浮点数)。这玩意儿精度高,但占地方、跑得慢。你想想看,一个参数占 4 个字节,一亿个参数就是 400MB。在服务器上还行,放到手机或者 MCU 上,直接内存爆炸。

量化的核心思路很简单:用更少的比特数去近似表示原来的浮点数。最常见的就是从 FP32 量化到 INT8,也就是 4 倍压缩。不光模型变小了,推理速度也能快 2-4 倍,因为整数运算比浮点运算快得多。

我打个比方。你原来用一把毫米刻度的尺子量东西,现在换成一把厘米刻度的尺子。精度肯定下降了,但量得快、尺子也小。量化就是这个道理。

核心收益:
  • 模型体积缩小:FP32 → INT8,体积变为原来的 1/4
  • 推理速度提升:整数运算比浮点运算快 2-4 倍
  • 内存带宽降低:更少的数据搬运,更省电

2.2 量化的数学原理 —— 一个线性映射

量化的本质,就是一个数学映射。把浮点数范围 [r_min, r_max] 映射到整数范围 [q_min, q_max]

这个映射是线性的,公式长这样:

r = S * (q - Z)

其中:

  • r 是真实的浮点数值
  • q 是量化后的整数值
  • S 是缩放因子(scale),一个浮点数
  • Z 是零点(zero-point),一个整数

反过来,从浮点量化到整数的公式是:

q = round(r / S + Z)

嗯,这里要注意。round 操作会带来精度损失,这是量化误差的主要来源。我在项目中遇到过,有些层对量化特别敏感,就是因为 round 误差被放大了。

2.3 对称量化 vs 非对称量化

根据零点 Z 的取值,量化分为两种:对称量化和非对称量化。这是面试常考的点,也是你选型时要做的第一个决策。

2.3.1 对称量化

对称量化,说白了就是让零点 Z = 0。公式简化成:

r = S * q
q = round(r / S)

它的特点是:浮点数的正负范围对称映射到整数的正负范围。比如 INT8 的 [-128, 127] 映射到浮点数的 [-128*S, 127*S]。

优点:实现简单,硬件友好。很多 NPU 和 DSP 只支持对称量化。

缺点:如果浮点数的分布不对称(比如 ReLU 后的激活值全是非负的),对称量化会浪费一半的整数表示范围。

我的经验:对于权重,我习惯用对称量化。因为训练好的权重分布通常接近对称。对于激活值,尤其是 ReLU 之后的,我会优先考虑非对称量化。

2.3.2 非对称量化

非对称量化允许零点 Z 不为 0。这样整数范围可以完全覆盖浮点数的实际分布范围,不浪费任何编码空间。

举个例子。假设浮点数范围是 [0.0, 10.0],量化到 INT8 [0, 255]。那么:

  • S = (10.0 - 0.0) / (255 - 0) = 0.0392
  • Z = 0(因为浮点 0 正好对应整数 0)

但如果浮点数范围是 [2.0, 12.0],量化到 INT8 [0, 255]:

  • S = (12.0 - 2.0) / (255 - 0) = 0.0392
  • Z = round(0 - 2.0 / 0.0392) = round(-51) = -51

你看,零点变成了 -51。这就是非对称量化的灵活性。

曾经踩过的坑:有一次我用非对称量化,零点算出来是负数。结果部署的芯片不支持负数零点,推理结果全错。后来我改成对称量化,或者手动把零点偏移到 0,才解决问题。所以选量化方案前,一定要先看目标硬件的文档!

2.4 量化参数的计算 —— scale 和 zero-point

好,现在咱们来手算一下 scale 和 zero-point。这是基本功,必须会。

假设我们要把浮点数范围 [r_min, r_max] 量化到整数范围 [q_min, q_max]

第一步:计算 scale

S = (r_max - r_min) / (q_max - q_min)

第二步:计算 zero-point

Z = round(q_min - r_min / S)

或者用浮点 0 对应的整数:

Z = round(0 - r_min / S)

注意,Z 必须被裁剪到 [q_min, q_max] 范围内。

咱们来个具体的例子。假设有一组浮点激活值,范围是 [-1.5, 6.0],要量化到 INT8 [-128, 127]。

S = (6.0 - (-1.5)) / (127 - (-128))
  = 7.5 / 255
  = 0.02941

Z = round(0 - (-1.5) / 0.02941)
  = round(51.0)
  = 51

所以,量化参数是 S=0.02941,Z=51。

验证一下:浮点 0 量化后是 q = round(0/0.02941 + 51) = 51,反量化回来 r = 0.02941 * (51 - 51) = 0。完美。

关键点:
  • Scale 决定了量化的“粒度”,S 越小,精度越高
  • Zero-point 负责对齐浮点 0 和整数 0,保证 0 的量化是无损的
  • 实际部署时,S 和 Z 会作为常量固化在模型里,推理时直接查表或计算

2.5 量化误差的来源与应对

量化不是无损的。误差主要来自三个方面:

  1. 舍入误差:round 操作导致的。这个没法避免,但可以通过更精细的校准数据集来缓解。
  2. 截断误差:浮点数超出量化范围时被截断。我建议你在校准阶段统计好 min/max,尽量覆盖 99% 以上的数据分布。
  3. 精度损失:小数值被量化成 0 或 1,丢失了细节。对于某些层(比如 attention 的 softmax 输出),这种损失影响很大。

我曾经在一个 NLP 模型上,因为量化后精度掉了 5%,排查了两天。最后发现是某一层的激活值范围特别小,量化后全变成了 0。解决办法是给那一层单独设置更大的量化范围,或者改用更高精度的量化(比如 INT16)。

避坑指南:
  • 先做逐层量化敏感性分析,找出“脆弱层”
  • 对脆弱层保留 FP32 或使用混合精度量化
  • 校准数据集要足够有代表性,别用训练集,用验证集

2.6 小结

这一讲咱们把量化的数学基础捋了一遍。核心就三件事:

  • 理解线性映射公式 r = S * (q - Z)
  • 区分对称量化和非对称量化的适用场景
  • 会手算 scale 和 zero-point

下一讲,咱们会深入聊量化校准方法、量化感知训练,以及如何在 TensorRT 和 TFLite 里实操。到时候我会拿一个实际项目案例,带你一步步把模型量化部署到开发板上。

嗯,今天就到这儿。你先把这些公式和概念消化一下,有问题随时翻回来看看。