Skip to main content

假设你有一个带自定义检测器和 TensorRT .engine 的 Jetson 应用。这是大多数 Jetson 团队真正在乎的迁移案例:
简短的答案:
最佳实践:恢复源模型并为 Qualcomm 重建部署工件,而不是把 TensorRT 引擎当成可移植的。
在本文中,假设我们贯穿的案例研究是:一个用于 Jetson 智能摄像头应用中的自定义 YOLO 人脸检测器。在 Jetson 上,它可能通过 PyTorch、Ultralytics、TensorRT 或 DeepStream 运行。在 Dragonwing 上,我们将通过 ONNX、QAIRT/QNN、量化、上下文二进制文件和应用侧后处理来重建部署路径。 在开始之前:

迁移路径

一次实用的自定义模型迁移是一组关口,而不是一条魔法转换命令:
这看起来可能比 Jetson 路径更长,这个差异本身也是产品故事的一部分。最快的 Jetson 路径通常以更少可见的硬件特定阶段到达推理。Qualcomm 的自定义模型路径把更多工作暴露出来:转换、量化、上下文生成、打包和后端验证。 自定义算子和专门的 HTP 工作并不是每个模型的常规前置条件。它们是升级路径:
每一道关口都消除一类故障,但也增加上手时间。这就是先检查 AI Hub 并在移植生产检测器之前先证明一个已知良好模型的原因。

步骤 0:盘点你拥有的东西

先把可移植的源工件与 Jetson 特定的部署工件分开。 有用的工件:
迁移期间需要替换的 Jetson 特定工件:
如果你只有这个:
那么下一步最好的方式是恢复训练检查点或一份中性的导出比如 ONNX。 那是最重要的迁移关口:

步骤 1:恢复并导出源模型

对于 YOLO 检测器,从训练检查点开始:
在禁用 NMS 的情况下导出静态 ONNX:
对于 HTP/NPU 部署,静态形状是最简单的起点。对于一个标准的 YOLO 图像模型,这通常意味着:
一开始把 NMS 排除在导出的图之外。在应用中控制后处理更容易,尤其是当你需要比较 CPU、GPU 和 HTP 输出时。 导出后,在动 Qualcomm 工具之前先验证 ONNX:
然后用 ONNX Runtime FP32 创建一个黄金输出。保存这个输出。你将把之后的每个阶段与它对比。 如果 ONNX 导出或验证失败,请先修复它,再去动 QNN: 最短原则:先让 PyTorch 和 ONNX Runtime 匹配。QNN 无法修复一个糟糕的 ONNX 导出。

步骤 2:先检查 AI Hub

在构建自定义转换流水线之前,检查你的模型架构在 AI Hub 中是否已存在。 对于 YOLO 类模型:
当前 Dragonwing 文档以 AI Hub IoT 模型目录为模型可用性的最新来源。它包括对 LLM、VLM、检测、分割、分类、嵌入、音频、深度、恢复和机器人的已验证覆盖。 如果你的确切模型不可用,一个兼容的架构可能仍然有用:
这能避免同时调试转换和应用集成。

步骤 3:把 ONNX 转换为 Qualcomm 工件

Qualcomm QAIRT/QNN 转换与 trtexec 的形态不同。 Jetson 常常感觉像一步构建:
Qualcomm 把工作分为两条通道之一:
公共的 Dragonwing 文档常展示本地 QNN 路径为 qnn-onnx-converter 后接 qnn-model-lib-generator。AI Hub 和某些 SDK 流程可能给你的是 DLC。请使用与你 SDK 版本匹配的流程,并用 --help 验证准确的标志。 本地 QNN 模型库流程:
现在在主机 CPU 上验证转换后的 FP32 工件。这能在量化引入噪声之前捕捉到转换问题。
建议的关口:
如果失败,我们建议先修复导出、图形状、预处理或不受支持算子的问题,再进行量化。

步骤 4:处理不受支持的算子

这是 Qualcomm 上手差距可能变得可见的地方。不要把它藏在一条通用的”调试转换器”指令后面。记录算子、图中位置、受影响的后端和回退成本;然后按以下顺序处理: 如果转换器拒绝了某个算子,请按以下顺序:
当不受支持的算子有等价的受支持算子时,图重写通常是最好的第一次尝试。自定义算子工作量更大但能让模型保留在预期的后端上。CPU 回退对于低频或非关键部分可以接受,但要记录延迟代价。 我们的最佳实践是在选择修复方式之前对失败进行分类。 有用的失败分桶: 对于 Z1_UNCLASSIFIED,最安全的路径是在升级之前保存日志、模型、输入、输出和工具版本。这为真正的修复保留了证据。

步骤 5:使用真实校准数据进行量化

如果你想要高效的 HTP/NPU 执行,量化是核心。 TensorRT 校准缓存在这里没有用。它是 TensorRT 特定的。为 Qualcomm 重新校准。 对于 YOLO 人脸检测器,使用来自目标环境的真实图像:
一个小型初始校准集可能是 64-200 张图像。如果图像错过了真实的工作条件,更多不会自动更好。有代表性胜过随机。 在公共的 QNN 转换器流程中,静态量化通常通过向 qnn-onnx-converter 传递 --input_list calibration_input_list.txt 来完成,然后用 qnn-model-lib-generator 编译生成的图:
如果 AI Hub 或你的 QAIRT SDK 给你的是量化后的 DLC,请保持同样的验证关口,并用 libQnnModelDlc.so 把该 DLC 送入上下文二进制步骤。 来自当前文档的重要陷阱:
a8w16 不是有效的 HTP 模式。请使用 a8w8、a16w8、a16w16 或 fp16。
如果准确率下降,请使用重试梯度而不是随机改标志: 使用第一个通过你准确率 SLA 的策略。

步骤 6:构建上下文二进制文件

QNN 上下文二进制文件是这条路径的目标部署工件。 先确定目标 SoC:
从与你确切模型和目标匹配的 SDK 示例中设置 dsp_arch、VTCM 和其他后端选项;未经验证不要跨 SoC 复制调优值。在已发布的命令中,包含经过测试的 context_config.json 或指引读者到该开发板使用的确切 SDK 示例配置。 构建上下文二进制文件:
如果你的输入是 AI Hub 或 DLC 工件,请使用 SDK 的 DLC 模型加载路径,通常是 --model libQnnModelDlc.so --dlc_path out/best_a8w8.dlc,配以相同的后端和配置。 预期输出:
这个工件对目标敏感。当你更换 SDK、BSP、SoC、精度或模型时都要重建。 如果上下文生成失败,先检查小的地方:

步骤 7:部署运行时包

一次部署不仅仅是模型文件。它还需要兼容的运行时库和配置。 至少期望一个像这样的包:
验证你部署到设备的是 ARM64 库:
你想要的是:
复制该包:
对 Linux 目标使用 scp。把 adb push 保留给 Android 目标。

步骤 8:在设备上运行推理

在设备上:
保持 backend_ext.json 最小化:
保持顶层的后端扩展文件与你已安装的 QAIRT 版本所示的 schema 一致。把图和设备调优放入被引用的 HTP 配置文件,而不是在 backend_extensions 旁边新造同级键。从匹配的 SDK 示例开始,并逐步添加设置。

步骤 9:对照正确的基线进行验证

始终对照 ONNX Runtime FP32 黄金基线,而不是 TensorRT FP16。 为什么?TensorRT 的输出已经包含了 NVIDIA 特定的图变换和精度行为。中立的参考是源模型的导出。 建议的验证关口: 一个小的比较辅助脚本:
对于目标检测,张量相似度不够。你还需要任务级指标:

步骤 10:把 YOLO 后处理移入应用

Jetson 的 Python 示例常常把 YOLO 解码和 NMS 隐藏在 Ultralytics 之后。一旦你导出和部署,你可能会收到原始张量。 让后处理显式化:
对于只有人脸的模型,保持简单。只有一个类别。只有在你真的需要时,通用的 COCO 后处理器才值得加入。 同时锁定预处理:
预处理不匹配可能看起来像量化问题。在指责加速器之前先对预处理进行数值验证。

步骤 11:只有在功能验证之后再做基准测试

先证明 HTP 工作:
然后带剖析运行:
在主机上解析:
查看加速器执行时间、CPU 回退层和瓶颈算子。如果模型延迟好但应用 FPS 差,瓶颈在推理之外。 捕获内存变化量而不是仅仅原始峰值:
然后在推理期间测量并报告与空闲的差值。

YOLO 迁移的基准测试计划

在两台设备上使用相同的输入片段、预处理契约、后处理阈值和准确率集。在做出平台声明之前,请捕获这个矩阵: 如果你引用外部基准数字,请把它们标为源文档参考,并与你自己的设备结果分开。基准方法比借用的头条数字更重要。

常见陷阱


这次迁移真正改变了什么

应用目标保持不变:
部署机制变化了:
最大的思维转变是模型部署变成了一条分阶段验证的流水线。每个阶段都有工件和关口。这不是仪式。这是你避免同时调试量化、算子支持、预处理、运行时库和应用代码的方式。 在下一篇文章中,我们将聚焦最被低估的阶段:量化。那是准确率退化通常出现的地方,是校准数据质量重要的地方,也是”能跑”和”能交付”之间的差异变得明显的地方。