官方文档onnx序列化教程与推理教程
- 一、构建TensorRT序列化模型
- 二、搭建阶段(三步走)
- 2.1 创建网络
- 2.2 使用ONNX解析器导入模型
- 2.3 构建推理引擎
- 三、反序列化模型
- 四、执行推理
一、构建TensorRT序列化模型
本博客主要说明的是TensorRT C++ API,从我们获取到onnx模型开始的流程。C++ API可以通过引用头文NvInfer.h
来进行访问(使用其命名空间nvinfer1
),代码示例:
#include "NvInfer.h"using namespace nvinfer1;
需要说明的是,TensorRT C++的接口类都以I
为前缀开头,例如ILogger
,IBuilder
,等等。如果在此之前不存在,则TensorRT第一次调用CUDA时会自动创建CUDA上下文。在第一次调用TensorRT之前,最好自己创建和配置CUDA上下文。
并且由于需要展示各对象的存在周期,实例代码中没有使用智能指针,但是建议在实际使用中加上智能指针来配合TensorRT接口。
二、搭建阶段(三步走)
要创建构建器,首先必须实例化ILogger接口。这个例子捕获所有警告消息,但忽略信息性消息:
inline const char* severity_string(nvinfer1::ILogger::Severity t) {switch (t) {case nvinfer1::ILogger::Severity::kINTERNAL_ERROR: return "internal_error";case nvinfer1::ILogger::Severity::kERROR: return "error";case nvinfer1::ILogger::Severity::kWARNING: return "warning";case nvinfer1::ILogger::Severity::kINFO: return "info";case nvinfer1::ILogger::Severity::kVERBOSE: return "verbose";default: return "unknow";}
}class My_Logger : public nvinfer1::ILogger
{
public:virtual void log(Severity severity, const char* msg) noexcept override{// suppress info-level messages// 捕获所有警告类消息并输出if (severity <= Severity::kWARNING)// 打印带颜色的字符,格式如下:// printf("\033[47;33m打印的文本\033[0m");// 其中 \033[ 是起始标记// 47 是背景颜色// ; 分隔符// 33 文字颜色// m 开始标记结束// \033[0m 是终止标记// 其中背景颜色或者文字颜色可不写// 部分颜色代码 https://blog.csdn.net/ericbar/article/details/79652086printf("\033[31m%s: %s\033[0m\n", severity_string(severity), msg);}
} my_logger;
然后你可以使用刚刚实例化的一个my_logger
作为参数来实例化一个builder
:
IBuilder* builder = createInferBuilder(my_logger);
2.1 创建网络
创建构建器之后,优化模型的第一步是创建网络定义:
uint32_t flag = 1U << static_cast<uint32_t>(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH); INetworkDefinition* network = builder->createNetworkV2(flag);
为了使用ONNX解析器导入模型,需要使用kEXPLICIT_BATCH标志。有关更多信息,请参阅显式批处理与隐式批处理部分。
2.2 使用ONNX解析器导入模型
现在,我们可以通过ONNX模型来填充网络定义,ONNX解析器API位于文件nvonnxparser.h
中,解析器位于nvonnxparser
c++命名空间中:
#include “NvOnnxParser.h”using namespace nvonnxparser;
我们可以通过创建一个ONNX解析器来填充网络定义:
IParser* parser = createParser(*network, my_logger);
然后,我们可以读取ONNX模型路径,并处理各类问题:
//your onnx model's path
const char* ONNX_MODEL = ";
//virtual bool parseFromFile(const char* onnxModelFile, int verbosity);
bool parser_status = parser->parseFromFile(ONNX_MODEL, static_cast<int32_t>(ILogger::Severity::kWARNING));
for (int32_t i = 0; i < parser.getNbErrors(); ++i)
{
std::cout << parser->getError(i)->desc() << std::endl;
}
需要注意的是,TensorRT网络定义的一个重要方面是它包含指向模型权重的指针,这些指针由构建器复制到优化的引擎中。由于网络是使用解析器创建的,所以解析器拥有权重占用的内存,因此在构建器运行之前,不可以删除解析器对象。
2.3 构建推理引擎
下一步则是创建一个构建配置,来告诉TensorRT该如何优化模型:
IBuilderConfig* config = builder->createBuilderConfig();
这个接口有很多属性,你可以设置这些属性来控制TensorRT如何优化网络。一个重要的属性是最大工作空间大小。层实现通常需要一个临时工作区,这个参数限制了网络中任何层可以使用的最大大小。如果提供的工作空间不足,TensorRT可能无法找到一个层的实现。默认情况下,工作区被设置为给定设备的总全局内存大小;必要时限制它,例如,当要在单个设备上构建多个引擎时:
config->setMemoryPoolLimit(MemoryPoolType::kWORKSPACE, 1U << 20);
一旦指定了配置,就可以构建引擎:
IHostMemory* serializedModel = builder->buildSerializedNetwork(*network, *config);
由于序列化的引擎包含必要的权重副本。解析器,网络定义,构建器配置和构建器不再是必需的,可以安全地删除:
delete parser;
delete network;
delete config;
delete builder;
然后可以将引擎保存到磁盘,并且可以删除序列化引擎的缓冲区:
delete serializedmodel;
注意:序列化引擎不能跨平台或跨TensorRT版本移植。引擎是特定于它们所构建的确切GPU模型的(除了平台和TensorRT版本)。(PS:在TensorRT8.6版本开始之后,可以通过在导出模型时添加一句代码,来实现导出模型对不同GPU平台的适配,前提是基于Ampere架构的GPU,可以参考我之前的博客关于8.6版本开始的硬件兼容性的一些试错)
由于构建引擎的目的是作为一个离线过程,它可能会花费大量时间。有关如何使构建器运行得更快,请参阅优化构建器性能部分。
三、反序列化模型
假设我们之前已经序列化了一个优化的模型,现在希望执行推理,那么我们必须创建runtime接口的实例,与构建器一样,运行时也需要一个日志记录器的实例:
IRuntime* runtime = createInferRuntime(my_logger);
当我们已经把模型读入缓冲区后,我们可以将模型反序列化来得到一个engine:
ICudaEngine* engine = runtime->deserializeCudaEngine(modelData, modelSize);
四、执行推理
引擎保存优化的模型,但是为了执行推理,我们必须为中间激活管理额外的状态,这是通过ExecutionContext的接口来实现的: