4、模型加载与推理:使用onnxruntime加载模型、创建推理会话、执行推理
4.1 核心原理:从ONNX文件到推理会话
ONNX Runtime(简称ORT)是微软主导的跨平台推理引擎,其核心设计思想是“一次导出,多处运行”。当我们拿到一个 .onnx 文件时,它本质上是一个包含计算图(Graph)、权重参数、算子版本和输入输出元数据的协议缓冲区(Protobuf)文件。ONNX Runtime 并不直接“读取”这个文件,而是通过其 C/C++ 核心引擎将 ONNX 计算图解析为内部优化的执行图,再根据当前硬件(CPU、CUDA、TensorRT 等)选择最优的算子内核(Kernel)来执行。
理解这一过程的关键在于区分三个概念:模型文件(静态的图定义)、推理会话(InferenceSession)(加载模型后创建的运行时实例)和执行推理(将输入数据送入会话并获取输出)。会话对象负责管理内存分配、算子调度和硬件加速,因此创建会话是资源密集型操作,应尽量复用而非每次推理都重新创建。
InferenceSession(经典接口)和 OrtSession(新式接口,从 1.16 版本开始引入)。本课程以经典接口为主,因为它兼容性最广、资料最丰富。
4.2 方法流程:标准推理五步法
在 Python 中调用 ONNX Runtime 执行推理,遵循一套固定的流程。掌握这套流程后,无论模型是图像分类、目标检测还是 NLP 任务,代码骨架都高度相似。下面给出标准流程及配套代码模板。
- 导入依赖:
import onnxruntime as ort,同时需要numpy用于数据预处理。 - 创建推理会话:调用
ort.InferenceSession(model_path, providers=None)。若不指定providers,ORT 会自动选择当前环境可用的最优执行提供程序。 - 检查模型输入输出:通过
session.get_inputs()和session.get_outputs()获取张量名称、形状和数据类型。这一步是避免推理报错的关键。 - 准备输入数据:将原始数据(图像、文本等)转换为符合模型要求的 NumPy 数组,注意
dtype必须与模型输入一致(通常是float32)。 - 执行推理并获取结果:调用
session.run(output_names, input_feed)。其中output_names传None表示返回所有输出,input_feed是字典,键为输入张量名,值为 NumPy 数组。
import onnxruntime as ort
import numpy as np
# 1. 创建推理会话(自动选择最优执行提供程序)
session = ort.InferenceSession("resnet50.onnx")
# 2. 查看输入输出信息(强烈建议打印确认)
input_info = session.get_inputs()[0]
output_info = session.get_outputs()[0]
print(f"输入: name={input_info.name}, shape={input_info.shape}, type={input_info.type}")
print(f"输出: name={output_info.name}, shape={output_info.shape}, type={output_info.type}")
# 3. 构造模拟输入数据(实际场景中需从图像/文本预处理得到)
dummy_input = np.random.randn(1, 3, 224, 224).astype(np.float32)
# 4. 执行推理
outputs = session.run(None, {input_info.name: dummy_input})
# 5. 后处理输出
logits = outputs[0] # shape: (1, 1000)
pred_class = np.argmax(logits, axis=1)
print(f"预测类别索引: {pred_class[0]}")
4.3 案例实战:图像分类模型的完整调用
下面以一个实际的 ResNet50 图像分类模型为例,演示从加载图片到输出分类结果的完整流程。该案例涵盖了数据预处理、会话创建、推理执行和结果解析四个环节,是生产环境中调用 ONNX 模型的标准范式。
案例背景:假设我们已通过 torch.onnx.export 导出了 ResNet50 的 ONNX 文件,输入为 [1, 3, 224, 224] 的 RGB 图像,输出为 [1, 1000] 的类别得分。
import onnxruntime as ort
import numpy as np
from PIL import Image
# ---------- 1. 图像预处理 ----------
def preprocess_image(image_path):
img = Image.open(image_path).resize((224, 224))
img_array = np.array(img).astype(np.float32) / 255.0
# 标准化(ImageNet 统计量)
mean = np.array([0.485, 0.456, 0.406], dtype=np.float32)
std = np.array([0.229, 0.224, 0.225], dtype=np.float32)
img_array = (img_array - mean) / std
# 调整维度为 (1, 3, 224, 224)
img_array = np.transpose(img_array, (2, 0, 1)) # HWC -> CHW
img_array = np.expand_dims(img_array, axis=0)
return img_array
# ---------- 2. 加载模型 ----------
session = ort.InferenceSession(
"resnet50.onnx",
providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
)
# ---------- 3. 执行推理 ----------
input_tensor = preprocess_image("cat.jpg")
outputs = session.run(None, {"input": input_tensor})
# ---------- 4. 解析结果 ----------
scores = outputs[0][0]
top5_idx = np.argsort(scores)[-5:][::-1]
print("Top-5 预测结果:")
for idx in top5_idx:
print(f" 类别 {idx}: 置信度 {scores[idx]:.4f}")
| 步骤 | 关键代码 | 常见错误 |
|---|---|---|
| 预处理 | np.transpose(img, (2,0,1)) |
忘记将 HWC 转为 CHW,导致维度不匹配 |
| 会话创建 | ort.InferenceSession(path) |
模型路径错误或 ONNX 文件损坏 |
| 输入命名 | session.get_inputs()[0].name |
硬编码输入名,但实际模型输入名不同 |
| 数据类型 | .astype(np.float32) |
使用 float64 输入导致报错 |
| 输出解析 | outputs[0][0] |
未取 batch 维度,直接对 (1,1000) 做 argmax |
4.4 常见误区与排障复盘
在实际使用 ONNX Runtime 的过程中,开发者经常遇到以下几类问题。理解这些误区的本质,能够显著提升调试效率。
InferenceSession 并非完全线程安全。
误区二:忽略输入张量的 dtype 和 shape 检查。ONNX Runtime 对输入类型非常严格。例如,模型期望 float32,你传入 float64 会直接抛出 InvalidArgumentError。此外,动态轴(如 batch 维度为 None)虽然允许变化,但其他维度必须严格匹配。建议在代码中显式打印 session.get_inputs() 信息,并与预处理输出对比。
误区三:混淆输出名称与输出索引。当模型有多个输出时,使用 session.run(None, feed) 返回的是按输出顺序排列的列表。若想按名称获取,可传入输出名列表:session.run(["output_name"], feed)。这在模型输出较多时能避免索引错乱。
排障复盘:当推理报错时,建议按以下顺序排查:
- 检查 ONNX 文件是否完整:
onnx.checker.check_model(onnx.load(path))。 - 确认输入数据的
shape、dtype、layout(NCHW vs NHWC)与模型要求一致。 - 查看
providers是否包含所需执行提供程序:ort.get_available_providers()。 - 若使用 GPU,确认 CUDA 版本与 ONNX Runtime GPU 包匹配(如
onnxruntime-gpu对应 CUDA 11.x/12.x)。 - 启用会话日志:
ort.SessionOptions()设置session.log_severity_level = 0查看详细日志。
通过以上系统化的方法,绝大多数模型加载与推理问题都能在几分钟内定位并解决。掌握这些细节后,你便可以将 ONNX Runtime 稳定地集成到自己的 Python 服务或脚本中,实现高效的跨平台模型部署。