TensorRT 怎么用:把推理延迟压下来的实际做法
从建立可重复的测量基线入手,说明 TensorRT 用批次大小、CUDA Graph、opportunistic batching、构建路线搜索与动态 shape 降低推理延迟的实际做法,以及精度、构建时间与可维护性的代价。
TensorRT 的调优重点并不是套用某个固定参数,而是先建立可重复的测量基线,再逐项验证批次、CUDA Graph、动态 shape 与构建路线。NVIDIA TensorRT 官方性能最佳实践明确指出:没有稳定的测量基线,每次优化都都是猜测。
先建立可重复的测量基线
在调整 TensorRT 前,应固定模型、精度模式、输入 shape、批次策略和请求到达方式,记录端到端延迟与吞吐。后续每轮只改变待验证变量,再回到相同基线比较。CPU 主机耗时、端到端延迟和系统吞吐也应分开观察,因为某个环节的优化并不必然改善其他指标。
TensorRT 官方的 Performance Best Practices 将 batching、CUDA Graph、跨推理多流、层融合、Q/DQ 融合、Tensor Core 对齐,以及 timing cache 和 builder 优化级别列为主要方向。它们是候选项,不是无需测量即可叠加的性能保证。
批次大小:吞吐经验需要实测修正
NVIDIA TensorRT 性能优化文档 给出的经验是:在硬件支持 Tensor Core 时,FP16、INT8 推理采用 32 的倍数作为批次大小,往往能获得较好性能。原因与 Tensor Core 利用率有关,而不是 32 本身构成通用最优值。
该文档同时给出了重要修正:在 Ada Lovelace 及更新 GPU 上,如果较小批次有助于输入和输出值留在 L2 缓存中,减小 batch size 可能显著提高吞吐。因此,32 的倍数只能作为 FP16、INT8 的起始测试点,仍需用实际模型和请求分布比较不同批次。
批次调整还会改变延迟与吞吐的平衡。扩大批次可以提高设备利用率,但会增加单批推理工作量;缩小批次可能改善缓存行为,却未必降低完整请求延迟。判断标准应是相同基线下的端到端测量,而不是仅观察 kernel 数量或 GPU 忙碌程度。
CUDA Graph:减少的是主机启动开销
普通执行中,CPU 需要启动推理展开的每个 CUDA kernel,每次启动大约消耗 5–15 微秒主机时间。这是主机侧开销,不是 kernel 执行时间,也不是完整请求延迟。
CUDA Graph 会把整段 kernel 序列折叠成一个可启动对象。按 TensorRT 官方表述,主机在捕获时支付相应的单次启动成本,后续推理复用已记录的 graph。它减少的是重复启动开销,并不表示整段计算只剩一个 kernel,也不意味着 GPU 工作本身消失。
enqueueV3() 支持 CUDA Graph 捕获,但前提是模型执行过程中不需要 CPU 交互。循环、条件分支以及依赖数据确定 shape 的层,典型地不属于可直接捕获的算子。存在这些结构时,cudaStreamEndCapture() 会返回 cudaErrorStreamCapture* 错误。
因此,出现捕获错误时,应检查模型是否包含动态控制流或数据相关 shape,而不只是重复提交捕获。CUDA Graph 适合控制流稳定的执行路径,不应被视为对任意模型都能透明启用的开关。
Opportunistic batching:用等待时间换吞吐
Opportunistic batching 的机制很直接:每个到达的请求等待时间 T;期间若有其他请求,就将它们组成批次;如果没有其他请求,则执行单请求推理。
TensorRT 官方将其描述为用额外固定延迟换取更高的系统最大吞吐。这里的 T 是服务策略,不是模型参数。等待窗口过短,可能组不成有效批次;等待窗口过长,则请求在进入推理前已经消耗了更多时间。是否启用、怎样设置 T,都应通过实际请求流量验证,不能仅凭空闲 GPU 比例决定。
构建路线搜索:先估算,再校验精度
Global Performance Tuner 可以搜索候选路线,但完整 sweep 可能消耗较多构建时间。官方提供了如下命令行配置接口:
--dryRun
--loadRefOutputs
--accuracyThreshold=<value>
--accuracyAlgorithm=<spec>
--tuningTimeOut=<seconds>
任意搜索模式都可以配合 --dryRun:它只打印完整路线列表,不构建 engine,用于估算 sweep 规模。确认规模后再启动构建,比直接进入长时间搜索更容易控制时间。
加载参考输出后,--accuracyThreshold 为必需配置。--accuracyAlgorithm 决定损失计算方式,其结果必须非负,且越低越好。损失超过阈值的路线会被排除在最佳引擎选择之外。未通过精度校验的迭代仍会记录在 tuning cache 中,但没有资格成为最佳引擎候选。
--tuningTimeOut 约束的是整个调优过程,而不是单条路线。官方 Global Performance Tuning 还把 timing cache 和 builder 优化级别列为压缩构建时间的手段。路线搜索、精度校验与时间预算需要同时考虑:扩大搜索范围意味着更多构建投入,精度阈值过严则可能排除可用路线,构建预算过短也可能使搜索尚未完成。
动态 shape:先明确维度的官方语义
构建网络时,输入 tensor 需要运行时确定的维度应写为 -1。同名维度被隐式视为相等,这既有助于优化器生成更高效 engine,也能在运行时暴露维度不匹配问题。
运行时可使用 setInputShape 明确输入 shape。其返回值需要按官方边界理解:对输入而言,该值只表示所设 shape 与对应 optimization profile 一致,不能把它扩写为端到端执行结果或输出正确性的证明。
数据相关维度也会体现为 -1。如果维度的取值依赖 INonZeroLayer 等算子的输入,outDims.d[k] 将返回 -1。这类不确定性会影响 shape 规划,也可能使相关层无法进入 CUDA Graph 捕获路径。动态 shape 提供了运行时灵活性,但维度约束、profile 与捕获兼容性仍需维护。
与 vLLM 通用服务路线对照
vLLM 官方 Engine Arguments 中的 --cpu-offload-gb、--device-memory-utilization、--kv-cache-memory-bytes 属于通用推理服务的资源配置。vLLM 默认运行现有模型执行后端,并不会因为设置这些参数就编译 TensorRT engine。
这条通用服务路线省去了自定义 engine 的路线搜索、精度校验和构建维护,但优化范围受现有后端约束。若业务需要直接调整 TensorRT 构建路线、shape 约束和 graph 捕获,则要承担构建时间、精度回归、运行时配置同步及版本维护成本。
实际调优应沿用同一条基线:先验证 shape 与批次,再检查 CUDA Graph 的捕获前提,最后评估构建路线。提高吞吐的批次策略可能增加等待,降低启动开销也不等于端到端延迟按相同幅度下降。TensorRT 官方没有给出跨模型、跨硬件的统一提升值,最终结论应来自当前业务负载下可复现的测量结果。