目录
- 前言
- 0. 简述
- 1. 案例运行
- 2. 代码分析
- 2.1 main.cpp
- 2.2 model.cpp
- 总结
- 下载链接
- 参考
前言
自动驾驶之心推出的 《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 优化前后的网络结构打印:
可以看到优化前有 126 层,都是 conv+relu,每一层的输入输出维度以及精度都有打印;优化后只有 79 层,可以看懂 tensorRT 做了很多层融合,比如 conv+relu 融合,conv+add+relu 融合等等
我们再看下 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 的 getInput
和 getOutput
方法返回的是一个 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 手动搭建网络模型,敬请期待😄
下载链接
- tensorrt_starter源码
- 5.4-TensorRT-network-structure案例文件
参考
- Ubuntu20.04软件安装大全
- https://github.com/kalfazed/tensorrt_starter.git