Skip to main content

Assume you have a Jetson app with a custom detector and a TensorRT .engine. This is the migration case most Jetson teams actually care about:
The short answer:
Best practice: recover the source model and rebuild the deployment artifact for Qualcomm instead of treating the TensorRT engine as portable.
For this post, assume the running case study: a custom YOLO face detector used inside a Jetson smart-camera app. On Jetson, it may run through PyTorch, Ultralytics, TensorRT, or DeepStream. On Dragonwing, we will rebuild the deployment path through ONNX, QAIRT/QNN, quantization, a context binary, and app-side postprocessing. Before you start:

The migration path

A practical custom model migration is a set of gates, not one magic conversion command:
That may look longer than the Jetson path, and that difference is part of the product story. The fastest Jetson path often reaches inference with fewer visible hardware-specific stages. Qualcomm’s custom-model path exposes more of the work: conversion, quantization, context generation, packaging, and backend validation. Custom operators and specialized HTP work are not routine prerequisites for every model. They are escalation paths:
Each gate removes one failure class, but it also adds onboarding time. That trade-off is the reason to check AI Hub first and to prove a known-good model before porting the production detector.

Step 0: inventory what you have

Start by separating portable source artifacts from Jetson-specific deployment artifacts. Useful artifacts:
Jetson-specific artifacts to replace during migration:
If all you have is this:
then the best next step is to recover the training checkpoint or a neutral export such as ONNX. That is the most important migration gate:

Step 1: recover and export the source model

For a YOLO detector, start from the training checkpoint:
Export static ONNX with NMS disabled:
For HTP/NPU deployment, static shapes are the simplest starting point. For a standard YOLO image model, that usually means:
Keep NMS out of the exported graph at first. Postprocessing is easier to control in the application, especially when you need to compare CPU, GPU, and HTP outputs. After export, validate ONNX before touching Qualcomm tooling:
Then create a golden output with ONNX Runtime FP32. Save this output. You will compare every later stage against it. If ONNX export or validation fails, fix that before touching QNN: Shortest rule: make PyTorch and ONNX Runtime match first. QNN cannot fix a bad ONNX export.

Step 2: check AI Hub first

Before building a custom conversion pipeline, check whether your model architecture already exists in AI Hub. For YOLO-style models:
The current Dragonwing docs point to the AI Hub IoT model catalog as the up-to-date source for model availability. It includes validated coverage across LLMs, VLMs, detection, segmentation, classification, embeddings, audio, depth, restoration, and robotics. If your exact model is not available, a compatible architecture may still be useful:
That avoids debugging conversion and application integration at the same time.

Step 3: convert ONNX to a Qualcomm artifact

Qualcomm QAIRT/QNN conversion is not the same shape as trtexec. Jetson often feels like one build step:
Qualcomm separates the work into one of two lanes:
Public Dragonwing docs commonly show the local QNN path as qnn-onnx-converter followed by qnn-model-lib-generator. AI Hub and some SDK flows may hand you a DLC instead. Use the flow that matches your SDK release, and verify exact flags with --help. Local QNN model-library flow:
Now validate the converted FP32 artifact on the host CPU. This catches conversion problems before quantization adds noise.
Suggested gate:
If this fails, we recommend fixing export, graph shape, preprocessing, or unsupported operator issues before quantizing.

Step 4: handle unsupported operators

This is the point where the Qualcomm onboarding gap can become visible. Do not hide it behind a generic “debug the converter” instruction. Record the operator, graph location, affected backend, and fallback cost; then use this order: If the converter rejects an operator, use this order:
Graph rewrite is usually the best first attempt when an unsupported op has a supported equivalent. Custom ops are more work but keep the model on the intended backend. CPU fallback can be acceptable for low-frequency or non-critical pieces, but document the latency penalty. Our best practice is to classify the failure before choosing a fix. Useful failure buckets: For Z1_UNCLASSIFIED, the safest path is to save logs, model, inputs, outputs, and tool versions before escalating. This preserves evidence for the real fix.

Step 5: quantize with real calibration data

If you want efficient HTP/NPU execution, quantization is central. A TensorRT calibration cache is not useful here. It is TensorRT-specific. Recalibrate for Qualcomm. For a YOLO face detector, use real images from the target environment:
A small first calibration set might be 64-200 images. More is not automatically better if the images miss real operating conditions. Representative beats random. In the public QNN converter flow, static quantization is usually part of conversion by passing --input_list calibration_input_list.txt to qnn-onnx-converter, then compiling the generated graph with qnn-model-lib-generator:
If AI Hub or your QAIRT SDK gives you a quantized DLC instead, keep the same validation gates and feed that DLC into the context-binary step with libQnnModelDlc.so. Important gotcha from the current docs:
a8w16 is not a valid HTP mode. Use a8w8, a16w8, a16w16, or fp16.
If accuracy drops, use a retry ladder instead of random flag changes: Use the first strategy that passes your accuracy SLA.

Step 6: build the context binary

A QNN context binary is the target deployment artifact for this path. Resolve the target SoC first:
Set dsp_arch, VTCM, and other backend options from the SDK examples for your exact model and target; do not copy tuning values across SoCs without validating them. In published commands, include the tested context_config.json or point readers to the exact SDK sample config used for that board. Build the context binary:
If your input is an AI Hub or DLC artifact, use the SDK’s DLC model loader path, commonly --model libQnnModelDlc.so --dlc_path out/best_a8w8.dlc, with the same backend and config. Expected output:
This artifact is target-sensitive. Rebuild when you change SDK, BSP, SoC, precision, or model. If context generation fails, check the small things first:

Step 7: deploy the runtime bundle

A deployment is not just the model file. It also needs compatible runtime libraries and config. At minimum, expect a bundle like this:
Verify you are deploying ARM64 libraries to the device:
You want:
Copy the bundle:
Use scp for Linux targets. Reserve adb push for Android targets.

Step 8: run inference on device

On the device:
Keep backend_ext.json minimal:
Keep the top-level backend-extension file aligned with the schema shown for your installed QAIRT release. Put graph and device tuning in the referenced HTP configuration file rather than inventing sibling keys beside backend_extensions. Start from the matching SDK example and add settings incrementally.

Step 9: validate against the right baseline

Always compare against the ONNX Runtime FP32 golden baseline, not TensorRT FP16. Why? TensorRT output already includes NVIDIA-specific graph transformations and precision behavior. The neutral reference is the source model export. Suggested validation gates: A tiny comparison helper:
For object detection, tensor similarity is not enough. You also need task-level metrics:

Step 10: move YOLO postprocessing into the app

Jetson Python examples often hide YOLO decode and NMS behind Ultralytics. Once you export and deploy, you may receive raw tensors. Make postprocessing explicit:
For a face-only model, keep it simple. There is one class. A generic COCO postprocessor is only worth adding when you actually need it. Also lock preprocessing:
Preprocessing mismatches can look like quantization problems. Validate preprocessing numerically before blaming the accelerator.

Step 11: benchmark only after functional validation

First prove HTP works:
Then run with profiling:
Parse on the host:
Look for accelerator execution time, CPU fallback layers, and bottleneck ops. If model latency is good but app FPS is bad, the bottleneck is outside inference. Capture memory delta, not just raw peak:
Then measure during inference and report the delta from idle.

Benchmark plan for the YOLO migration

Use the same input clip, preprocessing contract, postprocessing thresholds, and accuracy set on both devices. Capture this matrix before making platform claims: If you cite external benchmark numbers, label them as source-document references and keep them separate from your own device results. The benchmark method matters more than a borrowed headline number.

Common pitfalls


What this migration really changes

The application goal stays the same:
The deployment machinery changes:
The biggest mindset shift is that model deployment becomes a staged validation pipeline. Each stage has an artifact and a gate. That is not ceremony. It is how you avoid debugging quantization, operator support, preprocessing, runtime libraries, and app code all at once. In the next post, we will zoom in on the most underestimated stage: quantization. That is where accuracy regressions usually appear, where calibration data quality matters, and where the difference between “runs” and “ships” becomes obvious.