3. 模型量化基础理论:从浮点到定点,我们到底在做什么?

好,咱们进入第三章。说实话,量化这个主题,是嵌入式AI部署里最绕不开、也最容易踩坑的一关。很多同学跑完模型,发现精度掉得厉害,第一反应就是“量化有问题”。但到底问题出在哪?是表示方式选错了,还是参数没算对?

这一章,我就带你把这些基础概念彻底理清楚。浮点与定点、线性与非线性、对称与非对称,还有那两个最关键的参数——scale和zero-point。搞懂这些,你才算真正入了量化的门。

3.1 浮点与定点表示:数字的两种“语言”

计算机里表示数字,主要有两种方式:浮点和定点。咱们先看看它们长什么样。

3.1.1 浮点数(Float)

浮点数,就是带小数点的数,比如3.14、-0.001。它用科学计数法来存:数值 = 符号位 × 尾数 × 2^指数。常见的32位浮点(FP32)里,1位符号、8位指数、23位尾数。好处是动态范围大,精度高。坏处呢?计算慢、占内存、功耗高。在MCU上跑FP32,基本是“杀鸡用牛刀”,而且牛刀还跑不动。

3.1.2 定点数(Fixed-point)

定点数,就是小数点位置固定的数。比如Q7格式(1位符号,7位小数),能表示的范围是[-1, 1 - 2^-7]。定点数计算快,硬件实现简单,但动态范围小,容易溢出或精度不够。

我在一个项目里遇到过,用定点数做音频特征提取,结果因为动态范围没算好,信号一强就直接饱和了。嗯,从那以后我养成了一个习惯:先估算数据的实际分布范围,再选定点格式。

特性 浮点数 (FP32) 定点数 (Q7/Q15)
动态范围 大 (~10^38) 小 (取决于位宽)
精度 高 (23位尾数) 有限 (7/15位)
计算速度 慢 (需要FPU) 快 (纯整数运算)
硬件开销 大 小
典型应用 训练、服务器推理 MCU、DSP推理

核心观点:量化,本质上就是把浮点数“翻译”成定点数(或整数),让模型能在没有FPU的廉价芯片上跑起来。

3.2 线性量化与非线性量化:映射的两种“尺子”

量化就是找一个映射关系,把浮点值变成整数值。这个映射可以是线性的,也可以是非线性的。

3.2.1 线性量化

线性量化,说白了就是用一条直线做映射。公式很简单:

q = round(r / S) + Z

其中,r是浮点值,q是量化后的整数值,S是缩放因子(scale),Z是零点(zero-point)。

线性量化实现简单,硬件友好。CMSIS-NN里用的就是这种。但它的精度是均匀的——不管数值大小,误差都一样。对于权重分布比较均匀的网络(比如很多CNN),效果不错。

3.2.2 非线性量化

非线性量化,映射关系不是直线。比如对数量化:q = round(log2(r) / S)。它的特点是:小数值精度高,大数值精度低。这很符合人耳、人眼的感知特性——我们对微弱信号更敏感。

我记得有一次做语音唤醒词模型,用线性量化精度掉了3%,换成对数量化后只掉了0.5%。但代价是计算复杂,硬件不支持,最后还是老老实实用了线性量化+微调。

类型 映射关系 精度分布 硬件友好度 典型场景
线性量化 直线 均匀 高 CNN、全连接层
非线性量化 曲线(如对数) 非均匀 低 音频、传感器数据

我的建议:在MCU上做推理,优先选线性量化。除非你非常确定数据的分布特性,否则不要轻易上非线性——调试起来太痛苦了。

3.3 对称量化与非对称量化:零点到底要不要?

线性量化里,根据零点Z的取值,又分成两种:对称和非对称。

3.3.1 对称量化

对称量化,零点Z固定为0。公式简化为:q = round(r / S)。它的量化范围是对称的,比如int8的[-128, 127]映射到浮点的[-a, a]。

好处是计算简单,没有零点偏移。坏处是,如果数据分布不对称(比如ReLU后的输出全是非负的),就会浪费一半的量化范围。

3.3.2 非对称量化

非对称量化,零点Z可以不为0。公式就是完整的:q = round(r / S) + Z。它能灵活地匹配数据的实际分布范围,不浪费任何量化bin。

举个例子:一个浮点张量的范围是[0.0, 6.0]。对称量化会映射到[-6.0, 6.0],浪费了负半轴。非对称量化则映射到[0.0, 6.0],每个bin都用上了。

特性 对称量化 非对称量化
零点Z 固定为0 可调
范围利用率 低(数据不对称时) 高
计算复杂度 低 略高(需处理Z)
典型应用 权重(通常对称) 激活值(通常非对称)

避坑指南:我曾经在一个项目中,对ReLU后的激活值用了对称量化,结果精度掉了5%。换成非对称后,精度损失降到了1%以内。记住:激活值用非对称,权重用对称,这是很多工业实践的默认选择。

3.4 量化参数:Scale和Zero-Point

好了,终于到了最核心的部分。量化参数就两个:S(scale)和Z(zero-point)。它们决定了整个量化映射。

3.4.1 Scale(缩放因子)

Scale决定了每个量化步长代表多大的浮点值。公式:S = (r_max - r_min) / (q_max - q_min)。比如int8量化,q_max=127,q_min=-128,分母就是255。

Scale越小,精度越高,但容易溢出。Scale越大,动态范围大,但精度低。这是个trade-off。

3.4.2 Zero-Point(零点)

Zero-point是浮点0值对应的量化值。公式:Z = round(q_min - r_min / S)。在非对称量化里,它确保浮点0能被精确表示——这对填充(padding)操作特别重要。

我个人习惯,在计算scale和zero-point时,会先用一个校准数据集(calibration dataset)跑一遍推理,收集每层激活值的实际范围。然后用这个范围去算参数。这样比用理论范围(比如[-6, 6])要准得多。

// 伪代码:计算scale和zero-point
float r_min = find_min(activation_tensor);
float r_max = find_max(activation_tensor);
int q_min = -128, q_max = 127;

float scale = (r_max - r_min) / (q_max - q_min);
int zero_point = round(q_min - r_min / scale);

// 量化
int8_t q = round(r / scale) + zero_point;
// 反量化
float r_hat = (q - zero_point) * scale;

关键点:量化不是简单的“四舍五入”。它是一整套参数计算和映射过程。scale和zero-point算对了,量化就成功了一半。

3.5 知识体系总览

为了让你更直观地理解这些概念之间的关系,我画了一张图。你可以把它当作本章的“地图”。

模型量化基础理论知识体系 模型量化基础理论体系 模型量化 浮点 vs 定点表示 线性 vs 非线性量化 对称 vs 非对称量化 FP32 / FP16 Q7 / Q15 / Q31 均匀映射 对数/其他映射 Z=0 Z≠0 量化参数:Scale & Zero-Point 核心公式 量化:q = round(r / S) + Z 反量化:r_hat = (q - Z) × S S = (r_max - r_min) / (q_max - q_min) , Z = round(q_min - r_min / S)

这张图把本章的核心概念串起来了。从最上层的“模型量化”出发,分支出三种表示方式,最终汇聚到量化参数的计算。你可以在后续学习中反复回看这张图,它会帮你建立全局视角。

个人经验:刚开始学量化时,别急着调参。先把这张图里的每个概念搞明白,尤其是scale和zero-point的计算。我曾经花了一周时间调试一个量化模型,最后发现是zero-point算错了——小数点后几位没处理好。这种坑,踩一次就够了。