2. TensorRT核心机制回顾

在进入多模型推理服务工程之前,必须先把 TensorRT 的运行时对象模型理清楚。很多服务化问题的根源,并不是网络结构或算子本身,而是对“引擎、上下文、执行器、绑定”这几个对象的生命周期与共享关系理解不到位。本节从对象职责、内存绑定、动态形状三个角度回顾核心机制,并给出面向多模型服务的判断依据。

一、四个核心对象的职责边界

Engine(引擎)是序列化后的推理计划在内存中的只读表示,它由 Builder 从网络定义与优化配置生成,包含层融合结果、kernel 选择、精度策略(FP32/FP16/INT8)、工作空间需求以及可用的优化 profile。Engine 一旦构建完成就不可修改,可以序列化到磁盘,也可以被多个执行上下文共享。它的关键属性是“只读、可复用、构建昂贵”。

ExecutionContext(执行上下文)是 Engine 的一次运行实例,持有推理过程中需要的中间激活内存、kernel 状态和流绑定。同一个 Engine 可以创建多个 Context,它们共享权重与 kernel,但各自拥有独立的激活缓冲区。因此 Context 是并发推理的最小单位:单 Context 内部推理是串行的,多 Context 才能实现真正的并行执行。

Executor(执行器)是较新版本 TensorRT 引入的抽象,用于管理一组 Context 与一条 CUDA Stream 的绑定关系,简化了多流调度。在传统 API 中,开发者需要自己维护 stream 与 context 的对应;使用 Executor 后,可以把“一个模型 + 若干并发槽位”封装成一个可调度单元,这对多模型服务尤其友好。

Binding(绑定)描述的是输入输出张量在设备内存中的地址映射。每个 Binding 有一个索引、名称、维度、数据类型和内存指针。推理前必须把所有 Binding 的地址设置好,TensorRT 才能按计划读写。绑定不是数据本身,而是“张量名 → 设备地址”的契约。

TensorRT 运行时对象关系与内存绑定 Engine(只读计划) 层融合 / kernel 选择 精度策略 / 工作空间 优化 profile 集合 可序列化 · 可共享 Context #0 独立激活内存 + stream Context #1 独立激活内存 + stream Context #N 并发槽位 create Bindings input_0 → d_ptr input_1 → d_ptr output_0 → d_ptr output_1 → d_ptr 名称 · 维度 · dtype · 地址 推理前必须全部设置 Executor 管理 Context 与 Stream 封装并发调度单元 多模型服务友好 schedule 动态形状:Optimization Profile 约束 min / opt / max 输入维度落在 profile 区间内 → 复用同一 Engine;超出区间 → 需重新构建或切换 profile setInputShape() 必须在 enqueue 前调用,且与 Binding 维度一致

二、内存绑定与推理调用链

一次完整的推理调用可以拆成四步:准备输入数据到主机内存、拷贝到设备内存、设置 Binding 地址、调用 enqueue 并同步。其中最容易出错的是第三步——Binding 地址必须与 Engine 期望的维度、数据类型、内存对齐一致。如果使用动态形状,还需要在设置地址之前调用 setInputShape() 指定本次推理的实际输入维度。

// 伪代码:单 Context 推理调用链
context->setInputShape("input_0", Dims4{1, 3, 224, 224});
context->setTensorAddress("input_0", d_input);
context->setTensorAddress("output_0", d_output);
context->enqueueV3(stream);
cudaStreamSynchronize(stream);

注意 setTensorAddress 是“设置契约”,不是拷贝数据。真正的数据搬运需要开发者自己用 cudaMemcpyAsync 完成。这意味着在多模型服务中,输入输出缓冲区应当按 Context 独立分配,避免并发推理时互相覆盖。

三、动态形状与 Optimization Profile

动态形状是 TensorRT 支持变长输入的核心机制。构建 Engine 时,需要为每个输入张量指定一个或多个 Optimization Profile,每个 Profile 包含 min、opt、max 三组维度。opt 维度是性能最优的参考点,TensorRT 会围绕它选择 kernel;min 和 max 定义了合法输入范围。

运行时,输入维度必须落在某个 Profile 的 [min, max] 区间内。如果实际输入超出所有 Profile 的范围,推理会失败,必须重新构建 Engine 或增加 Profile。对于多模型服务,建议把同一模型的常见输入尺寸归并到少量 Profile 中,而不是为每个尺寸单独建 Engine,否则显存和构建时间都会迅速膨胀。

实践建议:为每个模型记录一份 Profile 表,包含输入名、min、opt、max、dtype。服务启动时校验请求维度是否落在 Profile 内,超出则直接拒绝或走降级路径,避免运行时崩溃。

四、面向多模型服务的关键判断

把上述机制映射到多模型服务,可以得到几条明确的工程判断:

  • Engine 按模型共享:同一模型的所有请求复用同一个 Engine,避免重复构建。
  • Context 按并发度分配:并发数决定 Context 数量,每个 Context 独立绑定输入输出缓冲区。
  • 显存预算 = 权重 + Context 数 × 激活内存:激活内存随 Context 线性增长,是并发上限的主要约束。
  • 动态形状用 Profile 收敛:把请求维度归并到有限 Profile,减少 Engine 数量与切换开销。
  • Binding 地址按 Context 隔离:禁止多个 Context 共用同一块输入输出缓冲区。