
INT8只有256个整数码模型需要用Scale说明相邻整数代表多大实数间隔用Zero Point说明实数0对应哪个整数码。量化与反量化都依赖这两个参数。权重的不同输出通道可能具有不同数值范围。Per-Channel量化为每个通道设置独立Scale通常能减少大范围通道对小范围通道的精度挤压。Scale决定分辨率Zero Point对齐实数零点超出可表示范围的值会饱和无法在反量化时恢复。完成本篇后你应该能够从浮点最小值和最大值推导scale与zero point。解释对称、非对称、per-tensor和per-channel量化。比较一组不同范围权重在共享scale与独立scale下的误差。scale表示相邻整数码之间隔多远zero point表示实数0落在哪个整数码。一、量化与反量化是线性映射量化先用实数除以Scale再加Zero Point并舍入到整数。反量化执行相反关系。Scale越小整数步进对应的实数间隔越细但可覆盖范围也越窄。Zero Point让非对称范围也能准确表示实数0。对称权重量化常让Zero Point为0便于某些内核优化。量化q round(r / scale) zero_point反量化r ≈ scale × (q − zero_point)图1不同权重通道使用独立Scale可以适配各自数值范围二、舍入和饱和形成不可逆误差多个相近实数可能映射到同一整数码形成舍入误差。超出校准范围的值会被裁剪到-128或127形成饱和误差。后者通常更严重。代表性数据集需要覆盖常见和边界输入使激活范围既不过窄导致饱和也不过宽导致有效分辨率下降。表1量化参数对结果的影响对象作用错误表现Scale实数步进与覆盖范围分辨率差或大量饱和Zero Point实数0的整数位置整体偏移舍入选择最近整数码细小量化噪声饱和限制到整数边界极端输入信息丢失通道粒度适配不同权重范围部分通道误差偏大三、Per-Tensor与Per-Channel关注不同粒度Per-Tensor为整个张量使用一组参数实现简单。Per-Channel通常沿卷积输出通道分别量化权重更适合通道范围差异较大的情况。激活常使用Per-Tensor参数权重常使用Per-Channel参数。具体约束由量化规范和算子实现决定不能任意选择维度。四、固件端读取参数并做边界测试输入量化代码应测试零点、最小值、最大值、半个Scale附近和超范围值。输出反量化后再与PC解释器结果比较。模型升级后如果Scale变化固件中的固定阈值需要重新换算。更稳妥的方法是保存实数阈值并在初始化时映射到当前输出整数域。C完整的int8量化与反量化函数int8_t quantize_i8(float real, float scale, int32_t zp){int32_t q (int32_t)lrintf(real / scale) zp;q q 127 ? 127 : q;q q -128 ? -128 : q;return (int8_t)q;}float dequantize_i8(int8_t q, float scale, int32_t zp){return scale * ((int32_t)q - zp);}Scale和Zero Point把实数区间放入256个格子非对称int8量化常先用 scale(rmax-rmin)/(qmax-qmin)再计算让实数0可表示的zero point并裁剪。激活分布偏向正值时非对称范围能更充分使用整数码。权重常使用对称量化把zero point固定为0范围按最大绝对值确定。这样乘法内核更简单。具体约束由算子量化规范决定不能给任意张量随意选择。反量化只能得到量化网格上的近似值。误差最大约为半个scale未饱和时scale越小分辨率越高能覆盖的实数范围越窄。scale(rmax-rmin)/(qmax-qmin)zero_pointround(qmin-rmin/scale)qclamp(round(r/scale)zero_point)Per-Channel让每个卷积输出通道拥有自己的尺子卷积不同输出通道的权重范围可能相差数十倍。共享一个大scale时小范围通道只能使用少量整数码精度损失大。Per-channel沿输出通道分别计算scale让每个卷积核使用更合适的网格。代价是模型保存多个scale内核在requantize时按通道选择乘法器和移位。现代TFLite INT8卷积规范和CMSIS-NN内核支持相应路径但仍要核对算子、维度和版本。调试时不能只打印一个scale。读取张量量化细节确认quantized_dimension和scale数组长度固件输入通常仍是per-tensor权重可能per-channel。量化粒度比较方式参数数量适合对象Per-tensor每张量1组输入、激活与输出Per-channel每输出通道1个scale卷积权重对称zero point常为0权重与高效内核非对称zero point可非0偏置分布激活动手比较共享Scale与Per-Channel误差三行权重范围分别约0.02、0.2和1.0。脚本用共享最大范围量化一次再逐行量化比较平均绝对误差。实验环境与输入Python 3标准库。保存为 per_channel.py 并运行。使用对称int8、zero point0。按顺序完成实验运行并比较每行误差。观察小范围行在共享scale下损失最大。将第三行范围扩大到5。打印每行整数码使用范围。可直接运行Per-Tensor与Per-Channel权重量化weights [[0.012, -0.018, 0.006, 0.020],[0.12, -0.18, 0.06, 0.20],[0.72, -0.95, 0.31, 1.00],]def quant_row(row, scale):q [max(-127, min(127, round(v/scale))) for v in row]restored [v*scale for v in q]mae sum(abs(a-b) for a, b in zip(row, restored))/len(row)return q, maeglobal_scale max(abs(v) for row in weights for v in row)/127print(global scale, global_scale)for i, row in enumerate(weights):q, error quant_row(row, global_scale)local_scale max(abs(v) for v in row)/127q_local, error_local quant_row(row, local_scale)print(i, global, q, mae, error,per-channel scale, local_scale, q, q_local, mae, error_local)先读懂代码中的关键路径global_scale由所有行最大绝对值决定。每行local_scale让最大权重接近±127。恢复值使用相同scaleMAE才有意义。对称示例不用-128以保持±127对称范围。你应该观察到什么共享scale由最大范围通道决定。第一行共享量化只使用很少整数码per-channel误差明显更小。大范围行两种方式接近。成功标准能手算一个实数的量化码。能解释per-channel维度对应输出通道。固件不自行猜测参数而从模型张量读取。失败时从哪里查起量化参数错误模式现象原因检查反量化偏移zero point符号错用实数0做单元测试小权重全为0共享scale过大检查per-channel支持某通道输出异常scale索引维度错核对quantized_dimensionPer-channel改善的是权重表示激活异常范围和代表性数据问题仍需单独解决。把实验迁移到真实MCU项目从解释器张量详情读取scale数组和quantized_dimension。卷积权重的通道维与模型布局有关不能假设永远是最后一维。建立三点测试实数0、可表示最小值和最大值。它能快速发现zero point符号、舍入和饱和顺序错误。卷积偏置的scale通常等于输入scale与对应权重通道scale的乘积因此每个输出通道的偏置尺度也可能不同。调试某一通道异常时应同时检查权重scale、偏置int32值和输出requantize参数。量化参数从FlatBuffer读取后建议打印一次摘要张量名、shape、scale数量、zero point数量和量化维。摘要与PC转换报告比较可以在Invoke前发现模型接口或通道量化变化。手算时还要明确舍入规则。Python round、C强制转换和内核定点舍入在半整数处可能不同使用恰好落在0.5码附近的值做测试并以目标运行时行为作为最终契约。Per-channel提高权重分辨率但不会自动解决输入预处理错误。若全部通道输出同时偏移优先查输入scale、zero point和标准化只有单独通道异常时再查通道量化参数。把结果再向前推进一步为[-1.2,2.0]推导int8非对称scale与zero point。统计一个卷积层各通道最大绝对权重。用实数0、最小值和最大值建立量化单元测试。收束Scale、Zero Point、舍入和饱和共同决定INT8近似误差。Per-Channel通过适配各权重通道范围提高有限整数码的利用效率。参考资料Arm CMSIS-NN 官方文档Google AI Edgetf.lite API