登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

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 指令,量化和反量化的额外成本可能抵消收益。

ONNX Runtime 动态量化与静态量化的激活参数来源、模型文件和部署边界说明图
图1:ONNX Runtime 两种量化方式的参数来源与部署边界说明图,不是运行截图。

静态量化结果异常,优先检查校准覆盖

静态量化把校准阶段观察到的激活范围写入模型。如果校准集只包含明亮图片、短文本或单一业务类别,而线上输入包含另一种分布,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 比较对应张量。

如果误差从某个激活节点开始突然放大,就针对该节点所在算子调整量化范围;如果中间激活都接近,但最终结果为空,更可能是输出解码、阈值或标签映射问题。这个顺序能避免把后处理故障误判成校准失败。

ONNX Runtime 量化排查中从输入契约到中间激活再到后处理输出的关系说明图
图2:从输入契约、中间激活到最终后处理的误差定位结构图,不是运行截图。

选择方案时保留四项部署记录

动态量化适合输入变化大、校准成本高的 Transformer 或 RNN 场景;静态量化适合输入分布稳定、吞吐更重要且能维护校准集的 CNN 服务。这只是起点,最终仍要以目标硬件和真实样本的准确率、延迟、内存四项数据决定。每次发布都保存模型哈希、量化 API 参数、校准集版本、执行提供者和回归阈值,才能解释下一次“同样配置结果为空”的差异。

常见问题

动态量化一定比静态量化准确吗?

不一定。动态量化能按每次输入计算激活范围,通常更能适应变化的输入,但权重量化误差和算子支持仍会影响结果;必须用同一回归集实测。

静态量化为什么没有报错却输出全零?

模型结构可能是合法的,但校准范围没有覆盖线上数据,或输出后处理阈值放大了小幅数值误差。先对比中间激活和非零元素数。

量化后文件变小但速度更慢怎么办?

确认目标 CPU/GPU 是否有对应 INT8 指令和 kernel,并区分冷启动、单请求延迟与批量吞吐;没有硬件收益时保留浮点或混合精度可能更合理。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>