第二章:量化原理入门——浮点数与定点数、精度损失来源、量化参数详解

各位同学,欢迎来到量化原理的硬核部分。

说实话,很多人一听到「量化」两个字,就觉得是玄学。不就是把大数变小嘛,能有多难?

嗯,我当年也是这么想的。直到第一次把 FP32 模型硬转成 INT8,跑出来的结果完全没法看——精度直接崩了。那时候我才意识到,量化这件事,背后是有数学逻辑的。

今天我们就来把这层窗户纸捅破。

2.1 浮点数 vs 定点数:本质区别在哪?

先问个问题:计算机里怎么存一个带小数点的数?

两种主流方案——浮点数和定点数。

浮点数,比如 FP32,用科学计数法存。它把数字拆成三部分:符号位、指数位、尾数位。好处是动态范围大,能表示从 1.4e-45 到 3.4e38 这么宽的范围。坏处呢?计算慢,占内存。

定点数就简单粗暴了。它固定小数点的位置,比如 Q7.8 格式,7 位整数,8 位小数。范围窄,但计算快,硬件友好。

我打个比方:浮点数像一把可以伸缩的尺子,能量蚂蚁也能量大楼;定点数像一把固定刻度的尺子,只能量某个范围的东西,但量得快。

量化,说白了就是把浮点数模型里的参数,从「伸缩尺子」换成「固定尺子」。代价是什么?精度损失。

核心结论:浮点数用「范围换精度」,定点数用「精度换速度」。量化就是在两者之间找平衡。

2.2 精度损失来源:到底丢在哪了?

很多同学问我:「老师,量化之后精度掉多少?」

我的回答是:看你怎么量化的。精度损失主要来自三个地方。

2.2.1 截断误差

这是最直观的。FP32 能表示 1.23456789,INT8 只能表示 1 或者 2。小数点后面的东西,直接扔了。

我在项目中遇到过,一个卷积层的权重范围是 [-0.5, 0.5],量化到 INT8 后,很多小权重直接变成 0。结果模型直接「失忆」了。

2.2.2 舍入误差

不是所有数都能刚好落在量化后的格点上。比如量化步长是 0.1,那 0.15 这个数怎么办?要么舍成 0.1,要么入成 0.2。每次舍入都有误差。

你想想看,一个模型几百万个参数,每个参数都舍入一次,累积起来可不是小数目。

2.2.3 范围溢出

这是最致命的。INT8 的范围是 [-128, 127]。如果某个权重是 200,量化后直接变成 127——截断了。

我曾经调试过一个模型,输出全是 NaN。查了半天,发现是中间激活值太大,量化后溢出,然后一路传播下去。嗯,从那以后我养成了一个习惯:量化前先看权重和激活值的分布。

避坑指南:量化前一定要做「范围分析」。用少量校准数据跑一遍推理,记录每层的 min/max。如果某层动态范围特别大,考虑用 per-channel 量化,别用 per-tensor。

2.3 量化参数详解:scale 和 zero-point

好了,现在进入核心。量化公式长什么样?

标准公式就一个:

q = round(r / scale) + zero_point

其中:

  • r:原始浮点数
  • q:量化后的整数
  • scale:缩放因子,决定步长
  • zero_point:零点偏移,决定浮点 0 对应哪个整数

反量化公式:

r = (q - zero_point) * scale

就这么简单?对,就这么简单。但魔鬼在细节里。

2.3.1 scale:步长怎么算?

scale 决定了量化后的「分辨率」。公式是:

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

举个例子:

  • 浮点范围:[-1.0, 1.0]
  • 量化范围:INT8 的 [-128, 127]
  • scale = (1.0 - (-1.0)) / (127 - (-128)) = 2.0 / 255 ≈ 0.00784

这意味着量化后的每个整数步长是 0.00784。如果原始浮点数是 0.0039,量化后就是 0——又丢了。

我个人习惯是先看权重分布。如果大部分值集中在 0 附近,我会考虑用对称量化(去掉 zero_point),这样能省一点计算量。

2.3.2 zero-point:零点偏移

zero_point 的作用是让浮点 0 能精确映射到某个整数。为什么重要?

因为很多神经网络层(比如 ReLU、Padding)对 0 有特殊处理。如果 0 映射不准,这些层的输出就全错了。

计算公式:

zero_point = round(-r_min / scale) + q_min

还是上面的例子:

  • r_min = -1.0, scale = 0.00784
  • zero_point = round(-(-1.0) / 0.00784) + (-128) = round(127.55) - 128 = 128 - 128 = 0

嗯,这个例子里 zero_point 刚好是 0,属于对称量化。但实际项目中很少这么完美。

小技巧:如果你用非对称量化,zero_point 一般不是 0。推理时记得把 zero_point 减掉,否则结果会整体偏移。我见过有人忘了这步,模型输出全是正的——因为 zero_point 是 128,没减。

2.4 量化参数的选择策略

选 scale 和 zero_point 不是随便算的。不同策略影响最终精度。

策略 适用场景 精度损失 计算复杂度
Min-Max 量化 权重分布均匀 中等
KL 散度量化 权重分布有长尾 较低
百分位量化 有异常值

我个人最常用的是 KL 散度法。为什么?因为神经网络权重的分布往往不是均匀的,中间密、两边稀。Min-Max 会被极端值带偏,KL 散度能找到信息损失最小的截断点。

举个例子:某层权重范围是 [-2.0, 3.0],但 99% 的值集中在 [-0.5, 0.5]。如果用 Min-Max,scale 会很大,导致中间区域分辨率不够。用 KL 散度,我会把范围截断到 [-0.6, 0.6],丢掉那 1% 的极端值,换来中间 99% 的精度提升。

核心原则:量化不是「无损压缩」,而是「有损压缩」。你的目标是让损失发生在不重要的地方。

2.5 知识体系总览

下面这张图是我自己整理的量化原理知识结构,方便你对照理解:

量化原理知识体系 量化:浮点数 → 定点数 浮点数(FP32/FP16) 定点数(INT8/INT4) 精度损失来源 ① 截断误差:小数部分被丢弃 ② 舍入误差:量化格点不匹配 ③ 范围溢出:超出量化范围 量化参数详解 scale:缩放因子,决定步长 zero_point:零点偏移 公式:q = round(r/scale) + zp 量化策略选择 Min-Max 量化 | KL 散度量化 | 百分位量化 核心原则:让损失发生在不重要的地方

这张图把量化原理拆成了四个层次:定义、数据类型、损失来源、参数选择。你顺着箭头看下来,就能理解整个量化流程的逻辑。

2.6 实战中的量化参数调试

最后分享一个我自己的调试流程:

  1. 先跑校准:用 500-1000 张图片做校准,记录每层的 min/max
  2. 看分布:画出权重和激活值的直方图,判断用哪种量化策略
  3. 试量化:先用 Min-Max 快速试一遍,看精度损失
  4. 调参数:如果精度掉太多,换成 KL 散度或百分位法
  5. 逐层排查:如果某层精度特别差,单独用 FP16 保留那层

我曾经调一个 YOLOv5 模型,量化后 mAP 掉了 8 个点。逐层排查后发现是最后一层检测头的激活值范围特别大。最后把那层单独用 per-channel 量化,mAP 只掉了 1.2 个点。嗯,这种「混合精度量化」的思路,后面章节会详细讲。

一句话总结:量化不是「一刀切」,而是「对症下药」。理解 scale 和 zero-point 的本质,你就能精准控制精度损失。


公众号:蓝海资料掘金营,微信deep3321