硬件层面最让我满意的一点,是NX系列对板级AI算力的定位非常明确。它不像台式机显卡那样动辄几百瓦的功耗,Jetson Xavier NX满载大概15瓦到20瓦,却提供了大约21 TOPS的INT8算力;后来的Jetson Orin NX 8GB在相近功耗下能干到100 TOPS级别。这个能效比意味着你可以在小车、机械臂、无人零售柜旁边直接跑目标检测,不用往数据中心搬数据,也不用忍受公网传输延迟。我这次用的就是Xavier NX,8GB内存版本,刷的是JetPack 5.1,后面讲的很多命令和参数在Orin NX上同样适用,只是软件版本和推理速度有差异。
整条部署链路我提炼一下就四步:刷机装系统,配置推理环境,把模型从PyTorch导出成ONNX再转成TensorRT引擎,最后写一个推理程序跑摄像头实时检测。你不需要在NX上训练模型,那既慢又折磨人。模型在PC上训练好,直接把权重文件拷过来,NX只负责高效推理,这才是边缘设备的正确用法。整个文章我会按实操顺序来,尽量把每个环节的坑都标出来,适合刚接触嵌入式AI的新手,也适合想从PyTorch迁移到TensorRT的老手。
1. 项目概述与整体设计思路
1.1 为什么选英伟达NX开发板
目标检测部署在边缘设备上有很多选择,树莓派能跑,Jetson系列也能跑,一些国产的RK3588、算能盒子同样可以。但如果你想要一套生态成熟、踩坑资料多、踩坑之后还能顺利爬出来的方案,NX系列是阻力最小的一条路。
先看一组对比,我用实际体验和公开数据整理过:
| 设备 | AI算力 | 内存 | 峰值功耗 | 部署难点 |
|---|---|---|---|---|
| 树莓派4B | 约0.1 TOPS | 4-8GB | 约7W | CPU推理太慢,实时性差 |
| Jetson Xavier NX | 21 TOPS | 8GB | 约15-20W | 环境配置稍复杂 |
| Jetson Orin NX 8GB | 100 TOPS | 8GB | 约10-25W | 价格偏高 |
| 便携式笔记本 | 几十到几百TOPS | 16GB以上 | 45W以上 | 体积大、功耗高、不嵌入式 |
Xavier NX虽然已经算不上最新,但胜在成熟稳定,二手价格也降下来了。它搭载的Volta架构GPU原生支持TensorCore和INT8推理,TensorRT能把这些算力吃得很透。Orin NX则是未来两年内更值得选的方案,Ampere架构在Transformer类模型上也更友好,如果你想在上面跑最新的大模型变体或者Vision Transformer,Orin NX的下限更高。
选NX还有一个隐藏优势,NVIDIA把JetPack这个打包好的SDK做得很完整,CUDA、cuDNN、TensorRT、OpenCV全部预编译进去,你不需要像在树莓派上那样一个个源码编译,省下来的时间非常可观。
1.2 目标检测算法选型
目标检测的算法谱系很长,从早期的Faster R-CNN到后来的SSD,再到YOLO系列和DETR系列,部署难度和精度表现差异巨大。在开发板这类嵌入式设备上,我首选还是YOLO系列。
原因有三。第一,YOLO是单阶段检测器,没有RPN那种多阶段流程,推理时特征提取一次、回归和分类各一次,结构上更适合TensorRT这种编译型优化。第二,社区生态太好了,导出ONNX的脚本、TensorRT推理模板、各种预训练权重到处都是,遇到问题搜索一下就有答案。第三,模型体积可以按算力量身定制,从YOLOv5n到YOLOv5x,参数量差一个数量级,开发板算力强就上大模型,算力弱就上小模型,取舍空间很大。
我这次用的是YOLOv5s,输入分辨率640x640,参数量大概7.2M,在Coco数据集上mAP 50-95约37左右。这个精度在边缘端做人员检测、车辆检测、安全帽检测这类任务完全够用。如果你要追求更高精度,可以换成YOLOv5m,代价是推理时间大概翻倍;如果精度要求不高但需要跑更高帧率,YOLOv5n会更合适。
1.3 整体部署链路
很多新手第一次接触边缘部署时,容易把注意力全放在“怎么把算法跑起来”上,结果陷在环境依赖里出不来。正确的方式是先站在高处把链路画出来,明确每一步的输入输出,然后再动手。
整条链路是这样的:
第一步,模型训练。在PC上用PyTorch训练YOLOv5,得到.pt权重文件。这个文件里包含了网络结构和参数。
第二步,格式转换。把.pt转成.onnx,ONNX是一个模型交换格式,它把PyTorch的动态图结构固化成静态计算图,这样才能被TensorRT解析。
第三步,引擎优化。将ONNX交给TensorRT进行层融合、精度校准、内存复用,生成一个高度优化过的.engine文件。这个文件是针对当前设备、当前TensorRT版本、当前精度模式生成的,换一台机器或者换一个JetPack版本都要重新生成。
第四步,推理部署。写一个Python或者C++程序加载.engine文件,把输入图像做预处理,送入引擎推理,再对输出做后处理,得到检测框并显示或传输。
我见过不少人跳过第二步和第三步,直接用PyTorch在开发板上推理,结果帧率只有两三帧,然后就开始怀疑硬件性能不行。实际不是硬件不行,而是软件没有做针对性优化。
2. 环境准备与硬件初始化
2.1 硬件清单与刷机操作
这次部署我准备了这么一套硬件:
- Jetson Xavier NX开发板(核心板加底板),8GB版本
- 64GB以上microSD卡,建议用A2级别,读写速度直接影响系统流畅度
- 5V 3A或者5V 4A电源适配器,具体要看底板供电要求
- USB摄像头或者CSI摄像头,我用的是一个普通免驱USB摄像头
- Type-C数据线,用来做烧录和调试
刷机有两个主流方案。方案一是用NVIDIA官方SDK Manager,它会通过USB连接开发板,把Ubuntu系统刷到板载存储或者SD卡里。方案二是直接把官方镜像写到SD卡,开发板跳过SDK Manager启动。我这次用的是方案二,因为不用额外准备一台装了Ubuntu的PC,SD卡插上就能用。
具体步骤不复杂,把官方L4T系统镜像下载下来,用balenaEtcher这个工具把.img文件写入SD卡。写完之后插卡接显示器,上电开机,第一次启动会进入系统配置界面,设置用户名、密码、时区这些。等它自动完成安装,你就能看到一个精简版的Ubuntu桌面了。
刷机这里有个很典型的坑,SD卡写入速度慢会直接导致系统启动后异常卡顿,开个终端都要等半天,很多人误以为是开发板坏掉了。另外如果你用的是EMMC版本NX,那就必须老老实实用SDK Manager通过网络刷机,方式完全不同。
2.2 系统基础配置与环境变量
系统起来之后,第一件事不是急着装机器学习库,而是先把基础环境理顺。
查看当前JetPack版本、L4T版本以及系统架构,用一条命令就能搞定:
sudo apt update sudo apt upgrade dpkg-query --show nvidia-l4t-core如果你拿到的是带完整JetPack的官方镜像,CUDA、cuDNN、TensorRT其实已经装好了,只是它们不在默认的/usr/local路径下,也不在系统环境变量里,需要手动配置。
我在~/.bashrc里添加了这么几行:
export PATH=/usr/local/cuda/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH export CUDA_HOME=/usr/local/cuda添加之后执行source ~/.bashrc,然后运行nvcc -V,看到CUDA版本信息就说明环境变量生效了。
Xavier NX的Ubuntu系统是aarch64架构,这跟你平时用的x86_64 PC架构不一样,很多预编译的Python包根本没有aarch64版本,所以后面装PyTorch的时候千万别直接pip install torch,那样大概率会提示找不到匹配的安装包,或者给你装一个CPU版本,没法利用GPU算力。
2.3 Python虚拟环境与JetPack自带依赖
我建议在开发板上用一个独立的Python虚拟环境,避免把系统Python搞乱。JetPack 5.1自带的是Python 3.8,可以参考下面这种方式创建虚拟环境:
sudo apt install python3-venv python3-pip mkdir ~/yolo && cd ~/yolo python3 -m venv venv source venv/bin/activate虚拟环境里安装pip包之前,先升级pip,不然会遇到一些旧版本的依赖解析问题:
pip install --upgrade pip setuptools wheel接下来最关键的一步,安装PyTorch。不能直接pip install,而是要下载NVIDIA官方为Jetson platform编译好的PyTorch轮子。它们的命名类似torch-1.14.0a0+44dac51c.nv22.09-cp38-cp38-linux_aarch64.whl,对应的Python版本是3.8,aarch64架构。这个轮子可以在NVIDIA官方L4T PyTorch发布页找到。
安装命令是:
pip install torch-xxx-cp38-cp38-linux_aarch64.whl装完之后验证一下GPU能否使用:
python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())这里有个小细节,Jetson上的torch.cuda.is_available()不会只返回True,它还会打印一行“True”和warn信息,提示TensorRT可以通过torch的扩展使用。看到True就说明CUDA引擎能用了。
3. 模型选型与部署方案设计
3.1 训练与推理的职责分离
很多从零开始的人最大的误区,是打算直接在开发板上又训练又推理。开发板确实能训练小模型,但训练耗时是PC的数倍,而且内存吃紧,一个YOLOv5s训练任务可能直接让系统OOM。
我的建议非常明确,训练放到PC或者云服务器上完成。开发板只做一件事:加载训练好的权重,完成前向推理。这也是边缘计算的主流架构,模型在云端生成,在边缘分发执行。
如果你没有自己的训练数据,想先跑通流程,直接下载官方预训练权重就行。YOLOv5官方仓库在GitHub上,直接用git clone拉取代码,然后下载yolov5s.pt这个权重文件。
3.2 推理引擎选型对比
同样的模型在NX上有几种跑法,性能差异很大。我实际测试过三种方案,对应数据供参考:
| 推理方式 | 单帧耗时(YOLOv5s,640分辨率) | 说明 |
|---|---|---|
| 纯PyTorch推理 | 200ms以上 | 状态较差,CPU占满 |
| ONNX Runtime CPU | 150ms左右 | 无GPU加速,无法发挥NX算力 |
| ONNX Runtime GPU | 60ms左右 | 有一定提升,但仍有优化空间 |
| TensorRT FP16 | 40ms左右 | 利用TensorCore,融合算子、减少显存占用 |
| TensorRT INT8 | 20ms左右 | 需要校准数据集,精度会小幅下降 |
从这个表格能清晰看出来,TensorRT FP16是性价比最高的选择,精度几乎无损,速度却比PyTorch翻了四五倍。INT8极限更快,但要处理精度衰减问题,我在后面的章节会单独说。
3.3 TensorRT的优化原理是什么
你可能好奇,TensorRT凭什么能把同一套模型跑得比PyTorch快那么多?我用大白话解释一下。
PyTorch的推理过程是把模型当做一个一个算子按顺序执行的,GPU每次启动一个kernel处理一个层,算完再进入下一个层,层与层之间很多临时数据都要反复读写显存。TensorRT不一样,它会先对整个模型的计算图做静态分析,然后把能合并的层合并掉。比如卷积层后面的批归一化层、ReLU激活层,正常情况下是三个独立的计算步骤,但TensorRT会把这几个层融合成一个kernel,一次计算直接输出最终结果。这就像你做菜,正常流程是洗菜切菜炒菜分开干,TensorRT则是把几个能一起处理的步骤合并,中间省掉了“把菜装盘端来端去”的过程。
另外TensorRT还会为每层自动选择最适合GPU的kernel实现,根据输入尺寸、显存带宽、算子组合条件去自动调优。这些都是标准运行时做不到的。
4. 核心实操:TensorRT模型转换与加速部署
4.1 导出ONNX文件
先进入YOLOv5目录,激活虚拟环境,然后用官方脚本导出ONNX。YOLOv5的export脚本封装得很完善,基本一条命令就能搞定。
source ~/yolo/venv/bin/activate cd ~/yolo/yolov5 python export.py --weights yolov5s.pt --include onnx --img 640 --batch 1 --opset 13解释一下几个关键参数。--weights指定权重文件;--img指定导出模型的输入分辨率,需要和后续TensorRT构建时的输入尺寸保持一致;--batch这个参数很关键,如果设为1,导出的ONNX是固定batch的静态模型,后续TensorRT只能一次处理一张图;如果省略batch参数或者用--dynamic,导出的是动态维度模型,后续可以在TensorRT构建时指定动态batch范围。
导出完成后会生成yolov5s.onnx文件,可以用onnxruntime再验证一遍模型能否正常推理,确认之前导出的计算图没有问题。
如果这一步报错说缺少onnx包,用pip install onnx onnxruntime装上就行。
4.2 使用trtexec将ONNX转成TensorRT引擎
ONNX是给人友好的中间格式,TensorRT引擎才是给硬件跑的最终形态。NX上JetPack自带了trtexec这个命令行工具,它是我见过最省事的TensorRT工具,比写一堆Python脚本转换要直观得多。
通过以下命令生成FP16引擎:
/usr/src/tensorrt/bin/trtexec \ --onnx=yolov5s.onnx \ --saveEngine=yolov5s_fp16.engine \ --fp16 \ --workspace=4096 \ --minShapes=images:1x3x640x640 \ --optShapes=images:1x3x640x640 \ --maxShapes=images:8x3x640x640如果导出ONNX时用了固定batch,那么minShapes和maxShapes可能不需要传;如果你导出时用了动态shape,这三个参数就必须一起传。workspace参数控制构建引擎时可用的显存上限,单位是MB,设得大一点能让TensorRT有更多空间做层融合优化,我习惯设4096。
构建过程会打印一长串日志,重点看最后几行,里面有网络吞吐量数据,比如Throughput。这个数字可以用来预估推理性能。
产出yolov5s_fp16.engine之后,部署文件就有了。它在Xavier NX上只能由当前JetPack版本的TensorRT使用,跨设备跨版本都要重新构建。
4.3 Python方式构建引擎
如果你不想用命令行,或者需要在程序里动态构建引擎,也可以用TensorRT的Python API。下面这段代码可以复用到大多数TensorRT部署项目里,它的作用是加载ONNX、配置builder、静态生成FP16引擎并保存到磁盘:
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("yolov5s.onnx", "rb") as f: parser.parse(f.read()) config = builder.create_builder_config() config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 4 << 20) # 4GB(注意单位是字节) config.set_flag(trt.BuilderFlag.FP16) engine = builder.build_serialized_network(network, config) with open("yolov5s_fp16.engine", "wb") as f: f.write(engine)注意set_memory_pool_limit的参数单位是字节,我用4 << 20这个写法容易误导人,其实那是4MB。要设4GB应该写4 << 30。所以这里提醒一下:真的想设4GB,请写4 << 30。
这段代码在转换一些结构特殊的模型时可能会报错,比如某个算子TensorRT不支持。遇到这种情况,要么升级ONNX算子集,要么手动把模型里的某些层替换成TensorRT兼容的等价实现。
4.4 加载TensorRT引擎进行推理
引擎文件生成之后,剩下的就是推理代码了。TensorRT的推理流程和普通深度学习框架有些区别,它需要你自己管理显存中的输入输出缓冲区。
下面这段代码是我在Xavier NX上实际验证过的最小推理流程,它加载引擎,准备一个随机输入,执行一次前向推理,打印输出形状。把它改造成真实图像输入时,只要把decode_image函数换成你自己的图像读取和预处理即可。
import numpy as np import tensorrt as trt import pycuda.driver as cuda import pycuda.autoinit class TRTInference: def __init__(self, engine_path): self.logger = trt.Logger(trt.Logger.WARNING) with open(engine_path, "rb") as f, trt.Runtime(self.logger) as runtime: self.engine = runtime.deserialize_cuda_engine(f.read()) self.context = self.engine.create_execution_context() self.stream = cuda.Stream() self.allocate_buffers() def allocate_buffers(self): self.inputs = [] self.outputs = [] self.bindings = [] for i in range(self.engine.num_io_tensors): name = self.engine.get_tensor_name(i) dtype = trt.nptype(self.engine.get_tensor_dtype(name)) shape = self.engine.get_tensor_shape(name) size = trt.volume(shape) host_mem = cuda.pagelocked_empty(size, dtype) device_mem = cuda.mem_alloc(host_mem.nbytes) self.bindings.append(int(device_mem)) if self.engine.get_tensor_mode(name) == trt.TensorIOMode.INPUT: self.inputs.append((name, host_mem, device_mem, shape)) else: self.outputs.append((name, host_mem, device_mem, shape)) def __call__(self, input_np): input_name, host_mem, device_mem, shape = self.inputs[0] host_mem[:] = input_np.reshape(-1) cuda.memcpy_htod_async(device_mem, host_mem, self.stream) self.context.execute_async_v2(bindings=self.bindings, stream_handle=self.stream.handle) cuda.memcpy_dtoh_async(self.outputs[0][1], self.outputs[0][2], self.stream) self.stream.synchronize() output = self.outputs[0][1].copy().reshape(self.outputs[0][3]) return output这里我刻意没有做后处理,因为YOLOv5的输出包含多个尺度的特征图,需要经过解码、过滤低置信度框、NMS这些步骤,逻辑比较复杂。实际项目中你可以直接用YOLOv5仓库里的detect.py,然后把它里面的模型加载部分替换成TensorRT引擎,这样后处理代码可以全部复用。
4.5 INT8量化初体验
如果你希望推理速度更进一步,可以尝试INT8。TensorRT的INT8不是简单把权重转换成INT8就行,它需要校准数据集,统计真实输入在每一层上的激活值范围,然后再把FP16或者FP32的模型映射到INT8。
YOLOv5提供了现成的PyTorch左右量化方案,但如果你走TensorRT,用Python写校准器会多一点。大致流程是:
- 准备几十张到一百张没有标注的图片作为校准集,推荐和推理场景的图片分布接近
- 实现一个calibrator类,由TensorRT的
IInt8Calibrator继承 - 构建builder时加上
config.set_flag(trt.BuilderFlag.INT8)并传入calibrator - 构建完成后保存engine
INT8带来的性能提升是实打实的,我在Xavier NX上测试YOLOv5s,FP16大概40ms一帧,INT8能压到20ms左右。代价是模型精度可能波动,有些人脸检测、车牌识别这种要求高的场景,INT8的漏检率会上升。我的建议是先在PC上评估INT8模型的mAP下降幅度,再决定是否要在设备上使用。
5. 实时推理与性能调优实战
5.1 接入摄像头实现视频流检测
模型能跑单张图只是第一步,真正落地还要接视频流。在NX上接入USB摄像头最直接的方式是OpenCV的VideoCapture。JetPack自带OpenCV,但它是为aarch64优化的版本,解码性能比通用版本好一些。
测试代码非常简单:
import cv2 cap = cv2.VideoCapture(0) if not cap.isOpened(): print("camera open failed") exit() while True: ret, frame = cap.read() if not ret: break # 将frame送入TRTInference做检测 # 绘制检测框后显示 cv2.imshow("result", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()如果你用的是CSI摄像头,那就没法直接用VideoCapture,需要走GStreamer pipeline。JetPack里的gst插件已经支持CSI摄像头了,pipeline大致长这样:
gst-launch-1.0 nvarguscamerasrc ! 'video/x-raw(memory:NVMM),width=1280,height=720,framerate=30/1' ! nvvidconv flip-method=2 ! videoconvert ! appsink把这条pipeline传给OpenCV的VideoCapture,就能在应用里读取CSI摄像头帧了。这里flip-method参数用来做画面翻转,不同的摄像头安装角度需要对应不同的值。
5.2 性能瓶颈分析与调优手段
一帧一帧跑通之后,就要关注性能了。我在Xavier NX上对YOLOv5s做过一轮完整调优,按下面的顺序逐步排查:
模型推理耗时是最大头。先用trtexec测试引擎纯推理时间,比如40ms。如果这个数字就很高,说明模型尺寸或者精度配置需要调整,后处理怎么优化都没用。
预处理耗时。图像从BGR转RGB、从HWC转CHW、缩放填充到640x640,这些操作如果全用Python纯循环做,耗时可能超过20ms。正确做法是先用OpenCV把图像resize到640,再做数据类型转换和归一化,最后用numpy做transpose和copy。这一步在GPU上其实就是一次memcpy和一次算子调用,不会太慢。
后处理耗时。NMS如果实现得不好,对小目标多的场景可能很慢。可以把置信度阈值调高一点,比如0.25以上,需要过滤的候选框少了,NMS自然就快。
除了代码层面,NX还有硬件层面的性能开关。nvpmodel命令用来切换CPU/GPU的电源模式,我一般会切到最高性能模式,模式编号要查你当前JetPack版本的支持列表。jetson_clocks命令用来固定CPU和GPU频率,跑实际性能测试时建议打开它,不然频率自动调节会让测试数据忽高忽低。
我实测过一组优化前后的数据,很有参考价值:
| 优化阶段 | 单帧总耗时 | 帧率 |
|---|---|---|
| 初始版本(PyTorch+无优化) | 220ms | 约4FPS |
| TensorRT FP16+固定输入 | 55ms | 约18FPS |
| 优化预处理+调高置信度阈值 | 48ms | 约20FPS |
| INT8模型 | 25ms | 约40FPS |
在Xavier NX上用YOLOv5s做到40FPS,实际项目中已经很能打了。如果你换Orin NX,同一套代码性能还能再翻一倍以上。
5.3 把自己的检测逻辑封装成服务
做完一帧帧的检测,最后要落地成一个可对外调用的服务。最轻量的方式是写一个HTTP接口,用Flask把TensorRT推理封装起来。这样另一个进程、另一台设备、甚至一个Web页面都能通过HTTP调用检测能力。
核心代码非常短:
from flask import Flask, request, jsonify import numpy as np import base64 import cv2 app = Flask(__name__) trt_infer = TRTInference("yolov5s_fp16.engine") @app.route("/detect", methods=["POST"]) def detect(): data = request.json img_b64 = data.get("image_base64", "") img_bytes = base64.b64decode(img_b64) img_np = np.frombuffer(img_bytes, dtype=np.uint8) img = cv2.imdecode(img_np, cv2.IMREAD_COLOR) # 预处理+推理+后处理 results = trt_infer.detect(img) return jsonify({"boxes": results}) if __name__ == "__main__": app.run(host="0.0.0.0", port=8080)如果你想让这个服务在开发板开机时就自动运行,可以写一个systemd服务文件,把启动命令指向这个Flask应用。这样开发板每次重启,检测服务都能自动拉起来,不用手动登录再执行脚本。这个设计我在实际项目中用了很久,稳定性很好。
6. 常见问题与排查技巧实录
6.1 新手最容易踩的五个坑
第一次在NX上部署模型的人,遇到的问题通常集中在几个固定方向上,这里按出现频率从高到低列一下。
第一个坑是内存不足。Xavier NX只有8GB统一内存,CPU和GPU共享。跑检测服务还好,但如果你同时开启桌面环境、浏览器、多个终端、TensorRT构建引擎,内存分分钟被打满。解决办法是构建引擎时关掉图形桌面,只用文本模式,通过SSH操作。另外可以设置一个swap文件,让系统内存不够时用SD卡或SSD顶一顶。
第二个坑是TensorRT引擎跨版本不可用。你在PC上生成一个engine,拷到NX上加载,十有八九报错。NVIDIA的优化策略是把引擎和GPU架构、TensorRT版本强绑定,别人给不了你,你也给不了别人。遇到这个情况别慌,删掉engine,在NX上用trtexec重新生成一份。
第三个坑是Python版本混用。系统自带Python 3.8,如果你自己又装了Miniconda或者从源码编译Python 3.10,很容易出现pip安装在解释器A里、运行时却用解释器B调用的情况,最终报一些看不懂的缺失模块错误。我的习惯是全程用虚拟环境,并且在实际运行之前打印sys.executable确认解释器路径。
第四个坑是YOLO后处理和TensorRT输出组织方式不一致。YOLOv5在PyTorch推理时输出已经做了解码,可以直接得到框坐标;但TensorRT原样输出的是模型的原始feature map,需要自己解码。如果没理清楚,很容易把检测结果画得满屏都是错框。YOLOv5仓库的detect.py里这段逻辑可以直接参考,不要自己硬写。
第五个坑是把编译类任务都压在开发板上做。如果你需要从源码编译某个依赖,建议先看看有没有aarch64的wheel包,比如pycuda这种,就用pip装预编译的。如果必须源码编译,尽量在测试机或者CI服务器编译好再拷贝过去。
6.2 常见问题速查表
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
CUDA error: no kernel image available | 当前PyTorch版本和数据格式不匹配 | 重新安装NVIDIA官方为Jetson编译的PyTorch轮子 |
engine file not found | 路径写错或engine未生成 | 用绝对路径,先用trtexec验证 |
Cannot allocate memory | 内存不足,尤其是构建引擎时 | 关闭图形界面,增加swap,减少workspace |
onnx: OpenGL is not supported | OpenCV显示问题,不是核心错误 | 改用SSH模式测试,用imwrite保存结果 |
| 推理速度比预期差很多 | CPU锁频或者电源管理模式不对 | 运行nvpmodel -q查看模式,改用性能模式,再配合jetson_clocks |
| 摄像头打开失败 | 被其他进程占用或驱动问题 | 拔插USB,检查ls /dev/video*,关闭桌面预览程序 |
6.3 我的独家避坑经验
最后分享几条我踩过很多次之后才总结出来的经验。
第一条,开发板上尽量少用容器化方案。虽然热词里大家都在聊docker,NX也支持docker,而且NVIDIA有l4t容器专门用来跑PyTorch和TensorRT。但我个人觉得在8GB内存的Xavier NX上跑容器,资源损耗和依赖冲突的排查成本都不低,还不如直接使用实际环境来得靠谱。Orin NX内存普遍是16GB起步,容器化体验会好一些,那时再上docker也不迟。
第二条,模型转换失败时先不要怀疑是TensorRT的问题,先回退一步,用onnxruntime做一次推理,确认ONNX本身的输出是正确的。如果ONNX输出有问题,那就是PyTorch导出环节的锅,跟TensorRT无关。这个排查思路能帮你把问题快速隔离在一个环节里,不会陷入无休止的调试循环。
第三条,构建引擎或者做跑分时,一定要开jetson_clocks,同时把CPU频率和GPU频率拉满。否则系统降频会导致测试数据波动很大,本来能跑30FPS的场景,可能因为一次降频变成20FPS,你还满世界找代码漏洞。性能调优的前提是硬件状态可复现。
第四条,如果你要长时间运行检测服务,给开发板配一个主动散热风扇比什么优化都管用。Xavier NX在高负载下芯片温度很容易升到85度以上,过热降频是性能杀手。我在室外场景部署的时候吃了这个亏,后来换了金属外壳加风扇,温度控制在70度以内,才勉强谈得上稳定性。
就我个人的感受来说,用英伟达NX开发板部署目标检测算法这件事,难点从来不在“模型怎么训练”上——那是PC上的老本行,而在于把一个训练好的模型折腾成在这个小功耗设备上每秒跑几十帧的高效引擎。这个过程会逼着你把模型计算图、算子的GPU执行方式、图像预处理这些底层细节全部过一遍,等彻底跑通之后,你对深度学习框架的理解会完全不一样。如果这篇记录能帮你少熬一两个通宵,那它就是有价值的。最后再啰嗦一句,万事开头难,第一次刷机失败、第一次构建引擎报错都非常正常,你只需要保持耐心,把每一步的日志和状态记清楚,碰到问题就知道往哪个方向查。