ONNX Runtime 模型推理引擎详解
1. 项目简介
ONNX Runtime是由Microsoft开发的高性能机器学习模型推理引擎,支持ONNX(Open Neural Network Exchange)格式的模型。它提供了跨平台、跨框架的模型推理能力,能够在各种硬件上高效运行机器学习模型,包括CPU、GPU、边缘设备等。
1.1 主要功能
- 高性能推理:针对不同硬件和平台进行优化
- 跨平台支持:支持Windows、Linux、macOS、Android、iOS等
- 多框架兼容:支持来自PyTorch、TensorFlow、scikit-learn等框架的模型
- 多种硬件加速:支持CPU、GPU、边缘设备等
- 灵活的API:提供C++、Python、C#、Java等多种语言的API
- 模型优化:内置模型优化功能,提高推理性能
1.2 应用场景
- 模型部署:将训练好的模型部署到生产环境
- 边缘设备推理:在资源受限的设备上运行模型
- 跨平台应用:在不同平台上使用同一模型
- 高性能推理:需要快速推理的场景,如实时应用
- 模型集成:将模型集成到现有应用中
2. 安装与配置
2.1 安装方法
ONNX Runtime可以通过多种方式安装:
2.1.1 使用pip安装(Python)
# 安装CPU版本
pip install onnxruntime
# 安装GPU版本(需要CUDA)
pip install onnxruntime-gpu2.1.2 从源码构建
对于需要自定义构建的用户,可以从源码构建ONNX Runtime:
- 克隆仓库:
git clone --recursive https://github.com/microsoft/onnxruntime.git - 进入目录:
cd onnxruntime - 运行构建脚本:
./build.sh --config Release --build_shared_lib --parallel
2.2 环境要求
ONNX Runtime的环境要求取决于目标平台和硬件:
CPU版本:
- Python 3.6+
- 支持的操作系统:Windows 10+, macOS 10.14+, Linux
GPU版本:
- CUDA 10.2+ 或 CUDA 11.0+
- cuDNN 7.6+
2.3 基本配置
在使用ONNX Runtime之前,需要确保模型已经转换为ONNX格式。大多数主流深度学习框架都支持导出为ONNX格式:
- PyTorch:
torch.onnx.export() - TensorFlow:使用TensorFlow ONNX转换器
- scikit-learn:使用skl2onnx库
3. 核心概念
3.1 ONNX格式
ONNX是一种开放的神经网络交换格式,定义了一组通用的运算符和数据类型,使得模型可以在不同框架之间无缝迁移。
3.2 推理会话(Inference Session)
推理会话是ONNX Runtime的核心概念,代表一个加载好的模型实例,用于执行推理。
3.3 执行提供者(Execution Provider)
执行提供者是ONNX Runtime中负责在特定硬件上执行模型的组件,如CPUExecutionProvider、CUDAExecutionProvider等。
3.4 输入/输出张量
模型的输入和输出以张量(tensor)形式表示,在Python中通常使用NumPy数组或PyTorch张量。
3.5 模型优化
ONNX Runtime提供了多种模型优化技术,如算子融合、常量折叠、内存优化等,以提高推理性能。
4. 基本使用
4.1 加载模型
使用ONNX Runtime加载ONNX模型:
import onnxruntime as ort
# 加载模型
session = ort.InferenceSession("model.onnx")
# 获取输入和输出名称
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
print(f"输入名称: {input_name}")
print(f"输出名称: {output_name}")4.2 执行推理
使用加载的模型执行推理:
import numpy as np
# 准备输入数据
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
# 执行推理
outputs = session.run([output_name], {input_name: input_data})
# 处理输出
result = outputs[0]
print(f"输出形状: {result.shape}")
print(f"输出值: {result}")4.3 指定执行提供者
指定使用GPU或其他硬件执行推理:
# 使用GPU执行推理
session = ort.InferenceSession(
"model.onnx",
providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
)
# 验证是否使用了GPU
print(f"执行提供者: {session.get_providers()}")4.4 批量推理
执行批量推理以提高效率:
# 准备批量输入数据
batch_size = 8
input_data = np.random.randn(batch_size, 3, 224, 224).astype(np.float32)
# 执行批量推理
outputs = session.run([output_name], {input_name: input_data})
# 处理输出
results = outputs[0]
print(f"批量输出形状: {results.shape}")5. 高级功能
5.1 模型优化
使用ONNX Runtime的模型优化功能:
from onnxruntime.quantization import quantize_dynamic, QuantType
# 量化模型以减小模型大小和提高推理速度
quantize_dynamic(
"model.onnx",
"model_quantized.onnx",
weight_type=QuantType.QUInt8
)
# 加载量化后的模型
session = ort.InferenceSession("model_quantized.onnx")5.2 性能调优
调整ONNX Runtime的性能参数:
# 设置会话选项
options = ort.SessionOptions()
# 设置执行模式(ORT_SEQUENTIAL或ORT_PARALLEL)
options.execution_mode = ort.ExecutionMode.ORT_PARALLEL
# 设置线程数
options.inter_op_num_threads = 4
options.intra_op_num_threads = 4
# 启用内存模式
options.enable_mem_pattern = True
# 加载模型时应用选项
session = ort.InferenceSession(
"model.onnx",
options=options,
providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
)5.3 多输入多输出模型
处理具有多个输入和输出的模型:
# 获取所有输入和输出名称
input_names = [input.name for input in session.get_inputs()]
output_names = [output.name for output in session.get_outputs()]
print(f"输入名称: {input_names}")
print(f"输出名称: {output_names}")
# 准备多输入数据
input_data_1 = np.random.randn(1, 3, 224, 224).astype(np.float32)
input_data_2 = np.random.randn(1, 10).astype(np.float32)
# 构建输入字典
inputs = {
input_names[0]: input_data_1,
input_names[1]: input_data_2
}
# 执行推理
outputs = session.run(output_names, inputs)
# 处理多输出
for i, output in enumerate(outputs):
print(f"输出 {i} 形状: {output.shape}")5.4 异步推理
使用异步API执行推理,提高应用响应速度:
import asyncio
async def async_inference(session, input_data, input_name, output_name):
# 创建异步推理任务
future = session.run_async([output_name], {input_name: input_data})
# 等待推理完成
outputs = await future
return outputs
# 执行异步推理
loop = asyncio.get_event_loop()
outputs = loop.run_until_complete(
async_inference(session, input_data, input_name, output_name)
)
# 处理输出
result = outputs[0]
print(f"异步推理输出形状: {result.shape}")6. 实用案例
6.1 图像分类
场景描述:使用ONNX Runtime部署图像分类模型。
实现步骤:
- 准备预训练模型并转换为ONNX格式
- 使用ONNX Runtime加载模型
- 预处理输入图像
- 执行推理
- 后处理输出结果
代码示例:
import onnxruntime as ort
import numpy as np
from PIL import Image
import torchvision.transforms as transforms
# 加载模型
session = ort.InferenceSession("resnet50.onnx")
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
# 图像预处理
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
# 加载和预处理图像
image = Image.open("cat.jpg").convert("RGB")
input_tensor = transform(image).unsqueeze(0).numpy()
# 执行推理
outputs = session.run([output_name], {input_name: input_tensor})
# 后处理结果
probabilities = np.exp(outputs[0]) / np.sum(np.exp(outputs[0]), axis=1, keepdims=True)
top5_indices = np.argsort(probabilities[0])[::-1][:5]
# 加载标签
with open("imagenet_classes.txt", "r") as f:
labels = [line.strip() for line in f.readlines()]
# 打印结果
print("Top 5 预测结果:")
for i, idx in enumerate(top5_indices):
print(f"{i+1}. {labels[idx]}: {probabilities[0][idx]:.4f}")6.2 目标检测
场景描述:使用ONNX Runtime部署目标检测模型。
实现步骤:
- 准备预训练目标检测模型并转换为ONNX格式
- 使用ONNX Runtime加载模型
- 预处理输入图像
- 执行推理
- 后处理输出结果,包括边界框解码和非极大值抑制
代码示例:
import onnxruntime as ort
import numpy as np
from PIL import Image, ImageDraw, ImageFont
# 加载模型
session = ort.InferenceSession("yolov5s.onnx")
input_name = session.get_inputs()[0].name
output_names = [output.name for output in session.get_outputs()]
# 图像预处理
def preprocess(image, input_size):
# 调整图像大小
image = image.resize(input_size)
# 转换为 numpy 数组
image = np.array(image) / 255.0
# 调整维度顺序 (H, W, C) -> (C, H, W)
image = np.transpose(image, (2, 0, 1))
# 添加批次维度
image = np.expand_dims(image, axis=0)
# 转换为 float32
image = image.astype(np.float32)
return image
# 后处理函数
def postprocess(outputs, conf_threshold=0.5, iou_threshold=0.45):
# 获取输出
predictions = outputs[0]
# 提取边界框、置信度和类别
boxes = predictions[..., :4]
scores = predictions[..., 4:5] * predictions[..., 5:]
# 非极大值抑制
# 这里简化处理,实际应用中需要实现完整的NMS
results = []
for i in range(scores.shape[1]):
for j in range(scores.shape[2]):
score = scores[0, i, j]
if score > conf_threshold:
box = boxes[0, i, :]
results.append((box, score, j))
return results
# 加载图像
image = Image.open("street.jpg").convert("RGB")
original_size = image.size
# 预处理图像
input_size = (640, 640)
input_tensor = preprocess(image, input_size)
# 执行推理
outputs = session.run(output_names, {input_name: input_tensor})
# 后处理结果
results = postprocess(outputs)
# 绘制边界框
draw = ImageDraw.Draw(image)
# 加载标签
with open("coco_labels.txt", "r") as f:
labels = [line.strip() for line in f.readlines()]
# 绘制结果
for box, score, class_id in results:
# 调整边界框坐标到原始图像大小
x1, y1, x2, y2 = box
x1 = int(x1 * original_size[0] / input_size[0])
y1 = int(y1 * original_size[1] / input_size[1])
x2 = int(x2 * original_size[0] / input_size[0])
y2 = int(y2 * original_size[1] / input_size[1])
# 绘制边界框
draw.rectangle([x1, y1, x2, y2], outline="red", width=2)
# 绘制标签和置信度
label = f"{labels[class_id]}: {score:.2f}"
draw.text([x1, y1 - 20], label, fill="red")
# 保存结果
image.save("detection_result.jpg")
print("目标检测完成,结果已保存")6.3 自然语言处理
场景描述:使用ONNX Runtime部署自然语言处理模型。
实现步骤:
- 准备预训练NLP模型并转换为ONNX格式
- 使用ONNX Runtime加载模型
- 预处理输入文本
- 执行推理
- 后处理输出结果
代码示例:
import onnxruntime as ort
import numpy as np
from transformers import BertTokenizer
# 加载分词器
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
# 加载模型
session = ort.InferenceSession("bert_classification.onnx")
input_names = [input.name for input in session.get_inputs()]
output_names = [output.name for output in session.get_outputs()]
# 文本预处理
def preprocess(text):
inputs = tokenizer(
text,
max_length=128,
padding="max_length",
truncation=True,
return_tensors="np"
)
return inputs
# 执行推理
def infer(text):
# 预处理
inputs = preprocess(text)
# 构建输入字典
input_dict = {
input_names[0]: inputs["input_ids"],
input_names[1]: inputs["attention_mask"],
input_names[2]: inputs["token_type_ids"]
}
# 执行推理
outputs = session.run(output_names, input_dict)
# 后处理
logits = outputs[0]
predictions = np.argmax(logits, axis=1)
return predictions[0]
# 使用示例
texts = [
"I love this movie, it's amazing!",
"This is the worst product I've ever bought.",
"The weather is nice today."
]
# 类别标签
labels = ["positive", "negative", "neutral"]
# 推理
for text in texts:
prediction = infer(text)
print(f"文本: {text}")
print(f"情感: {labels[prediction]}")
print()6.4 模型集成到Web应用
场景描述:将ONNX Runtime模型集成到Web应用中。
实现步骤:
- 准备模型并转换为ONNX格式
- 使用ONNX Runtime加载模型
- 创建Web API接口
- 处理客户端请求
- 返回推理结果
代码示例:
from fastapi import FastAPI, UploadFile, File
import uvicorn
import onnxruntime as ort
import numpy as np
from PIL import Image
import io
# 创建FastAPI应用
app = FastAPI()
# 加载模型
model_path = "resnet50.onnx"
session = ort.InferenceSession(model_path)
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
# 图像预处理
def preprocess_image(image_bytes):
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
# 调整大小
image = image.resize((224, 224))
# 转换为numpy数组
image = np.array(image) / 255.0
# 调整维度顺序
image = np.transpose(image, (2, 0, 1))
# 添加批次维度
image = np.expand_dims(image, axis=0)
# 转换为float32
image = image.astype(np.float32)
# 归一化
mean = np.array([0.485, 0.456, 0.406]).reshape(1, 3, 1, 1)
std = np.array([0.229, 0.224, 0.225]).reshape(1, 3, 1, 1)
image = (image - mean) / std
return image
# 加载标签
with open("imagenet_classes.txt", "r") as f:
labels = [line.strip() for line in f.readlines()]
# 定义API端点
@app.post("/predict")
async def predict(file: UploadFile = File(...)):
# 读取文件
contents = await file.read()
# 预处理图像
input_tensor = preprocess_image(contents)
# 执行推理
outputs = session.run([output_name], {input_name: input_tensor})
# 后处理结果
probabilities = np.exp(outputs[0]) / np.sum(np.exp(outputs[0]), axis=1, keepdims=True)
top5_indices = np.argsort(probabilities[0])[::-1][:5]
# 构建结果
results = []
for i, idx in enumerate(top5_indices):
results.append({
"rank": i + 1,
"label": labels[idx],
"confidence": float(probabilities[0][idx])
})
return {"predictions": results}
# 运行应用
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)7. 总结与展望
ONNX Runtime是一款强大的机器学习模型推理引擎,为模型部署和推理提供了高效、灵活的解决方案。它的主要优势包括:
- 高性能推理:针对不同硬件和平台进行优化
- 跨平台支持:支持多种操作系统和硬件
- 多框架兼容:支持来自不同框架的模型
- 灵活的API:提供多种语言的接口
- 模型优化:内置模型优化功能
未来,ONNX Runtime有望在以下方面继续发展:
- 更好的硬件支持:支持更多类型的硬件加速器
- 更高级的模型优化:提供更智能的模型优化技术
- 更广泛的框架集成:与更多深度学习框架无缝集成
- 更好的工具链:提供更完善的模型部署和管理工具
- 更丰富的生态系统:构建更强大的模型推理生态系统
通过使用ONNX Runtime,开发者可以更高效地部署和运行机器学习模型,为各种应用场景提供高性能的推理服务。ONNX Runtime的出现为机器学习模型的部署和推理带来了新的标准和方法,成为现代AI应用中不可或缺的工具之一。