TensorRT(C++)基础代码解析

TensorRT(C++)基础代码解析


文章目录

  • TensorRT(C++)基础代码解析
  • 前言
  • 一、TensorRT工作流程
  • 二、C++ API
    • 2.1 构建阶段
      • 2.1.1 创建builder
      • 2.1.2 创建网络定义
      • 2.1.3 定义网络结构
      • 2.1.4 定义网络输入输出
      • 2.1.5 配置参数
      • 2.1.6 生成Engine
      • 2.1.7 保存为模型文件
      • 2.1.8 释放资源
    • 2.2 运行期
      • 2.2.1 创建一个runtime对象
      • 2.2.2 反序列化生成engine
      • 2.2.3 创建一个执行上下文ExecutionContext
      • 2.2.4 为推理填充输入
      • 2.2.4 调用enqueueV2来执行推理
      • 2.2.5 释放资源
  • 总结


前言


一、TensorRT工作流程

在这里插入图片描述

在这里插入图片描述

二、C++ API

2.1 构建阶段

TensorRT build engine的过程

  1. 创建builder
  2. 创建网络定义:builder —> network
  3. 配置参数:builder —> config
  4. 生成engine:builder —> engine (network, config)
  5. 序列化保存:engine —> serialize
  6. 释放资源:delete

2.1.1 创建builder

nvinfer1 是 NVIDIA TensorRT 的 C++ 接口命名空间。构建阶段的最高级别接口是 Builder。Builder负责优化一个模型,并产生Engine。通过如下接口创建一个Builder。

nvinfer1::IBuilder *builder = nvinfer1::createInferBuilder(logger);

2.1.2 创建网络定义

NetworkDefinition接口被用来定义模型。接口createNetworkV2接受配置参数,参数用按位标记的方式传入。比如上面激活explicitBatch,是通过1U << static_cast<uint32_t (nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH); 将explicitBatch对应的配置位设置为1实现的。在新版本中,请使用createNetworkV2而非其他任何创建NetworkDefinition 的接口。

auto explicitBatch = 1U << static_cast<uint32_t
(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);// 调用createNetworkV2创建网络定义,参数是显性batch
nvinfer1::INetworkDefinition *network = builder->createNetworkV2(explicitBatch);

2.1.3 定义网络结构

将模型转移到TensorRT的最常见的方式是以ONNX格式从框架中导出(将在后续课程进行介绍),并使用TensorRT的ONNX解析器来填充网络定义。同时,也可以使用TensorRT的Layer和Tensor等接口一步一步地进行定义。通过接口来定义网络的代码示例如下:

添加输入层

const int input_size = 3;
nvinfer1::ITensor *input = network->addInput("data", nvinfer1::DataType::kFLOAT,nvinfer1::Dims4{1, input_size, 1, 1}) 		 

添加全连接层

nvinfer1::IFullyConnectedLayer* fc1 = network->addFullyConnected(*input, output_size, fc1w, fc1b);

添加激活层

nvinfer1::IActivationLayer* relu1 = network->addActivation(*fc1->getOutput(0), nvinfer1::ActivationType::kRELU);

2.1.4 定义网络输入输出

定义哪些张量是网络的输入和输出。没有被标记为输出的张量被认为是瞬时值,可以被构建者优化掉。输入和输出张量必须被命名,以便在运行时,TensorRT知道如何将输入和输出缓冲区绑定到模型上。

// 设置输出名字
sigmoid->getOutput(0)->setName("output");
// 标记输出,没有标记会被当成顺时针优化掉
network->markOutput(*sigmoid->getOutput(0));

2.1.5 配置参数

添加相关Builder 的配置。createBuilderConfig接口被用来指定TensorRT应该如何优化模型

 nvinfer1::IBuilderConfig *config = builder->createBuilderConfig();// 设置最大工作空间大小,单位是字节
config->setMaxWorkspaceSize(1 << 28); // 256MiB

2.1.6 生成Engine

 nvinfer1::ICudaEngine *engine = builder->buildEngineWithConfig(*network, *config);

2.1.7 保存为模型文件

nvinfer1::IHostMemory *serialized_engine = engine->serialize();// 存入文件
std::ofstream outfile("model/mlp.engine", std::ios::binary);
assert(outfile.is_open() && "Failed to open file for writing");
outfile.write((char *)serialized_engine->data(), serialized_engine->size());

2.1.8 释放资源

outfile.close();
delete serialized_engine;
delete engine;
delete config;
delete network;

完整代码

/*
TensorRT build engine的过程
7. 创建builder
8. 创建网络定义:builder ---> network
9. 配置参数:builder ---> config
10. 生成engine:builder ---> engine (network, config)
11. 序列化保存:engine ---> serialize
12. 释放资源:delete
*/#include <iostream>
#include <fstream>
#include <cassert>
#include <vector>#include <NvInfer.h>// logger用来管控打印日志级别
// TRTLogger继承自nvinfer1::ILogger
class TRTLogger : public nvinfer1::ILogger
{void log(Severity severity, const char *msg) noexcept override{// 屏蔽INFO级别的日志if (severity != Severity::kINFO)std::cout << msg << std::endl;}
} gLogger;// 保存权重
void saveWeights(const std::string &filename, const float *data, int size)
{std::ofstream outfile(filename, std::ios::binary);assert(outfile.is_open() && "save weights failed");  // assert断言,如果条件不满足,就会报错outfile.write((char *)(&size), sizeof(int));         // 保存权重的大小outfile.write((char *)(data), size * sizeof(float)); // 保存权重的数据outfile.close();
}
// 读取权重
std::vector<float> loadWeights(const std::string &filename)
{std::ifstream infile(filename, std::ios::binary);assert(infile.is_open() && "load weights failed");int size;infile.read((char *)(&size), sizeof(int));                // 读取权重的大小std::vector<float> data(size);                            // 创建一个vector,大小为sizeinfile.read((char *)(data.data()), size * sizeof(float)); // 读取权重的数据infile.close();return data;
}int main()
{// ======= 1. 创建builder =======TRTLogger logger;nvinfer1::IBuilder *builder = nvinfer1::createInferBuilder(logger);// ======= 2. 创建网络定义:builder ---> network =======// 显性batch// 1 << 0 = 1,二进制移位,左移0位,相当于1(y左移x位,相当于y乘以2的x次方)auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);// 调用createNetworkV2创建网络定义,参数是显性batchnvinfer1::INetworkDefinition *network = builder->createNetworkV2(explicitBatch);// 定义网络结构// mlp多层感知机:input(1,3,1,1) --> fc1 --> sigmoid --> output (2)// 创建一个input tensor ,参数分别是:name, data type, dimsconst int input_size = 3;nvinfer1::ITensor *input = network->addInput("data", nvinfer1::DataType::kFLOAT, nvinfer1::Dims4{1, input_size, 1, 1});// 创建全连接层fc1// weight and biasconst float *fc1_weight_data = new float[input_size * 2]{0.1, 0.2, 0.3, 0.4, 0.5, 0.6};const float *fc1_bias_data = new float[2]{0.1, 0.5};// 将权重保存到文件中,演示从别的来源加载权重saveWeights("model/fc1.wts", fc1_weight_data, 6);saveWeights("model/fc1.bias", fc1_bias_data, 2);// 读取权重auto fc1_weight_vec = loadWeights("model/fc1.wts");auto fc1_bias_vec = loadWeights("model/fc1.bias");// 转为nvinfer1::Weights类型,参数分别是:data type, data, sizenvinfer1::Weights fc1_weight{nvinfer1::DataType::kFLOAT, fc1_weight_vec.data(), fc1_weight_vec.size()};nvinfer1::Weights fc1_bias{nvinfer1::DataType::kFLOAT, fc1_bias_vec.data(), fc1_bias_vec.size()};const int output_size = 2;// 调用addFullyConnected创建全连接层,参数分别是:input tensor, output size, weight, biasnvinfer1::IFullyConnectedLayer *fc1 = network->addFullyConnected(*input, output_size, fc1_weight, fc1_bias);// 添加sigmoid激活层,参数分别是:input tensor, activation type(激活函数类型)nvinfer1::IActivationLayer *sigmoid = network->addActivation(*fc1->getOutput(0), nvinfer1::ActivationType::kSIGMOID);// 设置输出名字sigmoid->getOutput(0)->setName("output");// 标记输出,没有标记会被当成顺时针优化掉network->markOutput(*sigmoid->getOutput(0));// 设定最大batch sizebuilder->setMaxBatchSize(1);// ====== 3. 配置参数:builder ---> config ======// 添加配置参数,告诉TensorRT应该如何优化网络nvinfer1::IBuilderConfig *config = builder->createBuilderConfig();// 设置最大工作空间大小,单位是字节config->setMaxWorkspaceSize(1 << 28); // 256MiB// ====== 4. 创建engine:builder ---> network ---> config ======nvinfer1::ICudaEngine *engine = builder->buildEngineWithConfig(*network, *config);if (!engine){std::cerr << "Failed to create engine!" << std::endl;return -1;}// ====== 5. 序列化engine ======nvinfer1::IHostMemory *serialized_engine = engine->serialize();// 存入文件std::ofstream outfile("model/mlp.engine", std::ios::binary);assert(outfile.is_open() && "Failed to open file for writing");outfile.write((char *)serialized_engine->data(), serialized_engine->size());// ====== 6. 释放资源 ======// 理论上,这些资源都会在程序结束时自动释放,但是为了演示,这里手动释放部分outfile.close();delete serialized_engine;delete engine;delete config;delete network;delete builder;std::cout << "engine文件生成成功!" << std::endl;return 0;
}

2.2 运行期

在这里插入图片描述
TensorRT runtime 推理过程

  1. 创建一个runtime对象
  2. 反序列化生成engine:runtime —> engine
  3. 创建一个执行上下文ExecutionContext:engine —> context
  4. 填充数据
  5. 执行推理:context —> enqueueV2
  6. 释放资源:delete

2.2.1 创建一个runtime对象

TensorRT运行时的最高层级接口是Runtime

 nvinfer1::IRuntime *runtime = nvinfer1::createInferRuntime(logger);

2.2.2 反序列化生成engine

通过读取模型文件并反序列化,我们可以利用runtime生成Engine。

nvinfer1::ICudaEngine *engine = runtime->deserializeCudaEngine(engine_data.data(), engine_data.size(), nullptr);

2.2.3 创建一个执行上下文ExecutionContext

从Engine创建的ExecutionContext接口是调用推理的主要接口。ExecutionContext包含与特定调用相关的所有状态,因此可以有多个与单个引擎相关的上下文,且并行运行它们。

nvinfer1::IExecutionContext *context = engine->createExecutionContext();

2.2.4 为推理填充输入

首先创建CUDA Stream用于推理的执行。

cudaStream_t stream = nullptr;
cudaStreamCreate(&stream);

同时在CPU和GPU上分配输入输出内存,并将输入数据从CPU拷贝到GPU上。

// 输入数据
float* h_in_data = new float[3]{1.4, 3.2, 1.1};
int in_data_size = sizeof(float) * 3;
float* d_in_data = nullptr;
// 输出数据
float* h_out_data = new float[2]{0.0, 0.0};
int out_data_size = sizeof(float) * 2;
float* d_out_data = nullptr;
// 申请GPU上的内存
cudaMalloc(&d_in_data, in_data_size);
cudaMalloc(&d_out_data, out_data_size);
// 拷贝数据
cudaMemcpyAsync(d_in_data, h_in_data, in_data_size, cudaMemcpyHostToDevice, stream);
// enqueueV2中是把输入输出的内存地址放到bindings这个数组中,需要写代码时确定这些输入输出的顺序(这样容易出错,而且不好定位bug,所以新的接口取消了这样的方式,不过目前很多官方 sample 也在用v2)
float* bindings[] = {d_in_data, d_out_data};

2.2.4 调用enqueueV2来执行推理

bool success = context -> enqueueV2((void **) bindings, stream, nullptr);
// 数据从device --> host
cudaMemcpyAsync(host_output_data, device_output_data, output_data_size, cudaMemcpyDeviceToHost, stream);
// 等待流执行完毕
cudaStreamSynchronize(stream);
// 输出结果
std::cout << "输出结果: " << host_output_data[0] << " " << host_output_data[1] << std::endl;

2.2.5 释放资源

cudaStreamDestroy(stream);
cudaFree(device_input_data_address);
cudaFree(device_output_data_address);   
delete[] host_input_data;
delete[] host_output_data;delete context;
delete engine;
delete runtime;

完整代码

/*
使用.cu是希望使用CUDA的编译器NVCC,会自动连接cuda库TensorRT runtime 推理过程1. 创建一个runtime对象
2. 反序列化生成engine:runtime ---> engine
3. 创建一个执行上下文ExecutionContext:engine ---> context4. 填充数据5. 执行推理:context ---> enqueueV26. 释放资源:delete*/
#include <iostream>
#include <vector>
#include <fstream>
#include <cassert>#include "cuda_runtime.h"
#include "NvInfer.h"// logger用来管控打印日志级别
// TRTLogger继承自nvinfer1::ILogger
class TRTLogger : public nvinfer1::ILogger
{void log(Severity severity, const char *msg) noexcept override{// 屏蔽INFO级别的日志if (severity != Severity::kINFO)std::cout << msg << std::endl;}
} gLogger;// 加载模型
std::vector<unsigned char> loadEngineModel(const std::string &fileName)
{std::ifstream file(fileName, std::ios::binary);        // 以二进制方式读取assert(file.is_open() && "load engine model failed!"); // 断言file.seekg(0, std::ios::end); // 定位到文件末尾size_t size = file.tellg();   // 获取文件大小std::vector<unsigned char> data(size); // 创建一个vector,大小为sizefile.seekg(0, std::ios::beg);          // 定位到文件开头file.read((char *)data.data(), size);  // 读取文件内容到data中file.close();return data;
}int main()
{// ==================== 1. 创建一个runtime对象 ====================TRTLogger logger;nvinfer1::IRuntime *runtime = nvinfer1::createInferRuntime(logger);// ==================== 2. 反序列化生成engine ====================// 读取文件auto engineModel = loadEngineModel("./model/mlp.engine");// 调用runtime的反序列化方法,生成engine,参数分别是:模型数据地址,模型大小,pluginFactorynvinfer1::ICudaEngine *engine = runtime->deserializeCudaEngine(engineModel.data(), engineModel.size(), nullptr);if (!engine){std::cout << "deserialize engine failed!" << std::endl;return -1;}// ==================== 3. 创建一个执行上下文 ====================nvinfer1::IExecutionContext *context = engine->createExecutionContext();// ==================== 4. 填充数据 ====================// 设置stream 流cudaStream_t stream = nullptr;cudaStreamCreate(&stream);// 数据流转:host --> device ---> inference ---> host// 输入数据float *host_input_data = new float[3]{2, 4, 8}; // host 输入数据int input_data_size = 3 * sizeof(float);        // 输入数据大小float *device_input_data = nullptr;             // device 输入数据// 输出数据float *host_output_data = new float[2]{0, 0}; // host 输出数据int output_data_size = 2 * sizeof(float);     // 输出数据大小float *device_output_data = nullptr;          // device 输出数据// 申请device内存cudaMalloc((void **)&device_input_data, input_data_size);cudaMalloc((void **)&device_output_data, output_data_size);// host --> device// 参数分别是:目标地址,源地址,数据大小,拷贝方向cudaMemcpyAsync(device_input_data, host_input_data, input_data_size, cudaMemcpyHostToDevice, stream);// bindings告诉Context输入输出数据的位置float *bindings[] = {device_input_data, device_output_data};// ==================== 5. 执行推理 ====================bool success = context -> enqueueV2((void **) bindings, stream, nullptr);// 数据从device --> hostcudaMemcpyAsync(host_output_data, device_output_data, output_data_size, cudaMemcpyDeviceToHost, stream);// 等待流执行完毕cudaStreamSynchronize(stream);// 输出结果std::cout << "输出结果: " << host_output_data[0] << " " << host_output_data[1] << std::endl;// ==================== 6. 释放资源 ====================cudaStreamDestroy(stream);cudaFree(device_input_data); cudaFree(device_output_data);delete host_input_data;delete host_output_data;delete context;delete engine;delete runtime;return 0;
}

总结

TensorRT(C++)基础代码解析

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

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

相关文章

【elastic search】详解elastic search集群

目录 1.与集群有关的一些概念 2.集群搭建 3.集群搭建 4.kibana链接集群 5.选举流程 6.请求流程 7.master的作用 1.与集群有关的一些概念 数据分片&#xff1a; 数据分片&#xff08;shard&#xff09;&#xff0c;单台服务器的存储容量是有限的&#xff0c;把一份数据…

git提交记录全部删除

目录 问题描述 解决方案 结果 问题描述 新复制的项目具有特比多的提交记录我想给他清除&#xff0c;因为不清楚过多历史也就导致包特别大下载和提交等方面都不是很快 解决方案 查看代码clone网址&#xff1b; 打开远程仓库&#xff0c;选择要去除历史记代码分支&#xff08…

低代码助力制造业数智转型,激发创新力迎接工业 4.0

随着科技的不断进步&#xff0c;我们迈入了一个崭新的工业时代——工业4.0。这场工业革命不仅颠覆了制造业的传统形象&#xff0c;还为全球生产方式带来了前所未有的变革。 在这一过程中&#xff0c;制造业数字化转型逐渐成为主旋律&#xff0c;而低代码技术在这其中发挥着重要…

近红外光谱分析技术与基于深度学习的化学计量学方法

郁磊【副教授】&#xff1a;主要从事AI人工智能与大数据分析等相关研究&#xff0c;长期致力于人工智能与近红外生物医学工程等领域融合&#xff0c;主持并完成多项科研课题。著有《神经网络43个案例分析》等书籍。 // 讲座内容 1、近红外光谱基本理论、近红外光谱仪基本原理…

python爬虫-代理ip理解

目录 1、为什么使用代理IP 2、代理IP 3、IP池 4、代理分类&#xff1a; 5、python中使用代理IP 6、如何找可以使用的代理IP 7、拿到IP后&#xff0c;测试IP的有效性 8、扩展理解正向代理和反向代理 1、为什么使用代理IP 就是为了防止ip被封禁&#xff0c;提高爬虫的效…

【Proteus仿真】【Arduino单片机】智能窗户设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用蜂鸣器模块、LCD1602显示模块、雨滴传感器、风速传感器、人体红外模块、ADC模块、按键模块、28BYJ48步进电机 模块、DS18B20温度传感器等。 …

2023年北邮渣硕的暑期秋招总结

背景 实验室一般是在研究生二年级的时候会放实习&#xff0c;在以后的日子就是自己完成毕业工作要求&#xff0c;基本上不再涉及实验室的活了&#xff0c;目前是一月份也是开始准备暑期实习的好时间。实验室每年这个时候都会有学长学姐组织暑期实习经验分享&#xff0c;本着不…

外国入境的免签国家以及中外互免(普通护照*、普通公务护照)

2024.1.11起&#xff0c;外籍人员在北京首都、北京大兴、上海浦东、杭州萧山、厦门高崎、广州白云、深圳宝安、成都天府、西安咸阳9个国际机场&#xff0c;推行24小时直接过境旅客免办边检手续。对于持24小时内国际联程机票&#xff0c;经上述任一机场过境前往第三国或地区的出…

C#中对浮点数NaN,PositiveInfinity,NegativeInfinity的特殊处理

NAN NAN 整体意思为Not a Number 不是一个数&#xff0c; NaN&#xff08;Not a Number&#xff0c;非数&#xff09;是计算机科学中数值数据类型的一类值&#xff0c;表示未定义或不可表示的值。常在浮点数运算中使用。首次引入NaN的是1985年的IEEE 754浮点数标准。 EEE 75…

论文阅读 Self-Supervised Burst Super-Resolution

这是一篇 ICCV 2023 的文章&#xff0c;主要介绍的是用自监督的方式进行多帧超分的学习 Abstract 这篇文章介绍了一种基于自监督的学习方式来进行多帧超分的任务&#xff0c;这种方法只需要原始的带噪的低分辨率的图。它不需要利用模拟退化的方法来构造数据&#xff0c;而且模…

API设计:从基础到最佳实践

1*vWvkkgG6uvgmJT8GkId98A.png 在这次深入探讨中&#xff0c;我们将深入了解API设计&#xff0c;从基础知识开始&#xff0c;逐步进阶到定义出色API的最佳实践。 作为开发者&#xff0c;你可能对许多这些概念很熟悉&#xff0c;但我将提供详细的解释&#xff0c;以加深你的理解…

2024--Django平台开发-Django知识点(七)

频率超高的问题 Redis的问题虚拟环境mysqlcient和pymysql短信服务&#xff0c;一期用的是腾讯云短信 虚拟环境 可以用来创建虚拟环境的&#xff1a; virtualenv这个模块&#xff0c;简单易上手&#xff0c;推荐 小白不建议&#xff0c;conda&#xff0c;如果大家用这个&…

React项目实战--------极客园项目PC端

项目介绍&#xff1a;主要将学习到的项目内容进行总结&#xff08;有需要项目源码的可以私信我&#xff09; 关于我的项目的配置如下&#xff0c;请注意下载的每个版本不一样&#xff0c;写的api也不一样 一、项目介绍 1.资料 1&#xff09;短信接收&M端演示&#xff1a…

Vue中v-if与v-show区别详解

✨ 专栏介绍 在当今Web开发领域中&#xff0c;构建交互性强、可复用且易于维护的用户界面是至关重要的。而Vue.js作为一款现代化且流行的JavaScript框架&#xff0c;正是为了满足这些需求而诞生。它采用了MVVM架构模式&#xff0c;并通过数据驱动和组件化的方式&#xff0c;使…

寻找最富裕的小家庭 - 华为OD统一考试

OD统一考试(C卷) 分值: 100分 题解: Java / Python / C++ 题目描述 在一棵树中,每个节点代表一个家庭成员,节点的数字表示其个人的财富值,一个节点及其直接相连的子节点被定义为一个小家庭现给你一棵树,请计算出最富裕的小家庭的财富和。 输入描述 第一行为一个数N,…

关于tex中的表格设置

文章目录 控制表格列宽和行高控制表格列宽的同时实现居中tex中多表格排列单元格的合并与分割对单个单元格进行操作 控制表格列宽和行高 将下面的代码放在table环境内&#xff0c;放在tabular环境外 调整表格宽度和高度&#xff1a; \resizebox{\textwidth}{2cm}{%第一个{}是表…

C语言之从浅入深一步一步全方位理解指针【附笔试题】

文章目录 前言从浅入深理解指针《第一阶段》一、内存和地址1.1 内存1.2 究竟该如何理解编址 二、指针变量和地址2.1 取地址操作符&#xff08;&&#xff09; 三、指针变量和解引用操作符&#xff08;*&#xff09;3.1 指针变量3.2 如何拆解指针类型3.3 解引用操作符 四、指…

设计模式之解释器模式【行为型模式】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档> 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某…

为了这口醋,包的这饺子。为了Selenium,学有限的CSS,逐步替换XPATH

Learn about CSS rules and pseudo-classes to help you move your XPATH locators to CSS. 1. 最基本IdElement TypeDirect ChildChild or Sub-ChildClass 2. 深入一点Next SiblingAttribute ValuesChoosing a Specific Match Sub-String Matches 3 参考资料 In order for Sel…

transfomer中Multi-Head Attention的源码实现

简介 Multi-Head Attention是一种注意力机制,是transfomer的核心机制. Multi-Head Attention的原理是通过将模型分为多个头&#xff0c;形成多个子空间&#xff0c;让模型关注不同方面的信息。每个头独立进行注意力运算&#xff0c;得到一个注意力权重矩阵。输出的结果再通过…