五. TensorRT API的基本使用-TensorRT-network-structure

前言

自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》,链接。记录下个人学习笔记,仅供自己参考

本次课程我们来学习课程第五章—TensorRT API 的基本使用,一起来学习打印网络模型结构

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

在这里插入图片描述

0. 简述

本小节目标:学习如何打印优化前后网络模型结构

今天我们来讲第五章节第四小节—5.4-print-structure 这个案例,来看下经过 TensorRT 优化前后网络的结构有什么样的变化

下面我们开始本次课程的学习🤗

1. 案例运行

在正式开始课程之前,博主先带大家跑通 5.4-print-structure 这个小节的案例🤗

源代码获取地址:https://github.com/kalfazed/tensorrt_starter.git

首先大家需要把 tensorrt_starter 这个项目给 clone 下来,指令如下:

git clone https://github.com/kalfazed/tensorrt_starter.git

也可手动点击下载,点击右上角的 Code 按键,将代码下载下来。至此整个项目就已经准备好了。也可以点击 here 下载博主准备好的源代码(注意代码下载于 2024/7/14 日,若有改动请参考最新

整个项目后续需要使用的软件主要有 CUDA、cuDNN、TensorRT、OpenCV,大家可以参考 Ubuntu20.04软件安装大全 进行相应软件的安装,博主这里不再赘述

假设你的项目、环境准备完成,下面我们来一起运行 5.4 小节案例代码

开始之前我们需要创建几个文件夹,在 tensorrt_starter/chapter5-tensorrt-api-basics/5.4-print-structure 小节中创建一个 models 文件夹,接着在 models 文件夹下创建 onnx 和 engine 文件夹,总共三个文件夹需要创建

创建完后 5.4 小节整个目录结构如下:

在这里插入图片描述

接着我们需要执行 python 文件创建一个 ONNX 模型,先进入到 5.4 小节中:

cd tensorrt_starter/chapter5-tensorrt-api-basics/5.4-print-structure

执行如下指令:

python src/python/generate_onnx.py

Note:大家需要准备一个虚拟环境,安装好 torch、onnx、onnxsim 等第三方库

输出如下:

在这里插入图片描述

生成好的 onnx 模型文件保存在 models/onnx 文件夹下,大家可以查看

接着我们需要利用 ONNX 生成对应的 engine,在此之前我们需要修改下整体的 Makefile.config,指定一些库的路径:

# tensorrt_starter/config/Makefile.config
# CUDA_VER                    :=  11
CUDA_VER                    :=  11.6
    
# opencv和TensorRT的安装目录
OPENCV_INSTALL_DIR          :=  /usr/local/include/opencv4
# TENSORRT_INSTALL_DIR        :=  /mnt/packages/TensorRT-8.4.1.5
TENSORRT_INSTALL_DIR        :=  /home/jarvis/lean/TensorRT-8.6.1.6

Note:大家查看自己的 CUDA 是多少版本,修改为对应版本即可,另外 OpenCV 和 TensorRT 修改为你自己安装的路径即可

然后我们还要简单修改下源码,在 src/cpp/main.cpp 中默认打印的 ONNX 是 vgg16.onnx,我们修改为 sample.onnx,修改如下所示:

# src/cpp/main.cpp
int main(int argc, char const *argv[])
{
    Model model("models/onnx/sample.onnx");
    // Model model("models/onnx/resnet50.onnx");
    // Model model("models/onnx/vgg16.onnx");
    ...
}

接着我们就可以来执行编译,指令如下:

make -j64

输出如下:

在这里插入图片描述

接着执行:

./trt-infer

输出如下:

在这里插入图片描述

我们这里读取一个模型,然后将模型优化前后信息都打印出来了,我们的模型是使用的上节课的 onnx,就是一个 Linear 层

Note:博主这里也准备了 resnet50 和 vgg16 的 ONNX 模型,大家可以点击 here 下载,然后运行代码看下 resnet50 和 vgg16 在 TensorRT 优化前后有什么区别

如果大家能够看到上述输出结果,那就说明本小节案例已经跑通,下面我们就来看看具体的代码实现

2. 代码分析

2.1 main.cpp

我们先从 main.cpp 看起:

#include <iostream>
#include <memory>

#include "utils.hpp"
#include "model.hpp"

using namespace std;

int main(int argc, char const *argv[])
{
    Model model("models/onnx/sample.onnx");
    // Model model("models/onnx/resnet50.onnx");
    // Model model("models/onnx/vgg16.onnx");

    if(!model.build()){
        LOGE("fail in building model");
        return 0;
    }
    // if(!model.infer()){
    //     LOGE("fail in infering model");
    //     return 0;
    // }
    return 0;
}

与之前 build 的案例一样,这里只提供 build 的接口,然后在 build 函数中我们对模型的结构进行打印,除了 sample.onnx 外这边还有 resnet50.onnx 和 vgg16.onnx,大家可以都测试下

博主这边还测试了 resnet50.onnx 经过 tensorRT 优化前后的网络结构打印:

在这里插入图片描述

resnet50.onnx优化前

在这里插入图片描述

resnet50.onnx优化后

可以看到优化前有 126 层,都是 conv+relu,每一层的输入输出维度以及精度都有打印;优化后只有 79 层,可以看懂 tensorRT 做了很多层融合,比如 conv+relu 融合,conv+add+relu 融合等等

我们再看下 vgg16.onnx:

在这里插入图片描述

vgg16.onnx优化前

在这里插入图片描述

vgg16.onnx优化后

可以看到 vgg16 优化前有 49 个 layer,基本上就是 conv+relu+maxpool 的组合,优化后只有 25 层,tensorRT 做了一些层融合操作

2.2 model.cpp

相比于之前的 build 案例代码,它新增了如下代码:

bool Model::build(){
	...

    // 把优化前和优化后的各个层的信息打印出来
    LOG("Before TensorRT optimization");
    print_network(*network, false);
    LOG("");
    LOG("After TensorRT optimization");
    print_network(*network, true);
    return true;
};

那它通过调用 print_network 函数打印网络结构信息,其中该函数接受两个参数传递,一个是 network,一个是 bool 变量,用来指定打印优化前还是优化后的网络结构

我们重点来看下该函数的实现:

int inputCount = network.getNbInputs();
int outputCount = network.getNbOutputs();
string layer_info;

for (int i = 0; i < inputCount; i++) {
    auto input = network.getInput(i);
    LOG("Input info: %s:%s", input->getName(), printTensorShape(input).c_str());
}

for (int i = 0; i < outputCount; i++) {
    auto output = network.getOutput(i);
    LOG("Output info: %s:%s", output->getName(), printTensorShape(output).c_str());
}

首先我们通过 network 获取模型输入和输出的数量,接着对于每个 input 和 output,我们都把它的 name 和 shape 给打印出来。network 的 getInputgetOutput 方法返回的是一个 ITensor* 类型的变量,从 ITensor 中我们可以获取它的 dimension、name、type 等信息

其中的 printTensorShape 函数的内容如下:

string printTensorShape(nvinfer1::ITensor* tensor){
    string str;
    str += "[";
    auto dims = tensor->getDimensions();
    for (int j = 0; j < dims.nbDims; j++) {
        str += to_string(dims.d[j]);
        if (j != dims.nbDims - 1) {
            str += " x ";
        }
    }
    str += "]";
    return str;
}

通过 getDimensions 获取 ITensor 的维度信息,之后遍历 nbDims 将每个维度的信息给打印出来就行

OK,我们接着往下看:

int layerCount = optimized ? mEngine->getNbLayers() : network.getNbLayers();
LOG("network has %d layers", layerCount);

通过 optimized 变量我们选择不同的 API 接口获取 layer 总数量,如果 optimized 为 true 那么我们从 mEngine 中去拿 layer 总层数,如果 optimized 为 false,那么我们从 network 中去拿 layer 总层数,接着将网络总层数打印出来

其中的 IEngine 我们可以认为它是已经用 tensorR 优化好的模型,而 INetwork 我们可以认为它是还没有用 tensorRT 优化,刚刚通过 parser 给 parse 出来的模型

我们下面看优化前后模型结构如何打印:

if (!optimized) {
    for (int i = 0; i < layerCount; i++) {
        char layer_info[1000];
        auto layer   = network.getLayer(i);
        auto input   = layer->getInput(0);
        int n = 0;
        if (input == nullptr){
            continue;
        }
        auto output  = layer->getOutput(0);

        LOG("layer_info: %-40s:%-25s->%-25s[%s]", 
            layer->getName(),
            printTensorShape(input).c_str(),
            printTensorShape(output).c_str(),
            getPrecision(layer->getPrecision()).c_str());
    }
}

如果打印没有被优化的模型结构,则先遍历 layerCount,然后通过 getLayer 方法拿到层信息,接着通过 layer 的 getInput 和 getOutput 方法拿到对应层的输入输出,接着把层的 name、input shape、output shape、precision 给打印出来

我们再来看优化后:

auto inspector = make_unique<nvinfer1::IEngineInspector>(mEngine->createEngineInspector());
for (int i = 0; i < layerCount; i++) {
    string info = inspector->getLayerInformation(i, nvinfer1::LayerInformationFormat::kONELINE);

    info = info.substr(0, info.size() - 1);
    LOG("layer_info: %s", info.c_str());
}

这里我们通过 createEngineInspector 创建了一个 inspector 来查看 engine 内部信息,接着通过 inspector 的 getLayerInformation 获取每一层的信息,它直接返回的就是一个字符串。它有两个参数,第一个参数代表获取的是第几个 layer,第二个参数代表想获取的 layer info 的格式,可以有 JSON、ONELINE 格式

OK,这就是 print network structure 的全部内容,内容不多,比较简单

总结

本次课程我们主要学习了如何打印 tensorRT 优化前后的网络结构,可以方便我们对比 tensorRT 都做了哪些优化,优化前我们主要是通过 INetwork 获取 layer 信息,优化后我们主要是 ICudaEngine 获取 layer 信息,此外一些类的定义包括 ITensor、ILayer、INetwork、ICudaEngine、IExecutionContext、IBuilder 等等大家可以看下官方文档理解其含义

OK,以上就是 5.4 小节案例的全部内容了,下节我们来学习 5.5 小节来利用 C++ API 手动搭建网络模型,敬请期待😄

下载链接

参考

  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
牙科就诊管理系统利用当下成熟完善的SSM框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的Mysql数据库进行程序开发。实现了用户在线查看数据。管理员管理病例管理、字典管理、公告管理、药单管理、药品管理、药品收藏管理、药品评价管理、药品订单管理、牙医管理、牙医收藏管理、牙医评价管理、牙医挂号管理、用户管理、管理员管理等功能。牙科就诊管理系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。 管理员在后台主要管理病例管理、字典管理、公告管理、药单管理、药品管理、药品收藏管理、药品评价管理、药品订单管理、牙医管理、牙医收藏管理、牙医评价管理、牙医挂号管理、用户管理、管理员管理等。 牙医列表页面,此页面提供给管理员的功能有:查看牙医、新增牙医、修改牙医、删除牙医等。公告信息管理页面提供的功能操作有:新增公告,修改公告,删除公告操作。公告类型管理页面显示所有公告类型,在此页面既可以让管理员添加新的公告信息类型,也能对已有的公告类型信息执行编辑更新,失效的公告类型信息也能让管理员快速删除。药品管理页面,此页面提供给管理员的功能有:新增药品,修改药品,删除药品。药品类型管理页面,此页面提供给管理员的功能有:新增药品类型,修改药品类型,删除药品类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱听歌的周童鞋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值