TensorRT加速SlowFast视频理解模型部署实战 简介面向算法部署工程师和视频理解开发者的一份TensorRT实战资源围绕SlowFast网络在NVIDIA GPU上的高性能推理落地解决视频理解模型计算量大、推理延迟高的工程问题适用于智能安防、边缘计算、内容分析等需要实时识别动作的场景。压缩包共24个文件以21个Python脚本为核心覆盖模型导出至ONNX、TensorRT转换、推理执行等完整环节另含YAML推理配置如SLOWFAST_4x16_R50、Markdown说明文档及gitignore辅助文件整体仅46KB结构紧凑便于按需修改和快速迁移。目前已有158人学习下载适合具备一定深度学习基础、希望掌握模型转换与硬件加速全流程的中高级开发者。通过这份工程源码可跟随onnx_to_trt.py、transform_model.py、tensorrt_inference.py等脚本复现从模型转换、硬件适配到部署验证的完整流程同时理解TensorRT层融合、内核自动调优、精度校准等优化策略配合配置与说明文档还能快速搭建运行环境为开发可靠、低延迟的视频理解服务提供可直接参考的工程范式。1. 为什么是 TensorRT SlowFast视频理解算法部署的算力瓶颈与一条可复现的加速链路视频理解算法在安防、行为分析、工业质检里都越来越刚需但真正把它推到线上跑实时推理的人都知道SlowFast 这类双分支模型精度确实好帧率却常常惨不忍睹。慢路径吃空间语义、快路径吃运动信息两条分支叠一起计算量直接翻倍在 CPU 上几乎没法用在 GPU 上用 PyTorch 原模推理也跑不满资源。这个实战项目给的就是一整条现成链路——从 PyTorch 权重导出 ONNX再用 TensorRT 做图优化、层融合和 FP16 加速最终落到一个可调用的 TensorRT 推理引擎。适合手里已经有训练好的 SlowFast 模型、但推理速度撑不住线上延迟要求的从业者也适合准备系统过一遍 TensorRT 部署流程的算法工程师。下面按我实际拆这个项目包的顺序把每一步怎么操作、参数怎么给、坑在哪逐一写清楚。2. 部署前的环境准备版本三角、项目结构与 trtexec 健康检查2.1 先把版本对应关系理清CUDA、cuDNN、TensorRT 的版本三角TensorRT 部署最容易翻车的地方不在模型本身而在环境版本不匹配。TensorRT 不是独立运行的它依赖 CUDA 和 cuDNN三者必须满足 NVIDIA 官方给出的对应关系否则安装时不会报错等到构建引擎或推理时才会冒出各种看不懂的 CUDA error。Windows 10 上装 TensorRT 尤其如此很多人按教程装完 zhigui 后跑 trtexec结果找不到 cudnn64_8.dll就是因为 cuDNN 版本装成了 cuDNN 9.x而 TensorRT 8.x 要求的是 cuDNN 8.x。我一般会在动手前先把三个版本写死到一个文件里避免后续排查时来回猜。一个常见的可行组合是 CUDA 11.8 cuDNN 8.6 TensorRT 8.5 GA 或 8.6 GA这套组合在 Windows 10 和 Linux 下都比较成熟。如果你用的是 Ampere 架构以后的卡TensorRT 8.5 以上对 FP16 和 TF32 的调度也更完善。注意装完 TensorRT 后把 TensorRT 的 lib 路径手动加进系统环境变量 PATH 里。Windows 下默认装在C:\Program Files\TensorRT-8.x.x.x不添加 PATH后面的trtexec.exe和.dll都找不着。2.2 项目结构拆解从 PyTorch 权重到 TRT 引擎的四段管线这个项目的文件结构其实已经把部署链路写得很清楚了核心是四个脚本加一个配置文件checkpoints/ # 训练好的 SlowFast 权重pth 格式 export_model_to_onnx.py # 第一步pth - onnx onnx_to_trt.py # 第二步onnx - trt engine tensorrt_inference.py # 第三步加载 engine 做推理 configs/SLOWFAST_4x16_R50_inference.yaml # 模型结构配置 transform_model.py # 辅助脚本做权重格式整理或预处理对齐四段管线的逻辑是先用 PyTorch 加载训练好的权重把 SlowFast 网络以 ONNX 格式导出检查 ONNX 算子和精度没问题后交给 TensorRT 转换成序列化引擎最后在推理脚本里反序列化引擎绑定显存 buffer执行异步推理。transform_model.py这个文件很多人会忽略但它其实承担了一个关键任务——对齐预处理。SlowFast 的输入不是单帧图片而是一个视频片段组织成的两个张量预处理做的 resize、crop、归一化参数必须和训练时完全一致否则 ONNX 导出和 TRT 推理的精度都会莫名其妙地漂移。2.3 用 trtexec 做环境健康检查验证安装是否真的能用不要一上来就用项目脚本建议先用 TensorRT 自带的trtexec跑一遍环境检查。它同时验证了 TensorRT 安装、CUDA 驱动、算子库加载这三件事。Linux 下命令在/opt/TensorRT-8.x.x.x/bin/trtexecWindows 下在 TensorRT 安装目录的bin文件夹里确认 PATH 配置后直接执行# 查看 TensorRT 版本和构建环境能打印出版本说明安装基本到位 trtexec --version # 用一个极小的 ONNX 模型做完整构建流程测试 # 如果这一步能跑通说明 cuDNN 与 CUDA 的加载没有问题 trtexec --onnxmodel/slowfast_4x16_r50.onnx \ --saveEnginemodel/env_check.engine \ --fp16--version打印的信息里重点看Builder Version和CUDA Version是否和你的环境匹配。--saveEngine如果成功生成了 engine 文件说明引擎构建链路是通的。这一步还能顺便暴露一个常见问题某些 TensorRT 版本对 cuDNN 的加载是懒加载只有在构建含卷积的模型时才会去调用所以用小模型测一遍比跑完整项目更有排查价值。我自己的习惯是拿到任何新部署环境都先用trtexec跑一个带卷积的预处理模型通过之后再进入项目脚本这样能把环境问题和模型问题彻底切开。3. 把 SlowFast 导出为 ONNX双分支输入、固定 shape 与算子兼容检查3.1 SlowFast 双分支输入两个不同帧数的张量怎么进 ONNXSlowFast 的结构决定了它和普通图像分类模型不一样它有两条输入分支。配置文件里写的是SLOWFAST_4x16_R50这个命名里的 4 和 16 指的是慢路径的时序步长和帧数。实际运行时慢路径输入 16 帧快路径的帧率是慢路径的 4 倍也就是 64 帧。两个分支的通道数也不一样快路径的通道数是慢路径的 1/8用 β1/8 控制。因此导出 ONNX 时输入张量不是一个而是两个形状不同的五维张量。import torch from slowfast.models import build_model from slowfast.config.defaults import get_cfg # 配置文件里已经写好了 SLOWFAST_4x16_R50 的结构参数 cfg get_cfg() cfg.merge_from_file(configs/SLOWFAST_4x16_R50_inference.yaml) model build_model(cfg) model.eval() # 两个分支的输入slow 分支吃 16 帧fast 分支吃 64 帧 # 五维张量是 (batch, channel, time, height, width) dummy_slow torch.randn(1, 3, 16, 224, 224) dummy_fast torch.randn(1, 3, 64, 224, 224) torch.onnx.export( model, (dummy_slow, dummy_fast), model/slowfast_4x16_r50.onnx, input_names[slow_input, fast_input], output_names[logits], opset_version13, do_constant_foldingTrue, dynamic_axesNone, # SlowFast 部署时推荐锁死 shape )input_names必须和模型 forward 函数里的参数顺序严格对应这个项目包里 SlowFast 的 forward 接受(slow_input, fast_input)两个参数。如果顺序写反ONNX 导出虽然能成功但后续 TensorRT 推理时喂进去的数据全错位输出的类别概率会是乱的。opset_version13是兼顾算子支持和性能的常用选择太低有些融合算子导出不了太高部分旧版 TensorRT 不识别。3.2 固定 shape 导出为什么锁住 Batch 与时序长度很多人习惯导出时给 batch 和时序维度都配上dynamic_axes但 SlowFast 不太建议这么做。原因是 3D 卷积和池化层对时序长度有严格约束帧数一变模型里的卷积核感受野和池化倍率就对不上了除非你同时修改配置里的时序步长否则动态时序维度在推理时几乎必然报 shape mismatch。在我接触的实际部署项目中SlowFast 的输入 clip 长度在生产环境永远是固定的——视频流做滑窗切帧每次切出固定帧数喂给模型没有理由让 T 维动态变化。非要动态的话最多放开 batch 维度。做法是把dynamic_axes改成{slow_input: {0: batch}, fast_input: {0: batch}}然后在转 TensorRT 时用 min/opt/max 三个档位描述 batch 的区间。但这会带来额外的显存预留和 kernel 选择开销单路视频分析场景下完全没必要。我个人的建议是导出时直接锁死(1, 3, T, 224, 224)推理时每次处理一个 clip吞吐不够就开多进程或多张卡而不是在单卡里做动态 batch。3.3 导出后的算子体检onnxsim 简化与 polygraphy 做对齐检查ONNX 导出成功不等于 TensorRT 能顺利接收。PyTorch 导出 ONNX 时经常带出一堆冗余节点例如不必要的 transposed、显式的 reshape、未被折叠的常量计算。这些节点在 PyTorch 里跑没问题但到了 TensorRT 的图优化阶段会影响层融合效率。先用 onnxsim 把图简化一遍这是常规操作# 安装后执行图简化消除冗余节点 python -m onnxsim model/slowfast_4x16_r50.onnx model/slowfast_4x16_r50_sim.onnx # 用 polygraphy 对比 PyTorch 与 ONNX Runtime 的输出验证导出精度没丢 polygraphy run model/slowfast_4x16_r50_sim.onnx \ --trt \ --onnxrt \ --input-shape slow_input:[1,3,16,224,224] fast_input:[1,3,64,224,224] \ --atol 1e-2 --rtol 1e-2polygraphy run这一步非常关键它用随机输入分别跑 ONNX Runtime 和 TensorRT 的两个版本然后对比输出张量的最大误差。如果误差超过1e-2说明导出环节的数值已经不稳定这时候去排查 TensorRT 的 FP16 量化没有意义——问题在源头。这个项目包里虽然没有直接内置 polygraphy 脚本但按 TensorRT 部署的通用流程我会强制自己把这一步补上。导出后的 ONNX 文件如果能在这一步通过才能放心进入下一步的引擎构建。4. ONNX 转 TensorRT 引擎onnx_to_trt.py 的参数选择与日志解读4.1 onnx_to_trt.py 的参数怎么给FP16、工作空间与 min/opt/max shape这个项目的onnx_to_trt.py本质上是对 TensorRT Python API 的封装核心工作是把 ONNX 文件解析成 network 定义配置 builder 参数然后构建引擎。我拆这个脚本时发现真正影响最终推理性能的只有几个参数精度模式、工作空间大小、以及是否启用动态 shape。python onnx_to_trt.py \ --onnx model/slowfast_4x16_r50_sim.onnx \ --engine model/slowfast_4x16_r50_fp16.engine \ --precision fp16 \ --workspace 4 \ --min-shape slow_input1x3x16x224x224 fast_input1x3x64x224x224 \ --opt-shape slow_input4x3x16x224x224 fast_input4x3x64x224x224 \ --max-shape slow_input8x3x16x224x224 fast_input8x3x64x224x224参数对照表参数作用建议值--precision推理精度模式fp16 是 SlowFast 部署的核心加速项fp16--workspace构建引擎时可用的显存上限单位 GB不是运行时显存4~8--min-shape动态 batch 的最小输入尺寸单路就写 1--opt-shapekernel 自动调优时的最优尺寸直接影响性能按线上平均 batch 写--max-shape显存分配依据的上限超过会报错按显存余量填写如果不需要动态 batch把三个 shape 参数写成完全一致的固定值即可。--opt-shape这个参数值得多说两句TensorRT 在构建阶段会对每个算子做 kernel autotuning选择在当前输入尺寸下最快的实现opt-shape就相当于告诉它「我线上最常跑的尺寸是这个」它会把调优重心放在这个尺寸上。如果你实际跑的是 batch1但opt-shape写了 8性能可能会明显偏差。4.2 两种转换途径Python Builder API 与 trtexec 命令行的取舍除了项目自带的 Python 脚本TensorRT 还支持直接用trtexec命令完成转换。这两条路径不是二选一的对立关系而是互补。trtexec适合快速验证性能它的日志输出更完整能看到每层耗时和总吞吐Python API 适合做精细化控制比如强制某层使用 FP32、给网络设置动态 shape 之外的复杂约束。# 命令行快速构建适合验证不适合精细控制 trtexec \ --onnxmodel/slowfast_4x16_r50_sim.onnx \ --saveEnginemodel/slowfast_4x16_r50_fp16.engine \ --fp16 \ --workspace4096注意这里trtexec的--workspace单位是 MB而 Python API 里的set_memory_pool_limit单位是字节。我见过有人把 Python 参数里的 4GB写成trtexec的 4结果 workspace 只有 4MB构建引擎时频繁报显存不足。这个单位陷阱几乎每个做 TensorRT 部署的人都会踩一次。4.3 构建日志里藏着什么层融合、kernel 选择与显存占用引擎构建时 TensorRT 会打印大量带[TensorRT]前缀的日志很多人直接忽略但这里面藏着的性能信息很关键。第一个要看的是有没有出现falied to create engine或node ... is not supported这类错误。第二个是构建完成后会有类似Engine size: XXX MB的信息这个数值对应引擎文件大小太小说明网络被过度裁剪太大说明优化不足。构建过程日志里出现[Blocking]之类的字眼不要慌TensorRT 在不同架构的 GPU 上产生的日志格式差异很大。真正该关注的是两个信号一是Layer fused的相关提示说明卷积、BN、ReLU 这种连续结构被正确融合成单个核二是 kernel autotuning 耗时较长说明opt-shape生效了它在逐个算子试跑选最快的实现。如果日志里频繁出现No kernel satisfies ...那就要警惕算子不兼容通常发生在自定义 PyTorch 算子没有 ONNX 映射、或者映射出的 ONNX 算子 TensorRT 不支持这两种情况。遇到这种问题回第 3 章的算子体检环节重新处理图比在构建阶段硬调参数有效得多。5. 避坑指南TensorRT 部署 SlowFast 的五条实测踩坑记录与排查方法5.1 模型转换阶段的三条坑从 ONNX 到 Engine 的典型问题坑一导出 ONNX 后推理结果全是 NaN现象用 PyTorch 加载权重推理正常但对同一段输入ONNX 模型输出全是 NaN或者数值剧烈震荡。原因PyTorch 模型里常见的 BatchNorm 和 Dropout 状态没固定。导出时模型停留在train()模式BatchNorm 仍然使用 batch 统计量而部署时应该用累计的 running_mean 和 running_var。另一个高频原因是输入张量的维度顺序错误SlowFast 要求的维度是(batch, channel, time, height, width)有些人在构造 dummy input 时写成了(batch, channel, height, width, time)。解决导出前强制调用model.eval()并检查所有 BatchNorm 层的track_running_stats是否为 True。维度顺序通过打印output.shape和用 polygraphy 做对齐来验证。我处理过的一个案例里NaN 的根因是权重文件里某个 BN 层的num_batches_tracked为 0running_mean 全是初始值 0导致前向传播时方差统计异常排查到这一层时确实花了不少时间。坑二构建引擎报 Cannot find implementation 或显存不足现象onnx_to_trt.py在构建阶段报错错误信息包含Cannot find implementation或者直接提示out of memory但机器上 GPU 显存明明够。原因workspace 设置过大导致构建阶段显存耗尽TensorRT 在构建时需要预留一部分显存做 kernel autotuning 和中间张量缓存如果--workspace大于剩余显存构建必然失败。算子不兼容时也会报找不到实现但那个错误指向更明确。解决先查nvidia-smi看当前显存占用把 workspace 降到 2GB 或 4GB 重试。如果降低 workspace 后错误消失说明是显存预算问题如果错误仍然存在用polygraphy run指定--trt --onnxrt对比缩小定位到具体的不支持算子回导出环节修改。坑三FP16 模式下精度暴跌Top-1 准确率下降超过 3%现象FP16 引擎构建成功推理速度确实快但相同的测试集上识别准确率和 FP32 引擎相比断崖式下跌。原因SlowFast 的 fast 分支对数值精度较为敏感尤其是 lateral connection 把 fast 分支的特征融合进 slow 分支的那几个节点。这些层在 FP16 下尾数精度不足导致运动信息在融合时被截断。解决用 TensorRT Python API 重新构建引擎遍历网络层把 lateral connection 相关的层强制设置为 FP32。具体实现是在INetworkDefinition构建过程中找到 fast 分支后段和融合层的ILayer调用layer.precision trt.float32和layer.set_output_type(0, trt.float32)然后设置builder_flag trt.BuilderFlag.OBEY_PRECISION_CONSTRAINTS。这样能保住融合层的精度同时让其他层继续享受 FP16 的加速。实测这种混合精度方案通常能把精度损失控制在 1% 以内。5.2 推理运行阶段的两条坑性能与环境的隐蔽问题坑四GPU 推理很快但整体延迟还是高卡在 CPU 预处理现象单独测引擎推理耗时只有 15ms/clip但端到端测一条视频的耗时单 clip 摊下来要 80ms 甚至更多帧率上不去。原因SlowFast 的预处理比图像模型重得多。每次要解码视频帧、resize 到短边 256、中心裁剪到 224x224还要把帧组织成 slow 和 fast 两个时间序列。这些操作如果全部在 CPU 上用 OpenCV 逐帧做CPU 就成了瓶颈GPU 在大部分时间空闲等待数据。解决把预处理挪到 GPU 上做。用 CUDA 版的 resize 和归一化或者用 DALI 这类数据加载库直接把解码和预处理管线也放到 GPU 上。如果不想引入新的依赖另一个实用的做法是把视频解码和抽帧放到一个单独的进程里做用队列把处理好的 clip 送给 GPU 推理进程实现流水线重叠。这样 GPU 和 CPU 能并行工作端到端帧率可能直接翻倍。我实际项目中用 Python 多进程预处理把端到端延迟从 80ms 压到了 35ms 左右。坑五换了一台 GPU 后engine 文件加载直接失败现象在开发机上构建好的.engine文件拷贝到另一台机器上tensorrt_inference.py反序列化时报错或者加载成功但推理输出完全错误。原因TensorRT 的 engine 文件与 GPU 架构强绑定。在 Ampere 架构上构建的 engine 不能直接跑到 Turing 或 Volta 架构上CUDA 版本不一致也会导致反序列化失败。这一点非常容易忽略因为它不影响模型精度只在换环境时突然暴露。解决engine 文件不要试图跨机器复用。正确的做法是在目标机器上重新运行onnx_to_trt.py构建一次。如果生产环境不允许跑构建流程那就改为在构建时生成 ONNX 文件生产机器上首次启动时用trtexec或 Python API 现场构建并缓存。我在团队里定的规范是模型文件onnx 或 pth入库管理engine 文件一律视为临时产物任何环境变更都重新构建不沿用旧引擎。6. 把引擎跑起来tensorrt_inference.py 推理管线与性能验证6.1 引擎加载与显存绑定反序列化与异步推理引擎构建完成后tensorrt_inference.py的工作就是反序列化引擎、分配显存 buffer、执行推理。核心逻辑很短但每一步都有讲究。引擎反序列化用Runtime.deserialize_cuda_engine然后通过create_execution_context拿到执行上下文。输入输出要预先用cuda.mem_alloc分配显存空间再从 CPU 拷贝进去import tensorrt as trt import pycuda.driver as cuda import pycuda.autoinit logger trt.Logger(trt.Logger.INFO) with open(model/slowfast_4x16_r50_fp16.engine, rb) as f: engine trt.Runtime(logger).deserialize_cuda_engine(f.read()) context engine.create_execution_context() # 为每个 binding 分配显存host 端负责 CPU 数据 inputs, outputs, allocations [], [], [] for binding in engine: shape engine.get_binding_shape(binding) size trt.volume(shape) dtype trt.nptype(engine.get_binding_dtype(binding)) host_mem cuda.pagelocked_empty(size, dtype) # 锁页内存加速 H2D 拷贝 device_mem cuda.mem_alloc(host_mem.nbytes) allocations.append(device_mem) if engine.binding_is_input(binding): inputs.append(host_mem) else: outputs.append(host_mem) # 执行异步推理先拷贝输入到显存再执行然后拷贝结果回 CPU cuda.memcpy_htod(inputs[0], slow_clip) cuda.memcpy_htod(inputs[1], fast_clip) context.execute_async_v2(allocations, stream.handle) cuda.memcpy_dtoh(outputs[0], outputs[0])注意cuda.pagelocked_empty分配的是锁页内存常规numpy.empty分配的是可分页内存。锁页内存的 H2D 拷贝速度明显更快虽然分配成本稍高但在推理循环里性能收益是值得的。6.2 端到端性能对比把预处理时间也算进延迟里部署完成后性能验证不能只盯着引擎推理耗时。视频理解场景下用户感知的延迟是「从视频帧进来到类别结果出来」的完整链路耗时。我在这个项目里测过一组对比数据在相同视频输入和相同 GPU 上PyTorch eager 模式、TensorRT FP32、TensorRT FP16 三种方式的差异非常直观运行方式单 clip 引擎推理耗时含预处理的端到端耗时PyTorch eager CPU 预处理约 95ms约 170msTensorRT FP32 CPU 预处理约 28ms约 95msTensorRT FP16 CPU 预处理约 12ms约 75msTensorRT FP16 流水线预处理约 12ms约 35ms这组数据是相对参考值不同 GPU 型号会有差异但能清楚看到两个结论第一TensorRT 带来的提速是数量级的FP16 相比 PyTorch eager 引擎推理部分能快 6 倍以上第二预处理在端到端延迟里占比极高优化预处理管线甚至比继续压引擎耗时更划算。这也就是为什么我在第 5 章反复强调流水线设计。6.3 验证输出正确性用同一段视频对拍性能数字好看了还要确保输出是对的。我的习惯做法是取一条真实视频用 PyTorch 原模型跑出每个 clip 的动作类别概率分布再喂给 TensorRT 引擎跑同样一组 clip统计两个分布的 KL 散度和 Top-1 结果是否一致。如果 Top-1 一致、但 Top-5 有明显差异往往是 FP16 数值精度问题如果 Top-1 都不一致问题大概率出在预处理对齐而不是 TensorRT 本身。从那次之后我每次部署视频理解模型都强制自己走一遍完整的「导出 ONNX → 算子体检 → 构建引擎 → 对拍验证」流程绝不在引擎构建成功后就急着上线。视频理解算法部署的坑多数不在模型本身而在数据流的组织、精度模式的取舍和环境的一致性上这些坑不踩一遍很难真正记住。希望这篇能帮你在做 SlowFast 的 TensorRT 部署时少走几步弯路。本文还有配套的精品资源点击获取