
1. 项目缘起与整体设计思路手里这块Jetson Orin NX 16G到手之后我一直在琢磨它的边界到底在哪。官方标称的算力数字看着漂亮但真正做边缘端多模型并行推理的人都知道纸面参数和实际能跑起来的东西之间往往隔着一堆显存碎片、带宽瓶颈和散热降频的坑。这次我给自己定了一个有点极限的目标在同一块Orin NX 16G上同时跑4个YOLOv8模型其中还要包含姿态估计任务看看16G显存到底扛不扛得住以及TensorRT加速之后能到什么程度。先说结论方向免得你看到一半才发现不是自己想要的16G显存跑4个YOLOv8模型是可行的但前提是你得把模型格式、精度、输入尺寸和推理调度这几件事都想清楚。如果你打算直接拿PyTorch的.pt文件往上怼那大概率会在第二个模型加载的时候就OOM。这篇内容适合正在做边缘端多路视觉推理的开发者尤其是手上有Orin NX、AGX Orin或者类似嵌入式平台想搞清楚显存分配和TensorRT部署细节的人。不管你是刚接触Jetson的新手还是已经在上面跑过单模型的老手这里面的配置思路和踩坑记录应该都能帮你省不少时间。我这次测试的4个模型分别是2个YOLOv8n目标检测模型不同输入尺寸、1个YOLOv8s目标检测模型、1个YOLOv8n-pose姿态估计模型。选择这个组合的原因是它比较贴近真实场景——比如一个摄像头做行人检测一个做车辆检测一个做小目标检测再加一个做人体姿态分析。这种多模型并行的需求在智能安防、工业质检、行为分析里非常常见。为什么强调TensorRT因为在Jetson平台上PyTorch原生推理和TensorRT推理之间的差距不是百分之几十而是几倍甚至十几倍。Orin NX的GPU架构对TensorRT的优化支持非常到位尤其是FP16和INT8精度下算力利用率能拉到一个完全不同的水平。但TensorRT也带来了额外的复杂度模型转换、精度校准、动态shape处理、显存池管理这些都是绕不开的。注意TensorRT的版本和JetPack版本强绑定Orin NX上目前主流是JetPack 5.x对应TensorRT 8.xJetPack 6.x对应TensorRT 8.6/10.x。版本选错会导致模型转换失败或者推理结果异常这个后面会详细说。整体设计思路是这样的先用PyTorch导出ONNX再用trtexec或者Python API转成TensorRT engine然后在C或者Python里用CUDA stream做多模型并行推理。显存方面每个engine的显存占用包括权重、激活值、工作空间三部分需要分别估算和实测。最终目标是4个模型同时加载、同时推理总显存控制在16G以内并且留出足够余量给系统和其他进程。2. 环境搭建与TensorRT转换实操2.1 JetPack版本选择与基础环境确认Orin NX 16G出厂一般预装JetPack 5.1.x或者6.x我这次用的是JetPack 6.0对应Ubuntu 22.04、CUDA 12.2、TensorRT 8.6。如果你用的是JetPack 5.x对应Ubuntu 20.04、CUDA 11.4、TensorRT 8.5大部分操作逻辑是一样的但TensorRT 8.5和8.6在API上有一些细微差别后面会提到。先确认基础环境# 查看JetPack版本 cat /etc/nv_tegra_release # 查看CUDA版本 nvcc --version # 查看TensorRT版本 dpkg -l | grep tensorrt # 查看显存 free -h sudo tegrastatstegrastats这个工具非常重要它能实时显示GPU、CPU、内存、显存的使用情况。Orin NX的显存是和系统内存共享的16G所以你在跑模型的时候系统本身也会占用一部分。实测下来Ubuntu桌面环境加基础服务大概占1.5到2G如果你用headless模式可以降到800M左右。实操心得如果你打算做多模型并行强烈建议用headless模式启动关掉桌面环境。省下来的1G多内存对显存余量帮助很大。命令是sudo systemctl set-default multi-user.target重启后生效。2.2 YOLOv8模型导出ONNX的细节Ultralytics的YOLOv8导出ONNX很简单但有几个参数直接影响后续TensorRT转换的成功率和推理精度from ultralytics import YOLO # 导出目标检测模型 model YOLO(yolov8n.pt) model.export(formatonnx, opset12, simplifyTrue, dynamicFalse, imgsz640) # 导出姿态估计模型 pose_model YOLO(yolov8n-pose.pt) pose_model.export(formatonnx, opset12, simplifyTrue, dynamicFalse, imgsz640)这里有几个关键点。opset选12是因为TensorRT 8.6对opset 12的支持最稳定opset 17虽然更新但某些算子转换会出问题。simplifyTrue会用onnx-simplifier做图优化去掉冗余节点对TensorRT转换很有帮助。dynamicFalse表示固定输入尺寸这样TensorRT可以做更激进的优化显存占用也更可控。如果你需要动态batch或者动态尺寸显存会额外增加后面会讲。imgsz640是YOLOv8的默认输入尺寸。我这次测试用了两种尺寸640和416。416的模型显存占用明显更小但精度会下降一些适合对精度要求不那么极致的场景。注意YOLOv8-pose的ONNX输出和检测模型不一样它除了检测框还有17个关键点的坐标。转TensorRT的时候输出层数量不同但转换流程是一样的。2.3 TensorRT engine转换与精度选择转换命令用trtexec最直接# FP16精度转换640输入 /usr/src/tensorrt/bin/trtexec \ --onnxyolov8n.onnx \ --saveEngineyolov8n_fp16_640.engine \ --fp16 \ --workspace2048 \ --verbose # INT8精度转换需要校准集 /usr/src/tensorrt/bin/trtexec \ --onnxyolov8n.onnx \ --saveEngineyolov8n_int8_640.engine \ --int8 \ --calibcalibration_data.npz \ --workspace2048workspace参数控制TensorRT在转换和推理时可以用的最大工作空间单位是MB。2048MB对YOLOv8n来说足够了但如果你转YOLOv8s或者更大的模型建议加到4096。workspace太小的后果是某些层无法用最优kernel推理速度会下降。FP16和INT8的选择是个权衡。FP16精度损失很小YOLOv8n在FP16下mAP下降通常不到0.5个点但显存占用和FP32比直接减半。INT8精度损失更大需要校准集但显存和速度都更优。我这次4个模型全部用FP16原因是姿态估计对精度更敏感INT8下关键点坐标抖动比较明显。转换完成后可以用trtexec做benchmark/usr/src/tensorrt/bin/trtexec \ --loadEngineyolov8n_fp16_640.engine \ --batch1 \ --iterations100 \ --avgRuns10这个命令会输出推理延迟、吞吐量等信息。实测YOLOv8n FP16 640在Orin NX上单次推理大概8到12msYOLOv8s大概15到20msYOLOv8n-pose大概10到14ms。这些数字是单模型独占GPU的情况多模型并行时会因为GPU调度有变化。3. 显存占用实测与多模型并行配置3.1 单模型显存占用拆解先看单个模型的显存占用这是估算总显存的基础。用tegrastats在模型加载后和推理时分别记录模型精度输入尺寸加载后显存推理峰值显存推理延迟YOLOv8nFP16640约320MB约480MB8-12msYOLOv8nFP16416约280MB约380MB5-8msYOLOv8sFP16640约580MB约850MB15-20msYOLOv8n-poseFP16640约350MB约520MB10-14ms这里的“加载后显存”是指engine加载到GPU后权重和基本运行时占用的显存。“推理峰值显存”是加上激活值、工作空间后的峰值。可以看到推理峰值比加载后高出不少这是因为YOLOv8的neck部分在推理时会产生大量中间激活。实操心得TensorRT engine的显存占用不是固定的它和batch size、输入尺寸、workspace都有关。如果你在转换时设了dynamic shape推理时的显存会随实际输入尺寸变化峰值可能比固定shape高30%以上。3.2 4模型并行的显存分配方案4个模型同时加载显存不是简单相加因为TensorRT会复用一些运行时资源。但保守估计还是要按峰值相加再留余量YOLOv8n 640480MBYOLOv8n 416380MBYOLOv8s 640850MBYOLOv8n-pose 640520MB合计约2230MB也就是2.2G左右。加上系统占用的1.5到2G总共不到4.5G。16G显存看起来绰绰有余但实际情况没这么简单。问题出在CUDA context和TensorRT runtime的固定开销上。每个engine在创建ExecutionContext时都会占用一部分显存多个engine之间不能完全共享。实测4个engine同时加载后tegrastats显示的显存占用大概在5.5到6G之间比理论值高了1G多。这部分开销包括CUDA context、cuDNN/cuBLAS句柄、TensorRT的plugin层等。即便如此16G还是够用的。但如果你要加更多模型或者把输入尺寸加大或者用INT8之外的精度余量就会迅速缩小。3.3 多模型推理调度策略4个模型同时加载只是第一步怎么调度推理才是影响实际性能的关键。我试了三种方案方案一串行推理。4个模型依次推理每个模型推理时独占GPU。优点是实现简单显存峰值低。缺点是总延迟是4个模型延迟之和如果每个模型10ms一轮就是40ms帧率只有25FPS。方案二多stream并行。用4个CUDA stream每个模型在自己的stream里推理。理论上GPU可以同时执行多个kernel但实际上Orin NX的GPU核心数有限并行效果取决于模型的计算密度。实测下来4个模型并行推理的总延迟大概在18到25ms之间比串行快了一倍左右但达不到4倍。方案三分时复用。两个模型一组两组交替推理。这种方案适合对实时性要求不极端的场景显存峰值和串行差不多但吞吐量比串行高。我最终用的是方案二用Python的CUDA stream API实现import pycuda.driver as cuda import pycuda.autoinit import tensorrt as trt # 创建4个stream streams [cuda.Stream() for _ in range(4)] # 每个模型绑定一个stream for i, engine in enumerate(engines): context engine.create_execution_context() # 在对应stream里做推理 with streams[i]: context.execute_async_v3(stream_handlestreams[i].handle)注意多stream并行时显存峰值会比单stream高因为多个模型的激活值可能同时存在。实测4stream并行的显存峰值比串行高了约800MB但仍在16G范围内。4. 姿态估计模型的特殊处理与性能调优4.1 YOLOv8-pose的TensorRT转换要点姿态估计模型和纯检测模型在转换时有一个关键区别输出层。YOLOv8-pose的ONNX输出是一个形状为[1, 56, 8400]的张量其中56 4bbox 1conf 17*3关键点x,y,conf。TensorRT在转换时会对这个输出做优化但如果你发现转换后的engine推理结果和ONNX不一致大概率是输出解码的问题。我的做法是在转换前先用onnx-simplifier做一次简化然后在TensorRT里用自定义plugin处理关键点解码。如果不想写plugin也可以在engine输出后再用CPU做解码但会增加延迟。# 姿态估计后处理示例 def decode_pose_output(output, conf_thres0.5): # output shape: [1, 56, 8400] bbox output[0, :4, :] conf output[0, 4, :] kpts output[0, 5:, :].reshape(17, 3, -1) # 过滤低置信度 mask conf conf_thres # ... NMS和关键点解码 return bbox, kpts4.2 姿态估计的显存与延迟实测YOLOv8n-pose在FP16 640下的显存占用和YOLOv8n检测模型差不多但推理延迟略高因为输出层更多后处理更复杂。实测数据模型精度输入尺寸加载后显存推理峰值显存推理延迟YOLOv8n-poseFP16640约350MB约520MB10-14msYOLOv8n-poseINT8640约280MB约400MB7-10msINT8下姿态估计的延迟确实更低但关键点坐标的抖动比较明显。我对比了同一帧图像在FP16和INT8下的输出INT8的关键点坐标偏差最大能到3到5个像素对于需要精确姿态分析的应用来说不太能接受。所以最终姿态估计模型我保留了FP16。实操心得如果你非要用INT8跑姿态估计建议用更多的校准数据并且在校准时重点关注人体姿态相关的样本。校准集的质量直接决定INT8的精度损失。4.3 多模型并行时的GPU资源竞争4个模型并行推理时GPU的计算单元、显存带宽、L2缓存都是共享资源。YOLOv8s的计算量最大它会抢占更多的SM资源导致其他小模型的推理延迟被拉长。实测在4模型并行时YOLOv8n的延迟从单模型的8ms涨到了12到15msYOLOv8s从18ms涨到了25到30ms。缓解这种竞争的方法有几个一是给不同模型设置不同的CUDA stream优先级把实时性要求高的模型放在高优先级stream二是限制每个模型的推理频率比如检测模型30FPS姿态模型15FPS错开推理时间三是用TensorRT的multi-stream execution让TensorRT自己调度。# 设置stream优先级 high_priority cuda.Stream(flagscuda.stream_flags.NON_BLOCKING) # 低优先级stream low_priority cuda.Stream(flagscuda.stream_flags.NON_BLOCKING)5. 常见问题排查与避坑指南5.1 TensorRT转换失败与精度异常问题一ONNX转TensorRT时报错“Unsupported operation”。这通常是因为ONNX里有TensorRT不支持的算子。YOLOv8常见的坑是SiLU激活函数和某些版本的Resize算子。解决办法是升级TensorRT版本或者在导出ONNX时用opset 12并开启simplify。问题二转换成功但推理结果全错。大概率是输出层解析错了。YOLOv8的ONNX输出格式和TensorRT engine的输出格式可能不一样尤其是维度顺序。建议先用trtexec的--dumpOutput参数对比ONNX和engine的输出。问题三INT8精度下降太多。校准集不够代表性或者校准算法选错了。TensorRT默认用entropy calibration对于YOLOv8这种检测模型建议用minmax calibration并且校准集至少500张图。5.2 显存不足与OOM排查问题加载第3个模型时OOM。先确认系统本身占了多少显存。用sudo tegrastats看RAM和GPU的占用。如果系统占了3G以上考虑关掉桌面环境。另外检查每个engine的workspace设置workspace太大会导致加载时显存峰值过高。问题推理时OOM但加载时正常。这是推理峰值显存超了。解决办法是降低输入尺寸、减少batch size、或者用INT8。也可以把部分模型改成串行推理降低同时存在的激活值。问题显存碎片导致无法加载新模型。长时间运行后CUDA显存会产生碎片。解决办法是定期重启推理进程或者用TensorRT的显存池API做预分配。5.3 性能不达预期与调优方向问题4模型并行帧率只有10FPS。先看GPU利用率用tegrastats看GR3D_FREQ。如果GPU利用率已经90%以上说明算力到顶了只能降模型或降精度。如果GPU利用率不高但帧率低可能是CPU后处理成了瓶颈或者数据传输H2D/D2H耗时太多。问题推理延迟波动大。Orin NX的散热设计会影响持续性能。如果散热不好GPU会降频。实测加装散热风扇后持续推理的延迟波动从±30%降到了±10%。问题多模型并行时某个模型特别慢。检查是不是所有模型都在同一个stream里。如果是改成多stream。另外检查模型的输入尺寸大尺寸模型的推理时间会挤占小模型。5.4 常见问题速查表问题现象可能原因排查方法解决方案ONNX转TRT失败不支持的算子查看trtexec日志升级TRT或简化ONNX推理结果异常输出层解析错误对比ONNX和engine输出修正后处理代码加载模型OOM系统占用过高tegrastats查看关桌面环境减小workspace推理时OOM峰值显存超限逐步加载测试降尺寸/精度/改串行帧率低GPU算力到顶查看GR3D_FREQ降模型/精度/频率延迟波动大散热降频监控温度加散热风扇某模型特别慢stream竞争检查stream分配多stream优先级最后分享一个我在实际项目里总结的小技巧如果你不确定16G显存能不能跑下你的模型组合先用trtexec的--loadEngine逐个加载engine用tegrastats记录每个engine加载后的显存增量然后相加再乘以1.3的系数考虑运行时开销和碎片如果结果小于14G基本就稳了。留2G余量给系统和其他进程不要卡得太死。