第二章:量化原理入门——浮点数与定点数、精度损失来源、量化参数详解
各位同学,欢迎来到量化原理的硬核部分。
说实话,很多人一听到「量化」两个字,就觉得是玄学。不就是把大数变小嘛,能有多难?
嗯,我当年也是这么想的。直到第一次把 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 知识体系总览
下面这张图是我自己整理的量化原理知识结构,方便你对照理解:
这张图把量化原理拆成了四个层次:定义、数据类型、损失来源、参数选择。你顺着箭头看下来,就能理解整个量化流程的逻辑。
2.6 实战中的量化参数调试
最后分享一个我自己的调试流程:
- 先跑校准:用 500-1000 张图片做校准,记录每层的 min/max
- 看分布:画出权重和激活值的直方图,判断用哪种量化策略
- 试量化:先用 Min-Max 快速试一遍,看精度损失
- 调参数:如果精度掉太多,换成 KL 散度或百分位法
- 逐层排查:如果某层精度特别差,单独用 FP16 保留那层
我曾经调一个 YOLOv5 模型,量化后 mAP 掉了 8 个点。逐层排查后发现是最后一层检测头的激活值范围特别大。最后把那层单独用 per-channel 量化,mAP 只掉了 1.2 个点。嗯,这种「混合精度量化」的思路,后面章节会详细讲。
一句话总结:量化不是「一刀切」,而是「对症下药」。理解 scale 和 zero-point 的本质,你就能精准控制精度损失。
公众号:蓝海资料掘金营,微信deep3321