构建四件套:Builder + Network + Parser + Config → Engine
推理三件套:Runtime + Engine + Context → 结果
构建阶段(离线):
bash
auto builder = createInferBuilder(logger);
auto network = builder->createNetworkV2(...);
auto parser = createParser(*network, logger);
parser->parseFromFile("model.onnx", ...);
auto config = builder->createBuilderConfig();
config->setFlag(kFP16);
config->setMaxWorkspaceSize(1_GiB);
auto engine = builder->buildSerializedNetwork(*network, *config);
saveToFile(engine, "model.engine");
推理阶段(在线):
bash
auto runtime = createInferRuntime(logger);
auto engine = runtime->deserializeCudaEngine(data, size);
auto context = engine->createExecutionContext(); // 每线程一个
// 分配 buffer → memcpy in → execute → memcpy out
TensorRT 的执行流程是高性能推理的核心。下面我将以 C++ 中解析 ONNX 模型 → 生成 Engine → 加载并推理 为例,完整、清晰地解释 TensorRT 的标准工作流程,包括每个阶段的作用和创建方式。
🧩 整体流程概览(两阶段)
TensorRT 推理分为两个独立阶段:
| 阶段 | 目的 | 是否必须每次运行? | 输出 |
|---|---|---|---|
| 1. 构建阶段(Build Phase) | 将 ONNX 转为优化后的 TensorRT Engine | ❌ 只需一次(可离线) | .engine 文件 |
| 2. 推理阶段(Inference Phase) | 加载 Engine,执行前向计算 | ✅ 每次推理都要 | 推理结果 |
💡 最佳实践:构建一次,推理多次(Engine 可序列化保存,避免重复构建)
🔧 第一阶段:构建 Engine(从 ONNX 到 .engine)
步骤 1:创建 Builder
cpp
auto builder = std::unique_ptr<nvinfer1::IBuilder>(
nvinfer1::createInferBuilder(logger)
);
- 作用:TensorRT 的"总工程师",负责创建网络和配置优化。
- 参数 :需要一个
ILogger(日志回调)。
步骤 2:创建 Network Definition
cpp
const auto explicitBatch = 1U << static_cast<uint32_t>(
nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH
);
auto network = std::unique_ptr<nvinfer1::INetworkDefinition>(
builder->createNetworkV2(explicitBatch)
);
- 作用:定义计算图(节点、输入输出、算子)。
- 注意 :ONNX 使用显式 batch,必须开启
kEXPLICIT_BATCH。 - 💡 几乎所有现代模型(ONNX/TensorFlow/PyTorch 导出)都必须使用 kEXPLICIT_BATCH!
- 告诉 TensorRT:batch 维度是张量 shape 的一部分(即 shape 形如 [N, C, H, W])
这是 ONNX、PyTorch 导出模型的标准格式
步骤 3:创建 Parser 并解析 ONNX
cpp
auto parser = std::unique_ptr<nvonnxparser::IParser>(
nvonnxparser::createParser(*network, logger)
);
parser->parseFromFile("model.onnx",
static_cast<int>(nvinfer1::ILogger::Severity::kWARNING)
);
- 作用 :将 ONNX 文件解析成 TensorRT 的
INetworkDefinition。 - 依赖库 :需要链接
nvonnxparser。
步骤 4:配置 Builder Config
cpp
auto config = std::unique_ptr<nvinfer1::IBuilderConfig>(
builder->createBuilderConfig()
);
config->setMaxWorkspaceSize(1 << 30); // 1GB
config->setFlag(nvinfer1::BuilderFlag::kFP16); // 启用 FP16
- 作用 :设置优化策略:
- 工作空间大小(影响 kernel 选择)
- 精度模式(FP32 / FP16 / INT8)
- 动态 shape(如果需要)
动态batch推理(看需求设置)
bash
auto profile = builder->createOptimizationProfile();
profile->setDimensions("input", OptProfileSelector::kMIN, Dims4{1, 3, 640, 640});
profile->setDimensions("input", OptProfileSelector::kOPT, Dims4{4, 3, 640, 640});
profile->setDimensions("input", OptProfileSelector::kMAX, Dims4{16, 3, 640, 640});
config->addOptimizationProfile(profile);
config->setFlag(nvinfer1::BuilderFlag::kFP16);
步骤 5:构建 Engine
cpp
auto plan = std::unique_ptr<nvinfer1::IHostMemory>(
builder->buildSerializedNetwork(*network, *config)
);
- 作用:执行图优化(层融合、精度校准、内核选择等),生成可执行计划。
- 输出 :
IHostMemory是序列化的 Engine(二进制 blob)。
步骤 6:保存 Engine 到文件(可选但推荐)
cpp
std::ofstream engineFile("model.engine", std::ios::binary);
engineFile.write(static_cast<char*>(plan->data()), plan->size());
engineFile.close();
- 好处:下次直接加载,省去构建时间(构建可能耗时几十秒)。
▶️ 第二阶段:加载 Engine 并推理
步骤 1:读取 Engine 文件
cpp
std::ifstream file("model.engine", std::ios::binary);
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> engineData(size);
file.read(engineData.data(), size);
file.close();
步骤 2:创建 Runtime 并反序列化 Engine
cpp
auto runtime = std::unique_ptr<nvinfer1::IRuntime>(
nvinfer1::createInferRuntime(logger)
);
auto engine = std::unique_ptr<nvinfer1::ICudaEngine>(
runtime->deserializeCudaEngine(engineData.data(), size)
);
- Runtime:负责将序列化 Engine 加载到 GPU。
- ICudaEngine:优化后的可执行模型(只读,线程安全)。
步骤 3:创建 Execution Context
cpp
auto context = std::unique_ptr<nvinfer1::IExecutionContext>(
engine->createExecutionContext()
);
- 作用:持有推理时的状态(如中间张量内存)。
- 注意 :每个线程应有自己的
IExecutionContext(非线程安全)。
步骤 4:准备输入/输出内存(GPU + CPU)
获取输入/输出绑定信息
cpp
int inputIndex = engine->getBindingIndex("input"); // 绑定名来自 ONNX
int outputIndex = engine->getBindingIndex("output");
auto inputDims = context->getBindingDimensions(inputIndex);
auto outputDims = context->getBindingDimensions(outputIndex);
分配 GPU 内存
cpp
void* buffers[2];
cudaMalloc(&buffers[inputIndex], volume(inputDims) * sizeof(float));
cudaMalloc(&buffers[outputIndex], volume(outputDims) * sizeof(float));
准备 CPU 输入数据
cpp
std::vector<float> inputData(volume(inputDims));
// ... 填充 inputData ...
cudaMemcpy(buffers[inputIndex], inputData.data(),
volume(inputDims) * sizeof(float), cudaMemcpyHostToDevice);
步骤 5:执行推理
cpp
bool status = context->executeV2(buffers);
if (!status) { /* handle error */ }
executeV2:同步执行推理(输入/输出指针数组)。- 也有异步版本
enqueueV2(配合 CUDA Stream)。
步骤 6:拷贝结果回 CPU
cpp
std::vector<float> outputData(volume(outputDims));
cudaMemcpy(outputData.data(), buffers[outputIndex],
volume(outputDims) * sizeof(float), cudaMemcpyDeviceToHost);
步骤 7:释放资源
cpp
cudaFree(buffers[inputIndex]);
cudaFree(buffers[outputIndex]);
// unique_ptr 会自动释放 engine/context/runtime
📊 流程图总结
[ONNX 文件]
↓
Builder → Network → Parser → 解析 ONNX
↓
BuilderConfig → 设置 FP16 / workspace
↓
buildSerializedNetwork() → 生成序列化 Engine
↓
保存为 .engine 文件(离线完成)
───────────────────────────────
[推理时]
↓
Runtime ← deserializeCudaEngine(.engine)
↓
ExecutionContext ← createExecutionContext()
↓
分配 GPU 输入/输出缓冲区
↓
cudaMemcpy (Host → Device)
↓
executeV2() → 执行推理
↓
cudaMemcpy (Device → Host)
↓
得到结果
⚠️ 关键注意事项
-
Engine 与硬件/驱动/TensorRT 版本绑定
- 在 A100 上生成的 Engine 不能在 Jetson 上用
- 升级驱动或 TRT 版本后需重新构建
-
动态 Shape 支持
- 需在构建时设置
profile,推理时用setBindingDimensions
- 需在构建时设置
-
INT8 量化需要校准
- 额外步骤:提供校准数据集,实现
IInt8Calibrator
- 额外步骤:提供校准数据集,实现
-
多线程推理
- 共享
ICudaEngine(线程安全) - 每个线程独占
IExecutionContext
- 共享
✅ 总结
| 阶段 | 核心对象 | 作用 |
|---|---|---|
| 构建 | IBuilder, INetworkDefinition, IParser |
解析 ONNX,优化图,生成 Engine |
| 推理 | IRuntime, ICudaEngine, IExecutionContext |
加载 Engine,分配内存,执行计算 |
🌟 记住:构建一次,推理千次;Engine 是性能的关键!
通过这种方式,TensorRT 能将 ONNX 模型加速数倍甚至数十倍,广泛应用于生产环境。希望这个详解对你有帮助!