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

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 都来自同一份浮点模型。若把比较集混入校准,或者替换了输入预处理,部署决策就失去可比性。

区分静态校准依赖与共同输入
图1:区分静态校准依赖与共同输入。静态结构说明图,非截图或运行结果。

本例主动生成一个小 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 调用开销。这个极小模型和少量输入适合检查比较方法,不能据此宣布业务加速。

解释统一部署环境与独立指标
图2:解释统一部署环境与独立指标。静态结构说明图,非截图或运行结果。
维度动态量化静态量化
离线激活校准本例不需要Reader 提供校准输入
激活参数来源推理时计算由校准确定并写入产物
部署包量化模型及运行依赖量化模型及运行依赖,不必附原始校准集
风险重点运行时开销与算子支持分布偏移与校准代表性
可否直接上线需目标环境业务评测需目标环境业务评测

让部署记录能够支持回退

report.json 记录运行时版本、执行提供器、校准与比较样本数。正式实验还应保存原模型与量化产物标识、转换参数、依赖版本、CPU 型号、线程、预处理版本及数据集合版本;原始校准内容放在受限存储,部署日志只保留必要标识。CPUExecutionProvider 的结果不能直接外推到 GPU、移动端或其他执行提供器。

保留 FP32 作为回退项。若业务指标下降超过预先约定的容忍范围,或算子支持、资源占用不满足约束,先回退原模型,再分析偏差层或量化配置。不要仅凭文件变小就替换线上模型。对小模型而言,量化元数据甚至可能使文件更大。

静态校准可否直接使用随机数

本例的随机数只服务于自造 MatMul 的可复用演示。真实图像、语音或文本模型需要与生产预处理和输入分布相符的代表性校准集,并使用独立数据评估任务指标。随机校准所得结论不能迁移到业务模型。

输出误差很小就能认为效果相同吗

不能。接近决策阈值的微小变化也可能改变分类、排序或生成行为;应同时比较业务任务指标和关键输入子集。静态与动态没有脱离模型、硬件、算子和数据的通用胜者。

官方量化参数与支持说明:https://onnxruntime.ai/docs/performance/model-optimizations/quantization.html。

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