第4章:onnxruntime入门:安装与配置、Session创建与运行、CPU与GPU后端选择

好,咱们进入实战环节。这一章我带你从零搭建onnxruntime环境,然后跑通第一个推理程序。说实话,很多同学卡在第一步——装好了库,但不知道怎么创建Session,或者不知道什么时候该用GPU。别急,咱们一步步来。

4.1 安装与配置:别让环境成为拦路虎

安装onnxruntime其实很简单,但有几个坑我得提前说。我个人习惯用pip直接装,省事。

# CPU版本(最稳定)
pip install onnxruntime

# GPU版本(需要CUDA和cuDNN)
pip install onnxruntime-gpu

嗯,这里要注意:GPU版本对CUDA版本有要求。我曾在项目里踩过坑——装了最新版onnxruntime-gpu,结果CUDA 11.8不兼容,跑起来直接报错。后来查文档才发现,onnxruntime 1.15对应CUDA 11.8,1.16对应CUDA 12.x。所以建议你先查一下版本对应关系。

避坑指南:我曾经因为CUDA版本不匹配,折腾了整整一个下午。最后发现官方文档里有个版本兼容表,看一眼就解决了。所以,装之前一定去 onnxruntime官方安装指南 确认版本。

如果你用conda,也可以这样装:

conda install -c conda-forge onnxruntime

我个人更推荐pip,因为conda的包更新有时会慢半拍。不过看个人习惯,哪个顺手用哪个。

4.2 Session创建与运行:核心操作就这么几步

装好了,咱们来写第一个推理程序。说白了,onnxruntime的核心就是三个步骤:加载模型、创建Session、运行推理。

import onnxruntime as ort
import numpy as np

# 1. 加载模型
model_path = "model.onnx"

# 2. 创建Session
session = ort.InferenceSession(model_path)

# 3. 准备输入数据(假设模型输入是1x3x224x224)
input_name = session.get_inputs()[0].name
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)

# 4. 运行推理
outputs = session.run(None, {input_name: input_data})

print("推理完成,输出形状:", outputs[0].shape)

你看,代码就这么几行。但这里有个细节:session.run()的第一个参数是输出名称,传None表示返回所有输出。如果你只想拿某个特定输出,可以传输出名称的列表。

小技巧:我习惯先打印一下模型的输入输出信息,确认形状和数据类型。用session.get_inputs()session.get_outputs()就能看到。这样能避免很多低级错误。
# 查看模型输入输出信息
for input_meta in session.get_inputs():
    print(f"输入: {input_meta.name}, 形状: {input_meta.shape}, 类型: {input_meta.type}")

for output_meta in session.get_outputs():
    print(f"输出: {output_meta.name}, 形状: {output_meta.shape}, 类型: {output_meta.type}")

4.3 CPU与GPU后端选择:什么时候该用GPU?

这个问题其实挺常见的。你想想看,GPU不是万能的。我见过有人拿GPU跑一个单张图片的推理,结果因为数据传输开销,比CPU还慢。为什么会这样?因为GPU的优势在于并行计算,但数据从内存拷贝到显存需要时间。

简单来说,选择原则是这样的:

场景 推荐后端 原因
单张图片推理(延迟敏感) CPU 避免数据传输开销
批量推理(吞吐量优先) GPU 并行计算优势明显
模型很小(如MobileNet) CPU GPU加速不明显
模型很大(如ResNet-152) GPU CPU推理太慢
生产环境部署 按需选择 综合考虑成本和性能

那代码里怎么切换呢?其实很简单:

# CPU后端(默认)
session_cpu = ort.InferenceSession("model.onnx")

# GPU后端
session_gpu = ort.InferenceSession("model.onnx", providers=['CUDAExecutionProvider'])

# 也可以指定多个provider,按优先级排列
session_mixed = ort.InferenceSession(
    "model.onnx",
    providers=['CUDAExecutionProvider', 'CPUExecutionProvider']
)

嗯,这里要注意:如果你装了onnxruntime-gpu,但没装CUDA,或者CUDA版本不对,创建Session时会报错。我建议你写个简单的检查函数:

def check_available_providers():
    """检查可用的执行后端"""
    available = ort.get_available_providers()
    print("可用的providers:", available)
    if 'CUDAExecutionProvider' in available:
        print("GPU可用,可以加速推理")
    else:
        print("GPU不可用,将使用CPU")

check_available_providers()
核心要点:不要盲目追求GPU。如果你的应用场景是实时处理单张图片(比如摄像头实时检测),CPU往往更合适。如果是离线批量处理(比如视频分析),GPU能带来数倍加速。

4.4 实战:跑一个完整的推理流程

好,咱们把前面学的串起来,写一个完整的示例。假设你有一个训练好的ResNet-50模型,导出成了ONNX格式。

import onnxruntime as ort
import numpy as np
from PIL import Image
import time

def preprocess_image(image_path):
    """预处理图片:resize、归一化、转CHW"""
    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])
    std = np.array([0.229, 0.224, 0.225])
    img_array = (img_array - mean) / std
    # 转成CHW格式并增加batch维度
    img_array = np.transpose(img_array, (2, 0, 1))
    img_array = np.expand_dims(img_array, axis=0)
    return img_array

def run_inference(model_path, image_path, use_gpu=False):
    """运行推理"""
    # 选择后端
    if use_gpu:
        session = ort.InferenceSession(
            model_path,
            providers=['CUDAExecutionProvider', 'CPUExecutionProvider']
        )
    else:
        session = ort.InferenceSession(model_path)
    
    # 预处理
    input_data = preprocess_image(image_path)
    input_name = session.get_inputs()[0].name
    
    # 推理并计时
    start = time.time()
    outputs = session.run(None, {input_name: input_data})
    elapsed = time.time() - start
    
    # 后处理(假设是分类任务)
    probabilities = np.exp(outputs[0]) / np.sum(np.exp(outputs[0]), axis=1, keepdims=True)
    predicted_class = np.argmax(probabilities)
    
    print(f"推理耗时: {elapsed*1000:.2f} ms")
    print(f"预测类别: {predicted_class}, 置信度: {probabilities[0][predicted_class]:.4f}")
    
    return predicted_class, probabilities

# 运行
model_path = "resnet50.onnx"
image_path = "cat.jpg"

print("=== CPU推理 ===")
run_inference(model_path, image_path, use_gpu=False)

print("\n=== GPU推理 ===")
run_inference(model_path, image_path, use_gpu=True)
个人经验:我在实际项目中,经常用这个脚本来对比CPU和GPU的性能差异。有一次发现GPU只比CPU快20%,后来一查,是因为模型太小,数据传输开销占了大部分时间。所以,建议你每次部署前都跑一下这个对比,用数据说话。

4.5 常见问题与排查

最后,我总结几个新手容易遇到的问题:

  • Session创建失败:检查模型路径是否正确,ONNX文件是否损坏。我遇到过模型导出时版本不兼容的问题,用onnx.checker.check_model()可以验证。
  • GPU不可用:确认安装了onnxruntime-gpu,且CUDA版本匹配。用ort.get_available_providers()检查。
  • 推理结果不对:多半是预处理和后处理的问题。检查输入数据的形状、数据类型、归一化方式是否和训练时一致。
  • 性能不如预期:用session.run()options参数可以设置优化级别,或者尝试开启enable_profiling=True来分析瓶颈。

好了,这一章的内容就到这里。你学会了安装、创建Session、选择后端,还跑了一个完整的推理流程。下一章咱们会深入Session的配置选项,看看怎么进一步榨干性能。