4. TensorRT引擎构建基础
TensorRT 的推理部署并不是“加载模型直接跑”这么简单。它本质上是一个离线编译 + 在线执行的两阶段体系:先把训练框架导出的模型(ONNX、Caffe、UFF 等)翻译成 TensorRT 自己的计算图,再针对目标 GPU 架构做层融合、精度校准、内核自动调优,最终生成一个序列化的 Engine 文件;运行时再反序列化出 Engine,创建 Context 执行推理。理解这条链路上的核心对象,是写出可维护、可复现部署代码的前提。
一、六个核心对象的职责边界
TensorRT 的 C++/Python API 中,构建阶段和执行阶段各有一组对象,它们的关系可以用一句话概括:Builder 用 Network 描述计算图,用 Config 规定优化策略,产出 Engine;Engine 在运行时派生出 Context 承载实际推理状态。
- Builder(IBuilder):构建阶段的“总调度”。它负责创建 Network、创建 BuilderConfig、调用
buildSerializedNetwork()或buildEngineWithConfig()触发优化与序列化。Builder 本身不保存模型信息,只提供工厂能力。 - Network(INetworkDefinition):模型的计算图表示。它由 Tensor(张量)和 Layer(层)组成,记录每个层的类型、输入输出张量、维度、数据类型。Parser 解析 ONNX 后,结果就写进 Network。
- Parser(如 ONNXParser):模型格式的“翻译器”。它读取 ONNX 文件,把 protobuf 描述的算子图映射为 Network 中的 Layer 与 Tensor,并报告不支持的算子。
- Config(IBuilderConfig):构建的“策略面板”。它决定精度模式(FP32/FP16/INT8)、workspace 显存上限、是否启用 TF32、INT8 校准器、profiling 粒度、硬件兼容性等。同一个 Network 配不同 Config,会得到性能差异明显的 Engine。
- Engine(ICudaEngine):构建的最终产物,是针对特定 GPU 架构和 TensorRT 版本优化后的可执行计划。它包含融合后的层、选定的内核、张量内存布局,可序列化为
.plan文件持久化。 - Context(IExecutionContext):Engine 的“运行实例”。它持有推理所需的中间激活内存、绑定(binding)地址、流(stream)等状态。一个 Engine 可以创建多个 Context 并发执行,但每个 Context 需要独立的显存缓冲。
关键区分:Engine 是只读的、可共享的优化产物;Context 是有状态的、不可共享的执行上下文。多线程推理时,应共享 Engine、每线程一个 Context。
二、构建流程的对象流转
下面这张图展示了从 ONNX 文件到推理输出的完整对象流转关系,注意区分“构建期”和“运行期”两个阶段,以及序列化文件在其中的桥梁作用。
三、最小可运行构建流程
下面给出一段 Python 最小示例,目标是把一个 ONNX 分类模型构建为 FP16 Engine 并序列化。示例参数为教学假设值,实际使用时需按模型和 GPU 调整。
import tensorrt as trt
logger = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(logger)
# 1. 创建 Network,显式指定 batch 维度
network = builder.create_network(
1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
# 2. 创建 Parser 并解析 ONNX
parser = trt.OnnxParser(network, logger)
with open("resnet18.onnx", "rb") as f:
if not parser.parse(f.read()):
for i in range(parser.num_errors):
print(parser.get_error(i))
raise RuntimeError("ONNX 解析失败")
# 3. 创建 Config,设置精度与 workspace
config = builder.create_builder_config()
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30) # 1 GiB
if builder.platform_has_fast_fp16:
config.set_flag(trt.BuilderFlag.FP16)
# 4. 构建并序列化 Engine
serialized = builder.build_serialized_network(network, config)
with open("resnet18.plan", "wb") as f:
f.write(serialized)
print("Engine 构建完成,大小:", len(serialized) / 1024 / 1024, "MB")
运行这段代码后,如果终端没有报错并打印出 Engine 大小,说明构建成功。此时 resnet18.plan 已经是一个与当前 GPU 架构绑定的优化产物。接下来在运行期加载它:
runtime = trt.Runtime(logger)
with open("resnet18.plan", "rb") as f:
engine = runtime.deserialize_cuda_engine(f.read())
context = engine.create_execution_context()
# 查看绑定张量
for i in range(engine.num_io_tensors):
name = engine.get_tensor_name(i)
mode = engine.get_tensor_mode(name)
shape = engine.get_tensor_shape(name)
dtype = engine.get_tensor_dtype(name)
print(name, mode, shape, dtype)
四、构建结果如何判断
构建是否“成功”不能只看有没有抛异常,还要关注几个可观察信号:
- Parser 错误数:
parser.num_errors必须为 0。若 ONNX 含 TensorRT 不支持的算子(如某些自定义算子),解析会失败,需要先做算子替换或插件实现。 - Engine 大小与层数:构建后可用
engine.num_layers查看融合后的层数。通常远小于原始 ONNX 节点数,说明融合生效。 - 精度标志是否生效:若设置了 FP16 但
builder.platform_has_fast_fp16为 False,则 FP16 不会真正启用,需检查 GPU 计算能力。 - workspace 是否够用:workspace 不足时构建可能失败或回退到较慢的内核。可先给较大值(如 2 GiB)构建成功,再逐步下调观察性能变化。
- 序列化文件可反序列化:用
runtime.deserialize_cuda_engine()能成功加载,且engine.num_io_tensors与预期一致,才算闭环。
Engine 与 GPU 架构、TensorRT 版本强绑定。在 A100 上构建的
.plan 不能直接在 T4 上反序列化使用,必须重新构建。部署时建议在目标机型上构建,或使用 BuilderConfig 的硬件兼容性选项(会牺牲部分性能)。五、对象生命周期与常见误区
构建完成后,builder、network、parser、config 都可以释放,它们不参与推理。运行期真正需要长期持有的是 engine 和 context。常见误区有三个:
- 把 Engine 当模型文件反复构建:每次启动都重新 build 会浪费几十秒到几分钟。正确做法是首次构建后序列化,后续直接反序列化。
- 多线程共享一个 Context:Context 内部有推理状态,多线程同时调用会导致结果错乱或崩溃。应每线程创建独立 Context,并各自分配输入输出显存。
- 忽略 EXPLICIT_BATCH 标志:不设置该标志时,Network 使用隐式 batch 语义,动态 shape 和部分算子行为会受限。现代 TensorRT 建议始终显式启用。