量化基础:定点数与浮点数表示、量化参数(scale/zero-point)、对称与非对称量化
在TinyML的工程落地中,模型量化是连接“训练好的高精度浮点模型”与“低功耗、低资源嵌入式设备”之间的关键桥梁。理解量化,首先需要厘清计算机如何表示数值——尤其是浮点数与定点数的本质差异。浮点数(如FP32)动态范围大、精度高,但计算开销和存储占用也大;而定点数(如INT8)虽然范围和精度受限,却能在MCU、DSP或NPU上实现极低延迟的整数运算。本章将深入剖析定点数与浮点数的表示机制,详细拆解量化参数scale(缩放因子)与zero-point(零点)的数学含义,并对比对称量化与非对称量化的适用场景,为后续PTQ(训练后量化)校准集采集与模型转换打下坚实的理论基础。
2.1 定点数与浮点数的本质差异
在计算机系统中,数值表示方式直接决定了运算精度、动态范围和硬件资源消耗。浮点数(Floating-Point)采用科学计数法的二进制形式,由符号位、指数位和尾数位组成。以FP32为例,其包含1位符号位、8位指数位和23位尾数位,能够表示从约1.18e-38到3.4e38的极大动态范围,且在小数值附近保持较高精度。这种特性使得浮点数非常适合模型训练阶段——梯度更新需要精细的数值变化,权重在初始化时往往接近零,浮点表示可以避免下溢或溢出问题。
然而,浮点运算在硬件实现上代价高昂。FP32的加法或乘法需要复杂的指数对齐和尾数规格化逻辑,在MCU(如Cortex-M系列)或低端NPU上,浮点单元(FPU)要么缺失,要么运算周期长、功耗高。相比之下,定点数(Fixed-Point)将数值表示为纯整数乘以一个固定的缩放因子,例如INT8表示范围为[-128, 127]的整数。定点运算只需要简单的整数加减乘除,硬件逻辑简单,单周期即可完成,功耗和面积都大幅降低。但定点数的精度和范围受限于位宽,例如INT8的精度约为0.01到0.1(取决于scale),无法直接表示极小或极大的数值。
2.2 量化参数:scale与zero-point的数学定义
量化映射的核心公式为:r = scale × (q - zero_point),其中r是原始浮点实数值,q是量化后的整数值,scale是浮点缩放因子(步长),zero_point是整数零点。反向转换公式为:q = round(r / scale) + zero_point。这里的scale决定了量化分辨率——scale越小,相邻整数之间的浮点间隔越小,精度越高;但过小的scale会导致整数范围溢出。zero_point则用于对齐浮点零值,确保浮点0.0能够精确映射到某个整数,避免在填充(padding)或ReLU操作时产生偏差。
在实际PTQ流程中,scale和zero_point的计算依赖于校准集(Calibration Dataset)的统计特征。通常,我们会收集一批有代表性的输入数据,前向推理得到各层激活值的分布(如最小值、最大值、百分位数),然后根据量化方案确定scale和zero_point。例如,对于非对称量化,若激活值范围为[min, max],则scale = (max - min) / (q_max - q_min),zero_point = q_min - round(min / scale)。对于对称量化,zero_point固定为0,scale = max(|min|, |max|) / q_max。
| 量化参数 | 数学定义 | 作用 | 典型取值 |
|---|---|---|---|
| scale(缩放因子) | 浮点步长,表示每个整数单位对应的浮点范围 | 控制量化分辨率,影响精度与溢出风险 | 0.001 ~ 0.1(取决于数据分布) |
| zero_point(零点) | 浮点0.0映射到的整数值 | 对齐浮点零,避免填充/ReLU偏差 | 0(对称)或 0~255(非对称) |
| q_min / q_max | 量化整数的取值范围 | 定义整数域边界,如INT8为[-128, 127] | -128, 127 或 0, 255 |
2.3 对称量化与非对称量化的对比与选择
对称量化(Symmetric Quantization)要求浮点零值精确映射到整数零,即zero_point = 0。其量化范围关于原点对称,通常取浮点张量的最大绝对值作为量化边界。这种方案的优势在于实现简单,无需存储zero_point,且卷积运算中的乘加操作可以避免额外的零点修正,硬件实现效率高。但缺点是,如果浮点数据的分布偏向某一侧(例如ReLU后的激活值全为非负),对称量化会浪费一半的整数表示范围,导致精度下降。
非对称量化(Asymmetric Quantization)允许zero_point不为零,能够根据浮点数据的实际最小值和最大值动态调整量化范围。例如,若激活值范围为[2.0, 6.0],非对称量化可以将整个INT8范围[0, 255]用于映射这4.0的区间,scale = (6.0 - 2.0) / 255 ≈ 0.0157,精度远高于对称量化(对称量化需映射[-6.0, 6.0],scale = 12.0 / 255 ≈ 0.047)。因此,对于分布偏斜的激活值,非对称量化通常能获得更好的精度。其代价是需要额外存储和计算zero_point,在卷积运算中需要额外的整数加减操作来修正零点。
2.4 案例拆解:一个Conv2D层的量化参数计算
假设我们有一个简单的Conv2D层,其权重张量W的浮点分布范围为[-1.5, 1.5],激活值A(经过ReLU后)的范围为[0.0, 6.0]。我们计划将权重和激活值都量化为INT8(范围[-128, 127])。
步骤1:计算权重的量化参数(对称量化)
取最大绝对值max_abs = 1.5,则scale_w = 1.5 / 127 ≈ 0.01181。zero_point_w = 0。量化后的权重q_w = round(W / 0.01181)。
步骤2:计算激活值的量化参数(非对称量化)
激活值范围[0.0, 6.0],使用INT8的完整范围[0, 255](注意:这里我们使用无符号INT8,即0~255,或者使用有符号INT8的[-128, 127]但调整zero_point)。为简化,采用无符号INT8:scale_a = (6.0 - 0.0) / 255 ≈ 0.02353,zero_point_a = 0 - round(0.0 / 0.02353) = 0。若使用有符号INT8,则scale_a = 6.0 / 255 ≈ 0.02353,zero_point_a = -128 - round(0.0 / 0.02353) = -128。
步骤3:模拟量化误差
原始浮点激活值A = 3.1415,量化后q_a = round(3.1415 / 0.02353) + 0 = 133,反量化回浮点r_a = 0.02353 × (133 - 0) = 3.1295,误差约为0.012。这个误差在可接受范围内。
步骤4:实际部署中的考量
在MCU上,卷积运算将使用整数乘加:q_out = sum(q_w × q_a) + q_bias。其中偏置bias通常使用INT32存储,以避免累加溢出。最终输出需要乘以scale_w × scale_a进行反量化,再传递给下一层。
2.5 常见误区与总结复盘
误区一:认为量化只是简单的“除以最大值”。实际上,量化需要综合考虑scale、zero_point、数据分布和硬件支持。例如,某些NPU只支持对称量化,此时即使激活值偏斜,也只能使用对称方案,但可以通过调整校准集或使用per-channel量化来缓解精度损失。
误区二:忽略zero_point在卷积中的影响。在非对称量化中,卷积运算需要额外处理zero_point的修正项。若直接使用对称量化的卷积核,会导致输出偏差。正确做法是:q_out = sum(q_w × q_a) - zero_point_a × sum(q_w) + q_bias,其中sum(q_w)可以预先计算。
误区三:校准集越大越好。校准集过大不仅增加PTQ时间,还可能引入冗余数据导致分布估计偏差。通常,1000~5000张具有代表性的图片或样本即可获得稳定的统计特征。关键在于样本的多样性,而非数量。
总结复盘:本章的核心在于理解量化映射的数学本质——用整数近似浮点,用scale和zero_point控制映射关系。对称量化适合权重,非对称量化适合激活值,这是TinyML部署的黄金法则。在实际PTQ流程中,我们需要根据校准集的统计结果,动态计算每层的量化参数,并通过验证集评估量化误差。如果精度损失过大,可以尝试per-channel量化(对每个输出通道单独计算scale)或混合精度量化(部分层保留FP16)。下一阶段,我们将深入探讨如何采集高质量的校准集,确保量化参数能够真实反映模型在目标场景下的数据分布。