2、模型量化基础:量化的数学原理、定点数与浮点数、量化误差分析
2.1 为什么需要量化?
做边缘计算的朋友都知道,模型在服务器上跑得飞起,一放到嵌入式设备上就卡成PPT。原因很简单——服务器有强大的GPU和充足的内存,而边缘设备往往只有几MB的SRAM和几百MHz的MCU。
我最早接触量化是在一个智能门锁项目上。客户要求人脸识别延迟低于200ms,但模型有50MB,芯片只有4MB内存。当时我第一反应是「这不可能」,后来才发现量化就是专门解决这类问题的。
量化的核心思想很简单:用更少的比特数来表示模型的参数和中间结果。比如原本用32位浮点数,现在换成8位整数。这样一来,模型体积直接缩小4倍,推理速度也能提升好几倍。
量化带来的三大好处:
- 模型体积缩小:从FP32到INT8,体积缩小75%
- 推理速度提升:整数运算比浮点运算快得多,尤其在ARM Cortex-M这类芯片上
- 功耗降低:更少的数据搬运,更简单的计算单元
2.2 浮点数与定点数——两种世界观
要理解量化,得先搞清楚浮点数和定点数到底有什么区别。说白了,它们就是两种表示实数的方式。
2.2.1 浮点数(FP32)
浮点数用科学计数法来存数字。一个32位浮点数由三部分组成:1位符号位、8位指数位、23位尾数位。
FP32 = (-1)^sign × 2^(exponent - 127) × (1 + mantissa)
举个例子,3.14这个数在FP32里长这样:
符号位: 0 (正数)
指数位: 10000000 (表示2^1)
尾数位: 10010001111010111000011 (表示1.57)
结果: 1 × 2^1 × 1.57 = 3.14
浮点数的好处是动态范围大,能表示从10^-38到10^38这么宽的范围。但代价是计算复杂,硬件实现成本高。
我的一点经验:在边缘设备上,浮点运算往往需要软件模拟,或者依赖FPU硬件。很多低端MCU根本没有FPU,跑浮点运算慢得让人抓狂。
2.2.2 定点数(INT8)
定点数就简单多了。它用固定的小数点位置来表示数字。比如Q7格式,1位符号位,7位小数位:
INT8 = 整数部分 × 2^(-7)
拿Q7格式来说,它能表示的范围是[-1, 0.9921875],精度是2^(-7) ≈ 0.0078。
定点数的好处是计算简单,硬件实现成本低。但问题是动态范围小,精度有限。你想想看,如果模型参数分布在[-10, 10]这个范围,用Q7格式根本装不下。
| 数据类型 | 比特数 | 动态范围 | 精度 | 硬件复杂度 |
|---|---|---|---|---|
| FP32 | 32 | ~10^76 | ~10^-7 | 高 |
| FP16 | 16 | ~10^5 | ~10^-3 | 中 |
| INT8 | 8 | 256 | 1/256 | 低 |
| INT4 | 4 | 16 | 1/16 | 极低 |
2.3 量化的数学原理
量化的本质就是做一个映射:把浮点数范围映射到整数范围。最常见的做法是线性量化。
2.3.1 线性量化公式
线性量化的核心是两个参数:缩放因子(scale)和零点(zero_point)。
量化: q = round(r / scale) + zero_point
反量化: r = (q - zero_point) × scale
其中:
r是浮点数q是量化后的整数scale是缩放因子,决定每个整数步长代表多少浮点数zero_point是零点,保证浮点0能精确映射到整数
举个例子,假设我们要把[-1.0, 1.0]这个范围量化到INT8([-128, 127]):
scale = (1.0 - (-1.0)) / (127 - (-128)) = 2.0 / 255 ≈ 0.00784
zero_point = round(-(-1.0) / 0.00784) + (-128) = round(127.55) - 128 = 0
所以浮点数0.5量化后:
q = round(0.5 / 0.00784) + 0 = round(63.78) = 64
关键点:量化不是简单的截断,而是带舍入的映射。这个舍入操作就是量化误差的来源之一。
2.3.2 对称量化 vs 非对称量化
我刚开始做量化时,经常搞混这两种方式。后来发现其实很简单:
- 对称量化:zero_point = 0,浮点范围关于0对称。适合ReLU这类激活函数后的值(都是正数)
- 非对称量化:zero_point ≠ 0,可以更灵活地匹配数据分布。适合权重这类可能有正有负的参数
对称量化的好处是实现简单,计算时少一次减法。非对称量化则能更好地利用整数范围,减少量化误差。
2.4 量化误差分析
量化必然带来误差。我见过不少同学一看到精度下降就慌了,其实大可不必。关键是要搞清楚误差从哪里来,有多大影响。
2.4.1 误差来源
量化误差主要有三个来源:
- 舍入误差:浮点数映射到整数时,需要四舍五入。这个误差最大不超过半个scale
- 截断误差:超出量化范围的值会被截断到边界值。比如INT8范围是[-128, 127],浮点数200会被截断成127
- 精度损失:量化后的整数只能表示有限个数值,相邻两个量化值之间的浮点数无法区分
我曾经踩过的坑:有一次量化一个分类模型,精度从98%掉到了85%。排查了半天,发现是某个层的权重分布特别不均匀,大部分值集中在[-0.1, 0.1]之间,但有几个异常值达到了±5。结果量化时scale被拉得很大,导致大部分权重精度严重损失。
解决方案是:先做权重裁剪,把异常值限制在合理范围内,再重新量化。精度恢复到了96%。
2.4.2 量化误差的数学表达
假设原始浮点值为r,量化后的值为q,反量化后的值为r':
量化误差: e = r - r'
绝对误差: |e| ≤ scale / 2
相对误差: |e| / |r| ≤ scale / (2|r|)
从公式可以看出,当r很小时,相对误差会变得很大。这就是为什么量化对小数值特别不友好。
2.4.3 如何评估量化误差
我常用的评估方法有两种:
- 逐层误差分析:对比量化前后每一层的输出,计算均方误差(MSE)或信噪比(SNR)
- 端到端精度评估:直接跑验证集,看量化后的模型精度下降了多少
一般来说,如果逐层MSE小于10^-3,端到端精度下降不超过1%,这个量化方案就是可接受的。
我的建议:不要只看最终精度。有时候精度下降1%,但模型体积缩小了4倍,推理速度提升了3倍,这个trade-off是值得的。关键要看你的应用场景对精度有多敏感。
2.5 小结
量化说白了就是一场「精度换效率」的交易。浮点数给了我们无限接近真实值的可能,但代价是计算复杂、存储量大。定点数虽然精度有限,但胜在简单高效。
在实际项目中,我一般会先跑一遍FP32的模型作为baseline,然后尝试INT8量化。如果精度下降在可接受范围内,就直接用INT8。如果不行,再考虑混合精度或者更复杂的量化方案。
下一章我们会聊具体的量化方法——从最简单的MinMax量化到更高级的KL散度量化。到时候我会分享一些实际项目中的调参经验,保证让你少走弯路。