3、模型序列化基础:序列化概念、TensorRT序列化格式(.engine, .plan)、序列化流程
3.1 序列化的本质:从内存对象到持久化字节流
在深度学习推理场景中,模型训练完成后通常以框架原生格式保存(如 PyTorch 的 .pt、ONNX 的 .onnx)。这些格式包含网络结构定义、权重张量、优化器状态等冗余信息,且依赖原始框架环境才能加载。TensorRT 的序列化(Serialization)则是将已经完成引擎构建(Engine Build)的优化后推理计划,从 GPU 显存中的运行时对象,转换为平台无关的二进制字节流,并持久化到磁盘文件的过程。
序列化的核心价值在于:跳过重复的引擎构建时间。引擎构建涉及层融合、精度校准(Calibration)、内核自动调优(Tactic Selection)等耗时操作,在嵌入式设备或生产环境中每次启动都重新构建是不可接受的。通过序列化,我们可以将构建结果“冻结”为文件,在部署时直接反序列化(Deserialize)加载到 TensorRT 运行时中,实现毫秒级启动。
3.2 TensorRT 序列化格式:.engine 与 .plan 的演进
TensorRT 的序列化文件扩展名经历了从 .plan 到 .engine 的演进,但文件内部格式本质相同——都是经过版本化的二进制流。理解这一演进有助于排查“文件打不开”或“版本不兼容”类问题。
| 属性 | .plan(旧) | .engine(新) |
|---|---|---|
| 引入版本 | TensorRT 7.x 及更早 | TensorRT 8.0+ 推荐 |
| 文件头标识 | 包含 plan 魔数 |
包含 engine 魔数 + 版本号 |
| 内容组成 | 网络定义 + 权重 + 内核缓存 | 网络定义 + 权重 + 内核缓存 + 硬件绑定信息 |
| 跨版本兼容 | 完全不兼容(7.x 内小版本也可能不兼容) | 8.x 内小版本间部分兼容(需满足条件) |
| 加载方式 | deserializeCudaEngine |
runtime->deserializeCudaEngine |
序列化文件内部采用自描述格式(Self-Describing Format),包含以下关键区块:
- 文件头(Header):魔数、格式版本号、引擎大小、硬件架构标识(如
SM86表示 Ampere GA10x)。 - 网络定义区:张量名称、维度、数据类型(FP32/FP16/INT8)、层拓扑结构。
- 权重区:经过精度转换后的权重数据(可能已融合或量化)。
- 内核缓存区:每个层选择的 CUDA kernel 句柄、共享内存大小、启动参数。
- 显存规划区:张量在显存池中的偏移量、临时缓冲区大小。
.engine 文件或尝试修改其中的字节。该格式没有公开的文本表示,任何手动修改都会导致校验和失败。若需检查引擎信息,应使用 trtexec --loadEngine=xxx.engine --dumpProfile 或编写代码调用 IEngineInspector 接口。
3.3 标准序列化流程:从构建到落盘
以下代码展示了从 ONNX 模型构建 TensorRT 引擎并序列化到文件的完整流程。这是后续所有版本兼容问题讨论的基础。
// C++ 示例:构建并序列化引擎
#include <NvInfer.h>
#include <fstream>
#include <memory>
using namespace nvinfer1;
// 1. 创建构建器和网络定义
std::unique_ptr<IBuilder> builder{createInferBuilder(sample::gLogger)};
const auto explicitBatch = 1U << static_cast<uint32_t>(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
std::unique_ptr<INetworkDefinition> network{builder->createNetworkV2(explicitBatch)};
// 2. 解析 ONNX 模型
std::unique_ptr<nvonnxparser::IParser> parser{nvonnxparser::createParser(*network, sample::gLogger)};
parser->parseFromFile("model.onnx", static_cast<int>(sample::gLogger.getReportableSeverity()));
// 3. 配置构建选项
std::unique_ptr<IBuilderConfig> config{builder->createBuilderConfig()};
config->setMemoryPoolLimit(MemoryPoolType::kWORKSPACE, 1U << 30); // 1GB 工作空间
config->setFlag(BuilderFlag::kFP16); // 启用 FP16
// 4. 构建引擎(耗时操作)
std::unique_ptr<ICudaEngine> engine{builder->buildEngineWithConfig(*network, *config)};
if (!engine) { throw std::runtime_error("Engine build failed"); }
// 5. 序列化:引擎 → 内存字节流
IHostMemory* serializedData = engine->serialize();
// 6. 写入文件
std::ofstream out("model.engine", std::ios::binary);
out.write(static_cast<const char*>(serializedData->data()), serializedData->size());
out.close();
// 7. 释放序列化内存
serializedData->destroy();
反序列化加载流程则更为简洁:
// 读取文件到内存
std::ifstream in("model.engine", std::ios::binary);
std::vector<char> data(std::istreambuf_iterator<char>(in), {});
// 创建运行时并反序列化
std::unique_ptr<IRuntime> runtime{createInferRuntime(sample::gLogger)};
std::unique_ptr<ICudaEngine> engine{runtime->deserializeCudaEngine(data.data(), data.size())};
// 创建执行上下文
std::unique_ptr<IExecutionContext> context{engine->createExecutionContext()};
3.4 序列化流程中的关键决策点与常见误区
在实际项目中,序列化环节看似简单,但以下决策点直接影响后续的兼容性和部署稳定性:
- 序列化时机:应在目标 GPU 硬件上完成构建与序列化。若在开发机(如 RTX 4090, SM89)上构建,部署到生产机(如 A10, SM86)上加载,会因内核缓存不匹配而失败。
- 精度设置固化:FP16/INT8 的精度选择在构建时已固化到引擎中。序列化文件不会记录“原始 FP32 权重”,因此无法在加载时切换精度。
- 动态形状(Dynamic Shape):若网络使用动态 batch 或动态尺寸,序列化文件会保存优化配置(Optimization Profile)的边界值。加载后需通过
setBindingDimensions指定实际输入尺寸。 - 工作空间大小:构建时设置的
kWORKSPACE内存池大小会影响层融合策略。序列化后,运行时所需显存可能大于文件大小,需确保部署环境显存充足。
nvidia-smi 确认 GPU 架构一致。构建产物(.engine 文件)应作为二进制制品上传到制品库,并记录其构建环境指纹(TensorRT 版本、SM 版本、CUDA 版本、构建时间戳)。
3.5 案例复盘:一次典型的序列化版本不匹配故障
故障现象:某团队在开发机(TensorRT 8.6.1, RTX 3090)上构建引擎并序列化为 yolov8s.engine,部署到边缘设备(TensorRT 8.5.3, Jetson Orin)时,调用 deserializeCudaEngine 返回 nullptr,日志输出:
[E] Deserialization failed: engine version (8.6.1) is newer than
library version (8.5.3). Please rebuild the engine with TensorRT 8.5.3.
排查过程:
- 首先检查文件完整性:
ls -l yolov8s.engine确认文件大小约 23MB,非空文件。 - 使用
strings yolov8s.engine | grep -i version查看可打印字符串,发现包含8.6.1字样。 - 对比两端环境:开发机 TensorRT 8.6.1 + CUDA 12.0;边缘设备 TensorRT 8.5.3 + CUDA 11.4。
- 确认问题根因:TensorRT 序列化格式向后不兼容——高版本构建的引擎无法在低版本库中加载。
解决方案:在边缘设备上重新执行构建流程(或使用与部署环境一致的 Docker 镜像构建),生成 TensorRT 8.5.3 版本的引擎文件。同时修改 CI 流程,将构建环境锁定为 nvcr.io/nvidia/tensorrt:23.08-py3 镜像,确保版本一致性。
kFP16 而部署环境 GPU 不支持 FP16(极少数情况),也会导致加载失败。