ONNX Runtime 量化怎样比较动态量化与静态量化的部署差异怎样完成一个可复用的最小示例
来源:17golang原创
时间:2026-11-10 02:33:01 374浏览 收藏
ONNX Runtime 动态量化在推理时计算激活量化参数,静态量化则用校准数据预先确定这些参数。比较部署差异时,要固定原模型、执行提供器、线程与输入,再分开记录产物大小、输出误差和推理延迟。动态量化不需要同样的离线激活校准,但有运行时计算开销;静态量化依赖校准数据的代表性,二者都不保证一定更快或完全保留精度。
ONNX Runtime 官方地址:https://onnxruntime.ai/
先把输入模型、校准数据和比较数据分开
保护的资产有三类:可回退的 fp32.onnx、用于校准的 calibration,以及独立比较集 evaluation。Reader 负责把 calibration 交给静态量化;dynamic.onnx 与 static.onnx 都来自同一份浮点模型。若把比较集混入校准,或者替换了输入预处理,部署决策就失去可比性。

本例主动生成一个小 MatMul 模型,不下载模型、不接触生产数据。真实部署需把模型来源与运行资源限制纳入边界:不可信模型或超大输入可能消耗资源,校准数据还可能携带业务信息。量化转换不是模型内容安全检查,也不应把原始敏感样本打进部署包。
用最小 MatMul 模型生成两种量化产物
在独立 Python 环境安装依赖,保存下方脚本为 compare_quant.py 再运行。下面是供读者执行的示例,本文没有运行量化、代码或基准测试。
# 在隔离环境安装示例所需的模型工具与 CPU 运行时 python -m pip install numpy onnx onnxruntime # 正式复现时应记录并固定实际安装的依赖版本 python compare_quant.py
from pathlib import Path
import json
import time
import numpy as np
import onnx
from onnx import helper, TensorProto, numpy_helper
import onnxruntime as ort
from onnxruntime.quantization import (
CalibrationDataReader, QuantFormat, QuantType,
quantize_dynamic, quantize_static,
)
# 只生成教学模型,不加载来源不明的模型或真实用户数据
rng = np.random.default_rng(7)
out = Path("quant-demo")
out.mkdir(exist_ok=True)
weight = rng.normal(size=(8, 4)).astype(np.float32)
graph = helper.make_graph(
[helper.make_node("MatMul", ["x", "w"], ["y"])],
"demo",
[helper.make_tensor_value_info("x", TensorProto.FLOAT, [1, 8])],
[helper.make_tensor_value_info("y", TensorProto.FLOAT, [1, 4])],
[numpy_helper.from_array(weight, name="w")],
)
model = helper.make_model(
graph, opset_imports=[helper.make_opsetid("", 13)], ir_version=8
)
fp = out / "fp32.onnx"
onnx.save(model, str(fp))
# 校准集与比较集分开生成;随机校准只适用于此教学模型
calibration = [rng.normal(size=(1, 8)).astype(np.float32) for _ in range(32)]
evaluation = [rng.normal(size=(1, 8)).astype(np.float32) for _ in range(16)]
class Reader(CalibrationDataReader):
def __init__(self, values):
self.values = values
self.rewind()
def get_next(self):
return next(self.items, None)
def rewind(self):
self.items = iter({"x": x} for x in self.values)
dynamic = out / "dynamic.onnx"
static = out / "static.onnx"
quantize_dynamic(str(fp), str(dynamic), weight_type=QuantType.QInt8,
op_types_to_quantize=["MatMul"])
quantize_static(str(fp), str(static), Reader(calibration),
quant_format=QuantFormat.QDQ,
activation_type=QuantType.QInt8, weight_type=QuantType.QInt8,
op_types_to_quantize=["MatMul"])
def session(path):
# 固定线程和执行提供器,避免把配置差异误当量化收益
options = ort.SessionOptions()
options.intra_op_num_threads = 1
options.inter_op_num_threads = 1
return ort.InferenceSession(str(path), options,
providers=["CPUExecutionProvider"])
baseline = session(fp)
expected = [baseline.run(None, {"x": x})[0] for x in evaluation]
report = {"ort_version": ort.__version__, "provider": "CPUExecutionProvider",
"calibration_n": len(calibration), "evaluation_n": len(evaluation),
"variants": []}
for path in [fp, dynamic, static]:
engine = session(path)
# 会话初始化不计入热推理延迟;每个版本使用相同输入
for _ in range(10):
engine.run(None, {"x": evaluation[0]})
costs, errors = [], []
for x, target in zip(evaluation, expected):
start = time.perf_counter()
result = engine.run(None, {"x": x})[0]
costs.append((time.perf_counter() - start) * 1000)
errors.append(float(np.max(np.abs(result - target))))
report["variants"].append({
"file": path.name, "bytes": path.stat().st_size,
"max_abs_error": max(errors),
"median_ms": float(np.median(costs)),
})
# 保存原始测量字段,不预填“加速倍数”或精度结论
(out / "report.json").write_text(json.dumps(report, indent=2), encoding="utf-8")
这里使用显式形状和单一 MatMul,省去复杂模型预处理。生产模型应把形状推导与适用的优化放在独立准备阶段,让两个分支读取相同预处理产物。QInt8 与 QDQ 是本例选项,不是对所有执行提供器、CPU 指令集和算子的统一推荐;若量化算子无法加载,应先检查支持范围和依赖版本,不要直接宣称另一种量化方式失败。
把文件大小、误差和延迟分列记录
脚本为每个模型创建相同 CPUExecutionProvider 会话,并记录 bytes、max_abs_error 和 median_ms。max_abs_error 是与 FP32 输出相比的最大绝对误差,不是分类准确率。median_ms 只统计热推理调用,不含模型加载时间,也混有 Python 调用开销。这个极小模型和少量输入适合检查比较方法,不能据此宣布业务加速。

| 维度 | 动态量化 | 静态量化 |
|---|---|---|
| 离线激活校准 | 本例不需要 | Reader 提供校准输入 |
| 激活参数来源 | 推理时计算 | 由校准确定并写入产物 |
| 部署包 | 量化模型及运行依赖 | 量化模型及运行依赖,不必附原始校准集 |
| 风险重点 | 运行时开销与算子支持 | 分布偏移与校准代表性 |
| 可否直接上线 | 需目标环境业务评测 | 需目标环境业务评测 |
让部署记录能够支持回退
report.json 记录运行时版本、执行提供器、校准与比较样本数。正式实验还应保存原模型与量化产物标识、转换参数、依赖版本、CPU 型号、线程、预处理版本及数据集合版本;原始校准内容放在受限存储,部署日志只保留必要标识。CPUExecutionProvider 的结果不能直接外推到 GPU、移动端或其他执行提供器。
保留 FP32 作为回退项。若业务指标下降超过预先约定的容忍范围,或算子支持、资源占用不满足约束,先回退原模型,再分析偏差层或量化配置。不要仅凭文件变小就替换线上模型。对小模型而言,量化元数据甚至可能使文件更大。
静态校准可否直接使用随机数
本例的随机数只服务于自造 MatMul 的可复用演示。真实图像、语音或文本模型需要与生产预处理和输入分布相符的代表性校准集,并使用独立数据评估任务指标。随机校准所得结论不能迁移到业务模型。
输出误差很小就能认为效果相同吗
不能。接近决策阈值的微小变化也可能改变分类、排序或生成行为;应同时比较业务任务指标和关键输入子集。静态与动态没有脱离模型、硬件、算子和数据的通用胜者。
官方量化参数与支持说明:https://onnxruntime.ai/docs/performance/model-optimizations/quantization.html。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
139 收藏
-
242 收藏
-
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次学习