ONNX Runtime 量化怎样比较动态量化与静态量化的部署差异为什么结果为空或不符合预期
来源:17golang原创
时间:2026-11-10 02:36:28 242浏览 收藏
ONNX Runtime 的动态量化和静态量化都不是“把模型变小就一定更快”。动态量化在每次推理时计算激活张量的量化参数,通常省掉校准集准备、精度更稳,但会增加运行时开销;静态量化先用代表性数据校准,把激活的 scale 和 zero point 固定进模型,部署成本更低,却更容易因为校准分布不匹配而出现全零、空结果或数值明显漂移。
官方地址:https://onnxruntime.ai/docs/performance/model-optimizations/quantization.html
- 先用 float32 模型固定输入名、shape、dtype 和输出基线,空结果不一定是量化本身造成的。
- Transformer、RNN 通常先试动态量化;CNN 或输入分布稳定的服务更适合评估静态量化。
- 静态量化的校准数据必须覆盖线上范围;异常时比较中间激活,不要只盯最终输出。
先把“空结果”与量化误差分开
同一个输入同时跑原始模型和量化模型,先记录输入名、元素类型、实际 shape、输出数量及每个输出的 shape。若两者的输入绑定就不一致,例如把 int64 的 token 输入送成 int32,或者动态维度的 batch、序列长度没有按原模型约定组织,量化前就可能得到空数组或后处理无法解析的结果。
建议把基线保存成摘要,而不是只打印一行“推理成功”:输出 dtype、shape、最小值、最大值、均值和非零元素数都要记录。这样可以区分“模型真的没有候选框”和“输出有值但后处理阈值把它过滤掉”。量化模型的输出若 shape 相同但数值偏差增大,才进入量化定位。
用同一份模型分别生成动态与静态版本
动态量化不需要校准读取器,适合先做快速对照。静态量化则必须实现 CalibrationDataReader,并让校准样本的预处理方式、尺寸、归一化和线上请求保持一致。下面的示例只展示生成方式;实际项目应把 feeds 换成真实输入,而不是用随机数凑校准集。
from onnxruntime.quantization import (
CalibrationDataReader, QuantFormat, QuantType,
quantize_dynamic, quantize_static,
)
class Reader(CalibrationDataReader):
def __init__(self, feeds):
# feeds 中每项的键必须与原始 ONNX 输入名完全一致
self.feeds = iter(feeds)
def get_next(self):
# 迭代结束要返回 None,运行时才知道校准已经完成
return next(self.feeds, None)
model = "model.onnx"
dynamic_model = "model.dynamic.int8.onnx"
static_model = "model.static.int8.onnx"
calibration_feeds = load_representative_feeds() # 使用真实分布的预处理结果
quantize_dynamic(
model_input=model,
model_output=dynamic_model,
weight_type=QuantType.QInt8,
)
quantize_static(
model_input=model,
model_output=static_model,
calibration_data_reader=Reader(calibration_feeds),
quant_format=QuantFormat.QDQ,
activation_type=QuantType.QUInt8,
weight_type=QuantType.QInt8,
)
比较时不要只看文件大小。至少固定同一批输入,记录冷启动和稳定运行延迟、峰值内存、最终输出差异,以及执行提供者是否真的使用了量化算子。某些硬件没有高效的 INT8 指令,量化和反量化的额外成本可能抵消收益。

静态量化结果异常,优先检查校准覆盖
静态量化把校准阶段观察到的激活范围写入模型。如果校准集只包含明亮图片、短文本或单一业务类别,而线上输入包含另一种分布,scale 可能过窄,激活被大量截断;也可能范围过宽,使有效信号挤在少数整数刻度中。两种情况都会让最终输出接近全零、置信度异常或排序变化。
排查顺序可以固定为:一是核对校准输入的名称、dtype、shape 和预处理;二是统计每个输入场景的数量,补齐长序列、极值和边界类别;三是比较 MinMax、Entropy 或 Percentile 校准方法;四是尝试 per-channel、reduce-range 或暂时排除敏感算子。不要把随机输入当校准集,它只能证明接口能被调用,不能代表模型分布。
| 现象 | 优先证据 | 处理方向 |
|---|---|---|
| 输出 shape 变了 | 输入绑定与算子支持 | 先回退到 float32 对照,检查模型导出和 opset |
| shape 相同但全零 | 激活范围、zero point、后处理阈值 | 扩大校准覆盖,比较中间激活 |
| 只有部分输入漂移 | 该场景与校准分布的距离 | 增加分层样本或排除敏感节点 |
| 速度没有提升 | 硬件指令与执行提供者 | 确认 INT8 kernel 是否命中,再决定是否保留量化 |
用中间激活定位,而不是反复换参数
ONNX Runtime 提供 onnxruntime.quantization.qdq_loss_debug 来匹配浮点模型与量化模型的权重和激活。可以先用 create_weight_matching 找出权重量化误差,再用 modify_model_output_intermediate_tensors 给两份模型增加中间输出,收集同一批输入后通过 create_activation_matching 比较对应张量。
如果误差从某个激活节点开始突然放大,就针对该节点所在算子调整量化范围;如果中间激活都接近,但最终结果为空,更可能是输出解码、阈值或标签映射问题。这个顺序能避免把后处理故障误判成校准失败。

选择方案时保留四项部署记录
动态量化适合输入变化大、校准成本高的 Transformer 或 RNN 场景;静态量化适合输入分布稳定、吞吐更重要且能维护校准集的 CNN 服务。这只是起点,最终仍要以目标硬件和真实样本的准确率、延迟、内存四项数据决定。每次发布都保存模型哈希、量化 API 参数、校准集版本、执行提供者和回归阈值,才能解释下一次“同样配置结果为空”的差异。
常见问题
动态量化一定比静态量化准确吗?
不一定。动态量化能按每次输入计算激活范围,通常更能适应变化的输入,但权重量化误差和算子支持仍会影响结果;必须用同一回归集实测。
静态量化为什么没有报错却输出全零?
模型结构可能是合法的,但校准范围没有覆盖线上数据,或输出后处理阈值放大了小幅数值误差。先对比中间激活和非零元素数。
量化后文件变小但速度更慢怎么办?
确认目标 CPU/GPU 是否有对应 INT8 指令和 kernel,并区分冷启动、单请求延迟与批量吞吐;没有硬件收益时保留浮点或混合精度可能更合理。
-
210 收藏
-
124 收藏
-
113 收藏
-
360 收藏
-
145 收藏
-
139 收藏
-
374 收藏
-
442 收藏
-
383 收藏
-
289 收藏
-
158 收藏
-
400 收藏
-
500 收藏
-
102 收藏
-
145 收藏
-
212 收藏
-
科技周边 · 人工智能 | 2天前 | 人工智能 · vLLM KV Cache gpu-memory-utilization max-num-batched-tokens max-num-seqs 批处理显存265 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习