推理部署 ONNX Runtime

过程:训练---> 转换 ---> onnx模型 ---> 部署推理

1.先看一个简单案例

在这之前,你需要安装包 pytorch、numpy、onnxruntime 或者 onnxruntime-gpu,你可以下面命令安装,pytorch请查询百度

pip install numpy onnxruntime

1.1 导出onnx模型

案例实现两个数相加的简单模型,并且导出onnx模型。

import numpy as np
import torch
import onnxruntime


# 定义模型
def model():
    class Model(torch.nn.Module):
        def __init__(self):
            super(Model, self).__init__()

        def forward(self, x, y):
            return x.add(y)

    return Model()

# 创建模型
def create_model(type: torch.dtype = torch.float32):
    sample_x = torch.ones(3, dtype=type) # 创建一个张量
    sample_y = torch.zeros(3, dtype=type) # 创建一个张量

    # 导出onnx格式的模型
    torch.onnx.export(model(), 
                    (sample_x, sample_y),  # 两个输入
                    r'./model.onnx', # 输出文件名
                    opset_version=12, # opset版本
                    input_names=["x", "y"],  # 输入名称
                    output_names=["z"])     # 输出名称
   
    # torch.onnx.export(model(), 
    #                   (sample_x, sample_y), 
    #                   r'./model.onnx',
    #                 input_names=["x", "y"], 
    #                 output_names=["z"], 
    #                 dynamic_axes={"x":{0 : "array_length_x"}, "y":{0: "array_length_y"}}) # 动态轴设置
    
if __name__ == '__main__':
    create_model()
     

1.2 查看onnx 模型

可以在这个网站上查看,选择打开onnx文件 !Netron

如下图所示!反映了模型的输入和输出的名字,shape,类型等信息

1.3 python 实现推理

import numpy as np
import onnxruntime as ort



# 创建onnx session
def create_session(path):
    providers = ['CPUExecutionProvider'] # CPU
    # providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] # GPU
    sess = ort.InferenceSession(path, providers=providers)
    get_onnx_info(sess) # 打印模型信息
    return sess

# 推理
def run(x: np.array, y: np.array) -> np.array:
      z = session.run(["z"], {"x": x, "y": y})
      return z

def get_onnx_info(sess):
    input_tensors = sess.get_inputs()
    for input_tensor in input_tensors:         # 因为可能有多个输入,所以为列表
        input_info = {
            "name" : input_tensor.name,
            "type" : input_tensor.type,
            "shape": input_tensor.shape,
        }
        print("input_info:")
        print(input_info)

    out_tensors = sess.get_outputs()
    for out_tensor in out_tensors:         # 因为可能有多个输入,所以为列表
        out_info = {
            "name" : out_tensor.name,
            "type" : out_tensor.type,
            "shape": out_tensor.shape,
        }
        print("out_info:")
        print(out_info)



if __name__ == "__main__":
    onnx_path = "./model.onnx"
    session = create_session(onnx_path) # 创建session
    y = run(x=np.float32([1.0, 2.0, 3.0]),y=np.float32([4.0, 5.0, 6.0]))  # 推理 两个输入,实现相加
    print(y)

2. 评估onnx 推理

这个案例中,采用随机模型参数,没有进行训练。在cpu上测试

2.1 导出resnet 模型

import torchvision.models as models
import torch


# 加载预训练的 ResNet50 模型
resnet50 = models.resnet50(pretrained=False)


torch.onnx.export(
        resnet50,  # 模型
        torch.randn(1, 3, 224, 224),  # 输入
        "./resnet50.onnx", # 输出文件名
        opset_version=12, # opset版本 
        input_names=["input"], # 输入变量名
        output_names=["output"] # 输出变量名
        )

2.2 模型对比

 - 误差评估

import time
import numpy as np
import onnxruntime as ort
import torchvision.models as models
import torch


def export_onnx(onnx_path):
    # ResNet50 模型, 保存onnx model 返回 torch model
    resnet50 = models.resnet50(pretrained=False)
    torch.onnx.export(
            resnet50,  # 模型
            torch.randn(1, 3, 224, 224),  # 输入
            onnx_path, # 输出文件名
            opset_version=12, # opset版本 
            input_names=["input"], # 输入变量名
            output_names=["output"] # 输出变量名
            )
    return resnet50


class ONNXModel:
    def __init__(self, model_path):
        """
        初始化 ONNX 模型推理类。
        
        :param model_path: ONNX 模型文件路径
        """
        self.model_path = model_path
        self.session = None
        self.load_model()
        self.input_name = self.session.get_inputs()[0].name # 输入名字


    def load_model(self):
        """
        加载 ONNX 模型。
        """
        try:
            print(f"Loading model from {self.model_path}...")
            start_time = time.time()
            self.session = ort.InferenceSession(self.model_path)
            end_time = time.time()
            print(f"Model loaded successfully in {end_time - start_time:.4f} seconds.")
        except Exception as e:
            print(f"Failed to load model: {e}")
            raise

    def run_inference(self, input_data):
        """
        执行推理并返回结果。
        
        :param input_data: 输入数据,通常为 numpy 数组或字典
        :return: 推理结果
        """
        # 确保输入是字典格式
        input_data = {self.input_name: input_data}
        outputs = self.session.run(None, input_data)
        return outputs[0]


def eval_difference(torch_model,onnx_mode, n):
    # 推理n 次 返回对比结果
    print("评估中....")
    res_list = []
    for _ in range(n):
        
        torch_input = torch.randn(1, 3, 224, 224)
        onnx_input= np.asarray(torch_input).astype(np.float32)
        onnx_output = onnx_mode.run_inference(onnx_input)
        torch_output = torch_model(torch_input).detach().numpy()
        res_list.append(np.abs(onnx_output - onnx_output).max())
    
    print(f"平均误差为:{np.mean(np.array(res_list))}")


def test():
    onnx_path = "./resnet50.onnx"
    torch_model = export_onnx(onnx_path) # 替换为你的 ONNX 模型路径
    onnx_model = ONNXModel(onnx_path)
    eval_difference(torch_model,onnx_model, 10)



# 示例用法
if __name__ == "__main__":
    
    test()

 - 时间评估

cpu上

推理50次时间对比:
ONNX 模型推理时间: 0.7116 秒
Torch 模型推理时间: 2.9859 秒
ONNX 模型推理平均时间: 0.0142 秒
Torch 模型推理平均时间: 0.0597 秒
Torch 模型推理时间/ONNX 模型推理时间: 4.1960 秒
ONNX 模型推理时间/Torch 模型推理时间: 0.2383 秒
Torch 模型推理时间 - ONNX 模型推理时间: 2.2743 秒

import time
import numpy as np
import onnxruntime as ort
import torchvision.models as models
import torch


def export_onnx(onnx_path):
    # ResNet50 模型, 保存onnx model 返回 torch model
    resnet50 = models.resnet50(pretrained=False)
    torch.onnx.export(
            resnet50,  # 模型
            torch.randn(1, 3, 224, 224),  # 输入
            onnx_path, # 输出文件名
            opset_version=12, # opset版本 
            input_names=["input"], # 输入变量名
            output_names=["output"] # 输出变量名
            )
    return resnet50


class ONNXModel:
    def __init__(self, model_path):
        """
        初始化 ONNX 模型推理类。
        
        :param model_path: ONNX 模型文件路径
        """
        self.model_path = model_path
        self.session = None
        self.load_model()
        self.input_name = self.session.get_inputs()[0].name # 输入名字


    def load_model(self):
        """
        加载 ONNX 模型。
        """
        try:
            print(f"Loading model from {self.model_path}...")
            start_time = time.time()
            self.session = ort.InferenceSession(self.model_path)
            end_time = time.time()
            print(f"Model loaded successfully in {end_time - start_time:.4f} seconds.")
        except Exception as e:
            print(f"Failed to load model: {e}")
            raise

    def run_inference(self, input_data):
        """
        执行推理并返回结果。
        
        :param input_data: 输入数据,通常为 numpy 数组或字典
        :return: 推理结果
        """
        # 确保输入是字典格式
        input_data = {self.input_name: input_data}
        outputs = self.session.run(None, input_data)
        return outputs[0]


def eval_difference(torch_model,onnx_mode, n):
    # 推理n 次 返回对比结果
    print("评估中....")
    torch_input = torch.randn(1, 3, 224, 224)
    onnx_input= np.asarray(torch_input).astype(np.float32)


    # 1. onnx 时间
    t1 = time.time()
    for _ in range(n):
        onnx_mode.run_inference(onnx_input)
    t2 = time.time()
    t_onnx = t2 - t1
    # 2. torch 时间
    t1 = time.time()
    for _ in range(n):
        torch_model(torch_input).detach().numpy()
    t2 = time.time()
    t_torch = t2 - t1
    print(f"推理{n}次时间对比:")
    print(f"ONNX 模型推理时间: {t_onnx:.4f} 秒")
    print(f"Torch 模型推理时间: {t_torch:.4f} 秒")
    print(f"ONNX 模型推理平均时间: {t_onnx/n:.4f} 秒")
    print(f"Torch 模型推理平均时间: {t_torch/n:.4f} 秒")
    print(f"Torch 模型推理时间/ONNX 模型推理时间: {t_torch/t_onnx:.4f} 秒")
    print(f"ONNX 模型推理时间/Torch 模型推理时间: {t_onnx/t_torch:.4f} 秒")
    print(f"Torch 模型推理时间 - ONNX 模型推理时间: {t_torch - t_onnx:.4f} 秒")

def test():
    onnx_path = "./resnet50.onnx"
    torch_model = export_onnx(onnx_path) # 替换为你的 ONNX 模型路径
    onnx_model = ONNXModel(onnx_path)
    eval_difference(torch_model,onnx_model, 50)



# 示例用法
if __name__ == "__main__":
    
    test()

2.3 gpu 时间对比

import time
import numpy as np
import onnxruntime as ort
import torchvision.models as models
import torch
 
def export_onnx(onnx_path):
    # ResNet50 模型, 保存onnx model 返回 torch model
    resnet50 = models.resnet50(pretrained=False)
    torch.onnx.export(
            resnet50,  # 模型
            torch.randn(1, 3, 224, 224),  # 输入
            onnx_path, # 输出文件名
            opset_version=12, # opset版本 
            input_names=["input"], # 输入变量名
            output_names=["output"], # 输出变量名
            dynamic_axes={"input":{0 : "batch_x"}, "output":{0: "batch_y"}} # 动态轴设置
            )
 
 
class ONNXModel:
    def __init__(self, model_path, use_gpu=False):
        """
        初始化 ONNX 模型推理类。
        
        :param model_path: ONNX 模型文件路径
        :param use_gpu: 是否使用 GPU 推理
        """
        self.model_path = model_path
        self.use_gpu = use_gpu
        self.session = None
        self.load_model()
        self.input_name = self.session.get_inputs()[0].name  # 输入名字
 
    def load_model(self):
        """
        加载 ONNX 模型。
        """
        try:
            print(f"Loading model from {self.model_path}...")
            providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] if self.use_gpu else ['CPUExecutionProvider']
            self.session = ort.InferenceSession(self.model_path, providers=providers)
        except Exception as e:
            print(f"Failed to load model: {e}")
            raise
        
        providers = self.session.get_providers()
        if 'CUDAExecutionProvider' in providers:
            print("✅ ONNX Runtime 正在使用 GPU 推理")
        else:
            print("❌ ONNX Runtime 正在使用 CPU 推理")
 
    def run_inference(self, input_data):
        """
        执行推理并返回结果。
        
        :param input_data: 输入数据,通常为 numpy 数组或字典
        :return: 推理结果
        """
        # 确保输入是字典格式
        input_data = {self.input_name: input_data}
        outputs = self.session.run(None, input_data)
        return outputs[0]
 
 
def eval(onnx_model, n):
    # 推理n 次 返回对比结果
    batch_size = 32
    print("评估中....")
    onnx_input = np.random.randn(batch_size,3,224,224).astype(np.float32)  # ONNX 需要 CPU 数据
    # 1. ONNX 时间
    t1 = time.time()
    for _ in range(n):
        onnx_model.run_inference(onnx_input)
    t2 = time.time()
    t = t2 - t1
    print(f"推理{n}次时间对比:")
    print(f"模型推理时间: {t:.4f} 秒")
    print(f"模型推理平均时间: {t/n:.4f} 秒")
   
 
def test():
    onnx_path = "./resnet50.onnx"
    export_onnx(onnx_path)


    onnx_model_cpu = ONNXModel(onnx_path, use_gpu=False)
    # 评估推理性能
    eval(onnx_model_cpu, 50)
    
    onnx_model_gpu = ONNXModel(onnx_path, use_gpu=True)
    eval(onnx_model_cpu, 50)

 
# 示例用法
if __name__ == "__main__":
    test()

posted @ 2025-04-25 13:35  qinchaojie  阅读(221)  评论(0)    收藏  举报  来源