4. TensorRT基础:Builder/Config/Engine/Core概念解析、构建流程、序列化与反序列化

好,咱们进入TensorRT的核心环节。说实话,很多新手一上来就急着写代码,结果Builder、Config、Engine这几个概念搞得一团浆糊。我刚开始接触TensorRT时也踩过这个坑——明明模型导进去了,推理速度就是上不去,后来才发现是Config没配对。

今天咱们就把这几个核心概念掰开揉碎,讲清楚它们各自是干什么的,以及怎么配合工作。

4.1 四大核心概念:Builder / Config / Engine / Core

先给个直观的比喻,你想想看:

  • Builder:就像建筑公司的总工头,负责统筹整个模型构建过程
  • Config:是工头手里的施工图纸,告诉Builder怎么建——精度、内存限制、优化策略全写在这
  • Engine:是建好的大楼,可以直接投入使用,但不能改了
  • Core:是地基和框架,支撑整个TensorRT的运行环境

说白了,Builder拿着Config这张图纸,把模型盖成Engine这栋楼,然后Core负责让这栋楼能正常运转。

关键点:Engine一旦构建完成,就是只读的。你不能在推理时再改它的结构或精度。所以Config阶段一定要想清楚。

4.2 构建流程:从ONNX到Engine

我习惯把构建流程分成三步走:

  1. 创建Builder和Config:初始化环境,设置参数
  2. 解析模型:把ONNX或Caffe模型喂给Builder
  3. 构建Engine:Builder根据Config进行优化,生成最终的可执行引擎

这里贴一段我常用的构建代码,注释写得很清楚:

// 1. 创建Builder
nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger);

// 2. 创建Config
nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();

// 3. 设置Config参数
config->setMemoryPoolLimit(nvinfer1::MemoryPoolType::kWORKSPACE, 1 << 30); // 1GB
config->setFlag(nvinfer1::BuilderFlag::kFP16); // 启用FP16

// 4. 解析ONNX模型
const auto explicitBatch = 1U << static_cast<uint32_t>(
    nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
nvinfer1::INetworkDefinition* network = builder->createNetworkV2(explicitBatch);

nvinfer1::IONNXParser* parser = nvinfer1::createONNXParser(*network, logger);
parser->parseFromFile("model.onnx", 
    nvinfer1::OnnxParserFlags::kNONE);

// 5. 构建Engine
nvinfer1::IHostMemory* serializedModel = builder->buildSerializedNetwork(*network, *config);
nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger);
nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(
    serializedModel->data(), serializedModel->size());

我的经验:Config里的setMemoryPoolLimit别设太小。我曾经设成256MB,结果构建时直接OOM。建议至少1GB起步,除非你模型特别小。

4.3 序列化与反序列化:Engine的保存与加载

为什么要序列化?因为构建Engine是个耗时的过程。你想想看,每次启动程序都重新构建一遍,那得等到猴年马月。所以咱们把构建好的Engine保存成文件,下次直接加载。

序列化就是把Engine转成二进制数据,反序列化就是把这堆二进制数据还原成Engine。嗯,这里要注意:

  • 序列化后的文件:通常以.engine或.trt为后缀
  • 平台相关性:序列化文件跟CUDA版本、TensorRT版本、GPU架构都绑定的。换个环境就得重新构建

看代码:

// 序列化:保存Engine到文件
void saveEngine(nvinfer1::ICudaEngine* engine, const std::string& path) {
    nvinfer1::IHostMemory* serializedModel = engine->serialize();
    std::ofstream file(path, std::ios::binary);
    file.write(reinterpret_cast<const char*>(serializedModel->data()), 
               serializedModel->size());
    serializedModel->destroy();
}

// 反序列化:从文件加载Engine
nvinfer1::ICudaEngine* loadEngine(const std::string& path, 
                                   nvinfer1::IRuntime* runtime) {
    std::ifstream file(path, std::ios::binary | std::ios::ate);
    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);
    
    std::vector<char> buffer(size);
    file.read(buffer.data(), size);
    
    return runtime->deserializeCudaEngine(buffer.data(), size);
}

避坑指南:我曾经在A100上构建了Engine,然后直接拷贝到V100上用,结果反序列化失败。后来查文档才发现,不同架构的GPU生成的Engine不兼容。所以生产环境里,最好在目标机器上构建Engine,或者用TensorRT的兼容性模式。

4.4 知识体系总览

为了让你更直观地理解这些概念的关系,我画了张图:

TensorRT核心概念与构建流程 Builder 总工头:统筹构建过程 Config 施工图纸:精度/内存/优化 ONNX模型 输入源 Engine 可执行引擎:只读 序列化 保存为.engine文件 反序列化 从文件加载Engine Core (Runtime) 图例:构建流程 → 序列化/反序列化 → 推理运行

4.5 实战中的注意事项

最后总结几个我踩过的坑,你记一下:

环节 常见问题 我的建议
Builder创建 Logger未正确初始化 Logger一定要全局唯一,否则会崩溃
Config设置 内存池设太小导致OOM 先设1GB,不够再加
模型解析 ONNX算子不支持 用trtexec --onnx先测试一下
Engine构建 构建时间过长 用Config的setBuilderOptimizationLevel控制优化级别
序列化 跨平台不兼容 在目标机器上构建,或用兼容模式

小技巧:如果你不确定Config该怎么配,可以用trtexec命令行工具先跑一遍,它会打印出详细的构建参数。然后你照着抄就行。我刚开始就是这么干的,省了不少时间。

好了,Builder、Config、Engine、Core这几个概念,加上序列化反序列化,就是TensorRT的骨架。你把这些搞明白了,后面的INT8量化、动态形状什么的,学起来就顺了。

可可精品资料库