4、TensorRT API入门:创建Builder与Network、配置Builder参数(工作空间、精度)、构建Engine与序列化

好,咱们正式开始动手写代码了。

这一章,我带你走一遍 TensorRT 推理最核心的流程:创建 Builder → 定义 Network → 配置参数 → 构建 Engine → 序列化保存。说白了,就是教会 TensorRT 怎么“看懂”你的模型,然后把它变成一张能飞快运行的“蓝图”。

我个人习惯把这一步叫做“炼丹前的炉灶搭建”。炉灶搭不好,后面火候再大也白搭。

4.1 创建 Builder 与 Network

先上最基础的代码骨架。你想想看,TensorRT 本身是个 C++ 库,但咱们做工程时 Python 用得更多。所以我这里以 Python API 为例,C++ 的接口逻辑完全一样,只是语法不同。

import tensorrt as trt

# 创建 Builder
logger = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(logger)

# 创建 Network
network = builder.create_network(
    1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
)

# 创建配置对象
config = builder.create_builder_config()

嗯,这里要注意几个点。

第一,Logger 的级别。 我刚开始用的时候,图省事设成了 trt.Logger.INFO,结果控制台刷屏一样输出各种信息,找关键错误像大海捞针。后来我学乖了,开发调试用 INFO,正式部署一律用 WARNINGERROR。你想想看,生产环境谁有空看那些“加载了某个算子”的日志?

第二,EXPLICIT_BATCH 这个 flag。 这是 TensorRT 8.x 之后强烈推荐的模式。以前的老模式(隐式 Batch)会把 batch size 硬编码到网络中,换 batch size 就得重新构建 Engine,非常死板。显式 Batch 模式下,batch 维度被当作一个普通维度处理,灵活得多。我曾经在迁移一个老项目时,忘了加这个 flag,结果跑推理时 batch size 一变化就报错,排查了半天……嗯,从那以后我每次创建 Network 都会默念一遍:显式、显式、显式。

4.2 配置 Builder 参数

Builder 建好了,Network 也搭起来了,接下来就是告诉 TensorRT:“嘿,你想怎么优化这个模型?”

配置参数主要就两样:工作空间(Workspace)精度(Precision)

4.2.1 工作空间(Workspace)

工作空间,说白了就是 TensorRT 在构建 Engine 时能用的“临时内存上限”。它决定了 TensorRT 能尝试多少种算子融合策略。空间越大,它越有可能找到更优的融合方案,推理速度可能更快。

# 设置工作空间大小,单位是字节
# 这里设置为 1GB
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30)

我个人习惯给个 1GB 到 2GB。但要注意,这不是说你显卡显存有 8GB 就能设 8GB。构建时的内存消耗和推理时的显存占用是两码事。我曾经在一个边缘设备上(只有 4GB 内存),把工作空间设成了 3GB,结果构建 Engine 时直接 OOM 了。后来我改成 512MB,虽然构建时间长了点,但至少能跑起来。

我的建议: 先设大一点(比如 2GB),看构建能不能通过。如果内存不够,再逐步调小。别一上来就抠抠搜搜的。

4.2.2 精度设置

精度是 TensorRT 加速的核心手段之一。默认是 FP32,但你可以开启 FP16 或 INT8。

# 开启 FP16
if builder.platform_has_fast_fp16:
    config.set_flag(trt.BuilderFlag.FP16)
    print("FP16 已开启,速度起飞!")
else:
    print("你的显卡不支持 FP16,老老实实用 FP32 吧。")

# 开启 INT8(需要校准数据集)
# if builder.platform_has_fast_int8:
#     config.set_flag(trt.BuilderFlag.INT8)
#     # 还需要设置校准器,后面章节会细讲

这里有个坑:platform_has_fast_fp16 这个检查一定要做。我见过有人不管三七二十一直接设 FP16,结果在旧款 Tesla 显卡上跑,速度反而比 FP32 还慢。为什么?因为那些显卡的 FP16 计算单元是模拟的,没有硬件加速。

警告: INT8 精度虽然快,但需要校准数据集。如果你随便设个 INT8 而不提供校准数据,模型精度可能会崩得一塌糊涂。我曾经在一个目标检测模型上试过,没校准直接跑 INT8,结果 mAP 从 0.75 掉到了 0.3……嗯,那画面太美我不敢看。

4.3 构建 Engine 与序列化

参数配好了,Network 也定义完了,接下来就是见证奇迹的时刻——构建 Engine。

4.3.1 构建 Engine

# 序列化模型(构建 Engine)
serialized_engine = builder.build_serialized_network(network, config)

# 或者你也可以先构建 Engine,再序列化
# engine = builder.build_engine(network, config)
# serialized_engine = engine.serialize()

我个人更推荐第一种方式:build_serialized_network。一步到位,直接拿到序列化后的字节流。省去了先构建 Engine 再序列化的中间步骤,代码更简洁。

构建 Engine 这一步,是 TensorRT 最耗时的地方。一个复杂的模型(比如 ResNet-152 或 BERT-Large),构建时间可能长达几分钟甚至十几分钟。你想想看,TensorRT 在这段时间里在干嘛?它在疯狂地尝试各种算子融合、内存复用、层合并……说白了,就是在“编译”你的模型。

4.3.2 序列化与反序列化

构建好的 Engine 可以直接用,但更常见的做法是把它序列化保存到硬盘上。这样下次启动程序时,直接加载序列化文件,省去了重新构建的时间。

# 保存到文件
with open("model.trt", "wb") as f:
    f.write(serialized_engine)

# 加载时反序列化
with open("model.trt", "rb") as f:
    serialized_engine = f.read()

runtime = trt.Runtime(logger)
engine = runtime.deserialize_cuda_engine(serialized_engine)

这里有个小细节:序列化文件是平台相关的。你在 RTX 3090 上构建的 Engine,不能直接拿到 Jetson Nano 上用。因为不同 GPU 的架构不同(比如 Ampere 和 Turing),TensorRT 在构建时会针对特定架构做优化。我曾经犯过这个错,在开发机上构建好 Engine,拷到客户现场的 Tesla T4 上加载,结果直接报错……嗯,从那以后我每次部署都会在目标机器上重新构建一次。

核心要点:
  • Builder 是总指挥,Network 是模型定义,Config 是优化参数。
  • 工作空间设大一点,但别超过物理内存。
  • 精度选择要检查硬件支持,INT8 必须校准。
  • 序列化文件是平台相关的,别跨 GPU 架构使用。

好了,这一章的内容就到这里。你跟着代码走一遍,应该就能跑通一个最简单的 TensorRT 推理流程了。下一章,咱们聊聊怎么把 PyTorch 模型转成 TensorRT 能吃的格式——ONNX。那又是一个充满坑与经验的故事。