3、ONNX Runtime 量化工具:从入门到实战

ONNX Runtime(简称 ORT)是微软开源的跨平台推理引擎。说白了,它就是能把训练好的模型跑得更快、更省资源的一个工具。我个人习惯把它当作模型部署的「最后一公里」——模型训练完,转成 ONNX,然后用 ORT 量化、加速、上线。

这一章我们聚焦 ORT 的量化能力。你想想看,YOLOv11 模型动辄几十 MB,在边缘设备上跑起来帧率上不去,怎么办?量化就是最直接的解法。ORT 提供了两种量化方式:静态量化和动态量化。下面我带你一个个过。

3.1 ONNX Runtime 简介

ORT 支持多种硬件后端(CPU、GPU、NPU),也支持多种精度(FP32、FP16、INT8)。它的核心优势是:你不需要改模型代码,只需要加载 ONNX 模型,调用量化 API,就能得到量化后的模型。

特性 说明
跨平台 Windows / Linux / macOS / ARM
多精度 FP32 / FP16 / INT8 / UINT8
量化方式 静态量化、动态量化、QAT(量化感知训练)
硬件加速 CPU(MLAS / DNNL)、GPU(CUDA / TensorRT)
小提示:ORT 的量化工具是 onnxruntime.quantization 模块。安装 ORT 时记得带上 quantization 依赖:pip install onnxruntime-quantization。

3.2 使用 ORT 进行静态量化

静态量化,顾名思义,就是在推理之前先跑一遍「校准数据」,统计出每层激活值的分布范围,然后确定量化参数(scale 和 zero_point)。这样做的好处是:推理时不再需要动态计算量化参数,速度更快。

我在项目中遇到过一个问题:校准数据太少,导致量化后精度掉得厉害。后来我总结了一个经验——校准集至少要有 100~200 张图片,而且要覆盖各种光照、角度、目标大小。

3.2.1 静态量化流程

  1. 加载 FP32 的 ONNX 模型
  2. 准备校准数据集(通常是验证集的一个子集)
  3. 调用 quantize_static 接口
  4. 保存 INT8 模型
from onnxruntime.quantization import quantize_static, QuantType
from onnxruntime.quantization import CalibrationDataReader

# 自定义校准数据读取器
class YOLOCalibDataReader(CalibrationDataReader):
    def __init__(self, image_paths, input_name):
        self.image_paths = image_paths
        self.input_name = input_name
        self.index = 0

    def get_next(self):
        if self.index < len(self.image_paths):
            img = preprocess(self.image_paths[self.index])  # 自定义预处理
            self.index += 1
            return {self.input_name: img}
        return None

# 执行静态量化
quantize_static(
    model_input='yolov11_fp32.onnx',
    model_output='yolov11_int8_static.onnx',
    calibration_data_reader=YOLOCalibDataReader(calib_images, 'images'),
    quant_format=QuantType.QInt8,
    per_channel=True
)
注意:静态量化后的模型,输入输出仍然是 FP32 格式,但内部权重和激活值已经转为 INT8。ORT 在推理时会自动处理类型转换,你不需要手动改代码。

3.2.2 关键参数说明

参数 作用 推荐值
per_channel 是否按通道量化(精度更高) True
quant_format 量化类型(QInt8 / QUInt8) QInt8
calibration_method 校准方法(MinMax / Entropy / Percentile) Entropy(默认)

我曾经踩过一个坑:用 MinMax 校准方法,结果某个离群点把量化范围拉得特别大,导致大部分激活值都被压缩到很小的区间,精度直接掉了 5 个点。后来换成 Entropy 方法,问题就解决了。

3.3 使用 ORT 进行动态量化

动态量化跟静态量化的区别在于:它不跑校准数据,而是在推理时实时计算激活值的量化参数。好处是省去了校准步骤,坏处是推理速度提升不如静态量化明显。

动态量化适合什么场景?我个人觉得,如果你只是快速验证量化效果,或者模型对延迟要求不那么苛刻,动态量化是个不错的选择。但如果你要部署到边缘设备上跑实时推理,还是老老实实用静态量化。

from onnxruntime.quantization import quantize_dynamic, QuantType

# 执行动态量化
quantize_dynamic(
    model_input='yolov11_fp32.onnx',
    model_output='yolov11_int8_dynamic.onnx',
    weight_type=QuantType.QInt8
)

你看,代码比静态量化简单多了。不需要校准数据,一行调用就搞定。但要注意:动态量化只量化权重,不量化激活值。所以它的加速效果有限,一般能减少 20%~30% 的模型大小,推理速度提升 10%~20%。

核心区别总结:
  • 静态量化:需要校准数据,量化权重+激活,加速明显(2~4 倍)
  • 动态量化:无需校准数据,只量化权重,加速有限(1.2~1.5 倍)

3.4 量化后模型精度评估

量化完模型,第一件事就是跑精度评估。我见过太多人量化完直接上线,结果发现 mAP 掉了 10 个点,然后回头骂量化工具不好用。其实问题往往出在评估方法上。

3.4.1 评估流程

  1. 加载量化后的 ONNX 模型
  2. 用验证集跑推理,保存预测结果
  3. 跟 Ground Truth 对比,计算 mAP
  4. 跟 FP32 模型的 mAP 对比,计算精度损失
import onnxruntime as ort
import numpy as np

# 创建推理会话
session = ort.InferenceSession('yolov11_int8_static.onnx')
input_name = session.get_inputs()[0].name

# 跑推理
def infer(image):
    ort_inputs = {input_name: image}
    outputs = session.run(None, ort_inputs)
    return outputs

# 评估 mAP(伪代码,实际需用 COCO 评估工具)
total_map = 0
for img, gt in zip(val_images, val_labels):
    pred = infer(img)
    total_map += compute_map(pred, gt)
avg_map = total_map / len(val_images)
print(f'量化后 mAP: {avg_map:.4f}')

3.4.2 精度损失容忍度

任务类型 可接受精度损失 说明
目标检测(YOLO) < 1% mAP 优秀
目标检测(YOLO) 1%~3% mAP 可接受
目标检测(YOLO) > 3% mAP 需要排查问题
避坑指南:我曾经遇到一个情况——量化后 mAP 掉了 4 个点,排查了半天发现是校准数据跟验证集分布不一致。校准集用的是白天场景的图片,验证集里有一半是夜间图片。后来我把校准集换成跟验证集同分布的图片,精度损失降到了 1.2%。所以,校准数据一定要有代表性。

3.5 本章知识体系

下面这张图帮你梳理 ORT 量化的核心流程和决策路径:

ONNX Runtime 量化工具链流程图 ORT 量化工具链流程图 FP32 ONNX 模型 选择量化方式 动态量化 静态量化 INT8 模型 校准数据集 INT8 模型 精度评估(mAP) 动态量化:无需校准数据 静态量化:需要校准数据

嗯,到这里 ORT 量化的核心内容就讲完了。静态量化适合追求极致加速的场景,动态量化适合快速验证。不管用哪种,精度评估这一步不能省。我建议你把量化后的模型跟 FP32 模型在同一个验证集上跑一遍,对比 mAP,做到心里有数再上线。