8.6.tensorRT高级(3)封装系列-终极封装形态,以及考虑的问题

目录

    • 前言
    • 1. 终极封装
    • 总结

前言

杜老师推出的 tensorRT从零起步高性能部署 课程,之前有看过一遍,但是没有做笔记,很多东西也忘了。这次重新撸一遍,顺便记记笔记。

本次课程学习 tensorRT 高级-终极封装形态,以及考虑的问题

课程大纲可看下面的思维导图

在这里插入图片描述

1. 终极封装

这节我们学习 tensorRT 封装的终极形态

我们直接来看案例,我们先来分析上节课封装的 yolov5 案例的不足:

1. 预处理使用的是 CPU 版的 warpAffine

2. 后处理使用的是 CPU 版的 decode

3. commit 生产频率太高,而消费频率太低,内存很容易因为频率差导致内存占用过大,程序无法长时间运行,需要添加队列上限限制的机制

我们来看下终极封装的一个效果,我们首先来分析下目录下面的文件结构,在 src 文件夹下其结构如下:

src:.
├─app_yolo
└─tensorRT├─builder├─common├─infer├─onnx├─onnxplugin│  └─plugins└─onnx_parser

tensorRT 文件夹下的 builder 中封装了 trt_builder 即 tensorRT 模型编译的过程,相比于之前的 builder,这次的封装完善了一些,我们先来看 compile 函数的变化,其定义如下:

bool compile(Mode mode,unsigned int maxBatchSize,const ModelSource& source,const CompileOutput& saveto,const std::vector<InputDims> inputsDimsSetup = {},Int8Process int8process = nullptr,const std::string& int8ImageDirectory = "",const std::string& int8EntropyCalibratorFile = "",const size_t maxWorkspaceSize = 1ul << 30                // 1ul << 30 = 1GB
);

首先在 compile 函数中模型的来源变成了一个 ModelSource 类型,其定义如下:

enum class ModelSourceType : int{OnnX,OnnXData
};class ModelSource {public:ModelSource() = default;ModelSource(const std::string& onnxmodel);ModelSource(const char* onnxmodel);ModelSourceType type() const;std::string onnxmodel() const;std::string descript() const;const void* onnx_data() const;size_t onnx_data_size() const;static ModelSource onnx(const std::string& file){ModelSource output;output.onnxmodel_  = file;output.type_       = ModelSourceType::OnnX;return output;}static ModelSource onnx_data(const void* ptr, size_t size){ModelSource output;output.onnx_data_      = ptr;output.onnx_data_size_ = size;output.type_           = ModelSourceType::OnnXData;return output;}private:std::string onnxmodel_;const void* onnx_data_ = nullptr;size_t onnx_data_size_ = 0;ModelSourceType type_;
};

ModelSource 允许你的模型来自于 onnx 文件也能够来自于 onnxdata,为什么会有 ModelSource 呢?主要是方便用户自定义模型的输入来源,因为模型不只是来源于 onnx,还可以来自于 caffe、uff 等格式,用户可以在 ModelSource 中写扩展来支持这些格式的模型作为输入

然后输出变成了 CompileOutput 类型,其定义如下:

enum class CompileOutputType : int{File,Memory
};class CompileOutput{
public:CompileOutput(CompileOutputType type = CompileOutputType::Memory);CompileOutput(const std::string& file);CompileOutput(const char* file);void set_data(const std::vector<uint8_t>& data);void set_data(std::vector<uint8_t>&& data);const std::vector<uint8_t>& data() const{return data_;};CompileOutputType type() const{return type_;}std::string file() const{return file_;}private:CompileOutputType type_ = CompileOutputType::Memory;std::vector<uint8_t> data_;std::string file_;
};

CompileOutput 允许你有两种输出,一种输出到文件一种输出到内存

第三个就是在 compile 函数中提供了一个 inputsDimsSetup,这个参数允许你在编译模型时修改其 batch,假设目前你导出的 onnx 模型的 shape 是 1x3x640x640,是静态 batch,但是你想编译时修改它的 input shape 为 -1x3x640x640,修改为动态 batch,那么你在编译的时候指定好这个参数就行

第四个就是 int8process 函数,说明 compile 中还支持 int8 的编译,相比于之前 builder 的封装复杂了一些,功能也相对完善了一些,这是 builder 里面提供的内容,我们接下来看 common 里面

common:.├─cuda_tools.cpp├─cuda_tools.hpp├─ilogger.cpp├─ilogger.hpp├─infer_controller.hpp├─json.cpp├─json.hpp├─monopoly_allocator.hpp├─preprocess_kernel.cu├─preprocess_kernel.cuh├─trt_tensor.cpp└─trt_tensor.hpp

首先是 cuda-tools 就是之前 check runtime、check kernel 等一些关于 cuda 封装的小工具,然后就是 ilogger,提供了很多常用的小函数,相当于一个工具 utils 类,接下来是 infer_controller,它是一个消费者的封装,因为很多代码都是重复的,这边对它常用的东西进行了简单的一个封装

然后就是 json,这是一个第三方库用于解析 json 文件,而 monopoly_allocator 是一个独占分配器,解决的核心问题是队列上限没有限制的问题,我们之前加限制是用的 condition_variable,现在加限制是通过独占分配器来加,它还可以用来解决 tensor 复用问题

在往下就是 preprocess_kernel 预处理的核函数,最好就是我们的 trt_tensor,和之前封装的一样,没有啥区别,它包含 MixMemory 和 tensor 两部分

common 分析完了,我们来看 infer 部分,infer 就是我们之前写的 RAII + 接口模式的封装

再往下就是 onnx 文件夹,它就是 onnx 解析器所依赖的几个 cpp,是由 onnx.proto 生成的

再就是 onnx_parser 解析器的代码,再往后就是 onnxplugin,就是把 ONNXEasyPlugin 抽出来,也提供了几个简单的 plugin 示例,可以多看看

以上就是整个 tensorRT 的封装

我们再来看 yolo 部分

app_yolo:.├─object_detector.hpp├─yolo_decode.cu├─yolo.cpp└─yolo.hpp

我们先来看 yolo.hpp,由于 V5、X、V3 的后处理都是一样的,因此我们完全可以一套代码支持三种模型,如下:

enum class Type : int{V5 = 0,X  = 1,V3 = V5
};

然后就是 NMS 提供测试的 CPU 版本以及实际的 GPU 版本,如下:

enum class NMSMethod : int{CPU = 0,         // General, for estimate mAPFastGPU = 1      // Fast NMS with a small loss of accuracy in corner cases
};

接下来就是 Infer 的封装,接口类

class Infer{
public:virtual shared_future<BoxArray> commit(const cv::Mat& image) = 0;virtual vector<shared_future<BoxArray>> commits(const vector<cv::Mat>& images) = 0;
};shared_ptr<Infer> create_infer(const string& engine_file, Type type, int gpuid,float confidence_threshold=0.25f, float nms_threshold=0.5f,NMSMethod nms_method = NMSMethod::FastGPU, int max_objects = 1024,bool use_multi_preprocess_stream = false
);

create_infer 函数中多了可以选择模型的 Type,是 V3、V5 还是 X,然后是 nms 的方法选择以及 max_objects 的设置

这就是 yolo.hpp 中的内容

我们来看 yolo.cpp 中和之前不一样的地方,可以发现多了一个 ControllerImpl,它是一个模板类,就是我们之前说的 InferController 消费者模型的封装,可以避免我们写大量重复的线程代码,比如说启动线程,停止线程,添加任务到队列,从队列获取任务

using ControllerImpl = InferController<Mat,                    // inputBoxArray,               // outputtuple<string, int>,     // start paramAffineMatrix            // additional>;

然后在 preprocess 函数中不同的是我们会向 tensor_allocator_ 申请一个 tensor

job.mono_tensor = tensor_allocator_->query();
if(job.mono_tensor == nullptr){INFOE("Tensor allocator query failed.");return false;
}

那为什么要求申请 tensor 呢?我们先来考虑下实际遇到的问题:

1. tensor 的复用性差,每次你都要在 preprocess 上分配新的 tensor,在 worker 中使用完又会释放 tensor,性能很差

2. 预处理完的数据往队列中抛,会造成队列堆积大量的 tensor(commit 频率高,infer 频率低,很容易造成堆积),堆积的结果就是显存占用很高,导致系统不稳定,无法长期运行

我们是通过 tensor_allocator_ 管理 tensor 来解决上述两个问题的:

使用一个 tensor_allocator_ 来管理 tensor,所有需要使用 tensor 的,找 tensor_allocator_ 申请。我们会预先分配固定数量的 tensor(例如10个),申请的时候,如果有空闲的 tensor 没有被分配出去,则把这个空闲的给它,如果没有空闲的 tensor 则等待。如果使用者使用完毕了,它应该通知 tensor_allocator_,告诉 tensor_allocator_ 有空闲的 tensor 了,可以进行分配了。

这种方式处理了 tensor 复用的问题,它实现了申请数量太多,处理不过来时等待的问题,其实就等于处理了队列上限的问题

那最后我们看看 worker 中的实现:

for(int ibatch = 0; ibatch < infer_batch_size; ++ibatch){auto& job                 = fetch_jobs[ibatch];float* image_based_output = output->gpu<float>(ibatch);float* output_array_ptr   = output_array_device.gpu<float>(ibatch);auto affine_matrix        = affin_matrix_device.gpu<float>(ibatch);checkCudaRuntime(cudaMemsetAsync(output_array_ptr, 0, sizeof(int), stream_));decode_kernel_invoker(image_based_output, output->size(1), num_classes, confidence_threshold_, affine_matrix, output_array_ptr, MAX_IMAGE_BBOX, stream_);if(nms_method_ == NMSMethod::FastGPU){nms_kernel_invoker(output_array_ptr, nms_threshold_, MAX_IMAGE_BBOX, stream_);}
}

推理是怎么做呢?首先获取 input,然后拿到我们的 mono_tensor,然后把它的 gpu 地址传给 input,这个 input 其实就是 engine 的 input,拷贝好了以后就可以通知 mono_tensor 进行 release 了,注意 release 只是释放其所有权并不是释放其内存(即通知它我不需要使用了,你可以分配了,通知的是 tensor_allocator_),这个过程限制了队列的上限

然后去做推理,推理完之后把结果去做一个 decode,然后把 decode 的 boxes 塞到 promise 中

以上就是终极封装的全部内容了,其实也就是 tensorRT_Pro 这个 repo 的内容😂,实现高性能的同时稳定可靠

总结

本次课程我们学习了 tensorRT 的终极封装形态,也就是把 tensorRT_Pro 中的封装思想过了一遍,包括 builder、infer 的封装,还包括一些常见的比如 cuda_tools、ilogger、json、trt_tensor、preprocess_kernel 等的封装,关于 yolo 的封装我们使用了 InferController 封装的消费者模型以及 tensor_allocator_ 解决 tensor 复用和队列上限限制问题,具体细节还是得多去研究了。

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

android版本360ui,国产手机UI系统有哪些

国产手机UI系统有哪些 UI系统的用户体验、生态系统的建立等“软实力”将是移动终端厂商的主战场&#xff0c;拥有生态系统的厂商才能掌握主动。那么&#xff0c;都有国产手机UI系统?下面就和jy135小编一起看看吧! 最好用的九大国产手机UI系统 一、小米MIUI MIUI是小米旗下的定…

仿华为EmotionUI 3.0滑动效果

华为美腿妻手机卖的比较火&#xff0c;其中的一个两点是Emotion 3.0&#xff0c;里面各种UI让人耳目一新的感觉。 一开始看到我就喜欢了其中的很多设计。其中的一个是左右滑动类似于开源项目Indecator的。但是他的实现不仅仅是这个。 于是我就再别人的基础上改动了一下&#x…

中国人需要了解华为鸿蒙系统的8个事实,真的这么美好吗?

1. 华为的鸿蒙系统是怎么回事? 华为于昨天推出的鸿蒙系统是谷歌安卓系统的替代品&#xff0c;可应用于电视、汽车、平板电脑和其他设备。 之前民间一直传言说&#xff0c;华为正在为手机、平板电脑和其他智能设备开发自己的操作系统&#xff0c;以防无法使用谷歌的Android软件…

Android学习之路(11) ActionBar与ToolBar的使用

自android5.0开始&#xff0c;AppCompatActivity代替ActionBarActivity&#xff0c;而且ToolBar也代替了ActionBar&#xff0c;下面就是ActionBar和ToolBar的使用 ActionBar 1、截图 2、使用 2.1、AppCompatActivity和其对应的Theme AppCompatActivity使用的是v7的ActionBa…

十六、pikachu之SSRF

文章目录 1、SSRF概述2、SSRF&#xff08;URL&#xff09;3、SSRF&#xff08;file_get_content&#xff09; 1、SSRF概述 SSRF(Server-Side Request Forgery&#xff1a;服务器端请求伪造)&#xff1a;其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能&…

基于体素形态学测量分析(VBM)的工具包比较及其在年龄预测中的应用

摘要 基于体素的形态学测量分析(VBM)通常用于灰质体积(GMV)的局部量化。目前存在多种实现VBM的方法。然而&#xff0c;如何比较这些方法及其在应用中的效用(例如对年龄效应的估计)仍不清楚。这会使研究人员疑惑他们应该在其项目中使用哪种VBM工具包。本研究以用户为中心&#…

使用MV制作最简单的游戏:我要做游戏(4)

公众号原文 本期将设计游戏中基本,也是核心的数值元素。不想循规蹈矩的朋友也可自行回顾前三期的内容: 我要做游戏(1) 我要做游戏(2) 我要做游戏(3) 呃,说道数值,可能一些人脑海里觉得它是这样的: 这么想其实也没错啦,不过实际游戏设计中,攻击力与防御力只是…

On-Manifold Optimization: Local Parameterization

Overview Manifold Space vs Tangent Space Jacobian w.r.t Error State Jacobian w.r.t Error State vs True State According 1 2.4, The idea is that for a x ∈ N x \in N x∈N the function g ( δ ) : f ( x ⊞ δ ) g(\delta) : f (x \boxplus \delta) g(δ):f(x…

DeforGAN:用GAN实现星际争霸开全图外挂!

点击上方“机器学习与生成对抗网络”&#xff0c;关注"星标" 获取有趣、好玩的前沿干货&#xff01; 文章来源&#xff1a;机器之心 作者&#xff1a;Yonghyun Jeong等 参与&#xff1a;李诗萌、Geek AI 对于广大星际争霸迷来说&#xff0c;地图全开作弊代码「Black …

局域网联机_七日杀v17.2(B27)版/支持局域网联机/多项修改器/初始存档/局域网联机教程...

点击蓝字关注我们&#xff0c;每日提供优质游戏 游戏介绍 那时地球表面已经变成废墟&#xff0c;更糟的是&#xff0c;没有人知道到底是因为辐射、生化武器还是天灾&#xff0c;导致地面上出现了一群僵尸。玩家将扮演在美国亚历桑纳地区的一名幸存者&#xff0c;那里是地球最后…

魔兽争霸 / 星际争霸 无法使用 CTRL + 1 进行编队

打游戏时发现不好编队&#xff0c; 应该是快捷键冲突导致。 查了一下&#xff0c;是输入法的问题。 目前用的QQ五笔输入法里用到了 CTRL 1&#xff0c;所以在游戏里就用不了了。 如下面所示&#xff0c;把最后的 CTRL 1 的复选框勾掉就可以了。

DeepMind《星际争霸2》AI碾压人类遭Gary Marcus猛怼:通用智能就是空谈

来源&#xff1a;新智元 本文 3635 字 &#xff0c;建议阅读 10分钟 。 本文介绍了Marcus对AI碾压人类以及未来通用智能研究意义的质疑。 针对DeepMind前几日发布的《星际争霸2》智能体AlphaStar进化版&#xff0c;他在Twitter再次提出了自己的质疑。不过这次&#xff0c;Marcu…

星际2数据编辑器

转自&#xff1a;https://blog.csdn.net/xoyojank/article/details/8122886 只列了技能的划分&#xff0c;其余的参考链接 对象类型 星际2就对象有很多类型, 这里只说一下比较常见的. 这些类型还有子类型, 对象的实例之间是可以进行数据拷贝和派生的. Units(单位) 大多数人应…

nodejs里面的event loop

1. event loop 1.1 什么是event-loop js的标准文档定义如下 https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#event_loop https://javascript.info/event-loop html的标准定义 https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-proc…

chatgpt赋能python:如何用Python编写炒股软件

如何用Python编写炒股软件 随着股票投资的普及和互联网技术的发展&#xff0c;越来越多的人开始尝试使用计算机辅助炒股&#xff0c;以获取更好的投资回报。Python作为一种简单易学、高效实用的编程语言&#xff0c;受到了众多股票投资者的青睐。本文将介绍如何用Python编写炒…

单例模式:保证一个类仅有一个实例

单例模式&#xff1a;保证一个类仅有一个实例 欢迎来到设计模式系列的第二篇文章&#xff01;在上一篇中&#xff0c;我们已经对设计模式有了初步的了解&#xff0c;今天让我们深入研究第一个模式——单例模式。 什么是单例模式&#xff1f; 在软件开发中&#xff0c;我们经…

RabbitMQ从原理到实战—基于Golang【万字详解】

文章目录 前言一、MQ是什么&#xff1f;优势劣势 二、MQ的用途1、应用解耦2、异步加速3、削峰填谷4、消息分发 三、RabbitMQ是什么1、AMQP 协议2、RabbitMQ 包含的要素3、RabbitMQ 基础架构 四、实战1、Simple模式(即最简单的收发模式)2、Work Queues 模型3、Publish/Subscribe…

lnmp架构-mysql

1.MySQL数据库编译 make完之后是这样的 mysql 初始化 所有这种默认不在系统环境中的路径里 就这样加 这样就可以直接调用 不用输入路径调用 2.初始化 重置密码 3.mysql主从复制 配置master 配置slave 当master 端中还没有插入数据时 在server2 上配slave 此时master 还没进…

Tornado服务器连接数据库

环境 python3.6.1 vscode mysql navicat 安装需要的包 pip install torndb_for_python3 pip install pymysql0.8.0 #请安装pymysql的0.8.0版本&#xff0c;否则可能出现一个FLAG不存在的bug。亲测0.8.0可用。 tornadb不适用于python3&#xff0c;torndb_for_python3 是修改过的…

Tornado框架入门教程

Tornado框架入门教程 Tornado在知乎广为使用&#xff0c;当你用Chrome打开网页版本的知乎&#xff0c;使用开发者工具仔细观察Network里面的请求&#xff0c;就会发现有一个特别的状态码为101的请求&#xff0c;它是用浏览器的websocket技术和后端服务器建立了长连接用来接收服…