ONNX Runtime 量化的价值,不在于把模型文件“压小一点”这么简单。更准确地说,它是在精度、带宽、缓存命中率和算子执行效率之间重新做一次工程权衡。很多模型在 FP32 下看起来只是“慢”,真正上到边缘 CPU、移动端 NPU 或低功耗服务器后,瓶颈往往不是算力,而是内存搬运。INT8 量化恰好击中了这个痛点:权重体积通常可降到原来的 25% 左右,矩阵乘法的数据吞吐压力明显下降。
ONNX Runtime 量化到底量化了什么?
ONNX Runtime 的量化核心是把浮点张量映射到整数域,常见公式是:
real_value = scale × (quantized_value – zero_point)
其中 scale 负责表达数值范围,zero_point 处理非对称分布。对模型来说,被量化的对象主要有两类:
- 权重量化:把 Conv、MatMul、Gemm 等算子的权重从 FP32 转成 INT8/UINT8,模型体积下降最明显。
- 激活量化:把运行时中间张量也转成低比特表示,对性能提升更彻底,但校准要求更高。
动态量化通常只在运行时计算激活范围,适合 Transformer、BERT、部分文本模型;静态量化会提前用校准数据统计激活分布,更适合 CNN、检测模型、语音模型这类输入分布相对稳定的场景。
动态量化与静态量化的取舍
动态量化的优势是省事。拿一个 ONNX 模型,几行脚本就能把权重转成 INT8,不需要准备校准集。缺点也直白:激活仍然可能保留浮点计算,速度提升不一定夸张。一个 110M 参数的 BERT-base,在 x86 CPU 上动态量化后,常见延迟可下降 30% 到 50%,但如果算子未命中优化内核,收益会缩水。
静态量化更像“精装修”。它需要喂入几十到几百条代表性样本,让 ONNX Runtime 记录每层激活的 min/max 或分布信息。好处是推理链路可以更完整地走 INT8,尤其在 AVX512-VNNI、ARM dotprod、TensorRT EP 这类后端上,差距很明显。遗憾的是,校准集一旦偏离真实业务数据,精度会掉得很难看。
QOperator 与 QDQ:别忽略模型格式
ONNX Runtime 里常见两种量化表示:
| 格式 | 特点 | 适用场景 |
|---|---|---|
| QOperator | 直接替换为 QuantizedConv、MatMulInteger 等量化算子 | CPU 推理较常见 |
| QDQ | 在原算子前后插入 QuantizeLinear / DequantizeLinear | TensorRT、硬件加速器更友好 |
QDQ 格式看起来啰嗦,但它保留了原始图结构,后端编译器更容易识别融合机会。比如 Conv + BatchNorm + Relu 的组合,在某些 Execution Provider 中可以继续被折叠成高效 kernel。
精度损失通常来自哪里?
量化翻车,问题多半不在“INT8 太粗糙”,而在数值范围估计不准。激活里偶发的极端值会把 scale 撑大,导致大量普通值挤在很少几个整数刻度里,细节直接糊掉。工程上常用几招缓解:
- 使用 percentile 校准,丢弃极少数离群点;
- 对权重启用 per-channel 量化,而不是整层共享一个 scale;
- 对 Softmax、LayerNorm、输出头等敏感节点保留 FP32;
- 用真实线上样本做校准,不要拿随机噪声糊弄模型。
尤其是视觉模型,分类任务可能只掉 0.3% top-1,检测和分割却可能因为边界框回归被量化扰动,mAP 掉两三个点。看指标,不要只看文件大小。
一个实用判断标准
如果模型主要由 MatMul 构成,比如 BERT、推荐模型、轻量 LLM 编码器,动态量化值得直接试;如果模型是 CNN 或部署目标有专用 INT8 加速,静态 QDQ 更值得投入。若量化后延迟没变,先检查算子是否真正落到了 INT8 kernel,而不是在 Quantize 和 Dequantize 之间来回倒腾——那种“假量化”,看着很忙,实际只是在给 CPU 添堵。

这量化省事,直接几行脚本搞定,挺爽的。
听说有人量化后精度掉好多,真心怕翻车🤔