TensorRT部署SlowFast视频理解算法:从模型导出到推理优化实战
发布时间:2026/9/28 16:32:10来源:尧图网络
简介面向视频理解与算法部署工程师的 TensorRT 部署实战资源围绕 SlowFast 视频理解模型解决模型计算量大、推理延迟高的问题提供从模型导出、转换优化到推理验证的完整 Python 工程。包内共 24 个文件以 21 个 Python 脚本为主体覆盖 export_model_to_onnx、onnx_to_trt、tensorrt_inference 等关键阶段YAML 文件用于定义 SlowFast 模型结构和推理配置MD 文档对部署流程做了说明整体仅 46KB结构紧凑便于查阅。已有 158 人浏览学习适合具备一定深度学习基础、正在上手 TensorRT 的算法开发或部署人员。通过实际运行和修改这些脚本读者能够直观理解图优化、层融合、精度校准等加速原理并可将该套流程迁移至其他视频理解或动作识别模型提升项目落地的实时推理性能。1. 用TensorRT部署SlowFast从能跑到能上线中间隔着什么先把结论放前面视频理解算法的上线难度和图像识别不在一个量级。图像模型在TensorRT里往往一条命令就能转完而SlowFast这类双路径视频模型有两个输入、两条分支、多条横向连接中间还有三次时间维度的特征对齐。你从GitHub跑通PyTorch版demo只算万里长征第一步真正要上业务的是那个能在GPU上稳定跑到几十毫秒一帧的推理引擎。这篇实战笔记就围绕“TensorRT部署SlowFast视频理解算法”这条线把模型导出、engine构建、runtime推理和踩坑记录完整过一遍适合做动作识别、内容理解、视频审核的算法工程师或部署工程师参考。2. 理解SlowFast的双路径结构才能预测TensorRT的瓶颈在哪2.1 慢路径和快路径为什么让部署不止一个难题SlowFast的设计思路并不复杂慢路径slow pathway用低帧率加宽通道负责人物、场景、物体这类空间语义快路径fast pathway用高帧率加窄通道捕捉手势、转头、跳跃这类随时间快速变化的信息两条路径每隔几个stage就做一次横向连接把快路径的时间分辨率注入慢路径。模型跑起来时两个输入张量同时进入网络一路4帧一路32帧计算图从一开始就是分裂的。部署视角下这个设计给TensorRT带来三个麻烦。第一两条路径的特征形状始终不一致横向连接处有插值和3D卷积这些算子在不同opset下的语义飘移很大。第二时间维上的卷积核大小跟图像模型完全不同TensorRT内核库对3D卷积的覆盖面永远比2D卷积少遇到不支持的layout就要走通用实现速度直接掉一个档次。第三输入输出绑定数量是图像模型的两倍调试的时候bindings顺序、shape对齐多一重出错面。如果只在PyTorch里跑这些问题全是隐性的因为各层调用的是cuDNN的通用kernel速度慢但都能跑。到了TensorRT优化器逐层挑kernel并且力图融合算子任何一个算子边缘情况超出内核库覆盖范围构建就会失败或者用fallback kernel守住正确性但丢掉性能。理解了这条主线就不会在部署报错时只是一味改版本而会沿着算子和shape的脉络排查。2.2 TensorRT版本和GPU算力怎么配GTX 1070要不要升10.x部署视频理解模型之前先查一下GPU的compute capability再决定TensorRT主版本。这个话题最近问的人很多常见困惑就是“TensorRT 10.x是否支持GTX 1070”。GTX 1070是Pascal架构、算力6.1实测在TensorRT 10.x上构建引擎经常遇到UNSUPPORTED算子能建出引擎的层也常常落到通用kernel上性能不升反降。这不是你的代码有问题而是TensorRT 10.x把Pascal调出了主优化列表老卡用户继续用8.4或8.6是风险最低的选择。| GPU架构 | compute capability | 推荐TensorRT版本 | 备注 | | PascalGTX 1070 | 6.1 | 8.4.x / 8.6.x | 10.x已走下优化列表 | | TuringRTX 2080 Ti | 7.5 | 8.6.x ~ 10.x | FP16收益明显 | | AmpereA100 / 3090 | 8.0 | 8.6.x / 9.x / 10.x | 部署首选 | | AdaRTX 4090 | 8.9 | 9.x / 10.x | 新架构专用 |建议是训练和部署如果共用同一张卡按卡选TensorRT如果构建机和运行机不是同一张卡最好在同一架构上构建跨架构复制engine是不可靠的。有人想图省事把在3090上构建的engine拷到T4上跑TensorRT会拒绝加载或者表现异常。这类问题的定位成本很高因为报错信息往往只说cubin不匹配不会告诉你该换哪一版TensorRT。2.3 TensorRT给SlowFast提速的三个主要来源第一个来源是层融合。PyTorch的计算图里3D卷积、偏置、ReLU三个op分三次读写显存TensorRT构建时会把它们融成一个kernel。以SlowFast的慢路径为例每层stage能减少两次全局显存往返整网十几层卷积下来访存开销削减非常可观。这个收益不需要你做任何额外配置构建engine时自动完成但前提是ONNX导出的图足够干净别带一大堆恒等节点干扰融合判断。第二个来源是FP16精度。绝大多数消费级和专业级GPU上FP16算力是FP32的两倍显存占用还减半。配置合理的视频推理服务会跑到不小的batch显存下压之后并行度能拉得更开。代价是精度尤其是SlowFast的横向连接对时间维细节敏感这个我放在避坑章单独讲。FP16不是开关一开就万事大吉它需要你用真实数据做精度验收。第三个来源是kernel autotuning。TensorRT在构建engine时会对每一层做benchmark从kernel库中挑最快的那一个。这个机制能否发挥前提是shape确定。工程上我强烈建议固定T/H/W三个维度只保留batch维度动态否则kernel搜索空间成倍扩张构建时间可能从分钟级变成小时级。你可以在优化阶段解除限制看看效果但线上正式engine一定用固定shape构建。2.4 数据预处理CPU喂帧还是GPU喂帧很多人在这一步把“视频理解部署”做成了“视频理解部署的灾难”。SlowFast需要从视频片段里分别抽取慢路径和快路径的帧再做缩放、归一化、排列组合最终拼成两个四维张量。如果在Python里用OpenCV逐帧读、再逐帧resize等待你的是GPU利用率上不去、吞吐量上不来的尴尬局面。实测中CPU端预处理的开销经常比TensorRT推理本身还高。我一般的做法是解码交给PyAV用硬件解码器把视频帧解出来预处理从OpenCV换成GPU上的张量变换最后一步在同一个CUDA stream里和推理串联。如果项目简单也可以用torchvision的read_video配合Transform虽然代码好写但显存占用多出一截。数据喂养这块建议在写部署代码时就列为正式环节否则后期优化无从下手只能靠玄学调参。3. 把SlowFast的pt权重转成TensorRT engine导出、构建、推理一条龙3.1 环境版本怎么搭先用我组合过多次的一个配置Ubuntu 20.04、CUDA 11.8、cuDNN 8.9、TensorRT 8.6.1、PyTorch 2.0.1。这个组合出问题最少尤其是手头有一张Pascal或Turing卡的时候基本一路顺畅。TensorRT 10.x可以装在单独的机器上做实验但不要贸然用于生产构建它对新架构的优化激进对老卡和旧opset的兼容就没那么好。安装TensorRT时常见的坑是pip包和系统库版本错位。正确做法是先下载NVIDIA官网的TensorRT tar包解压到 /usr/local/tensorrt随后把lib路径加进LD_LIBRARY_PATH再用pip安装同版本的python wheel。装完先验证版本一致/usr/local/tensorrt/bin/trtexec --version执行这条命令会打印出TensorRT版本和构建信息。如果打印出来的版本跟你pip包里的版本不一致后面解析engine时会遇到找不到符号或加载失败这些都是可以提前排掉的雷。另外显卡驱动要支持CUDA 11.8装好后用nvidia-smi看驱动版本驱动太老TensorRT 8.6起不来。很多网上流传的“按教程装好却跑不起来”的案例最后都查到了驱动和CUDA版本不匹配上。3.2 导出ONNXpt文件转TensorRT时最影响成败的一步现在行业里的常见做法是把pt权重转成onnx再由TensorRT解析onnx直接用torch_tensorrt属于少数场景。SlowFast模型有两个输入导出的时候要把两个输入都命名、都声明动态轴。下面这段是我项目里常用的导出脚本import torch # model 是你加载了预训练权重的 SlowFast 实例 # 示例中 T4 是慢路径帧数T_fast32 是快路径帧数 model.eval().cuda() slow_input torch.randn(1, 3, 4, 224, 224).cuda() fast_input torch.randn(1, 3, 32, 224, 224).cuda() torch.onnx.export( model, (slow_input, fast_input), slowfast.onnx, input_names[slow_input, fast_input], output_names[class_logits], dynamic_axes{ slow_input: {0: batch}, fast_input: {0: batch}, class_logits: {0: batch} }, opset_version13, do_constant_foldingTrue, verboseFalse )这段脚本里有三个参数值得单独说明。dynamic_axes只声明batch维度为动态不把时间维和空间维设动态原因前面说过维度全动态后TensorRT的kernel搜索会失去方向构建时间不可控。opset_version固定13这是ONNX Resize算子语义较稳定的版本opset 13升到16的过程中Resize的语义有过调整容易在TensorRT解析阶段报错。do_constant_folding开启后会把BN的scale/shift折叠进卷积权重导出图更干净导出耗时略有增加但值得。导出结束后建议先用onnxsim做一次图简化python -m onnxsim slowfast.onnx slowfast_sim.onnxonnxsim会做常量折叠和图简化多数情况下能删掉一批TensorRT不关心的identity节点。如果这个命令报错不代表模型有问题可以直接跳过简化输出仍然可用。简化完成后用netron打开看一眼Resize节点的属性重点确认coordinate_transformation_mode是不是asymmetric这决定TensorRT用哪个kernel实现时间维插值。这一步做完导出阶段才算真正闭环。注意onnxsim输出的文件名不要覆盖原文件保留一份未简化版本。有些情况下简化后的图在TensorRT里能构建推理结果却异常保留原版便于回溯对比。3.3 用trtexec构建engineFP16、shape范围与内存池拿到onnx之后最快捷的方式是用trtexec命令行构建engine。我用的构建命令/usr/local/tensorrt/bin/trtexec \ --onnxslowfast_sim.onnx \ --saveEngineslowfast_fp16.engine \ --fp16 \ --minShapesslow_input:1x3x4x224x224,fast_input:1x3x32x224x224 \ --optShapesslow_input:4x3x4x224x224,fast_input:4x3x32x224x224 \ --maxShapesslow_input:8x3x4x224x224,fast_input:8x3x32x224x224 \ --memPoolSizeworkspace:4096命令里每个参数对应一类需求。--fp16表示允许TensorRT将层降到FP16精度实际哪些层降精度由构建器决定如果后续发现精度掉点可以用per-layer精度控制把关键层拉回FP32。--minShapes/--optShapes/--maxShapes这组参数只在动态batch场景需要表示batch从1到8的覆盖范围三组shapes缺一不可TensorRT构建时会把optShapes附近的kernel选为默认。--memPoolSizeworkspace:4096设置构建和运行时的工作内存上限单位是MB显存充裕的机器可以放宽到8192engine构建时tactic搜索会更充分。注意TensorRT 8.x里可以用--workspace4096这种老写法9.x之后统一改成了--memPoolSizeworkspace:4096。两种写法在不同的版本里混用会直接报参数解析错误所以构建脚本里建议加上版本判断。构建日志建议重定向到文件保存TensorRT会在其中打印每一层选用的kernel名称这在后续做per-layer精度控制时是重要的索引表。3.4 引擎验证与Python推理engine构建完先用trtexec自带的性能模式跑一轮测速/usr/local/tensorrt/bin/trtexec \ --loadEngineslowfast_fp16.engine \ --shapesslow_input:1x3x4x224x224,fast_input:1x3x32x224x224 \ --iterations50 \ --avgRuns10这条命令会在当前GPU上跑50次推理取10次均值打印出延迟和吞吐。要提醒的是trtexec的纯引擎测速不包含预处理和后处理前端接入真实业务后延迟会高于这个数字这个差距就是CPU喂帧的时间所以前面说预处理要专门优化。如果发现引擎测速和PyTorch FP32的延迟差距不到一半先别急着调batch回去检查是不是有层走了fallback kernel。Python侧的最小推理代码长这样import tensorrt as trt import pycuda.driver as cuda import pycuda.autoinit import numpy as np # 加载engine runtime trt.Runtime(trt.Logger(trt.Logger.WARNING)) with open(slowfast_fp16.engine, rb) as f: engine runtime.deserialize_cuda_engine(f.read()) ctx engine.create_execution_context() ctx.set_input_shape(slow_input, (1, 3, 4, 224, 224)) ctx.set_input_shape(fast_input, (1, 3, 32, 224, 224)) # 分配显存slow 4帧、fast 32帧、输出400类 d_slow cuda.mem_alloc(1 * 3 * 4 * 224 * 224 * 4) d_fast cuda.mem_alloc(1 * 3 * 32 * 224 * 224 * 4) d_out cuda.mem_alloc(1 * 400 * 4) # 一次性拷贝输入并执行 cuda.memcpy_htod(d_slow, slow_np.ravel()) cuda.memcpy_htod(d_fast, fast_np.ravel()) ctx.execute_v2(bindings[int(d_slow), int(d_fast), int(d_out)]) # 拷贝输出回内存 out_np np.empty(1 * 400, dtypenp.float32) cuda.memcpy_dtoh(out_np, d_out)代码里有几个关键点。ctx.set_input_shape必须在第一次execute之前调用动态shape引擎如果没有显式设置输入shape就直接执行会按构建时默认的optShapes跑和实际输入不一致。bindings列表的顺序必须和engine里的binding顺序一致通常先遍历engine对象把name和index记下来再按index传地址直接手写顺序容易在输入输出多的模型上出错。pycuda的mem_alloc返回的是device pointer传给execute_v2时需要转成inttensorrt和pycuda版本更新中这个行为有变化报类型错误时就检查这里。到这里pt到engine再到runtime推理的链路就闭环了。下面这些坑是在真机上踩过一遍之后整理出来的值得在项目里留个神。4. 避坑TensorRT部署SlowFast最常踩的五个坑这一章的五条记录是按照现场排障的顺序整理的。每一条都保留现象和解决办法遇到报错先对号入座比从头开始重新读日志省时间。4.1 在GTX 1070上构建TensorRT 10.x的engine报错UNSUPPORTED现象使用TensorRT 10.x加载导出的onnx或构建engine时日志出现[E] [TRT] unsupported op...而且报错的算子集中在3D卷积或Resize换成TensorRT 8.6后同样的onnx又顺利构建。原因TensorRT 10.x把Pascal架构compute capability 6.1移出了主优化列表部分算子的kernel实现没有为6.1编译解析器会直接判定为不支持而不是走通用实现。网上讨论“TensorRT 10.x是否支持GTX 1070”的帖子很多实际结论就是能装上不代表能好好用。解决在GTX 1070或同代老卡上部署固定用TensorRT 8.4或8.6版本不要追新。如果业务要求必须用新版本换一台Turing或Ampere架构的机器做engine构建和运行不要在Pascal卡上浪费排错时间。另外跨架构拷贝engine这件事也别做在3090上构建好的engine拷贝到T4上会报cubin不匹配正确做法是目标机器上重新构建。4.2 ONNX导出时interpolate节点语义变化模型变成黑匣子现象同一个SlowFast权重在A机器上导出onnx后转engine顺利在B机器上导出后构建时报Resize相关错误或者engine能建出来但推理结果跟PyTorch对不上。原因torch版本和opset版本组合不同导致interpolate被导出为不同形态的Resize算子。opset 13之后Resize新增了多个控制属性TensorRT对部分属性组合没有实现构建阶段报错或运行阶段走进通用路径。解决统一导出环境中的torch版本opset固定13并在导出后检查onnx图里Resize节点的coordinate_transformation_mode属性。如果发现不是asymmetric在导出前把模型里的插值方式改成nearest或者在onnx里手动修正节点属性。改模型结构是最后手段因为重新训练或微调的成本不可控。4.3 开启FP16后精度掉点视频模型比图像模型更敏感现象FP16 engine推理出来的top-5准确率比PyTorch的FP32低3到5个百分点部分类别的预测概率混在一起个别动作类别直接错乱。原因SlowFast的横向连接会把fast路径的时间特征逐层叠加到slow路径FP16的尾数精度在多次加法中累积误差时间维上比空间维更容易放大。图像分类模型FP16掉点通常可以忽略但视频理解模型对时间维差分敏感这个误差会被横向连接放大。解决不追求全局FP16改用per-layer精度策略。导出onnx时给关键层命名构建时用TensorRT的层精度接口把head和横向连接部分的层设为FP32。实际项目中可以先全局FP16跑一遍trtexec用日志确认哪些层被降精度再把敏感层挑出来。做法上我一般先固定batch1验证精度对齐再逐步加batch排除batch维对数值范围的影响。4.4 动态shape的引擎测出来延迟高一倍固定shape却很快现象engine在minShapes到maxShapes范围内都能跑但延迟波动极大batch1时都比固定shape慢了近一倍。把时间维和空间维也设为动态后构建时间从10分钟涨到2小时。原因TensorRT的kernel autotuning会为每一组shape挑选专用kernel动态维度越多候选kernel越泛化最后选到的不是最优实现。固定shape时TensorRT可以在构建期确定所有张量维度和对齐方式kernel选择空间小而精准。解决把T/H/W三个维度固定只保留batch维度动态。如果业务要覆盖多分辨率输入为每种分辨率单独构建一个engine运行时按输入尺寸加载对应engine比用一个万能engine靠谱得多。engines多了以后记得做懒加载按需加载到显存避免初始化时把显存占满。4.5 预处理拖后腿GPU利用率上不去现象推理延迟单独测只有30ms接入完整服务后变成120msGPU利用率不到40%看着nvidia-smi一头雾水。原因视频解码、抽帧、缩放、归一化全在CPU上串行执行CPU端耗时超过GPU推理。SlowFast两个路径的帧数差异大预处理要分别组织两套图像序列比图像分类的预处理重得多。很多人把优化重点放在engine上忽略了这个环节。解决解码换成PyAV并用硬件解码预处理做成异步流水线最后把帧数据直接放到GPU显存再进TensorRT。排查这类问题的一般顺序是先测TensorRT引擎本身的延迟再测接上解码和预处理的整链路延迟两者相减就是数据管线的开销。用这个差值定位瓶颈比直接看GPU利用率直观得多。视频理解的GPU利用率本身就比图像模型低因为输入张量大数据传输和预处理占用的时间比例高。5. 进阶三个让视频理解推理进一步变快的小技巧前面把部署链路走通了接下来谈三个我反复用到的小技巧正好衔接真实业务里常遇到的吞吐问题。第一个技巧是多batch处理。单路视频的延迟再低qps都有天花板。把不同视频片段拼成batch一次推理才是拉吞吐的正路。TensorRT动态batch在这里非常有用但拼接时要保证每段视频的时间轴对齐。SlowFast的输入是固定帧数所以拼接难度反而比图像模型低。显存允许的前提下batch4往往是延迟和吞吐的甜点继续加大后显存占用上升却换不来同比例的吞吐提升需要拿真实数据测一下曲线。第二个技巧是在关键层上做混合精度。完全FP16和完全FP32之外第三条路是per-layer精度控制。我建议先用FP16构建再把横向连接和分类头这两个部件单独切回FP32保留推理速度的同时把精度拉回可接受区间。trtexec构建日志里的层名列表是找敏感层的索引图比逐层试错省时间得多。做这一步时一定要用带标签的验证集跑一遍对比不能只看一两帧的结果下结论。第三个技巧是用CUDA stream并行解码与推理。如果预处理已经搬上GPU可以让一个stream跑TensorRT推理另一个stream同时做下一批帧的预处理两者之间用event同步能抵消预处理和推理串行的等待时间。这个模式下GPU利用率能稳定跑在70%以上之前的CPU方案跑不到这个数。如果对延迟波动敏感还可以把解码放到独立线程用有界队列控制喂帧节奏避免突发流量把CPU打满。视频理解算法上线和图像模型最大的区别是它不止有网络还有一套围绕时间轴的输入流水线。TensorRT能帮你把网络压到极限但喂得进去才行。这套流程我跑过三轮之后最深的体会是不要一上来就堆新版本先把版本、shape、预处理三件事钉死再谈优化。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网