3、量化基础:量化原理、定点与浮点、对称与非对称量化、量化参数(scale/zero-point)
在进入 Ethos-U NPU 的延迟调优之前,我们必须先建立统一的量化语言。量化本质上是将连续(或高精度)的浮点数值空间映射到离散(或低精度)的整数数值空间的过程。对于 Ethos-U 系列 NPU 而言,其核心计算单元通常以 8-bit 或 16-bit 整数执行乘加运算,权重和激活值若以浮点格式存储,不仅会占用大量内存带宽,更会因硬件不支持浮点运算而导致无法推理。因此,理解量化原理、掌握量化参数的含义,是后续分析延迟预算、选择量化策略的基石。
3.1 核心原理:从浮点域到整数域的数学映射
量化的数学本质是一个仿射变换。假设我们有一个浮点张量 r,我们希望将其映射到整数张量 q。两者之间的关系由以下公式定义:
r = S × (q - Z)
其中:
- S (Scale):浮点缩放系数,表示整数中每个“步长”对应的浮点数值大小。S 是一个正浮点数。
- Z (Zero-Point):整数零点,表示浮点数值 0.0 对应的整数表示。Z 的数据类型与量化后的整数类型一致(如 INT8 或 UINT8)。
- q:量化后的整数值。
- r:原始的浮点数值。
在 Ethos-U NPU 的硬件实现中,卷积运算的乘加操作全部在整数域完成。例如,两个浮点数的乘积 r1 × r2 可以转化为:
r1 × r2 = S1 × (q1 - Z1) × S2 × (q2 - Z2) = (S1 × S2) × (q1 - Z1) × (q2 - Z2)
由于 S1 × S2 在编译期即可预先计算为一个浮点常数,实际推理时只需要执行整数乘法 (q1 - Z1) × (q2 - Z2),最后再乘以该常数并累加偏置。这种设计使得 NPU 的 MAC(乘加)单元可以完全避免浮点运算,从而在相同功耗预算下获得更高的吞吐量。
3.2 定点与浮点:为什么 NPU 偏爱定点运算
浮点运算(FP32/FP16)虽然精度高,但硬件实现复杂,需要专门的浮点运算单元(FPU),且功耗和面积开销大。相比之下,定点运算(如 INT8)只需要简单的整数乘法器和加法器,功耗低、速度快、逻辑门数量少。对于 Ethos-U 这类面向边缘计算的 NPU,其设计目标是在有限的功耗和面积预算内实现最高的 TOPS/W(每瓦特万亿次运算)性能,因此几乎无一例外地采用定点运算。
在模型部署中,我们通常将训练好的 FP32 模型转换为 INT8 模型。这个过程包括:
- 权重量化:将卷积层、全连接层的权重从 FP32 转换为 INT8。
- 激活量化:将每一层的输出特征图(激活值)从 FP32 转换为 INT8。
- 偏置量化:偏置通常保留为 INT32 或 FP32,以避免精度损失,因为偏置的数值范围较大。
值得注意的是,Ethos-U 的硬件累加器通常使用 INT32 或 INT48 来存储中间累加结果,以防止多次乘加操作后的溢出。这意味着,虽然输入和权重是 INT8,但部分和(Partial Sum)的精度更高,最终在输出阶段再通过 Scale 和 Zero-Point 转换回 INT8。
3.3 对称与非对称量化:适用场景与选择依据
根据 Zero-Point 是否为 0,量化可以分为对称量化和非对称量化两种策略。
| 对比维度 | 对称量化 | 非对称量化 |
|---|---|---|
| Zero-Point | 固定为 0 | 可为任意整数(通常非 0) |
| 映射公式 | r = S × q | r = S × (q - Z) |
| 数值范围 | [-max, max] | [min, max] |
| 硬件实现 | 无需减法操作,计算更快 | 需要额外的减法操作 |
| 典型应用 | 权重(分布近似对称) | 激活值(如 ReLU 后非负) |
| 精度表现 | 若数据分布偏斜,精度损失大 | 能更好地利用动态范围,精度更高 |
在 Ethos-U 的部署流程中,通常对权重采用对称量化,对激活采用非对称量化。这是因为权重经过训练后,其分布通常近似于均值为 0 的高斯分布,对称量化不会浪费整数表示范围;而激活值经过 ReLU 等激活函数后,往往是非负的,使用非对称量化可以将 [0, 5.0] 的范围完整映射到 [0, 255],而对称量化则只能映射到 [-128, 127],导致一半的整数范围被浪费。
3.4 量化参数 Scale 与 Zero-Point 的计算方法
量化参数的计算依赖于校准数据集(Calibration Dataset)。校准过程通常使用一小部分具有代表性的训练数据(如 100-500 张图片),统计每一层激活值的动态范围。具体计算步骤如下:
- 收集统计值:在校准数据集上运行 FP32 模型,记录每一层输出的最小值和最大值(或使用百分位法,如 99.99% 分位数,以排除异常值)。
- 计算 Scale:根据目标整数位宽(如 INT8,范围 [-128, 127])计算 Scale。
# 对称量化 scale = max(abs(min_val), abs(max_val)) / 127.0 # 非对称量化 scale = (max_val - min_val) / 255.0 - 计算 Zero-Point:
# 对称量化 zero_point = 0 # 非对称量化 zero_point = round(-min_val / scale) # 然后裁剪到 [0, 255] - 量化与反量化:将浮点值转换为整数,或反向转换。
# 浮点 -> 整数 q = round(r / scale) + zero_point # 整数 -> 浮点 r = scale * (q - zero_point)
3.5 案例拆解:一个 Conv2D 层的量化全过程
假设我们有一个简单的卷积层,输入激活值范围为 [0.0, 6.0],权重范围为 [-1.0, 1.0]。我们使用 INT8 量化(范围 [-128, 127])。
步骤 1:计算量化参数
# 激活(非对称)
act_scale = (6.0 - 0.0) / 255.0 = 0.023529
act_zero_point = round(-0.0 / 0.023529) = 0
# 权重(对称)
w_scale = max(|-1.0|, |1.0|) / 127.0 = 1.0 / 127.0 = 0.007874
w_zero_point = 0
步骤 2:量化具体数值
假设输入激活值 r_act = 3.5,权重 r_w = 0.5。
q_act = round(3.5 / 0.023529) + 0 = round(148.75) = 149
q_w = round(0.5 / 0.007874) + 0 = round(63.5) = 64
步骤 3:整数域计算
乘积 = q_act × q_w = 149 × 64 = 9536
步骤 4:反量化回浮点(用于验证)
乘积_浮点 = act_scale × w_scale × (乘积 - act_zero_point × w_zero_point)
= 0.023529 × 0.007874 × (9536 - 0)
= 0.0001853 × 9536
= 1.767
而原始浮点乘积为 3.5 × 0.5 = 1.75,量化误差约为 0.017,相对误差约 1%。这个误差在可接受范围内。
复盘经验:在实际 Ethos-U 部署中,Vela 编译器会自动处理上述步骤,并生成优化的 C 代码。但理解这个过程有助于我们诊断精度问题。例如,如果发现某层精度异常,可以检查该层的 Scale 是否过大(说明动态范围利用不充分),或者 Zero-Point 是否偏移严重。
3.6 常见误区与总结复盘
- 误区一:所有层都使用相同的量化参数。实际上,每一层都有独立的 Scale 和 Zero-Point,因为它们的数据分布不同。
- 误区二:对称量化总是比非对称量化差。对于权重而言,对称量化通常足够且更高效;对于激活,非对称量化更优。选择取决于数据分布。
- 误区三:校准数据集越大越好。过大的校准集可能导致过拟合于校准数据的分布,而忽略了真实场景的多样性。通常 100-500 个样本足够。
- 误区四:忽略偏置的量化。偏置通常不量化到 INT8,而是保留 INT32,否则累加误差会迅速累积。