量化基础:定点数表示、对称/非对称量化、量化参数(scale/zero-point)与量化公式

在TinyML的工程实践中,模型量化(Quantization)是将浮点(FP32)权重与激活映射到低比特整数(如INT8)的核心手段。PTQ(训练后量化)之所以能成为部署首选,正是因为它不需要重新训练模型,仅通过少量校准数据即可完成精度恢复。然而,若不了解定点数表示、对称/非对称量化以及scale/zero-point的数学本质,任何调优都将是无源之水。本章将系统拆解量化的数学骨架,为后续逐层误差分析打下坚实基础。

定点数表示 Qm.f格式 符号位 + 整数位 + 小数位 动态范围 = [-2^(m-1), 2^(m-1)-2^-f] 精度 = 2^-f INT8: Q1.7 / Q0.8 INT16: Q1.15 / Q8.8 量化参数 scale (s) = (r_max - r_min) / (q_max - q_min) zero_point (z) = q_min - round(r_min / s) 浮点实数 r ∈ [r_min, r_max] 整数 q ∈ [q_min, q_max] INT8: q_min=-128, q_max=127 量化/反量化公式 量化: q = round(r / s) + z 反量化: r = (q - z) * s 对称量化: z = 0 非对称量化: z ≠ 0 误差来源: 舍入 + 截断 对称 vs 非对称量化对比 对称: 范围 [-127, 127] (INT8) 非对称: 范围 [-128, 127] (INT8) 对称: 适合权重(分布近似零中心) 非对称: 适合激活(ReLU后全正) 非对称多1个量化步长, 精度略高 量化误差来源 ① 舍入误差: round() 操作 ② 截断误差: 超出范围被裁剪 ③ 精度损失: 步长过大 调优方向: 选择合适范围/粒度 per-tensor → per-channel → per-group

2.1 定点数表示:从浮点到定点的思维转换

浮点数(FP32)用32位表示一个数,其中1位符号、8位指数、23位尾数,动态范围可达10^38,但代价是计算单元面积大、功耗高。在TinyML设备(如Cortex-M4、RISC-V MCU)上,FP32运算往往需要软件模拟或专用FPU,而INT8整数运算则能利用硬件乘法器直接完成,速度提升4~8倍,功耗降低10倍以上。定点数表示的核心思想是:用固定的小数点位置来表示实数,即约定整数位宽和小数位宽。

定点数格式通常写作Qm.f,其中m表示整数位(含符号位),f表示小数位。例如Q1.7表示1位符号+0位整数+7位小数,能表示的范围是[-1, 1-2^-7],精度为2^-7≈0.0078。对于TinyML中的权重,常用Q1.7或Q0.8(即纯小数);对于激活值,若经过ReLU后全为正,则可用Q0.8表示[0, 1)区间。定点数的运算规则是:乘法后小数点位置变为f1+f2,需要重新归一化;加法要求两个数的小数位对齐。

工程要点:在PTQ中,我们并不直接使用Qm.f格式存储权重,而是使用整数加scale的隐式定点表示。但理解Qm.f有助于你判断:当scale过小时,实际等效于小数位过多,导致整数部分溢出;当scale过大时,等效于小数位不足,精度急剧下降。这种“溢出-精度”权衡是逐层调优的核心矛盾。

2.2 量化参数:scale与zero-point的数学定义

量化本质是一个仿射变换(affine mapping),将浮点实数区间[r_min, r_max]映射到整数区间[q_min, q_max]。两个关键参数:

  • scale(缩放因子):表示一个量化步长对应的浮点数值,计算公式为:s = (r_max - r_min) / (q_max - q_min)。scale越小,量化精度越高,但需要更大的整数范围来覆盖浮点动态范围。
  • zero_point(零点):浮点值0对应的整数表示,计算公式为:z = q_min - round(r_min / s)。zero_point的作用是保证浮点0能被精确表示,这对于处理padding、ReLU等操作至关重要。

量化公式为:q = round(r / s) + z,反量化公式为:r = (q - z) * s。在PTQ中,我们通常先统计校准数据集上每层激活的min/max,然后计算scale和zero_point。对于权重,由于分布相对稳定,可以直接从预训练模型中统计。

参数对称量化非对称量化适用场景
zero_point固定为0根据数据分布计算非对称适合ReLU后激活
有效范围[-127, 127] (INT8)[-128, 127] (INT8)非对称多1个量化级别
计算复杂度低(无需零点偏移)较高(需处理零点)硬件加速器偏好对称
精度表现对零中心分布友好对偏态分布更优权重用对称,激活用非对称

2.3 对称量化与非对称量化的选择策略

对称量化强制zero_point=0,即浮点0映射到整数0。其优点是计算时无需减去零点,卷积运算可以简化为纯整数乘加,适合硬件实现。但缺点是:若浮点分布不对称(如ReLU后的激活全为正),则量化范围会浪费一半(负半轴全空),导致有效精度减半。非对称量化通过引入zero_point来适配任意分布,但代价是每个操作都需要额外的零点偏移计算。

在TinyML PTQ实践中,权重通常采用对称量化,因为预训练权重的分布近似零中心的高斯分布;激活通常采用非对称量化,因为ReLU等激活函数会产生全正输出。这种混合策略在TensorRT、TFLite、CMSIS-NN中均有体现。若你的模型包含大量偏置(bias),则偏置通常不量化,以FP32存储并在整数累加后加入。

经验法则:当激活值的min > 0时,非对称量化比对称量化多获得约1bit的有效精度。例如,若激活范围是[0, 6.0],对称量化实际使用[-6.0, 6.0]的范围,浪费一半;非对称量化则完整利用[0, 6.0]。在逐层误差分析中,若发现某层激活的min远大于0,优先检查是否错误使用了对称量化。

2.4 量化误差的数学本质与逐层传播

量化误差来源于三个环节:舍入误差(round操作导致的信息丢失)、截断误差(超出[min,max]的值被裁剪)、参数误差(min/max统计不准确导致scale偏差)。在PTQ中,我们通常假设校准数据集能代表真实数据分布,但实际部署时输入分布可能偏移,导致激活值超出校准范围,产生截断误差。

误差在层间传播时具有累积效应。第L层的量化误差会通过权重乘法放大或缩小,再传递到第L+1层。具体而言,若第L层激活的量化误差为Δa,则第L+1层输出的误差约为Δa * |w|(w为该层权重)。因此,误差敏感层通常是权重范数较大或激活范围较宽的层。在后续章节中,我们将通过逐层计算信噪比(SNR)来定位这些敏感层。

// 量化误差计算示例(Python伪代码)
import numpy as np
def quantize(r, s, z, q_min, q_max):
    q = np.round(r / s) + z
    q = np.clip(q, q_min, q_max)
    return q

def dequantize(q, s, z):
    return (q - z) * s

# 假设某层激活范围 [0.0, 6.0]
r = np.array([0.0, 0.5, 1.2, 3.4, 5.9, 6.0])
s = (6.0 - 0.0) / (255 - 0)  # 非对称INT8
z = 0 - round(0.0 / s)
q = quantize(r, s, z, 0, 255)
r_hat = dequantize(q, s, z)
error = np.abs(r - r_hat)
print(f"最大误差: {error.max():.4f}, 平均误差: {error.mean():.4f}")

2.5 案例拆解:一个卷积层的量化参数计算与误差分析

假设我们有一个3×3卷积层,权重分布为[-0.5, 0.5],激活分布为[0.0, 4.0]。我们分别采用两种方案:方案A(权重对称+激活非对称),方案B(权重对称+激活对称)。

方案A计算:权重scale = (0.5-(-0.5))/254 ≈ 0.00394,zero_point=0;激活scale = (4.0-0.0)/255 ≈ 0.01569,zero_point = 0 - round(0/0.01569) = 0。量化后权重范围[-127,127],激活范围[0,255]。

方案B计算:激活对称量化时,范围变为[-4.0, 4.0],scale = 8.0/254 ≈ 0.0315,zero_point=0。此时激活的量化步长是方案A的2倍,精度损失约1bit。

实际测试中,方案A的模型精度(Top-1)为68.2%,方案B为66.7%,下降1.5%。进一步分析发现,方案B中激活值0.5被量化为16(0.5/0.0315≈15.87),反量化后为0.504,误差0.004;而方案A中0.5被量化为32(0.5/0.01569≈31.87),反量化后为0.502,误差0.002。虽然单点误差差异不大,但经过多层累积后,方案B的误差被放大到可观测的程度。

风险提示:不要盲目追求非对称量化。某些硬件加速器(如ARM Ethos-U55)仅支持对称量化,若强行使用非对称,会导致额外的软件补偿计算,反而降低推理速度。在调优前,务必确认目标硬件的量化约束。

2.6 常见误区与调优方向总结

误区一:认为scale越小越好。实际上,scale过小会导致整数溢出(q超出[q_min,q_max]),产生严重的截断误差。正确做法是确保r_max/s ≤ q_max - z。

误区二:忽略zero_point的舍入影响。zero_point计算时使用了round,若r_min/s接近整数边界,舍入误差会被放大。建议在统计min/max时加入少量margin(如1%~2%),避免边界值恰好落在量化台阶上。

误区三:对所有层使用相同的量化配置。不同层的激活分布差异巨大(如第一层输入范围[0,1],中间层可能达到[0,10]),应逐层统计、逐层配置。

调优方向总结:

  1. 优先使用per-channel量化(对权重按输出通道分别计算scale),可显著减少权重误差。
  2. 对激活使用per-tensor量化,但需结合校准数据集统计min/max,并考虑使用百分位数(如99.99%)而非绝对min/max,以抵抗异常值。
  3. 若某层误差过大,可尝试混合精度(该层保留FP16或INT16),但需权衡存储与计算开销。
  4. 在PTQ后使用偏差校正(bias correction),补偿量化引起的均值偏移。