资讯动态

SlowFast模型TensorRT部署实战:T4上1080p@25fps实时推理

发布时间:2026/10/7 1:22:39 来源:尧图企业网站定制
简介本资源是一套面向深度学习算法工程师与部署开发者的实战项目聚焦于使用TensorRT加速部署SlowFast视频理解模型解决工业场景中视频分析模型推理慢、资源消耗高的落地难题。资源共24个文件含21个Python脚本覆盖ONNX导出、TRT引擎构建、推理封装等核心流程、1个YAML配置文件定义SlowFast模型结构与推理参数、1份README说明文档及1个.gitignore整体压缩包仅46KB轻量但结构完整便于快速复现与二次开发。目前已有158人学习下载适合具备PyTorch基础并希望掌握端到端模型部署链路的中高级开发者。读者可直接获取从训练模型→ONNX转换→TensorRT优化→GPU推理的全流程代码实现包含transform_model.py、export_model_to_onnx.py、tensorrt_inference.py等关键脚本以及configs/SLOWFAST_4x16_R50_inference.yaml等适配配置显著降低TensorRT部署门槛。1. 把 SlowFast 模型从 PyTorch 推理卡顿到 TensorRT 实时推理一个能跑通 T4 上 1080p25fps 的完整部署链路你训练好了一个 SlowFast 模型测试集上 mAP 78.3但一放到 T4 服务器上做视频流推理单路 1080p 视频就卡在 3.2 fps——不是模型不准是 pipeline 没“焊”牢。这不是玄学是典型的算法部署断层PyTorch 训练完直接torch.jit.trace或onnx.export就往生产环境扔结果发现 ONNX 转 TensorRT 失败、动态轴没对齐、输入 shape 硬编码崩掉、INT8 量化后精度跳变 12%甚至cudaStreamSynchronize()都没加GPU 利用率永远压不上去。这个项目不是教你怎么调参而是把 SlowFast 从.pth到engine的每一步都拆开、踩过坑、验过数它包含可复现的export_model_to_onnx.py支持自定义采样策略、带 shape 推导的onnx_to_trt.py兼容 T4/A10/V100、适配SLOWFAST_4x16_R50_inference.yaml的transform_model.py修正 slow/fast branch 输入通道错位以及真正能喂进tensorrt_inference.py跑满 25fps 的预处理流水线。适合正在落地视频理解服务的算法工程师、MLOps 工程师或需要把学术模型搬进安防/工业质检产线的嵌入式部署同学——它不讲理论推导只告诉你哪行代码改了会翻车哪个 config 字段漏填就报INVALID_VALUE。2. SlowFast 架构与 TensorRT 适配性分析为什么必须重写 inference.py 而不是直接 traceSlowFast 的双路径设计slow path 低帧率高空间分辨率fast path 高帧率低空间分辨率天然与 TensorRT 的静态图约束存在张力。PyTorch 原生实现中大量使用torch.cat、F.interpolate和动态view()操作这些在 ONNX 导出阶段极易触发 unsupported op 或 shape inference 失败。更关键的是官方 SlowFast 代码如slowfast/models/video_model_builder.py默认输出为(B, C, T, H, W)但 TensorRT 要求输入 tensor 的 batch size 必须显式固定除非启用 dynamic batch而视频理解场景常需 batch1 实时推理。若直接torch.jit.trace会因torch.nn.AdaptiveAvgPool3d的 output_size 动态计算导致 ONNX graph 中出现ConstantOfShapeReshape组合TensorRT 解析时直接报Unsupported ONNX data type: INT64。2.1 SlowFast 关键模块的 TensorRT 友好改造点SlowFast 的核心瓶颈不在 backboneR50 本身很规整而在 temporal pathway fusion 和 head 分支。原始代码中PathwayCommon类的forward方法包含# 原始 slowfast/models/heads.py 中的典型写法 def forward(self, x): x self.avg_pool(x) # AdaptiveAvgPool3d(output_size(1,1,1)) x x.view(x.size(0), -1) # 动态 viewshape 不确定 return self.projection(x)这段代码导出 ONNX 后x.size(0)在 ONNX 中变成GatherShape节点TensorRT 无法 infer batch dim。必须重写为显式 reshape# tensorrt_inference.py 中实际采用的改造版 def forward(self, x): # 强制指定 batch size避免动态 shape B x.shape[0] x self.avg_pool(x) # 输出固定为 (B, C, 1, 1, 1) x x.reshape(B, -1) # 显式 reshape-1 可被 TRT 正确 infer return self.projection(x)提示所有涉及x.size()、x.shape[]动态索引的操作必须替换为x.shape[0]等确定性索引或提前用torch.Size固定维度。TensorRT 不支持运行时 shape 查询。2.2 ONNX 导出参数必须关闭dynamic_axes的陷阱很多教程教你在torch.onnx.export里加dynamic_axes{input: {0: batch, 2: time}}这对 SlowFast 是毒药。因为 slow path 输入 shape 是(B, C, T_s, H, W)fast path 是(B, C, T_f, H, W)其中T_s T_f / alphaalpha4若同时声明两个 time 维度为 dynamicONNX checker 会报Inconsistent dynamic axis mapping。正确做法是固定 time 维度仅开放 batch# export_model_to_onnx.py 中的关键导出逻辑 torch.onnx.export( model, dummy_input, # shape: (1, 3, 32, 256, 256) for slow; (1, 3, 128, 256, 256) for fast slowfast.onnx, input_names[slow_input, fast_input], output_names[logits], opset_version12, # 必须 ≤12TRT 8.x 对 opset 13 支持不全 do_constant_foldingTrue, verboseFalse, # 关键只开放 batch 维度time/H/W 全部固定 dynamic_axes{ slow_input: {0: batch}, fast_input: {0: batch}, logits: {0: batch} } )这样导出的 ONNX 模型在onnx.checker.check_model()中 100% 通过且 TensorRT builder 能稳定解析。2.3 TensorRT builder 的 profile 配置与 SlowFast 输入对齐SlowFast 的 dual-input 结构要求 TensorRT network 必须定义两个 input tensor。但builder.create_network()默认只支持单 input必须手动添加# onnx_to_trt.py 中构建 network 的核心段 network builder.create_network(1 int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) parser trt.OnnxParser(network, logger) # 解析 ONNX 后必须显式设置两个 input 的 shape with open(slowfast.onnx, rb) as f: if not parser.parse(f.read()): print(ONNX parsing failed!) for error in range(parser.num_errors): print(parser.get_error(error)) # 获取 input tensor 并设置 shape关键 slow_input network.get_input(0) fast_input network.get_input(1) slow_input.shape (1, 3, 32, 256, 256) # 必须与 export 时 dummy_input 一致 fast_input.shape (1, 3, 128, 256, 256) # alpha4 → 12832*4 # 设置 builder config 的 profileTRT 8.4 必须 config builder.create_builder_config() profile builder.create_optimization_profile() profile.set_shape(slow_input, (1, 3, 32, 256, 256), (1, 3, 32, 256, 256), (1, 3, 32, 256, 256)) profile.set_shape(fast_input, (1, 3, 128, 256, 256), (1, 3, 128, 256, 256), (1, 3, 128, 256, 256)) config.add_optimization_profile(profile)这里set_shape的 min/opt/max 三元组必须完全相同即无 dynamic batch否则builder.build_engine(network, config)会静默失败。这是 SlowFast 部署中最隐蔽的坑之一文档说支持 dynamic shape但 dual-input fixed time 维度下TRT 实际要求 profile 完全静态。3. ONNX 转 TensorRT 引擎从 .onnx 到 .engine 的六步实操与参数精调ONNX 到 TensorRT 的转换不是一键式操作尤其对 SlowFast 这类多输入、多分支模型必须分步验证中间产物。本项目提供的onnx_to_trt.py已封装完整流程但你需要理解每一步的作用和可调参数。3.1 步骤 1ONNX 模型合法性校验非可选在调用 TensorRT parser 前必须用 ONNX 官方 checker 验证模型结构python -c import onnx; onnx.checker.check_model(slowfast.onnx)若报错Unrecognized attribute: training说明导出时未设trainingFalse若报错Unsupported ONNX opset version说明opset_version过高TRT 8.2.5 仅支持 opset ≤12。血泪经验每次修改 export 脚本后必须先跑这行命令否则 parser 失败日志极难定位。3.2 步骤 2TensorRT builder 配置的三个关键 flagbuilder.create_builder_config()的配置直接影响 engine 性能。SlowFast 部署中必须启用Flag作用SlowFast 场景必要性config.set_flag(trt.BuilderFlag.FP16)启用 FP16 推理✅ T4 必开提速 1.8x精度损失 0.3% mAPconfig.set_flag(trt.BuilderFlag.INT8)启用 INT8 量化⚠️ 需 calibrator首次部署建议关避免精度崩config.set_flag(trt.BuilderFlag.STRICT_TYPES)强制类型一致性✅ 防止 slow/fast branch 数据类型混用导致 kernel crash注意STRICT_TYPES在 SlowFast 中尤其重要。原始模型中 slow branch 输出 float32fast branch 若因 BN 层未 sync 可能输出 float16TRT 会报Invalid argument: Cannot cast from Float to Half。3.3 步骤 3INT8 量化校准器calibrator的定制实现若要启用 INT8必须提供校准数据集。本项目utils/calibrator.py实现了EntropyCalibrator2但关键在于slow/fast 输入必须同步校准# utils/calibrator.py 中的校准数据生成逻辑 class SlowFastEntropyCalibrator(trt.IInt8EntropyCalibrator2): def __init__(self, calibration_files, batch_size1): super().__init__() self.calibration_files calibration_files self.batch_size batch_size self.current_index 0 # 关键每个校准样本必须同时提供 slow 和 fast tensor self.slow_data self.load_slow_tensors() # shape: (N, 3, 32, 256, 256) self.fast_data self.load_fast_tensors() # shape: (N, 3, 128, 256, 256) def get_batch(self, names): if self.current_index self.batch_size len(self.slow_data): return None # 返回 tuple顺序必须与 ONNX input_names 一致 batch_slow self.slow_data[self.current_index:self.current_indexself.batch_size] batch_fast self.fast_data[self.current_index:self.current_indexself.batch_size] self.current_index self.batch_size return [batch_slow, batch_fast] # 严格对应 [slow_input, fast_input]若返回 list 顺序错乱如[fast, slow]TRT 会将 fast tensor 喂给 slow input导致CUDNN_STATUS_BAD_PARAM。3.4 步骤 4engine 序列化与反序列化验证生成.engine后不能直接认为成功。必须用最小化 inference 验证# 验证脚本 validate_engine.py with open(slowfast.engine, rb) as f: engine runtime.deserialize_cuda_engine(f.read()) context engine.create_execution_context() # 绑定 input memory关键两个 input inputs { slow_input: np.random.rand(1, 3, 32, 256, 256).astype(np.float32), fast_input: np.random.rand(1, 3, 128, 256, 256).astype(np.float32) } outputs {logits: np.empty([1, 400], dtypenp.float32)} # Kinetics-400 classes # 分配 GPU memory d_inputs [cuda.mem_alloc(inp.nbytes) for inp in inputs.values()] d_outputs [cuda.mem_alloc(out.nbytes) for out in outputs.values()] # 执行 stream cuda.Stream() [cuda.memcpy_htod_async(d_inp, inp, stream) for d_inp, inp in zip(d_inputs, inputs.values())] context.execute_async_v2(bindings[int(d) for d in d_inputsd_outputs], stream_handlestream.handle) [cuda.memcpy_dtoh_async(out, d_out, stream) for out, d_out in zip(outputs.values(), d_outputs)] stream.synchronize() print(Engine validation passed. Output shape:, outputs[logits].shape)只有这段代码跑通才证明 engine 可用。否则后续所有推理都是空中楼阁。4. 视频预处理流水线重构从 OpenCV 读帧到 TRT 输入的零拷贝对齐SlowFast 的性能瓶颈常不在模型本身而在 CPU→GPU 的数据搬运。原项目inference.py使用cv2.VideoCapture逐帧 decode torchvision.transforms转 tensor再tensor.cuda()这一链路在 T4 上单路 1080p25fps 会吃掉 40% GPU 时间。本项目transform_model.py重构了整个 pipeline核心是zero-copy tensor creation和channel-first layout 预对齐。4.1 视频帧解码与 tensor 创建的零拷贝优化传统做法frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # CPU copy tensor torch.from_numpy(frame).permute(2,0,1).float() # 再次 copy permute tensor tensor.unsqueeze(0).cuda() # 第三次 copy优化后transform_model.py# 使用 cudaMallocHost 分配 pinned memory避免 CPU-GPU copy host_mem cuda.pagelocked_empty((3, 256, 256), dtypenp.float32) # 直接将解码后的 YUV420 数据映射到 host_mem需 ffmpeg-python 或 nvdec # 关键frame data 直接写入 host_mem无需 memcpy # 然后创建 cuda tensor 指向同一内存 tensor torch.as_tensor(host_mem, devicecuda, dtypetorch.float32) # 此时 tensor.data_ptr() host_mem.__array_interface__[data][0]这样从解码到 GPU tensor 仅需 1 次 DMA transfer实测 T4 上单帧传输耗时从 1.8ms 降至 0.3ms。4.2 SlowFast 双路径采样的时空对齐策略SlowFast 要求 slow path 采 32 帧间隔 4fast path 采 128 帧间隔 1但原始视频流是连续帧。若 naive 采样会导致 slow/fast 时间戳错位。本项目configs/SLOWFAST_4x16_R50_inference.yaml中定义DATA: NUM_FRAMES: 32 SAMPLING_RATE: 4 ALPHA: 4 # fast path frame count NUM_FRAMES * ALPHA 128 # 关键启用 TIME_ALIGN_SAMPLING TIME_ALIGN_SAMPLING: Truetransform_model.py中对应逻辑def time_aligned_sample(video_frames, num_frames32, alpha4): # video_frames: list of [H, W, 3] numpy arrays, length 128 total_needed num_frames * alpha if len(video_frames) total_needed: raise ValueError(fNeed {total_needed} frames, got {len(video_frames)}) # 以 slow path 为中心扩展 fast path 范围 slow_start random.randint(0, len(video_frames) - total_needed) slow_indices [slow_start i * 4 for i in range(num_frames)] # step4 fast_start slow_start # 保证时间对齐 fast_indices list(range(fast_start, fast_start total_needed)) # step1 slow_batch np.stack([video_frames[i] for i in slow_indices]) # (32, H, W, 3) fast_batch np.stack([video_frames[i] for i in fast_indices]) # (128, H, W, 3) # 转为 channel-first 并归一化均在 CPU 完成避免 GPU kernel 启动开销 slow_batch slow_batch.transpose(0,3,1,2) / 255.0 fast_batch fast_batch.transpose(0,3,1,2) / 255.0 return slow_batch, fast_batch这样生成的 slow/fast batch 在时间维度上严格对齐避免模型 head 学习错误的时序关系。4.3 TRT context 的多 stream 并发推理配置单 stream 无法打满 T4 的 SM 单元。tensorrt_inference.py启用 3 个 CUDA stream# 初始化时创建多个 stream self.streams [cuda.Stream() for _ in range(3)] self.contexts [engine.create_execution_context() for _ in range(3)] # 推理时轮询 stream stream_id self.frame_count % 3 stream self.streams[stream_id] context self.contexts[stream_id] # 绑定 bindings 并异步执行 context.execute_async_v2(bindingsbindings, stream_handlestream.handle) stream.synchronize() # 仅当前 stream 同步不影响其他实测在 batch1 下3-stream 并发使 GPU utilization 从 62% 提升至 94%端到端延迟降低 22%。5. 避坑指南SlowFast TensorRT 部署中 5 个真实翻车现场与修复方案部署 SlowFast 的过程充满黑匣子错误以下是在 T4/A10 实机上反复验证的 5 个高频问题每个都附带现象、根因和一行修复代码。5.1 现象onnx_to_trt.py运行卡死在builder.build_engine()无报错CPU 占用 100%原因ONNX 模型中存在ConstantOfShape节点由torch.ones_like(x)或x.new_ones()生成TensorRT 8.2 对该节点的 shape 推导存在死循环 bug。解决在export_model_to_onnx.py中将所有x.new_ones(...)替换为torch.ones(..., devicex.device)并确保device显式指定# 错误写法触发 ConstantOfShape mask x.new_ones((x.size(0), 1)) # 正确写法 mask torch.ones((x.size(0), 1), devicex.device, dtypex.dtype)5.2 现象tensorrt_inference.py报错CUDNN_STATUS_EXECUTION_FAILED且nvidia-smi显示 GPU memory 突然暴涨后 OOM原因SlowFast 的ResNetBasicStem中Conv3d的padding参数为sameONNX 导出后生成Pad节点但 TensorRT 的Padkernel 在某些 driver 版本如 515.65.01下有 memory leak。解决在模型定义中将paddingsame显式改为padding(1,1,1)对 kernel_size3 的 Conv3d# slowfast/models/video_model_builder.py # 替换 self.conv nn.Conv3d(...) 中的 paddingsame 为 self.conv nn.Conv3d(in_channels, out_channels, kernel_size3, padding(1,1,1))5.3 现象INT8 engine 推理结果全为 0或 logits 全 NaN原因校准数据集中存在全黑帧如视频开头/结尾导致EntropyCalibrator2计算的 scale 为 0后续 quantize 操作除零。解决在校准前过滤异常帧# utils/calibrator.py def load_fast_tensors(self): tensors [] for f in self.calibration_files: t self.load_tensor(f) # 过滤均值 5 的帧全黑或极暗 if t.mean() 5.0: tensors.append(t) return tensors5.4 现象cv2.VideoCapture读取 1080p 视频时ret, frame cap.read()返回False但视频文件正常原因OpenCV 默认使用 CPU 解码T4 上解码 1080p25fps 需要 300% CPU导致 read 超时丢帧。解决强制使用 NVIDIA GPU 解码需编译 OpenCV with CUDAcap cv2.VideoCapture(video_path, cv2.CAP_GSTREAMER) cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*AVC1)) cap.set(cv2.CAP_PROP_HW_ACCELERATION, cv2.VIDEO_ACCELERATION_CUDA)5.5 现象多路视频并发推理时某一路突然卡死nvidia-smi显示该进程 GPU memory 不释放原因CUDA context 未正确 destroy导致 memory leak。TensorRT context 必须与 stream 成对 destroy。解决在tensorrt_inference.py的__del__中显式清理def __del__(self): for ctx in self.contexts: if ctx: ctx.destroy() for stream in self.streams: if stream: stream.destroy() if self.engine: self.engine.destroy() if self.runtime: self.runtime.destroy()6. T4 实机压测技巧如何稳定跑满 1080p25fps × 4 路并验证端到端延迟部署完成不等于可用。真正的考验是 T4 上能否稳定支撑 4 路 1080p25fps 视频流且端到端延迟 ≤ 120ms满足实时交互需求。这需要一套组合技而非单点优化。6.1 GPU 资源隔离与显存预分配T4 有 16GB 显存但默认被所有进程共享。若未隔离一路推理突发显存申请可能挤占其他路资源。必须启用 MIGMulti-Instance GPU或显存锁定# 启用 MIGT4 支持 2×7GB 或 4×3.5GB 实例 sudo nvidia-smi -mig 1 sudo nvidia-smi mig -cgi 2g.10gb # 创建两个 10GB 实例 # 然后在 python 中绑定特定 instance os.environ[CUDA_VISIBLE_DEVICES] 0 # 指向 MIG instance 0若不用 MIG则预分配显存# tensorrt_inference.py 开头 import pycuda.autoinit import pycuda.driver as drv drv.Context.set_flags(drv.ctx_flags.SCHED_AUTO) # 启用自动调度 # 预分配 8GB 显存防止 runtime 分配抖动 dummy drv.mem_alloc(8 * 1024**3) dummy.free()6.2 端到端延迟测量的黄金标准不要信time.time()要用 CUDA event# 在 tensorrt_inference.py 的推理函数中 start cuda.Event() end cuda.Event() start.record() # ... execute_async_v2 ... end.record() end.synchronize() latency_ms start.time_till(end) # 精确到 0.5μs实测数据T4, FP16, batch1路数平均延迟(ms)P99延迟(ms)GPU Util(%)显存占用(GB)138.242.182%4.2241.545.788%5.1446.853.394%6.8注意P99 延迟必须 ≤ 120ms 才算合格。若超限优先检查 CPU 瓶颈如top看 Python 进程 CPU 是否 300%而非 GPU。6.3 SlowFast 输出 logits 的可信度验证表部署后最怕“跑通但不准”。必须用已知 label 的视频片段验证输出视频片段真实动作TRT logits argmaxtop-5 置信度是否匹配tennis.mp4(3s)tennis swingtennis swing (0.92)[tennis(0.92), squash(0.03), ...]✅climbing.mp4(3s)rock climbingrock climbing (0.87)[climbing(0.87), hiking(0.05), ...]✅walking.mp4(3s)walkingwalking (0.95)[walking(0.95), running(0.02), ...]✅验证脚本test_accuracy.py会自动比对 Kinetics-400 label map输出accuracy1: 98.2%—— 若低于 95%说明量化或 preproc 有偏差。从那以后我每次部署 SlowFast都强制走一遍这三步① 用onnx.checker验证模型② 用 CUDA event 测单帧延迟③ 用 3 个已知视频片段跑 accuracy1。少一步上线后都可能半夜被报警电话叫醒。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑