这个系列需要注意可读性,便于以后迭代阅读。
框架必须明晰
参考:Developer Guide :: NVIDIA Deep Learning TensorRT Documentation
1 须知
- 接口类以字母"I"作为前缀,例如ILogger、IBuilder等等;
- TensorRT在第一次调用CUDA时会自动创建一个CUDA上下文,所以,首次调用TensorRT之前自己创建和配置CUDA上下文。
- 为了说明对象的生命周期,本章代码不使用智能指针;然而,建议在使用TensorRT接口时使用智能指针。
2 构建阶段
ILogger
class Logger : public ILogger
{
void log(Severity severity, const char* msg) noexcept override
{
// suppress info-level messages
if (severity <= Severity::kWARNING)
std::cout << msg << std::endl;
}
} logger;
// 创建推理引擎
IBuilder* builder = createInferBuilder(logger);
2.1 创建网络定义
网络创建选项是通过组合多个标志位(flags)进行“或”运算(OR-d)来指定的。
- ONNX:kEXPLICIT_BATCH
- 是否使用强类型:NetworkDefinitionCreationFlag::kSTRONGLY_TYPED
INetworkDefinition* network = builder->createNetworkV2(flag);
2.2 导入ONNX文件
ONNX解析器API位于文件NvOnnxParser.h中,解析器位于nvonnxparser C++命名空间中。
#include “NvOnnxParser.h”
using namespace nvonnxparser;
创建ONNX parser并读取模型文件
IParser* parser = createParser(*network, logger);
parser->parseFromFile(modelFile,
static_cast<int32_t>(ILogger::Severity::kWARNING));
for (int32_t i = 0; i < parser->getNbErrors(); ++i)
{
std::cout << parser->getError(i)->desc() << std::endl;
}
它包含指向模型权重的指针,这些权重会被构建器复制到优化后的引擎中。由于网络是使用解析器创建的,所以解析器拥有权重所占用的内存,因此在构建器运行完成之前,不应该删除解析器对象。
2.3 构造引擎
创建构造参数。
IBuilderConfig* config = builder->createBuilderConfig();
可以设置:最大工作区域尺寸,限制了任何层可用的最大工作空间大小。一般为给定设备的全局内存大小。除非多个引擎一起构建
config->setMemoryPoolLimit(MemoryPoolType::kWORKSPACE, 1U << 20);
另一个重要的考虑因素是CUDA后端实现的最大共享内存分配。在需要与其他应用程序共存的场景中,这个分配变得尤为关键。
config->setMemoryPoolLimit(MemoryPoolType::kTACTIC_SHARED_MEMORY, 48 << 10);
开始构造引擎
IHostMemory* serializedModel = builder->buildSerializedNetwork(*network, *config);
构造完了,保存引擎,删掉不用的
delete parser;
delete network;
delete config;
delete builder;
delete serializedModel
至此,转化完毕,接下来推理。
2.4 逆序列化一个引擎
runtime时间,可以吃logger参数,也可以把缓存的model读入
IRuntime* runtime = createInferRuntime(logger);
ICudaEngine* engine =
runtime->deserializeCudaEngine(modelData, modelSize);
2.5 进行推理
要执行推理,你必须管理中间激活的额外状态
IExecutionContext *context = engine->createExecutionContext();
一个引擎有多个执行上下文,一组权重进行多个推理任务。(例外是使用动态形状时,除非指定了预览功能kPROFILE_SHARING_0806。)
必须为输入和输出传递TensorRT缓冲区,TensorRT要求你通过调用setTensorAddress来指定,这个函数需要传入张量的名称和缓冲区的地址。你可以使用你为输入和输出张量提供的名称来查询引擎。
context->setTensorAddress(INPUT_NAME, inputBuffer);
context->setTensorAddress(OUTPUT_NAME, outputBuffer);
动态尺寸就得指定输入尺寸
context->setInputShape(INPUT_NAME, inputDims);
用流开始推理
context->enqueueV3(stream);
可能同步的情况:
- 数据依赖的形状
- DLA(深度学习加速器)的使用
- 循环
- 同步插件