Pytorch导出onnx模型,C++转化为TensorRT并实现推理过程

Pytorch导出onnx模型,C++转化为TensorRT并实现推理过程

前言

  1. 本文为旨在实现整个Python导出PyTorch模型,C++转化为TensorRT并实现推理过程过程,只与模型推理,模型部署相关,不涉及模型训练。
  2. 为突出整个部署过程而非具体模型本身,本文模型就采用最简单的分类模型,并且直接使用 torchvision.model 中的权重。检测、分割等其他模型在前后处理部分会有不同,但是模型本身的导出、转换和推理的过程基本是一致的。
  3. 本文会先用 Pytorch 测试一个分类模型在一张测试图片上的结果,将其转换为 onnx 模型,再用 onnxruntime 测试结果,再用 C++ 将其转换为 TensorRT 模型,再测试推理结果。预期三者测试结果一致,则转换成功。
  4. 如果想要测试速度,Python 可以使用 time.perf_counter(), C++ 可以使用 std::chrono::high_resolution_clock 。建议多测数据集中的一些图片,计算推理时间的均值和方差,而不是只测一张图片。

1 Pytorch模型推理测试导出onnx

这部分我们使用 torchvision 实例化一个简单的 ResNet50 分类模型,并将其导出为 onnx 模型。在这个过程中,我们还需要使用一张图片进行推理,并记录下 Python 模型的输出,方便我们后面到处 TensoRT 模型并进行推理时进行准确性的验证。

由于 torchvision 中的 resnet50 分类模型中是没有进行最后的 softmax 操作的,这里我们为了之后使用方便,自己新建一个类 ResNet50_wSoftmax 将后处理 softmax 添加到模型中一起导出。

这也是 pytorch 导出 onnx 模型的一个推荐的方式,就是将一些必要后处理添加到模型中一起导出,这样做有两个优点:

  • 可以直接得到端到端的 onnx/tensorrt 模型,不必在外面再做后处理操作
  • 再之后我们会将 onnx 模型转换为 tensorrt 模型,在转换过程中 tensorrt 会对我们的模型进行一些针对特定的 Nvidia GPU 的推理优化,我们将后处理一起合并到 onnx 模型中,可能可以使得一些算子操作再转换为 tensorrt 的过程中同样得到优化。

最终代码如下:

# export_onnx.py
import torch
import torchvision.models as models
import cv2
import numpy as npclass ResNet50_wSoftmax(torch.nn.Module):# 将softmax后处理合并到模型中,一起导出为onnxdef __init__(self):super().__init__()self.base_model = models.resnet50(pretrained=True)self.softmax = torch.nn.Softmax(dim=1)def forward(self, x):y = self.base_model(x)prob = self.softmax(y)return probdef preprocessing(img):# 预处理:BGR->RGB、归一化/除均值减标准差IMAGENET_MEAN = [0.485, 0.456, 0.406]IMAGENET_STD = [0.229, 0.224, 0.225]img = img[:, :, ::-1]img = cv2.resize(img, (224, 224))img = img / 255.0img = (img - IMAGENET_MEAN) / IMAGENET_STDimg = img.transpose(2, 0, 1).astype(np.float32)tensor_img = torch.from_numpy(img)[None]return tensor_imgif __name__ == '__main__':# model = models.resnet50(pretrained=True)image_path = 'test.jpg'img = cv2.imread(image_path)tensor_img = preprocessing(img)model = ResNet50_wSoftmax()   # 将后处理添加到模型中model.eval()pred = model(tensor_img)[0]max_idx = torch.argmax(pred)print(f"test_image: {image_path}, max_idx: {max_idx}, max_logit: {pred[max_idx].item()}")dummpy_input = torch.zeros(1, 3, 224, 224)  # onnx的导出需要指定一个输入,这里直接用上面的tenosr_img也可torch.onnx.export(model, dummpy_input, 'resnet50_wSoftmax.onnx',input_names=['image'],output_names=['predict'],opset_version=11,dynamic_axes={'image': {0: 'batch'}, 'predict': {0: 'batch'}}		# 注意这里指定batchsize是动态可变的)

执行结果会输出:

test_image: test.jpg, max_idx: 971, probability: 0.994541585445404

这些结果我们一会测试 onnx/tensorrt 模型时用于比对转换是否有误差。并得到一个 onnx 模型文件:classifier.onnx

2 onnxruntime推理测试

我们将刚刚得到的 classifier.onnx ,用 onnxruntime 来进行推理测试,看结果是否相同。

这里,我们就复用刚才测试 pytorch 模型时的预处理函数,整个 onnxruntime 推理测试代码如下:

import onnxruntime as ort
import numpy as np
import cv2
from export_onnx import preprocessingimage_path = 'test.jpg'
ort_session = ort.InferenceSession("classifier.onnx") # 创建一个推理sessionimg = cv2.imread(image_path)
input_img = preprocessing(img)[None]pred = ort_session.run(None, { 'image' : input_img } )[0][0]
max_idx = np.argmax(pred)
print(f"test_image: {image_path}, max_idx: {max_idx}, probability: {pred[max_idx]}")

输出:

test_image: test.jpg, max_idx: 971, probability: 0.994541585445404

可以看到,跟我们 pytorch 模型的测试结果是一致的。

3 C++ onnx模型转换为tensorrt模型

本部分重度参考自课程:tensorRT从零起步迈向高性能工业级部署(就业导向)

我们进行模型部署推理肯定是追求极致的推理速度,这时再用 Python 来进行转换和推理就不合适了,接下来我们就转战到 C++ 上,将onnx模型转换为tensorrt模型。

对于大部分深度学习部署的 C/C++ 的初学者而言,环境配置都是个老大难的问题。本身 C/C++ 的包管理就不如 Python 的 pip、conda 等来的直接方便,再加上各种 nvidia driver/cuda/cudnn/cuda-runtime 的各种版本不对齐的问题,包括笔者在内的许多萌新们初期总是会在环境配置遇到许多问题。但是本文关注的重点是整个模型转换和部署的过程,不可能花大篇幅再去介绍环境配置,将来有机会再单独写一篇介绍 Python/C++ 深度学习模型部署时环境配置的问题,这里就直接给出笔者使用的关键软硬件的版本号/型号。

GPU: RTX 3060ti 12GB

OS: ubuntu 18.04

gcc: 7.5

TensorRT: 8.x

CUDA: 11.2

cuDNN: 8.x

头文件

包含的头文件:

// tensorrt相关
#include <NvInfer.h>
#include <NvInferRuntime.h>// onnx解析器相关
#include <onnx-tensorrt/NvOnnxParser.h>// cuda_runtime相关
#include <cuda_runtime.h>// 常用头文件
#include <stdio.h>
#include <math.h>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <memory>
#include <functional>
#include <unistd.h>
#include <chrono>// opencv
#include <opencv2/opencv.hpp>

logger类

首先我们要准备一个 logger 类,来打印构建 tensorrt 模型过程中的一些错误或警告。按照指定的严重性程度 (severity),来打印信息。

inline const char* severity_string(nvinfer1::ILogger::Severity t) {switch (t) {case nvinfer1::ILogger::Severity::kINTERNAL_ERROR: return "internal_error";case nvinfer1::ILogger::Severity::kERROR: return "error";case nvinfer1::ILogger::Severity::kWARNING: return "warning";case nvinfer1::ILogger::Severity::kINFO: return "info";case nvinfer1::ILogger::Severity::kVERBOSE: return "verbose";default: return "unknown";}
}class TRTLogger : public nvinfer1::ILogger {
public:virtual void log(Severity severity, nvinfer1::AsciiChar const* msg) noexcept override {if (severity <= Severity::kWARNING) {if (severity == Severity::kWARNING) printf("\033[33m%s: %s\033[0m\n", severity_string(severity), msg);else if (severity == Severity::kERROR) printf("\031[33m%s: %s\033[0m\n", severity_string(severity), msg);else printf("%s: %s\n", severity_string(severity), msg);}}
};

build_model函数

build_model 函数,各步骤已在代码中添加注释:

bool build_model() {if (isFileExist( "classifier.trtmodel" )) {printf("classifier.trtmodel already exists.\n");return true;}TRTLogger logger;// 下面的builder, config, network是基本需要的组件// 形象的理解是你需要一个builder去build这个网络,网络自身有结构,这个结构可以有不同的配置nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger);// 创建一个构建配置,指定TensorRT应该如何优化模型,tensorRT生成的模型只能在特定配置下运行nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();// 创建网络定义,其中createNetworkV2(1)表示采用显性batch size,新版tensorRT(>=7.0)时,不建议采用0非显性batch sizenvinfer1::INetworkDefinition* network = builder->createNetworkV2(1);// onnx parser解析器来解析onnx模型auto parser = nvonnxparser::createParser(*network, logger);if (!parser->parseFromFile("classifier.onnx", 1)) {printf("Failed to parse classifier.onnx.\n");return false;}// 设置工作区大小printf("Workspace Size = %.2f MB\n", (1 << 28) / 1024.0f / 1024.0f);config->setMaxWorkspaceSize(1 << 28);// 需要通过profile来使得batchsize时动态可变的,这与我们之前导出onnx指定的动态batchsize是对应的int maxBatchSize = 10;auto profile = builder->createOptimizationProfile();auto input_tensor = network->getInput(0);auto input_dims = input_tensor->getDimensions();// 设置batchsize的最大/最小/最优值input_dims.d[0] = 1;profile->setDimensions(input_tensor->getName(), nvinfer1::OptProfileSelector::kMIN, input_dims);profile->setDimensions(input_tensor->getName(), nvinfer1::OptProfileSelector::kOPT, input_dims);input_dims.d[0] = maxBatchSize;profile->setDimensions(input_tensor->getName(), nvinfer1::OptProfileSelector::kMAX, input_dims);config->addOptimizationProfile(profile);// 开始构建tensorrt模型enginenvinfer1::ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);if (engine == nullptr) {printf("Build engine failed.\n");return false;}// 将构建好的tensorrt模型engine反序列化(保存成文件)nvinfer1::IHostMemory* model_data = engine->serialize();FILE* f = fopen("classifier.trtmodel", "wb");fwrite(model_data->data(), 1, model_data->size(), f);fclose(f);// 逆序destory掉指针model_data->destroy();engine->destroy();network->destroy();config->destroy();builder->destroy();printf("Build Done.\n");return true;
}

调用 build_model 函数成功后,我们会得到一个 classifier.trtmodel 文件。

make_nvshared

上面的实现有个比较不优雅的地方,对于我们创建的 builderconfig 等指针,我们都需要一一进行 destroy,从而避免内存泄漏。实际上,这里我们可以通过共享指针,来实现自动释放。

shared_ptr<_T> make_nvshared(_T *ptr) {return shared_ptr<_T>(ptr, [](_T* p){p->destroy();});
}

在这里指定一下释放内存的方式,之后就可以通过类似:

auto network = make_nvshared(builder->createNetworkV2(1));

这样的方式创建智能指针,他会自己 destroy 释放,这样最后几行 destory 就不用写了。

4 tensorrt模型推理测试

我们上一步已经成功将 onnx 模型导出为了 tensorrt 模型,现在我们用 tensorrt 模型来进行推理,看一下结果是否与之前 pytorch 和 onnx 推理的结果一致,如果一致,则模型转换成功。

load_file

load_file 函数用于加载我们的 tensorrt 模型:

vector<unsigned char> load_file(const string& file) {ifstream in(file, ios::in | ios::binary);if (!in.is_open()) return {};in.seekg(0, ios::end);size_t length = in.tellg();vector<uint8_t> data;if (length > 0) {in.seekg(0, ios::beg);data.resize(length);in.read((char*)&data[0], length);}in.close();return data;
}

inference

void inference(const string& image_path) {TRTLogger logger;// 加载模型auto engine_data = load_file("classifier.trtmodel");// 执行推理前,需要创建一个推理的runtime接口实例。与builer一样,runtime需要loggerauto runtime = make_nvshared(nvinfer1::createInferRuntime(logger));auto engine = make_nvshared(runtime->deserializeCudaEngine(engine_data.data(), engine_data.size()));if (engine == nullptr) {printf("Deserialize cuda engine failed.\n");runtime->destroy();return;}if (engine->getNbBindings() != 2) {printf("Must be single input, single Output, got %d output.\n", engine->getNbBindings() - 1);return;}// 创建CUDA流,以确定这个batch的推理是独立的cudaStream_t stream = nullptr;checkRuntime(cudaStreamCreate(&stream));auto execution_context = make_nvshared(engine->createExecutionContext());int input_batch = 1;int input_channel = 3;int input_height = 224;int input_width = 224;// 准备好input_data_host和input_data_device,分别表示内存中的数据指针和显存中的数据指针// 一会儿将预处理过的图像数据搬运到GPUint input_numel = input_batch * input_channel * input_height * input_width;float* input_data_host = nullptr;float* input_data_device = nullptr;checkRuntime(cudaMallocHost(&input_data_host, input_numel * sizeof(float)));checkRuntime(cudaMalloc(&input_data_device, input_numel * sizeof(float)));// 图片读取与预处理,与之前python中的预处理方式一致:// BGR->RGB、归一化/除均值减标准差float mean[] = {0.406, 0.456, 0.485};float std[] = {0.225, 0.224, 0.229};auto image = cv::imread(image_path);cv::resize(image, image, cv::Size(input_width, input_height));int image_area = image.cols * image.rows;unsigned char* pimage = image.data;float* phost_b = input_data_host + image_area * 0;float* phost_g = input_data_host + image_area * 1;float* phost_r = input_data_host + image_area * 2;for (int i=0; i<image_area; ++i, pimage += 3) {*phost_r++ = (pimage[0] / 255.0f - mean[0]) / std[0];*phost_g++ = (pimage[1] / 255.0f - mean[1]) / std[1];*phost_b++ = (pimage[2] / 255.0f - mean[2]) / std[2];}// 进行推理checkRuntime(cudaMemcpyAsync(input_data_device, input_data_host, input_numel *sizeof(float), cudaMemcpyHostToDevice, stream));const int num_classes = 1000;float output_data_host[num_classes];float* output_data_device = nullptr;checkRuntime(cudaMalloc(&output_data_device, sizeof(output_data_host)));auto input_dims = engine->getBindingDimensions(0);input_dims.d[0] = input_batch;execution_context->setBindingDimensions(0, input_dims);// 用一个指针数组bindings指定input和output在gpu中的指针。float* bindings[] = {input_data_device, output_data_device};bool success = execution_context->enqueueV2((void**)bindings, stream, nullptr);checkRuntime(cudaMemcpyAsync(output_data_host, output_data_device, sizeof(output_data_host), cudaMemcpyDeviceToHost, stream));checkRuntime(cudaStreamSynchronize(stream));float* prob = output_data_host;int predict_label = max_element(prob, prob + num_classes) - prob;float conf = prob[predict_label];printf("test_image: %s, max_idx: %d, probability: %f", image_path.c_str(), predict_label, conf);// 释放显存checkRuntime(cudaStreamDestroy(stream));checkRuntime(cudaFreeHost(input_data_host));checkRuntime(cudaFree(input_data_device));checkRuntime(cudaFree(output_data_device));
}

最终得到输出:

test_image: test.jpg, max_idx: 971, probability: 0.994527

与之前 pytorch 和 onnx 推理的结果基本一致,模型转换成功。

附录

给出完整的参考代码:https://github.com/Adenialzz/Hello-AIDeployment/tree/master/HAID/tensorrt/resnet

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/532459.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零Makefile落地算法大项目,完整案例教程

从零Makefile落地算法大项目&#xff0c;完整案例教程 转自&#xff1a;从零Makefile落地算法大项目&#xff0c;完整案例教程 作者&#xff1a;手写AI 前言 在这里&#xff0c;你能学到基于Makefile的正式大项目的使用方式和考虑&#xff0c;相信我&#xff0c;其实可以很简单…

PyTorch扩展自定义PyThonC++(CUDA)算子的若干方法总结

PyTorch扩展自定义PyThon/C(CUDA)算子的若干方法总结 转自&#xff1a;https://zhuanlan.zhihu.com/p/158643792 作者&#xff1a;奔腾的黑猫 在做毕设的时候需要实现一个PyTorch原生代码中没有的并行算子&#xff0c;所以用到了这部分的知识&#xff0c;再不总结就要忘光了 &a…

给 Python 算法插上性能的翅膀——pybind11 落地实践

给 Python 算法插上性能的翅膀——pybind11 落地实践 转自&#xff1a;https://zhuanlan.zhihu.com/p/444805518 作者&#xff1a;jesonxiang&#xff08;向乾彪&#xff09;&#xff0c;腾讯 TEG 后台开发工程师 1. 背景 目前 AI 算法开发特别是训练基本都以 Python 为主&…

chrome自动提交文件_收集文档及提交名单统计

知乎文章若有排版问题请见谅&#xff0c;原文放在个人博客中【欢迎互踩&#xff01;】文叔叔文档收集使用动机在我们的学习工作中&#xff0c;少不了要让大家集体提交文件的情况&#xff0c;举个最简单的例子&#xff1a;收作业。 传统的文件收集流程大致是&#xff1a;群内发出…

Pytorch自定义C++/CUDA扩展

Pytorch自定义C/CUDA扩展 翻译自&#xff1a;官方文档 PyTorch 提供了大量与神经网络、张量代数、数据整理和其他操作。但是&#xff0c;我们有时会需要更加定制化的操作。例如&#xff0c;想要使用论文中找到的一种新型的激活函数&#xff0c;或者实现自己设计的算子。 在 Py…

惠普800g1支持什么内存_惠普黑白激光打印机哪种好 惠普黑白激光打印机推荐【图文详解】...

打印机的出现让我们在生活和日常工作中变得越来越方便&#xff0c;不过随着科技的发展&#xff0c;打印机的类型也变得非常多&#xff0c;其中就有黑白激光打印机&#xff0c;而黑白激光打印机的品牌也有很多&#xff0c;比如我们的惠普黑白激光打印机&#xff0c;今天小编就给…

控制台输出颜色控制

控制台输出颜色控制 转自&#xff1a;https://cloud.tencent.com/developer/article/1142372 前端时间&#xff0c;写了一篇 PHP 在 Console 模式下的进度显示 &#xff0c;正好最近的一个数据合并项目需要用到控制台颜色输出&#xff0c;所以就把相关的信息整理下&#xff0c;…

idea连接跳板机_跳板机服务(jumpserver)

一、跳板机服务作用介绍1、有效管理用户权限信息2、有效记录用户登录情况3、有效记录用户操作行为二、跳板机服务架构原理三、跳板机服务安装过程第一步&#xff1a;安装跳板机依赖软件yum -y install git python-pip mariadb-devel gcc automake autoconf python-devel readl…

【详细图解】再次理解im2col

【详细图解】再次理解im2col 转自&#xff1a;https://mp.weixin.qq.com/s/GPDYKQlIOq6Su0Ta9ipzig 一句话&#xff1a;im2col是将一个[C,H,W]矩阵变成一个[H,W]矩阵的一个方法&#xff0c;其原理是利用了行列式进行等价转换。 为什么要做im2col? 减少调用gemm的次数。 重要…

反思 大班 快乐的机器人_幼儿园大班教案《快乐的桌椅》含反思

大班教案《快乐的桌椅》含反思适用于大班的体育主题教学活动当中&#xff0c;让幼儿提高协调性和灵敏性&#xff0c;创新桌椅的玩法&#xff0c;正确爬的方法&#xff0c;学会匍匐前进&#xff0c;快来看看幼儿园大班《快乐的桌椅》含反思教案吧。幼儿园大班教案《快乐的桌椅》…

DCN可形变卷积实现1:Python实现

DCN可形变卷积实现1&#xff1a;Python实现 我们会先用纯 Python 实现一个 Pytorch 版本的 DCN &#xff0c;然后实现其 C/CUDA 版本。 本文主要关注 DCN 可形变卷积的代码实现&#xff0c;不会过多的介绍其思想&#xff0c;如有兴趣&#xff0c;请参考论文原文&#xff1a; …

蓝牙耳机声音一顿一顿的_线控耳机党阵地转移成功,OPPO这款TWS耳机体验满分...

“你看到我手机里3.5mm的耳机孔了吗”&#xff0c;这可能是许多线控耳机党最想说的话了。确实&#xff0c;如今手机在做“减法”&#xff0c;而厂商们首先就拿3.5mm耳机孔“开刀”&#xff0c;我们也丧失了半夜边充电边戴耳机打游戏的乐趣。竟然如此&#xff0c;那如何在耳机、…

AI移动端优化之Im2Col+Pack+Sgemm

AI移动端优化之Im2ColPackSgemm 转自&#xff1a;https://blog.csdn.net/just_sort/article/details/108412760 这篇文章是基于NCNN的Sgemm卷积为大家介绍Im2ColPackSgemm的原理以及算法实现&#xff0c;希望对算法优化感兴趣或者做深度学习模型部署的读者带来帮助。 1. 前言 …

elementui的upload组件怎么获取上传的文本流、_抖音feed流直播间引流你还不会玩?实操讲解...

本文由艾奇在线明星优化师写作计划出品在这个全民惊恐多灾多难且带有魔幻的2020&#xff0c;一场突如其来的疫情改变了人们很多消费习惯&#xff0c;同时加速了直播电商的发展&#xff0c;现在直播已经成为商家必争的营销之地&#xff0c;直播虽然很火&#xff0c;但如果没有流…

FFmpeg 视频处理入门教程

FFmpeg 视频处理入门教程 转自&#xff1a;https://www.ruanyifeng.com/blog/2020/01/ffmpeg.html 作者&#xff1a; 阮一峰 日期&#xff1a; 2020年1月14日 FFmpeg 是视频处理最常用的开源软件。 它功能强大&#xff0c;用途广泛&#xff0c;大量用于视频网站和商业软件&…

checkbox wpf 改变框的大小_【论文阅读】倾斜目标范围框(标注)的终极方案

前言最常用的斜框标注方式是在正框的基础上加一个旋转角度θ&#xff0c;其代数表示为(x_c,y_c,w,h,θ)&#xff0c;其中(x_c,y_c )表示范围框中心点坐标&#xff0c;(w,h)表示范围框的宽和高[1,2,7]。对于该标注方式&#xff0c;如果将w和h的值互换&#xff0c;再将θ加上或者…

彻底理解BP之手写BP图像分类你也行

彻底理解BP之手写BP图像分类你也行 转自&#xff1a;https://zhuanlan.zhihu.com/p/397963213 第一节&#xff1a;用矩阵的视角&#xff0c;看懂BP的网络图 1.1、什么是BP反向传播算法 BP(Back Propagation)误差反向传播算法&#xff0c;使用反向传播算法的多层感知器又称为B…

h5页面禁止复制_H5移动端页面禁止复制技巧

前言&#xff1a;业务需要&#xff0c;需要对整个页面禁止弹出复制菜单。在禁止的页面中加入以下css样式定义* {-webkit-touch-callout:none;/*系统默认菜单被禁用*/-webkit-user-select:none;/*webkit浏览器*/-khtml-user-select:none;/*早起浏览器*/-moz-user-select:none;/*…

梯度下降法和牛顿法计算开根号

梯度下降法和牛顿法计算开根号 本文将介绍如何不调包&#xff0c;只能使用加减乘除法实现对根号x的求解。主要介绍梯度下降和牛顿法者两种方法&#xff0c;并给出 C 实现。 梯度下降法 思路/步骤 转化问题&#xff0c;将 x\sqrt{x}x​ 的求解转化为最小化目标函数&#xff…

汇博工业机器人码垛机怎么写_全自动码垛机器人在企业生产中的地位越来越重要...

全自动码垛机器人在企业生产中的地位越来越重要在智能化的各种全自动生产线中&#xff0c;全自动码垛机器人成了全自动生产线的重要机械设备&#xff0c;在各种生产中发挥着不可忽视的作用。全自动码垛机器人主要用于生产线上的包装过程中&#xff0c;不仅能够提高企业的生产率…