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

news2024/11/16 11:05:55

目录

    • 前言
    • 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 mAP
    FastGPU = 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,                    // input
        BoxArray,               // output
        tuple<string, int>,     // start param
        AffineMatrix            // 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://www.coloradmin.cn/o/936333.html

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

相关文章

Python序列类型

序列&#xff08;Sequence&#xff09;是有顺序的数据列&#xff0c;Python 有三种基本序列类型&#xff1a;list, tuple 和 range 对象&#xff0c;序列&#xff08;Sequence&#xff09;是有顺序的数据列&#xff0c;二进制数据&#xff08;bytes&#xff09; 和 文本字符串&…

webassembly009 transformers.js 网页端侧推理

之前试用过两个网页端的神经网络框架&#xff0c;一个是 Tensorflow PlayGround&#xff0c;它相当与实现了一个网页端的简单的训练框架&#xff0c;有关节点的数据结构可看这篇。另一个是onnx的网页端(nodejs绿色免安装try onnx on web(chrome))&#xff0c;需要自己转换onnx模…

XSS攻击是怎么回事?记录一下

title: XSS攻击 date: 2023-08-27 19:15:57 tags: [XSS, 网络安全] categories: 网络安全 今天学习了一个网络攻击的手段&#xff0c;XSS攻击技术&#xff0c;大家自建网站的朋友&#xff0c;记得看看是否有此漏洞。 &#x1f388; XSS 攻击 全称跨站脚本攻击 Cross Site Sc…

Spring Boot中通过maven进行多环境配置

上文 java Spring Boot将不同配置拆分入不同文件管理 中 我们说到了&#xff0c;多环境的多文件区分管理 说到多环境 其实不止我们 Spring Boot有 很多的东西都有 那么 这就有一个问题 如果 spring 和 maven 都配置了环境 而且他们配的不一样 那么 会用谁的呢&#xff1f; 此…

2023年最新版IDEA安装(超详细)

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【JavaSE_primary】 写在前面&#xff0c;IDEA的安装是建立在JDK安装好了的前提下&#xff0c;否则IDEA是无法使用的&#xff0c;具体JDK…

教你如何美化自己的Typora

美化你的Typora 前提 很多朋友习惯使用Typora打字或电子笔记&#xff0c;虽然市面上有很多Markdown工具&#xff0c;但是我尤爱Typora。 虽然它没有云存储不方便多设备同步&#xff0c;本地管理也不是很强大&#xff1b;可它简约的md语法和窗口界面&#xff0c;让我能够沉浸在…

Vue3(开发h5适配)

在开发移动端的时候需要适配各种机型&#xff0c;有大的&#xff0c;有小的&#xff0c;我们需要一套代码&#xff0c;在不同的分辨率适应各种机型。 因此我们需要设置meta标签 <meta name"viewport" content"widthdevice-width, initial-scale1.0">…

解决Spring Boot项目中pom.xml环境配置 打包后生效 但idea版本运行无效的问题

上文 Spring Boot中通过maven进行多环境配置 中我们通过pom.xml配置了环境选择 但这个只有在打包出来的jar中生效 我们直接通过 idea启动 这个东西确实是有点问题 其实 我们执行一下 compile 手工编译一下 然后重新启动 很明显 我们这里配置就已经生效了 这个就是 我们每次…

8.7.tensorRT高级(3)封装系列-调试方法、思想讨论

目录 前言1. 模型调试技巧总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 tensorRT 高级-调试方法、思想讨论 课程大纲可看…

Go语言入门记录:从基础到变量、函数、控制语句、包引用、interface、panic、go协程、Channel、sync下的waitGroup和Once等

程序入口文件的包名必须是main&#xff0c;但主程序文件所在文件夹名称不必须是main&#xff0c;即我们下图hello_world.go在main中&#xff0c;所以感觉package main写顺理成章&#xff0c;但是如果我们把main目录名称改成随便的名字如filename也是可以运行的&#xff0c;所以…

hiredis的安装与使用

hiredis的介绍 Hiredis 是一个用于 C 语言的轻量级、高性能的 Redis 客户端库。它提供了一组简单易用的 API&#xff0c;用于与 Redis 数据库进行交互。Hiredis 支持 Redis 的所有主要功能&#xff0c;包括字符串、哈希、列表、集合、有序集合等数据结构的读写操作&#xff0c…

Git企业开发控制理论和实操-从入门到深入(五)|标签管理

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总 然后就是博主最近最花时间的一个专栏…

python+协同过滤算法实现简单的图书推荐系统

背景介绍 当我们做一些推荐系统网站时&#xff0c;通常需要合适的推荐算法&#xff0c;下面给大家介绍推荐系统中经典的推荐算法——协同过滤算法。在本文中通过Python语言&#xff0c;以一个图书推荐系统为案例&#xff0c;最终实现一个基于用户对图书的评分而对指定的用户个…

带你启用window10专业版系统自带的远程桌面

启用window10专业版系统自带的远程桌面 文章目录 启用window10专业版系统自带的远程桌面前言1.找到远程桌面的开关2. 找到“应用”项目3. 打开需要远程操作的电脑远程桌面功能 总结 前言 Windows操作系统作为应用最广泛的个人电脑操作系统&#xff0c;在我们身边几乎随处可见。…

Feign在实际项目中使用详解

Feign在实际项目中使用详解 简介一 Feign客户端应该如何提供&#xff1f;二 Feign调用的接口要不要进行包装&#xff1f;2.1.问题描述2.2.问题解决 三 Feign如何抓取业务生产端的业务异常&#xff1f;3.1.分析3.2.Feign捕获不到异常3.3.异常被额外封装3.4.解决方案 案例源码 简…

Visual Studio2022史诗级更新,增加多个提高生产力的功能

Visual Studio 2022发布了17.7x版&#xff0c;这次更新中&#xff0c;增加多个提高生产力的功能以及性能进一步改进。 如果要体验新功能&#xff0c;需要将Visual Studio 2022的版本升级到17.7及以上 下面我们看看新增的功能以及改进的功能&#xff01; 目录 文件比较自动修复代…

206. 反转链表 (简单系列)

给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1] 示例 3&#xff1a; 输…

【数据结构与算法篇】 手撕八大排序算法之选择排序

​&#x1f47b;内容专栏&#xff1a; 《数据结构与算法篇》 &#x1f428;本文概括&#xff1a;选择排序包括直接选择排序与堆排序&#xff0c;本篇讲述直接选择排序与堆排序的思想及实现、复杂度及稳定性的分析。 &#x1f43c;本文作者&#xff1a; 花 蝶 &#x1f438;发布…

CSS中的margin与padding

目录 一、margin 1.概念及作用 2.基本语法 3.margin的用法 二、padding 1.介绍 2.基本语法及要求 3. 用法 4.内边距和元素宽度 讲这些之前&#xff0c;先看一张图&#xff0c;便于理解 一、margin 1.概念及作用 CSS margin 属性用于在任何定义的边框之外&#xff0c;…

K8S最新版本集群部署(v1.28) + 容器引擎Docker部署(上)

温故知新 &#x1f4da;第一章 前言&#x1f4d7;背景&#x1f4d7;目的&#x1f4d7;总体方向 &#x1f4da;第二章 基本环境信息&#x1f4d7;机器信息&#x1f4d7;软件信息&#x1f4d7;部署用户kubernetes &#x1f4da;第三章 Kubernetes各组件部署&#x1f4d7;安装kube…