2、量化基础:理解线性量化原理,掌握scale和zero_point的定义,区分对称与非对称量化,为后续Int8推理打下基础
在CMSIS-NN的Int8推理链路中,量化是连接浮点模型与嵌入式部署的桥梁。若不了解scale(缩放因子)与zero_point(零点)的数学含义,后续的卷积、全连接、激活函数等算子实现将无从谈起。本章从线性量化的数学定义出发,逐步拆解对称与非对称两种模式的差异,并结合CMSIS-NN的实际数据结构,帮助读者建立从“浮点张量”到“Int8张量”的完整映射认知。
2.1 线性量化的数学本质
线性量化(Linear Quantization)的核心思想是将连续的浮点数值映射到离散的整数网格上。这种映射是均匀的,即浮点区间被等分为若干段,每段对应一个整数。数学上,量化过程可表示为:
q = clamp(round(r / scale) + zero_point, q_min, q_max)
其中,r为原始浮点值,q为量化后的整数,scale为缩放因子(浮点数),zero_point为整数零点。反量化(Dequantization)则是其逆过程:
r ≈ (q - zero_point) × scale
这里的关键在于scale决定了量化步长,即相邻两个整数所代表的浮点间隔;zero_point则确保浮点值0在量化域中有精确的整数表示,这对于处理填充(Padding)和偏置(Bias)至关重要。在CMSIS-NN中,scale通常以float32存储,而zero_point以int32或int8存储,具体取决于算子需求。
2.2 对称量化与非对称量化的对比
对称量化(Symmetric Quantization)强制zero_point = 0,即浮点0映射到整数0。此时量化范围关于原点对称,即[-a, a]映射到[-127, 127](或[-128, 127])。这种模式的优势在于计算效率高——无需处理零点偏移,乘法累加运算中不需要额外的减法操作。但代价是若浮点分布不对称(如ReLU后的激活值全为非负),则会浪费部分量化范围,导致精度下降。
非对称量化(Asymmetric Quantization)允许zero_point为任意整数,将浮点区间[r_min, r_max]完整映射到整数区间[q_min, q_max]。这能充分利用Int8的256个离散级别,尤其适合分布偏移明显的张量。CMSIS-NN的arm_convolve_s8等函数默认采用非对称量化,因为激活值经过ReLU后通常非负,非对称模式能显著降低量化误差。
| 对比维度 | 对称量化 | 非对称量化 |
|---|---|---|
| zero_point | 固定为0 | 任意整数(通常为int8) |
| 浮点0映射 | 精确映射到整数0 | 映射到zero_point |
| 计算复杂度 | 低(无需零点修正) | 较高(需额外减法) |
| 精度表现 | 分布对称时最优 | 分布偏移时更优 |
| 典型应用 | 权重(分布近似对称) | 激活(ReLU后非负) |
| CMSIS-NN支持 | 部分算子(如arm_fully_connected_s8) | 主流卷积/池化算子 |
2.3 scale与zero_point的确定方法
在实际部署中,scale和zero_point通常由训练后量化(Post-Training Quantization, PTQ)或量化感知训练(Quantization-Aware Training, QAT)阶段确定。其计算流程如下:
- 收集浮点分布:统计张量在验证集上的最小值和最大值(或使用百分位法避免异常值干扰)。
- 计算scale:
scale = (r_max - r_min) / (q_max - q_min),其中q_max和q_min为量化域边界(如127和-128)。 - 计算zero_point:
zero_point = round(q_min - r_min / scale),并钳位到[q_min, q_max]范围内。 - 验证映射:确保浮点0能精确映射到整数
zero_point,避免反量化时出现系统性偏差。
在CMSIS-NN中,这些参数被封装在arm_quant_info结构体中,包含scale(float)、offset(即zero_point,int32)和shift(用于定点化加速)。值得注意的是,CMSIS-NN为了在Cortex-M内核上高效运行,常将浮点scale转换为定点数表示:scale_fixed = scale × 2^shift,其中shift为右移位数,这样可将浮点乘法转化为整数乘法和移位操作。
实战场景:PTQ量化一个ReLU后的激活张量
假设某层激活值经统计得到r_min = 0.0,r_max = 6.0,使用非对称量化到Int8(q_min = -128,q_max = 127)。计算过程:
scale = (6.0 - 0.0) / (127 - (-128)) = 6.0 / 255 ≈ 0.023529
zero_point = round(-128 - 0.0 / 0.023529) = round(-128) = -128
此时浮点值3.0对应的量化整数为:q = round(3.0 / 0.023529) + (-128) = round(127.5) - 128 = 128 - 128 = 0。反量化得到r = (0 - (-128)) × 0.023529 ≈ 3.012,误差约0.012,在可接受范围内。若改用对称量化,则scale = 6.0 / 127 ≈ 0.047244,量化步长增大一倍,精度明显下降。
2.4 常见误区与工程注意事项
在CMSIS-NN开发中,量化参数的错误处理是导致推理结果异常的常见原因。以下是几个高频问题:
- zero_point类型混淆:CMSIS-NN的
offset字段是int32类型,但实际值应在[-128, 127]范围内。若误用uint8存储,会导致负数零点被截断。 - scale溢出:当浮点范围极小(如
r_max - r_min < 1e-6)时,scale会非常小,定点化后shift可能超过32位,需使用int64中间变量或调整量化范围。 - 忽略偏置量化:卷积层的偏置通常不量化,而是以
int32精度累加。若偏置的scale与输入/权重的scale不匹配,需在算子内部进行重新缩放。 - 激活函数后的零点漂移:ReLU等激活函数会改变数据分布,若在量化模型中未重新统计
zero_point,会导致后续层误差累积。
风险提示:在CMSIS-NN中,arm_convolve_s8要求输入和权重的scale满足input_scale × weight_scale / output_scale为2的整数次幂,否则会引入额外的重缩放开销。设计网络时建议保持各层scale为2的幂次倍数,以利用移位操作替代除法。
2.5 量化参数在CMSIS-NN中的存储与传递
CMSIS-NN通过arm_cmsis_nn_quant_params结构体传递量化参数,其定义如下:
typedef struct {
int32_t multiplier; // 定点化后的scale,即 round(scale * 2^shift)
int32_t shift; // 右移位数
int32_t offset; // zero_point(非对称量化时使用)
} arm_cmsis_nn_quant_params;
在算子内部,反量化操作被融合到计算流程中。例如,在arm_convolve_s8中,累加器为int32,最终输出前会执行:out = (acc × multiplier) >> shift + offset。这种设计避免了逐元素浮点运算,大幅提升了Cortex-M内核上的执行效率。
经验总结:调试量化模型时,建议先打印各层的scale和offset值,与浮点模型逐层对比输出分布。若发现某层输出偏差超过5%,优先检查该层的zero_point是否设置正确,其次检查scale的定点化精度(shift是否过大导致精度丢失)。