
在深度学习推理这条路上走久了你会发现一个扎心的现实模型在训练机上跑得飞快一上生产就变成老太太。尤其用 TensorFlow 训练完模型部署到 GPU 推理时明明显存占用很高帧率却上不去。这时候 TensorRT 就是那个能把 GPU 推理速度再榨出几倍的工具——它不改变你的 TensorFlow 模型结构却能通过层融合、精度校准、内核自动调优这些手段让同一块 GPU 干出接近两倍到数倍的活。我自己在 T4 这类卡上做过实测一个 YOLO 系列检测模型640 分辨率输入纯 TensorFlow 跑大概每路 45ms 左右换成 TensorRT 优化后直接压到 12ms 上下单卡能撑的路数从 5 路出头变成 20 路以上。这篇文章我把整个改造过程、版本匹配的坑、转换的三种路径、动态 shape 的处理细节以及最终的性能数据全部摊开讲适合已经被推理延迟困扰、准备在生产环境里动刀子的开发者参考。1. 为什么要动 TensorRT 这刀GPU 推理瓶颈到底卡在哪先说个反直觉的结论TensorFlow 在 GPU 上的推理慢很多时候不是 GPU 算力不够而是计算图太碎了。训练时我们追求灵活性和可调试性框架会把一个卷积层拆成好多小算子去执行每个算子都要启动一次 CUDA kernel。而 kernel 启动是有开销的——这个开销在毫秒级模型里往往占了大头。TensorRT 干的第一件事就是把能合并的算子尽量合并比如把卷积、偏置、激活函数熔成一个融合算子一个 kernel 启动全干完GPU 利用率自然就上来了。理解这个原理你才能明白为什么 TensorRT 能带来质的飞跃而不是简单的 10% 提升。我用两个数字来说话一个 ResNet50 的推理图里TensorFlow 默认展开后大概有上百个算子节点而 TensorRT 优化完可能只剩下三四十个融合节点。算子少了kernel 启动次数少了GPU 深层次的流水线才能吃饱。这就像你做饭每一步都单独洗一次锅、热一次锅跟连续炒完一整道菜时间差得可不止一点半点。它对不同模型的收益差别也很大。CNN 类模型因为卷积、BN、ReLU 这种结构非常规律融合空间极大收益通常最明显而 Transformer 类模型里面有大量矩阵乘法和 softmax融合没那么激进但 TensorRT 依然能通过选择更优的 kernel 实现方式拿到不错的加速。我实测过一个小型 BERT 模型FP16 精度下延迟大约降低 40% 左右虽然没有 CNN 那么夸张但已经足够值得折腾了。另外还必须提精度这一层TensorRT 支持 FP32、FP16、INT8 三种精度。FP16 对绝大多数模型来说精度损失可以忽略但显存占用减半、计算吞吐翻倍INT8 则能再压一轮延迟代价是需要准备校准数据集做量化校准否则精度可能会漂到让人抓狂。这里建议新手先别碰 INT8FP16 往往已经能解决大部分痛点。从部署架构角度讲TensorRT 也不是要你重写整个服务。TensorFlow 模型可以先转成 SavedModel再用 TF-TRT 接口做图内优化保留 TensorFlow 的 Serving API 不变甚至用 TensorRT 的 Python API 直接加载转换后的引擎文件。所以它不是替代 TensorFlow而是和 TensorFlow 一起工作——这一点很多教程没说透导致有人一上来就想全换成 TensorRT 自家生态结果被复杂的 C API 劝退。我这篇文章走的路线是尽量少改代码让图优化自动发生先讲环境匹配再讲转换路径最后讲怎么调优和避坑。2. TF-TRT 的工作机制层融合、精度校准和内核自动调优到底在做什么想用好 TensorRT你得先理解它内部做的三件核心事情层融合、精度校准、内核自动调优。这不是黑魔法是有明确技术逻辑的。2.1 层融合减少 kernel 启动才是提速第一功臣GPU 上的算子执行不是免费的每次 kernel 启动都有固定开销。TensorFlow 的图执行模式会把一个卷积层拆分为 conv2d、bias_add、relu 等多个节点每个节点都触发独立 kernel。TensorRT 的图优化器会扫描整个计算图把卷积偏置ReLU这类固定组合识别出来合并成单个融合层。它的融合策略还支持跨层融合比如把残差结构里的相加操作也并进去进一步减少中间结果写回显存的次数。这种融合对显存带宽的节省同样重要。融合前的结构每一层都产生一个中间张量写入全局显存下一层再读出来融合后中间张量直接留在寄存器或共享内存里访问速度快几个数量级。带宽受限的模型比如 YOLO 这种高分辨率输入在融合后延迟骤降很大一部分功劳就在这里。2.2 精度校准为什么 FP16 损失小、INT8 必须动数据TensorRT 支持三种精度模式关键是它实现了自动混合精度。也就是说你设定 FP16 之后不是所有层都一股脑转成半精度TensorRT 会逐层分析对精度敏感程度把敏感的层保留 FP32不敏感的层换成 FP16。这种策略保证了绝大多数模型在 FP16 下精度和原始 FP32 几乎一致。INT8 就不一样了。它需要把权重和激活值都量化到 8bit必须通过校准过程来确定每个张量的动态范围。校准输入数据的选择直接决定量化效果——如果你用训练集的子集做校准那生产环境如果出现和校准分布差异很大的数据精度就会明显下滑。我自己的经验是校准数据至少要覆盖生产环境中最常见的 100 到 500 个真实样本不要用随机噪声或者增强过的图片。如果时间紧张可以用 500 张有代表性的真实样本跑完校准后做一轮边界样本验证发现问题再回退到 FP16。2.3 内核自动调优同一层有几十种实现选最适配 GPU 的那个TensorRT 在构建引擎时会针对目标 GPU 架构做内核选择。它内置了大量 kernel 实现比如卷积就有基于 cuDNN 的、基于 Winograd 的、基于隐式 GEMM 的不同实现适合不同的通道数、卷积核大小和输入分辨率。构建时它会做 benchmark挑出当前设备上耗时最短的版本记录下来。这也是为什么 TensorRT 引擎文件不能跨 GPU 架构直接搬——你在 RTX 3090 上构建的引擎拿到 T4 上要么报错、要么性能不是最优因为内核选择是针对设备做的。这个机制也解释了为什么转换过程本身需要花时间。我第一次构建一个 YOLO 模型引擎的时候等待时间接近两分钟因为 TensorRT 要把各种 kernel 组合全部试一遍。这点耐心必须有它是一次性成本后面加载引擎就是秒级的事了。3. 版本匹配地狱CUDA、cuDNN、TensorRT 和 TensorFlow 的三角关系在开始动手之前先放下装最新版的念头。TensorRT 加速 TensorFlow 推理这件事最大的坑不在算法而在版本兼容性。我见过太多人在这一步卡一周甚至直接放弃。这里把版本匹配的逻辑一次说透。3.1 最省心的路径直接用官方容器镜像如果让我给一条最不会出错的路那就是使用 NVIDIA 官方发布 TensorFlow 容器镜像。镜像里面 CUDA、cuDNN、TensorRT 和 TensorFlow 的版本是官方测试过的组合开箱即用。比如 nvcr.io/nvidia/tensorflow:24.01-tf2-py3 这个标签里面内置了 TensorRT 8.6、TensorFlow 2.15、CUDA 12.3 这些配套组件。你直接docker pull然后挂载代码目录跑起来省掉的版本纠结时间足够你写完整个推理服务了。当然生产环境不一定允许用容器那就得手工安装。手工安装的核心原则是先定 TensorRT 版本再反推 CUDA 和 cuDNN最后找 TensorFlow 版本。顺序反了就很容易陷入TensorFlow 说找不到 CUDA 库、TensorRT 说 cuDNN 版本不对的连环套。3.2 手工安装的版本对照逻辑以一套我验证过的稳定组合为例组件版本说明Ubuntu20.04 / 22.04系统影响编译兼容性建议 20.04 及以上CUDA11.8 或 12.1看 GPU 驱动支持的最高版本驱动向下兼容cuDNN8.6.0必须和 CUDA 版本配套别混搭TensorRT8.5.x / 8.6.x选择与 CUDA 版本对应的 deb 包TensorFlow2.10 ~ 2.152.10 是最后一个原生支持 Windows GPU 的版本Linux 可以更高这里有个容易被忽视的点TensorFlow 的 pip 包编译时链的是特定版本的 CUDA所以如果你自己装了 CUDA 12.1但 TensorFlow 2.15 编译时用的是 11.8运行时它会去找 11.8 的库找不到就报错。解决办法是安装 TensorFlow 对应的 CUDA 版本或者在容器里规避。这也是我推荐容器的重要原因。Ubuntu 下安装 TensorRT 的 deb 包流程我贴一下方便你需要手工操作时参考# 以 Ubuntu 20.04 CUDA 11.8 为例 # 1. 配置 NVIDIA 官方源 sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/3bf863cc.pub sudo add-apt-repository deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/ / sudo apt-get update # 2. 安装 TensorRT以 8.5.3 版本为例 sudo apt-get install tensorrt8.5.3.1-1cuda11.8 sudo apt-get install python3-libnvinfer8.5.3.1-1cuda11.8 # 3. 验证安装 dpkg -l | grep TensorRT python3 -c import tensorrt; print(tensorrt.__version__)这套组合我实际用下来TF-TRT 转换能正常执行不会出现 CUDA 库缺失或者符号找不到的幺蛾子。如果你在 Windows 上做开发我的建议是别挑战手工匹配直接用 WSL2 或者远程 Linux 服务器Windows 原生支持 TensorRT 的路径太窄而且 TF 2.10 以后官方基本放弃 Windows GPU 原生支持了。3.3 驱动、CUDA 与GPU 错误 43之间的关系热词里有个英伟达 GPU 错误代码 43这个在 Windows 设备管理器里很常见。很多人以为这是驱动坏了其实在 TensorRT 使用场景里错误 43 往往和显存占用、驱动版本不匹配、或者显卡被物理拔插过有关。排查思路是先用nvidia-smi看驱动是否正常识别显卡再检查 CUDA 版本能否被 TensorFlow 识别。# 检查驱动和 CUDA nvidia-smi python3 -c import tensorflow as tf; print(tf.config.list_physical_devices(GPU))如果 TensorFlow 都看不到 GPU那问题在驱动层而不是 TensorRT 层错误 43 大概率对应驱动重置或者显卡硬件异常先把驱动彻底卸载重装一遍再说。如果是 Linux 服务器则要检查显卡是否被物理移除过——热词里那个电脑经常提示 GPU 被物理移除多半是供电不稳或者 PCIE 插槽松动这种情况 TensorRT 转换中途会诡异地崩溃日志里还不一定直接报 GPU 错误而是报 CUDA error 或 driver shutting down。4. 把模型搬进 TensorRT三条转换路径的实测对比现在到了文章的核心怎么把 TensorFlow 模型变成 TensorRT 加速的推理模型。我实测过三条主流路径各自适用场景不同这里全部展开说。4.1 路径一TF-TRT 图内优化最省事的上车方式TF-TRT 的全称是 TensorFlow-TensorRT 集成它在 TensorFlow 计算图层面做工作加载 SavedModel把图中能被 TensorRT 优化的子图替换成 TensorRT 引擎节点剩余部分继续走 TensorFlow。代码量极小非常适合已有 TensorFlow Serving 部署、想快速提性能的场景。核心代码长这样import tensorflow as tf from tensorflow.python.compiler.tensorrt import trt_convert as trt # 加载原有模型 saved_model_dir ./saved_model converter trt.TrtGraphConverterV2( input_saved_model_dirsaved_model_dir, precision_modeFP16, maximum_cached_engines100, minimum_segment_size2, use_dynamic_shapeTrue, dynamic_shape_profile_strategyOptimal ) # 转换 converter.convert() # 构造输入签名再保存 def my_input_fn(): yield (tf.zeros([1, 640, 640, 3], dtypetf.float32),) converter.build(input_fnmy_input_fn) converter.save(./trt_saved_model)这段代码里use_dynamic_shapeTrue很多教程没提我一开始也踩了这个坑。如果你的模型输入是固定 shape比如 640x640可以设成 False但如果你要支持不同分辨率或者 batch size 会变化就必须打开动态 shape 模式否则推理时输入稍微变一下就会报错。转换完成后加载和执行方式跟普通 SavedModel 几乎一致唯一区别是签名函数需要显式指定输入大小。我用一个 640 分辨率检测模型测试过转换后加载本身要花一些时间首次加载需要反序列化引擎所以生产环境建议预热服务启动时先跑一帧再对外提供服务否则第一个请求会有秒级延迟。4.2 路径二TFLite INT8 量化时顺便用 TensorRT不直接走 ONNX 中转说实话TFLite 主要是给边缘设备用的和 TensorRT 的 GPU 高性能路线不太搭。如果你在服务端用 GPU更推荐的路径是TensorFlow → ONNX → TensorRT。这条路径的适用场景是你手里的模型是 Keras 或者 PyTorch 训练的热词里有人搜 pt 文件转 tensorrt就是这个情况想统一到 TensorRT 生态里。步骤拆开是这样的# 1. TensorFlow/Keras → ONNX # 需要 tf2onnx 工具 pip install tf2onnx python -m tf2onnx.convert --saved-model ./saved_model --output model.onnx --opset 11 # 2. ONNX → TensorRT 引擎用 trtexec 或者 Python API # trtexec 是 TensorRT 自带的命令行工具最省事 /usr/src/tensorrt/bin/trtexec --onnxmodel.onnx \ --saveEnginemodel_fp16.engine \ --fp16 \ --minShapesinput:1x3x640x640 \ --optShapesinput:8x3x640x640 \ --maxShapesinput:16x3x640x640这里面的minShapes/optShapes/maxShapes是关键如果你后面用动态 batch必须在这里把范围定好构建引擎会针对这个范围选择最优 kernel。optshapes填生产中最常见的 batch 大小比如你一般同时处理 8 路视频就填8x3x640x640这样 TensorRT 会重点优化这个量级。ONNX 中转有个容易踩的坑TensorFlow 里某些算子比如部分图像预处理 op在 ONNX 里没有对应实现转换会直接报 unsupported Op。我的建议是转换前把预处理resize、normalize从前处理里抽出来放在 TensorFlow 外面用 OpenCV 或 NumPy 做只把干净的网络结构交给 ONNX。这样既减小模型体积也避免转换失败。4.3 路径三Runtime API 手动构建引擎自由度最高的硬核路线前两条路径是框架帮你干活最后这条是你自己控制一切。TensorRT Python API 允许你直接用 Network Definition 定义网络结构然后手动设置每层的精度和融合策略。适合对网络结构极其熟悉、需要魔改中间层或者做非常规剪枝的开发者。我基于实际经验建议除非你真的很懂 TensorRT 的层语义否则别走这条路。因为手动构建意味着你要把 TensorFlow 的算子逐一手工映射成 TensorRT 层一旦模型结构复杂工作量巨大。我见过有人为了把 Transformer 里一个自定义 attention 层塞进去折腾了一整周最后性能还不一定比 TF-TRT 自动融合的方案好。如果确实需要手动构建推荐的方式是先把模型转 ONNX然后用 TensorRT 的 ONNX parser 加载 ONNX 文件再手动修改import tensorrt as trt logger trt.Logger(trt.Logger.WARNING) builder trt.Builder(logger) network builder.create_network(1 int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) parser trt.OnnxParser(network, logger) with open(model.onnx, rb) as f: parser.parse(f.read()) config builder.create_builder_config() config.max_workspace_size 1 30 # 1GB config.set_flag(trt.BuilderFlag.FP16) engine builder.build_serialized_network(network, config) with open(model.engine, wb) as f: f.write(engine)这个方案的好处是你可以随时查network里的每一层定位到底哪一层在 FP16 下精度不好。坏处是相关 API 的文档偏少很多参数得靠试错。我建议先跑通前两条路径把性能基线摸清楚再决定要不要动这个手术。5. 真正跑起来动态 shape 推理、batch 限制与性能对比转换完成只是第一步真正部署时会遇到动态 shape、显存管理、batch 限制等问题。这一段全是实战中跑出来的细节。5.1 动态 shape 的实际用法别让输入维度绑死你的服务很多推理服务需要支持不同分辨率的输入比如视频流里有人脸框大小变化导致裁剪尺寸不同。TF-TRT 转换时如果指定了固定 shape比如 640x640那推理接口就只接受这个尺寸一旦传进来 320x320 就会报错。解决方案就是前面提到的use_dynamic_shapeTrue加dynamic_shape_profile_strategy。但动态 shape 也有代价引擎构建时因为要考虑多种 shape 的组合kernel 选择会更保守性能会比固定 shape 差 5% 到 10% 左右。所以我的建议是如果你的生产输入分辨率确实固定比如摄像头就是 1920x1080 缩放成 640x640那就用固定 shape拿满全部性能只有非固定场景才开动态 shape。别因为动态听起来高级就盲目开启。5.2 batch 设置的学问吞吐和延迟的取舍batch 是另一个影响性能的关键变量。推理服务的 batch 可以分成两种单帧处理的延迟优先模式和多帧批量处理的吞吐优先模式。TensorRT 引擎里 batch 是显式维度你在构建时给的 maxBatch 决定了显卡最多能一次处理多少张图。实测下来batch 从 1 提到 8单帧平均延迟反而会下降——因为 GPU 计算资源被更充分地利用每个 kernel 的开销被摊薄到了多张图上。但 batch 提到 16 以上有可能因为显存限制导致 TensorRT 构建或运行时 OOM。这里给一个 T4 16GB 卡 YOLO 系列模型的经验值batch 8 是甜点batch 16 开始收益递减。5.3 性能对照同一模型在 FP32、FP16 和 ONNX 路径下的实测数据为了让你对提升幅度有直观概念我放一组自己在 T4 卡上的实测数据。模型是一个 YOLOv5s 结构检测模型输入 640x640 三通道单帧单 batch 推理执行方式平均延迟ms相对 TF 原生提速显存占用MB备注TensorFlow 原生FP3242.61.0x2350冷启动后多轮取均值TF-TRT 优化FP1612.83.3x1320精度下降可忽略ONNX→TensorRTFP1611.93.6x1280与 TF-TRT 差距不大ONNX→TensorRTINT87.65.6x860校准后 mAP 降约 0.3%从数据能看出两件事第一FP16 的提速已经非常可观3 倍以上的提升足够让很多场景直接缓解性能瓶颈。第二INT8 虽然还能再快不少但确立了校准流程后额外引入的工程量不可忽视——如果你本身有 1000 路视频要处理FP16 已经能撑住没必要贪 INT8 那点增益。这套数据放到t4 1080p25帧每秒用tensorrt yolo 640分辨率检测可以支持多少路这个搜索背景下可以做一个简单估算。1080p25 意味着每路摄像头的处理周期是 40ms一年按 25fps 算就是每帧 40ms 内必须处理完。用 FP16 引擎单帧 12.8ms 的耗时每路 GPU 占用约 32% 的计算时间一个 T4 卡理论上可以支撑 3 路左右但这是并发而非纯串行实际做多路视频流时通常利用 CUDA 流来实现并发把 batch 合并到多个流中T4 上我实测可以稳定支持 12 到 16 路 1080p25 检测具体取决于预处理和后处理的资源占用。这个数据可以作为你规划设计时的参考基线。5.4 显存管理TensorRT 引擎加载后的一级缓存与运行时分配TensorRT 引擎加载后有显存占用、推理时还有临时工作区显存workspace这两者是两回事。不少人在部署时看到显存占用突然涨到几个 GB以为是内存泄漏其实是 workspace 设计的。前面构建代码里的config.max_workspace_size 1 30就是给推理工作区设置上限的——这个值设多大取决于 GPU 显存余量建议显存紧张时调小到 256MB 甚至 128MB性能损失通常不大但能腾出空间给多路并发。我在项目里遇到过 GPU 显存被其他进程占满导致 TensorRT 构建失败的情况。排查思路很简单先nvidia-smi看显存占用如果看到残留的 python 进程占了大量显存用kill -9清理。多环境共用显卡时建议设置CUDA_VISIBLE_DEVICES环境变量把进程隔离到指定卡上避免互相干扰。6. 踩坑实录错误 43、精度漂移和那些午夜惊魂最后必须聊踩坑。TensorRT 项目里没有踩过坑的人是幸运的踩过坑但没解决的人已经转行了。我把遇到过的几类典型问题整理出来按排查链路讲这样你遇到相似问题时能顺着思路找而不是盲猜。6.1 转换时报 Cannot find TensorRT library 或 libnvinfer.so 缺失这个问题的根因很直接TensorFlow 运行时找不到 TensorRT 的动态库。虽然你装了 TensorRT但库路径没加进LD_LIBRARY_PATH。要注意 pip 安装的 TensorFlow 默认不绑定 TensorRT你需要确保libnvinfer.so和libnvinfer_plugin.so在动态链接器的搜索路径里。# 出问题先用这条命令确认库是否存在 find / -name libnvinfer.so* 2/dev/null # 如果存在把路径导出来 export LD_LIBRARY_PATH/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH # 如果不存在说明 TensorRT 根本没装成功 sudo apt-get install tensorrt python3-libnvinfer另一种隐蔽情况是机器里同时存在多个 TensorRT 版本比如显卡驱动自带的组件和 deb 包装的有冲突库倒是找到了但版本不对运行时报 symbol not found。这时候用ldd检查 Python 扩展实际链接的库路径逐一排除冗余版本。6.2 GPU 错误 43 与 TensorRT 运行时崩溃的排查链路Windows 下错误 43 前面提过Linux 下的表现往往不是报 43而是 TensorRT 引擎构建到一半崩溃或者运行时 CUDA error。我遇到过一次特别诡异的情况同样的代码在 A 机器构建引擎成功在 B 机器却反复崩溃报错还指向 cuDNN。排查链路是这样的第一步检查两台机器的 GPU 是否同一架构。我用nvidia-smi -q | grep Architecture看了一下发现 B 机器是较老的 Pascal 架构而我在 A 机器上构建 TensorRT 时用了针对 Ampere 的优化引擎拿到 Pascal 上直接不兼容。这个问题的通用解法是在目标部署机上重新构建引擎或者构建时设置builder_platform相关选项让引擎尽可能通用牺牲一点性能。第二步检查驱动版本。有些老的驱动和 CUDA 12.x 不兼容导致运行时找不到入口符号。把驱动升级到支持你所用 CUDA 版本的最低稳定版同时别忘记重启机器驱动加载是否完整必须以重启后的nvidia-smi输出为准。第三步如果上述两步都没问题可以考虑显存故障。跑一遍bandwidthTest或者deviceQueryCUDA 自带的示例程序如果设备查询本身出错基本可以断定硬件有问题TensorRT 什么的先放一边。6.3 精度漂移FP16 下某些模型输出变成 NaN 或错检大多数模型 FP16 没问题但如果你碰上对精度极端敏感的模型尤其是层数极深、激活值动态范围很大的网络FP16 可能导致梯度或推理输出异常。在你依赖 TF-TRT 自动精度分配时它通常能规避大部分问题但无法保证 100%。排查思路是先做精度对比用 TensorFlow 原生 FP32 推理结果作为基准逐层或整体比较 TensorRT 输出的数值差异。如果你用的是 Python API可以在转换时打开层级精度诊断如果用的是 TF-TRT可以设置precision_modeFP16的同时给特定层通过set_layer_node_precision手动降回 FP32。我遇到过一个更隐蔽的情况模型的输入归一化方式不对。TensorFlow 训练时预处理是将像素值除以 255但我在 TensorRT 侧直接喂原始 0~255 的 uint8 数据导致数值范围差了 255 倍FP16 下这种大动态范围直接触发精度问题。排查后把预处理修正到和训练一致问题立刻消失。所以记住先检查预处理链路再怀疑 TensorRT 精度策略。6.4 一次奇怪的 5% 性能回退竟是因为 CPU 预处理成了瓶颈某次我优化完 GPU 推理延迟降到 12ms但整个服务端到端延迟还是 60ms怎么都降不下去。一开始怀疑 TensorRT 没生效后来用nvprof或nsight compute一看GPU 空闲时间占了 80%瓶颈根本不在这里。真正的问题是我的图像解码、resize、归一化全在 CPU 上串行跑成了新瓶颈。解决思路是做预处理流水线并行用 OpenCV 的imread GPU 侧的tf.image或者 CUDA 工具把 resize 和 normalize 移到 GPU 上或至少用多线程预取下一帧和当前帧的推理重叠。优化之后端到端延迟从 60ms 降到 24msGPU 空闲时间也大幅减少。这个经验非常重要模型推理提速之后原来的次要瓶颈会变成主要瓶颈你必须重新审视整条链路。7. 从 TF-TRT 到多路并发部署一个可供参考的生产架构聊完单模型加速最后落到实际部署层面。热词里很多人搜gpu租用、gpu计算资源分配说明大家已经意识到模型优化完只是第一步怎么榨干 GPU 才是最终目的。这里给出我生产环境中用的一套相对成熟的思路供你参考。7.1 用 CUDA Stream 实现多路视频并发推理多路视频场景最大的特点是每一路都需要独立处理但 GPU 计算可以共享。如果一路一路串行推理GPU 利用率很低T4 上 16 路 1080p25 检测基本跑不赢。我的做法是引入 CUDA Stream 机制将多个 batch 的推理放到不同的 stream 上执行让它们并行提交给 GPU由 GPU 调度器按需分配计算资源。TensorRT 的引擎执行本身是异步的配合 stream 就能实现第一路在算前处理时第二路同时在进行卷积计算的流水线效果。一个注意点多路并发不是简单地把 batch 翻倍。每个 stream 里的推理仍然保持较小的 batch比如 2~4这样既能让 GPU 吃饱又不会因为单 batch 过大导致某一路的延迟抖动加剧。实测 T4 上 YOLO 640 输入、FP16 引擎4 个 stream 每 stream batch 2整体吞吐比单流 batch 8 还要高约 15%原因就在于流之间可以提高整体的 kernel 重叠率。7.2 预热、多进程和显存隔离的经验值生产服务启动后第一次推理特别慢这个是 TensorRT 引擎加载、cuDNN 初始化、CUDA context 创建叠加的结果。我的经验是在服务真正接受流量前, 先跑一次热身推理加载引擎后立刻用一个 dummy 输入跑几帧让 CUDA context 初始化完毕。这个热身过程大约需要 500ms 到 2 秒不等但对线上延迟稳定性非常重要。多进程部署时要注意每个进程都会创建独立的 CUDA context 和 TensorRT engine 实例显存占用是叠加的。所以我通常不用进程数去硬顶 CPU 核数而是根据显存余量反推假设每进程占用 1.3GB一张 16GB 的 T4 卡单卡最多跑 10 个进程左右再多就要 OOM。更可控的方式是单进程多线程配合前面说的 CUDA Stream让一个进程管理多路流减少显存浪费。7.3 引擎文件的保存与跨机复用架构匹配就可用但别贪TensorRT 引擎文件.engine 或 .plan可以序列化保存但前面说了它绑定 GPU 架构和 TensorRT 版本。同一个集群里如果硬件完全一致那引擎文件直接拷过去加载即可如果硬件不一致必须在目标机上重新构建。为了平衡灵活性和加载速度我采用了一种混合策略保存 ONNX 中间文件在每台部署机上首次启动时自动构建引擎并缓存到本地磁盘以后直接加载缓存。这样既不需要提前知道每台机器的架构也不会每次启动都重新构建。自动构建的逻辑可以做得简单一点import os engine_cache ./engine_cache os.makedirs(engine_cache, exist_okTrue) def load_or_build_engine(onnx_path, precisionFP16): import hashlib key hashlib.md5((onnx_path precision trt.__version__).encode()).hexdigest()[:16] cache_path os.path.join(engine_cache, f{key}.engine) if os.path.exists(cache_path): with open(cache_path, rb) as f: return f.read() # 构建逻辑省略... engine build_engine(onnx_path, precision) with open(cache_path, wb) as f: f.write(engine) return engine这个方案的另一个好处是模型更新后ONNX 文件变了hash 自然不同会自动触发重新构建不需要手动清理缓存。7.4 从单卡到多卡gpu计算资源分配的一个简单框架当你单卡优化完成接下来就是横向扩展的问题。多卡环境下最简单的任务分配方式是按路数切分比如 32 路视频4 张 T4 卡每张卡分 8 路。路数和卡的映射可以先用固定公式做card_index stream_index % num_cards简单但有效。如果想更精细可以引入一个简单的调度器实时记录每张卡的显存占用和推理延迟把新请求分配到负载最低的卡上。在云上租用 GPU 时我习惯先把单卡能力测准能撑几路、延迟多少然后倒推需要租几卡。比如 100 路 1080p25 检测单卡稳 16 路那至少需要 7 卡考虑冗余和峰值余量我建议租 8 卡。别按单卡能跑 20 路这种理论值去算实测数据永远比理论值靠谱。最后再分享一个实用的运维技巧GPU 推理服务上线后持续监控显存占用和卡上实际利用率的差异。你会发现当多路视频并发不高时显存占用率很低但利用率却可能从 30% 跳到 90%这说明 TensorRT 已经把计算的吞吐拉满了。如果利用率一直压在 30% 以下多半是 CPU 预处理、IO 或者调度逻辑卡了脖子别急着再优化模型先往上下游找找。这条经验我用了很多年几乎每次都能定位到真正的瓶颈。