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) |
onnxruntime.quantization 模块。安装 ORT 时记得带上 quantization 依赖:pip install onnxruntime-quantization。
3.2 使用 ORT 进行静态量化
静态量化,顾名思义,就是在推理之前先跑一遍「校准数据」,统计出每层激活值的分布范围,然后确定量化参数(scale 和 zero_point)。这样做的好处是:推理时不再需要动态计算量化参数,速度更快。
我在项目中遇到过一个问题:校准数据太少,导致量化后精度掉得厉害。后来我总结了一个经验——校准集至少要有 100~200 张图片,而且要覆盖各种光照、角度、目标大小。
3.2.1 静态量化流程
- 加载 FP32 的 ONNX 模型
- 准备校准数据集(通常是验证集的一个子集)
- 调用
quantize_static接口 - 保存 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
)
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 评估流程
- 加载量化后的 ONNX 模型
- 用验证集跑推理,保存预测结果
- 跟 Ground Truth 对比,计算 mAP
- 跟 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 | 需要排查问题 |
3.5 本章知识体系
下面这张图帮你梳理 ORT 量化的核心流程和决策路径:
嗯,到这里 ORT 量化的核心内容就讲完了。静态量化适合追求极致加速的场景,动态量化适合快速验证。不管用哪种,精度评估这一步不能省。我建议你把量化后的模型跟 FP32 模型在同一个验证集上跑一遍,对比 mAP,做到心里有数再上线。