量化基础理论:定点数与浮点数、量化参数、量化映射函数、量化误差分析
各位同学,今天我们正式进入量化训练的第一章。说实话,量化这个概念,很多做边缘计算的同学一开始都觉得有点虚——不就是把32位浮点数变成8位整数嘛,能有多复杂?嗯,我当年也是这么想的,直到第一次在嵌入式设备上跑模型,发现精度掉了5个点,才意识到这里面的门道比想象中深得多。
咱们今天就把这块硬骨头啃下来。我会尽量用我踩过的坑,帮你们把路铺平。
1. 定点数与浮点数:计算机怎么表示数字?
先问个问题:为什么模型在PC上跑得好好的,一上边缘设备就崩?
说白了,PC上的GPU和CPU默认用浮点数(FP32)做运算,而边缘设备上的NPU、DSP、甚至MCU,很多只支持定点数(INT8)。这两种数的表示方式完全不同。
浮点数(Floating Point)
浮点数用科学计数法存数字。比如FP32,它用1位符号位、8位指数位、23位尾数位。好处是动态范围大,能表示从1.4e-45到3.4e38这么宽的范围。坏处呢?计算慢、占内存。
我记得有一次在树莓派上跑一个轻量级检测模型,光模型参数就占了200MB,推理一次要3秒。你说这能商用吗?显然不能。
定点数(Fixed Point)
定点数就简单多了。它把小数点固定在一个位置,比如Q7.8格式,7位整数位、8位小数位。范围有限,但计算快、省资源。
你想想看,边缘设备上内存和算力都金贵,定点数简直就是为它们量身定做的。
核心区别一句话:浮点数用指数换范围,定点数用范围换效率。
2. 量化参数:Scale 和 Zero-Point
好,现在我们知道要把FP32变成INT8。但怎么变?直接截断?那精度就没了。
这里就要引入两个关键参数:Scale(缩放因子) 和 Zero-Point(零点偏移)。
Scale(缩放因子)
Scale 决定了浮点数和定点数之间的缩放比例。公式很简单:
scale = (r_max - r_min) / (q_max - q_min)
其中 r_max 和 r_min 是浮点数的最大值和最小值,q_max 和 q_min 是定点数的范围(比如INT8就是-128到127)。
我在项目中遇到过一个问题:如果数据分布不均匀,比如大部分值集中在0附近,但有几个离群点特别大,那scale会被拉得很大,导致量化后精度损失严重。后来我用了校准数据集来统计真实的r_max和r_min,而不是直接用训练集的最大最小值,效果好了很多。
Zero-Point(零点偏移)
Zero-Point 用来对齐浮点数的0和定点数的0。为什么需要这个?因为很多激活函数(比如ReLU)的输出是非负的,但INT8是有符号的(-128到127)。如果没有zero-point,0在定点数里可能对应一个非零值,那ReLU之后的结果就全偏了。
公式:
zero_point = q_min - r_min / scale
注意,zero_point 通常要取整,而且不能超出定点数的范围。
我的习惯:在做量化之前,先画一下数据的分布直方图。如果数据分布比较对称,zero_point 往往接近0;如果严重偏斜,zero_point 会偏移很多。这个偏移量越大,量化误差通常也越大。
3. 量化映射函数:怎么把浮点数变成定点数?
有了scale和zero-point,我们就可以做映射了。映射函数其实就两个:
量化(Quantize):浮点数 → 定点数
q = round(r / scale) + zero_point
这里有个坑:round操作会引入舍入误差。我建议用最近邻舍入,而不是向下取整或向上取整,因为最近邻的期望误差最小。
反量化(Dequantize):定点数 → 浮点数
r = (q - zero_point) * scale
反量化其实就是一个线性变换。注意,反量化后的值跟原始浮点值之间是有误差的,这个误差就是量化误差。
曾经踩过的坑:有一次我在做量化推理时,发现输出结果全是NaN。查了半天,原来是scale算出来是0(因为r_max等于r_min)。这种情况通常发生在某些通道的权重全部为0的时候。解决方案是在计算scale之前加一个极小值epsilon,比如1e-10。
4. 量化误差分析:精度到底掉了多少?
量化误差是绕不开的话题。你想想看,把一个32位的数硬塞进8位的容器里,怎么可能没损失?关键是要知道损失在哪、有多大。
误差来源
- 舍入误差:round操作导致的,不可避免,但可以控制。
- 截断误差:当浮点数值超出定点数范围时,会被截断到边界值。这个误差很大,要尽量避免。
- 量化噪声:量化过程本质上是在原始信号上叠加了一个噪声。这个噪声的方差跟scale的平方成正比。
如何评估误差?
我常用的指标有两个:
- SNR(信噪比):信号功率与噪声功率的比值。SNR越高,量化损失越小。
- KL散度:量化前后输出分布的差异。这个指标对分类任务特别敏感。
我记得有一次做语音识别模型的量化,SNR看起来还不错,但实际识别率掉了3%。后来用KL散度一分析,发现是某些频段的分布被严重扭曲了。所以,不要只看一个指标,要结合任务本身的评估指标一起看。
5. 知识体系图:量化基础理论全景
下面这张图是我自己整理的,把今天讲的内容串起来。你可以把它当作一个思维导图来用。
6. 实战建议:从理论到代码
理论讲完了,咱们来点实际的。下面是一个简单的量化函数实现,用Python写的。你可以直接拿去用。
import numpy as np
def quantize(r, scale, zero_point, q_min=-128, q_max=127):
"""
将浮点数张量量化为INT8
r: 浮点数输入
scale: 缩放因子
zero_point: 零点偏移
"""
q = np.round(r / scale) + zero_point
q = np.clip(q, q_min, q_max) # 防止溢出
return q.astype(np.int8)
def dequantize(q, scale, zero_point):
"""
将INT8反量化为浮点数
"""
return (q.astype(np.float32) - zero_point) * scale
# 示例
r = np.array([0.5, 1.2, -0.3, 2.0], dtype=np.float32)
scale = 0.02
zero_point = 0
q = quantize(r, scale, zero_point)
r_hat = dequantize(q, scale, zero_point)
print("原始值:", r)
print("量化值:", q)
print("反量化值:", r_hat)
print("误差:", r - r_hat)
小提示:实际工程中,scale和zero_point通常不是手动指定的,而是通过校准数据集统计出来的。常用的校准方法有MinMax、Percentile、KL散度等。后面章节我们会详细讲。
7. 总结
今天的内容就到这里。我们讲了定点数和浮点数的区别,量化参数scale和zero-point的含义,量化映射函数的实现,以及量化误差的来源和分析方法。
说实话,量化这个领域,理论只是敲门砖。真正让你成长的是在项目中一次次调试、一次次踩坑。我建议你回去之后,找一个小模型(比如MobileNetV2),手动做一次量化推理,看看精度变化。相信我,亲手做一遍比看十遍文章都管用。
好,今天就到这里。有问题随时交流。