INT8 quantization and Ethos-U65 plan for the i.MX93¶
This document preserves the current state and the intended next steps for quantizing the DroneVision detector and running it on the Arm Ethos-U65 in the NXP i.MX93 FRDM board. It was last updated on 9 August 2026. Commands below are plans or command templates unless their section explicitly says that they have already been run.
Current checkpoint¶
| Work item | Status | Evidence / artifact |
|---|---|---|
| FP32 i.MX93 CPU baseline and Pi 5 comparison | Complete | docs/IMX93_VALIDATION.md |
| Ethos-U65 platform sanity check | Complete | MobileNet V1, 100% NPU placement, 3.78 ms |
| Phase 1: calibration/holdout split | Complete and committed | commit df30963, data/corpus/quantization_split.json |
| Phase 2: raw FP32 YOLO export | Complete | models/drone_yolo26n_v4_raw.onnx |
| Phase 2: full-corpus raw FP32 validation | Complete | 337/337, 30.87 mm mean, 59.13 mm P95 |
| Phase 3: full-integer TFLite conversion | Complete | models/drone_yolo26n_v4_raw_int8_io.tflite: INT8 [1,3,320,320] input and INT8 [1,5,2100] output |
| Phase 4: Vela compatibility and compilation | Complete | bench/out/vela/drone_yolo26n_v4_raw_int8_io_vela.tflite: 1/1 node delegated, no CPU boundary nodes |
| Phase 5: DroneVision TFLite/Ethos-U runtime | Complete | dronevision/l2_perception/runtimes/tflite_rt.py (/usr/lib/libethosu_delegate.so backend) |
| Phase 6: NPU accuracy, speed and scheduling | Complete | 40.18 ms isolated inference, 167.8 ms (5.96 Hz) 4-cam 2-worker fix, 52.39 mm 3D error |
| Separate CPU track: static INT8 ONNX | Pending | Does not block the NPU track |
Repository state at this checkpoint:
df30963is the committed phase-1/NPU-context checkpoint.- Phase-2 source, documentation and model changes are present in the working tree but have not yet been committed.
- The final integer-I/O artifact has been executed on the NPU and validated on the full 337-set corpus.
Why this work is necessary¶
The current detector is a FP32 ONNX model executed by ONNX Runtime on the two Cortex-A55 cores. The best measured CPU scheduling strategy is two camera workers with one ONNX thread each:
| Measurement | Current i.MX93 result |
|---|---|
| Isolated inference, one CPU thread | 396.4 ms/image |
| Isolated inference, two CPU threads | 237.9 ms/image |
| Best four-camera fix | 875-882 ms/fix |
| Best sustained rate | 1.14 fixes/s |
The original end-to-end FP32 reference is 36.42 mm mean 3D error, with 333 localized samples and 4 misses out of 337. The phase-2 raw export improves this to 30.87 mm and 337/337 localized; it is now the primary reference for quantization. The Pi 5 is approximately 7-8x faster than the i.MX93 for the complete FP32 CPU pipeline, so CPU optimization alone is unlikely to make the i.MX93 real-time. The board's differentiating resource is its integrated 0.5-TOPS Ethos-U65 NPU.
Verified hardware and software state¶
The following was inspected directly on the board through ssh imx:
| Item | Verified state |
|---|---|
| Board | NXP i.MX93 11x11 FRDM |
| Application CPU | 2x Cortex-A55, AArch64 |
| CPU INT8 feature | Arm dot-product present (asimddp) |
| NPU | Arm Ethos-U65, nominally 0.5 TOPS |
| NPU device | /dev/ethosu0 |
| Device permissions | crw------- root root; current tests run as root |
| Reserved NPU memory | 128 MiB at 0xa8000000-0xafffffff |
| Kernel driver | ethosu, device and sysfs class present |
| Firmware | /usr/lib/firmware/ethosu_firmware |
| Ethos-U driver/firmware stack | NXP 24.05 |
| TensorFlow Lite runtime | 2.16.2, Python module available |
| Ethos-U delegate | /usr/lib/libethosu_delegate.so |
| Ethos-U support library | /usr/lib/libethosu.so.1.0.0 |
| Vela compiler | 3.12.0 at /usr/bin/vela |
| Vela accelerator default | ethos-u65-256 |
| Vela configuration file | Arm/vela.ini |
| ONNX Runtime | 1.17.1, CPUExecutionProvider only |
The kernel log confirms that the 128 MiB reserved region was initialized and assigned to the Ethos-U driver. The presence of the device, firmware and delegate means the board-side execution stack is installed. It does not prove that the current model can be delegated; there is not yet a compatible compiled model or a DroneVision TFLite runtime.
NPU sanity check: bundled MobileNet V1¶
Status: complete on 7 August 2026. This validates the installed Vela/compiler, delegate, device, firmware and RPMsg execution path independently of the DroneVision model.
The board bundles this full-integer MobileNet artifact:
| Property | Value |
|---|---|
| Original model | /usr/bin/tensorflow-lite-2.16.2/examples/mobilenet_v1_1.0_224_quant.tflite |
| Original SHA-256 | ecc3a67c47c5a609ec35f6a58a7d97532834e43df4cb7d3f1204a8164b7d20dd |
| Input | NHWC [1,224,224,3], uint8, scale 0.0078125, zero point 128 |
| Output | [1,1001], uint8, scale 0.00390625, zero point 0 |
| Test image | bundled grace_hopper.bmp |
The unmodified model was compiled on the board:
vela /usr/bin/tensorflow-lite-2.16.2/examples/mobilenet_v1_1.0_224_quant.tflite \
--accelerator-config ethos-u65-256 \
--optimise Performance \
--show-cpu-operations \
--show-subgraph-io-summary \
--verbose-performance \
--output-dir /root/dronevision-ai/bench/out/npu-sanity
Vela assigned all 60 operators to one NPU subgraph and reported zero CPU operators. It
estimated 4.49 ms/inference, 222.62 inferences/s, 572,406,226 MACs, 370.91 KiB maximum
SRAM use and 3,719.84 KiB DRAM use. The compiled model is 3.3 MiB and has SHA-256
f57cf65901827a9fb1c5917cdf859c158044a3e23385a790b64787470e6ab6c3.
The original model was then run through TFLite/XNNPACK on one A55 thread, and the compiled
model was loaded through /usr/lib/libethosu_delegate.so. Each case used 10 warm-ups and
100 timed invocations of the same input:
| Runtime | Mean | Median | P95 | Minimum | Maximum |
|---|---|---|---|---|---|
| TFLite CPU, original model | 51.7906 ms | 51.6983 ms | 52.1594 ms | 51.2735 ms | 55.3803 ms |
| Ethos-U65, Vela model | 3.7754 ms | 3.7695 ms | 3.8210 ms | 3.7527 ms | 3.8782 ms |
The measured mean and median NPU speed-ups were both about 13.7x. Top-1 was identical
(military uniform, class 653). Of 1,001 quantized output elements, 996 were bit-exact;
the remaining five differed by one uint8 unit, with mean absolute delta 0.004995 and no
change in top-1. This is the expected scale of integer-backend rounding variation.
Execution evidence was explicit:
Ethosu delegate: device_name set to /dev/ethosu0
EthosuDelegate: 1 nodes delegated out of 1 nodes with 1 partitions
remoteproc0: Booting fw image ethosu_firmware
rpmsg-ethosu-channel created
The board finished at 58.35 degrees C with 1.6 GiB memory available. There was no timeout,
delegate fallback, driver error or memory pressure. Vela's generated model and CSV reports
were copied to the local ignored directory bench/out/npu-sanity/.
This proves the board's NPU stack works end-to-end. It does not establish that DroneVision's YOLO graph is compatible or fast; that still depends on raw-head export, INT8 calibration, operator coverage and end-to-end accuracy.
Reference artifacts¶
The model used for all existing results is 320x320 and has one object class.
| Artifact | SHA-256 | State |
|---|---|---|
models/drone_yolo26n_v4.pt |
841edc03a993fabd57916f0622d2a66af384f66a1f73b6fd7444ed92a893ed6d |
PyTorch source weights available |
models/drone_yolo26n_v4.onnx |
7acd721e718a6fe444b22eb93300d2316ae4413209c2a89789072d4e1a4a3bc9 |
FP32 CPU reference |
models/drone_yolo26n_v4_raw.onnx |
f43e8f0f7445a89d8dcfa1d03fa3be98ace40e165d079e2d7cd2a4aa95d1b6f7 |
FP32 raw-head quantization reference |
The current ONNX graph has FP32 input images[1,3,320,320] and end-to-end output
output0[1,300,6], where each row is x1,y1,x2,y2,confidence,class. That export includes
the final detection-selection head.
Two separate INT8 tracks¶
INT8 CPU execution and Ethos-U execution should be treated as separate experiments. They need different model formats and answer different questions.
Track A: INT8 ONNX on the Cortex-A55 CPU¶
Create a statically quantized ONNX QDQ or QOperator model and run it with ONNX Runtime's
CPUExecutionProvider. This measures the benefit from the A55 asimddp instructions and
from reduced weight/memory traffic. It is a useful baseline and fallback, but it does not
use /dev/ethosu0.
Requirements:
- Use static calibration, not weights-only/dynamic quantization, for convolution-heavy vision inference.
- Quantize convolution and other supported compute nodes while retaining unsupported nodes in FP32 if necessary.
- Record the quantizer/tool versions, quantization format, tensor types, calibration manifest, model hash and any excluded nodes.
- Run the same
bench.speed,bench.parallelandbench.accuracytests as FP32. - Confirm from the runtime description that the provider remains CPU and the artifact is reported as INT8.
This result is also the closest architectural comparison to future Pi 5 INT8 CPU tests, because both the Cortex-A55 and Cortex-A76 expose Arm dot-product instructions.
Track B: full-integer TFLite compiled for Ethos-U65¶
The NPU path needs a fully quantized TensorFlow Lite model, followed by an offline Vela
compile. Vela replaces supported TFLite subgraphs with an Ethos-U custom operator. At run
time, TFLite loads the compiled model and /usr/lib/libethosu_delegate.so sends those
subgraphs to /dev/ethosu0. Remaining unsupported operations execute on the CPU.
The useful performance number is therefore not merely "delegate loaded". We must record how much of the graph Vela delegates and which operations fall back to the CPU. A small unsupported island or repeated CPU/NPU tensor conversions can dominate end-to-end time.
Required model re-export¶
Status: phase 2 complete on 7 August 2026. python -m bench.export_raw used the
checkpoint's original Ultralytics 8.4.104 exporter with batch 1, static 320x320 input,
opset 12, simplification enabled, nms=False and end2end=False. The resulting graph has
input [1,3,320,320] and decoded raw output [1,5,2100]. It contains no TopK,
GatherElements or NonMaxSuppression nodes, passes the ONNX checker and records the
source checkpoint hash in its metadata.
The existing end-to-end ONNX output is not the preferred quantization source. Static
quantization and Vela commonly reject or leave detection-head operations such as TopK
and GatherElements on the CPU. The completed export omits the end-to-end selection/NMS
head and exposes the raw decoded YOLO tensor instead:
or its transpose. For the one-class 320x320 detector this is a five-channel raw head with
2,100 anchors for strides 8, 16 and 32. DroneVision's yolo_codec.py already detects and
decodes both raw layouts as well as the current (1,N,6) end-to-end layout. It also does
confidence filtering and optional NMS on the CPU, where that small amount of work is
preferable to preventing NPU delegation.
The raw export must keep:
- Static batch size 1;
- Static 320x320 spatial dimensions;
- The same RGB input convention;
- Pixel-space box coordinates, not normalized coordinates;
- The same one-class weights and confidence semantics;
- No embedded NMS, TopK or final detection selection.
The comparison against the end-to-end FP32 model is complete on all 337 frame sets. The raw model uses YOLO26's trained one-to-many branch rather than the end-to-end model's one-to-one branch, so its output is not expected to be bit-identical. It produced a strict accuracy improvement:
| FP32 artifact | Localized | Misses | Mean 3D | Median 3D | P95 3D | Maximum 3D |
|---|---|---|---|---|---|---|
| End-to-end reference | 333/337 | 4 | 36.42 mm | 32.07 mm | 77.58 mm | 181.64 mm |
| Raw-head reference | 337/337 | 0 | 30.87 mm | 28.10 mm | 59.13 mm | 162.78 mm |
The raw model averaged 980.44 ms per four-camera fix with two ONNX threads, essentially the same CPU cost as the original graph. Isolated inference was 237.38 ms and raw decode added approximately 0.83 ms. All subsequent INT8 accuracy comparisons must use this raw FP32 result as their primary reference; retain the end-to-end row only for continuity with the Pi measurements.
Representative calibration data¶
Status: phase 1 complete on 7 August 2026. data/corpus/quantization_split.json
(SHA-256 9745f562f99087a2a558e141b15a1f1aa587d848f9d0cdaea3750a60fb121e24)
selects 64 evenly spaced frame sets across the complete recording, including every camera:
256 calibration images. The other 273 synchronized frame sets (1,092 images) form the
quantization holdout. The generator is python -m bench.quant_split; it validates source
hashes, camera completeness, ZIP membership, overlap and full-corpus coverage. All 256
selected JPEGs decoded successfully as 360x640 RGB-capable uint8 images, and the complete
ZIP passed its CRC check.
Use real DroneVision inputs, not random tensors or generic images. Calibration samples
should be drawn deterministically from data/corpus/frames.zip and include all four
cameras across the full recording. Preserve the production preprocessing:
- Decode JPEG to RGB.
- Letterbox without changing aspect ratio to 320x320.
- Use padding value 114.
- Preserve the network's expected value range and dtype during conversion.
The current FP32 codec converts HWC RGB uint8 to NCHW FP32 in [0,1]. A TFLite export
will normally use NHWC and may expose int8 or uint8 input. The new runtime must inspect
the model's input quantization scale and zero point instead of assuming that dividing by
255 is correct. Output tensors must likewise be dequantized using their declared scale
and zero point before the existing box decoder consumes them.
Keep a calibration manifest containing the exact frame-set IDs and camera names. Start with several hundred images distributed across cameras and time; enlarge it if activation ranges or accuracy are unstable. The validation corpus may be used for this engineering comparison, but a separate held-out sequence will eventually be needed to make a general accuracy claim.
Conversion and compilation workflow¶
The exact exporter commands depend on the installed Ultralytics/PyTorch version and must be captured when run. The intended artifact chain is:
PyTorch `.pt`
-> raw-head FP32 export
-> FP32 TFLite functional reference
-> representative-dataset full INT8 TFLite (Ultralytics FP32 public boundaries)
-> Vela-compiled `_vela.tflite`
-> remove only the compiled graph's boundary QUANTIZE/DEQUANTIZE nodes
-> TFLite + Ethos-U delegate on i.MX93
The TFLite converter must request full integer quantization. Do not accept a model that only quantizes weights while leaving activations FP32. Prefer integer model input/output when supported, because it avoids CPU float/INT8 conversion around every inference.
Compile the resulting full-integer model on the board with a command of this form:
mkdir -p bench/out/vela
vela models/drone_yolo26n_raw_int8.tflite \
--accelerator-config ethos-u65-256 \
--optimise Performance \
--show-cpu-operations \
--show-subgraph-io-summary \
--verbose-performance \
--output-dir bench/out/vela
Vela 3.12.0 defaults to ethos-u65-256, Performance optimization and a 393,216-byte
arena cache, but these values should still be explicit in a reproducible command. The
installed generic Arm/vela.ini contains several example memory systems; do not select a
named --system-config or --memory-mode until it is confirmed against the i.MX93/NXP
memory integration. The first correctness test can use Vela's internal defaults.
Save the complete compiler output. In particular, preserve:
- Input model hash and compiled model hash;
- Vela version and full command line;
- Operator allocation and CPU fallback list;
- Estimated operations, cycles, bandwidth and peak memory;
- Subgraph input/output types, shapes, scales and zero points;
- Warnings about unsupported operators or tensor constraints.
Verified integer input/output boundary export¶
Ultralytics 8.4.104 deliberately applies NO_QUANTIZE to the public input and output.
Compiling that calibrated graph with Vela 3.12.0 works, but leaves the runtime graph as
QUANTIZE (CPU) -> ethos-u -> DEQUANTIZE (CPU). Requantizing the FP32 model directly with
integer boundaries was rejected: it changed constant encoding and Vela failed with an
AssertionError. Removing the two boundary operators before Vela also triggers that
Vela 3.12 limitation for this NCHW graph.
The verified workflow therefore compiles the original calibrated graph first, then exposes
the already-calibrated INT8 tensors around the compiled ethos-u node:
.venv-export/bin/python -m bench.export_tflite --mode int8-io
# For the deployable compiled model, after the normal Vela compilation:
.venv-export/bin/python -m bench.export_tflite --mode int8-io \
--source-int8-io bench/out/vela/drone_yolo26n_v4_raw_int8_vela.tflite \
--output-int8-io bench/out/vela/drone_yolo26n_v4_raw_int8_io_vela.tflite
Verified final contract and results on the i.MX93:
| Property | Result |
|---|---|
| SHA-256 | 8c8e91d09a6ea20ed64ed128034183a5c058b35952fbff214d8a62309e711e61 |
| Input | INT8 NCHW [1,3,320,320], scale 1/255, zero point -128 |
| Output | INT8 [1,5,2100], scale 0.0078429878, zero point -128 |
| Delegation | 1 nodes delegated out of 1; no CPU boundary nodes |
| Raw-output parity | Exact against the prior compiled model (max_abs=0) |
| Invoke latency, 100 calls | 36.84 ms mean versus 37.70 ms; 2.3% lower |
| Full corpus, confidence 0.25 | 336/337, 52.39 mm mean, 95.94 mm P95; unchanged |
Minimal board-side delegate smoke test¶
The following is the intended loading pattern once a _vela.tflite artifact exists:
from tflite_runtime.interpreter import Interpreter, load_delegate
delegate = load_delegate("/usr/lib/libethosu_delegate.so")
interpreter = Interpreter(
model_path="bench/out/vela/drone_yolo26n_raw_int8_vela.tflite",
experimental_delegates=[delegate],
)
interpreter.allocate_tensors()
print(interpreter.get_input_details())
print(interpreter.get_output_details())
Before timing, run a real corpus image, verify tensor quantization metadata and compare its decoded boxes with the FP32 raw-head reference. Loading without an error is necessary but is not proof of NPU execution; use the Vela allocation report and board logs/profiling to confirm delegation.
DroneVision integration needed¶
There is currently no TFLite/Ethos-U runtime implementation in DroneVision. Add one behind
the existing TensorRuntime contract, without changing the detector or geometry layers.
It should:
- Load a normal
.tflitemodel on CPU or a Vela model with an explicit delegate path; - Query input/output shape, dtype, scale and zero point from the interpreter;
- Handle TFLite's expected NHWC layout while preserving RGB and letterboxing semantics;
- Quantize input and dequantize raw output correctly;
- Reuse preallocated input buffers where the Python TFLite API permits it;
- Report model hash, runtime/delegate versions, tensor metadata and whether delegation was
requested in
describe(); - Fail clearly if the delegate, device or required quantization metadata is missing;
- Support clean teardown and repeated benchmark construction;
- Keep raw-head decoding in the shared
yolo_codec.pyimplementation.
Do not silently fall back to CPU when an NPU benchmark was requested. A separate explicit TFLite-CPU mode is useful for attribution, but an Ethos-U mode should fail if the delegate cannot load.
Validation sequence and gates¶
Change one variable at a time and retain every intermediate artifact:
- Raw-head FP32 parity (complete): compare the new raw export with the current FP32 end-to-end model on the full corpus.
- Uncompiled INT8 parity: run the full-integer TFLite model on TFLite CPU. This isolates quantization error from Vela and the NPU.
- Vela compatibility: compile it and inspect CPU fallbacks before benchmarking.
- Single-image NPU parity: compare raw/dequantized outputs and decoded boxes for known corpus frames.
- Full-corpus NPU accuracy: repeat all 337 synchronized frame sets.
- Isolated NPU speed: warm up, then report mean, median, P95 and maximum inference time separately from preprocessing and postprocessing.
- Four-camera scheduling: test one shared sequential NPU stream first, then controlled one- versus two-worker execution. There is one NPU, so CPU-style four-worker scaling must not be assumed.
- End-to-end timing: include JPEG decode, quantization, NPU inference, raw-head decode and triangulation.
- Sustained run: monitor latency drift, CPU utilization, temperature, memory and errors for at least 10-15 minutes.
The original end-to-end reference is:
337 frame sets
333 localized / 4 misses
36.42 mm mean error
32.07 mm median error
77.58 mm P95 error
181.64 mm maximum error
The primary quantization reference is now the raw FP32 result: 337 localized, zero misses, 30.87 mm mean, 28.10 mm median, 59.13 mm P95 and 162.78 mm maximum error.
Accuracy and performance must be reported together. The original engineering notes used a provisional 10% degradation gate, but it is not an established project requirement and should not be presented as one. Report isolated images/s and complete four-camera fix latency so scheduling effects remain visible.
Applied to the new raw FP32 reference, the provisional INT8 gate is:
localized 337/337 raw FP32 reference
mean 3D error 30.87 mm raw FP32 reference
P95 3D error 59.13 mm raw FP32 reference
complete fix report measured latency and fixes/s
camera inference report measured latency and images/s
Next steps: phase 3 full-integer TFLite¶
The NPU track is the priority. Static INT8 ONNX for the A55 remains useful for attribution and Pi 5 comparison, but it should not delay the TFLite/Vela path.
3.1 Prepare the pinned conversion environment¶
- Recreate an isolated Python 3.12 exporter environment.
- Pin Ultralytics
8.4.104, matching the checkpoint and raw ONNX export. - Capture the exact Torch, TensorFlow/LiteRT, ONNX and conversion-tool versions.
- Check the installed exporter's LiteRT requirements before downloading the larger TensorFlow conversion stack; record any host-platform restriction.
Do not keep the generated environment in Git. The previous temporary export environment was removed after phase 2; only the command/version record and generated artifacts belong in the repository.
3.2 Materialize the representative images without changing bytes¶
Add a small utility that reads data/corpus/quantization_split.json and extracts exactly
its 256 calibration JPEG members into ignored data/scratch/ storage. It must:
- Verify the three corpus hashes from the manifest before extraction;
- Preserve original JPEG bytes rather than decoding and re-encoding;
- Preserve camera and frame-set identity in paths or a generated index;
- Verify 64 frame sets, four cameras each, 256 unique files;
- Produce a minimal dataset YAML or image list accepted by the pinned exporter.
Labels are not required to estimate activation ranges, but the converter must demonstrably consume the selected images rather than silently falling back to a default sample dataset.
3.3 Export an FP32 TFLite functional reference¶
Before introducing quantization, export end2end=False, batch-1, static 320x320 FP32
TFLite/LiteRT. Inspect its input layout and output shape, then run several corpus images on
TFLite CPU. Decoded boxes should agree closely with drone_yolo26n_v4_raw.onnx. This
isolates ONNX-to-TFLite/export differences from INT8 calibration loss.
Stop here if the FP32 TFLite output layout, coordinates or detections do not match the raw ONNX semantics.
3.4 Export full-integer TFLite¶
Run representative-dataset static quantization using only the 256 manifested images. Require and record:
- Integer weights and activations, not weights-only quantization;
- Preferably
int8oruint8model input and output; - Static
[1,320,320,3]NHWC input unless the exporter proves otherwise; - Raw decoded
[1,5,2100]output or a documented transpose; - Every input/output scale and zero point;
- Exporter command, dependency versions, calibration-manifest hash and model SHA-256;
- An operator/dtype inventory proving that unexpected FP32 islands are absent.
3.5 Quantization-only correctness gate¶
Run the uncompiled INT8 model with TFLite CPU before Vela. Compare the same images in this order:
- Individual calibration and holdout frames at tensor and decoded-box level;
- The 273-set holdout for the primary quantization gate;
- All 337 frame sets for continuity with the existing tables.
If the TFLite CPU result fails the accuracy gate, correct calibration/export first. Vela cannot recover accuracy already lost in the quantized artifact.
Phase-3 exit criteria¶
- FP32 TFLite artifact has raw-ONNX functional parity (exact 30.87 mm mean 3D error, 0 misses).
- Full-integer TFLite consumes the committed 256-image calibration selection.
- Tensor dtypes, shapes, scales and zero points are recorded.
- INT8 TFLite CPU evaluated on calibration (56.18 mm), holdout (53.59 mm), and full corpus (54.08 mm, 0 misses).
- Commands, tool versions and artifact hashes are reproducible (
bench.export_tflite,bench.eval_quant_gate). - The INT8 artifact is ready for Vela operator-coverage analysis.
Benchmark matrix¶
At minimum retain these comparable rows:
| Artifact/runtime | Device | Purpose |
|---|---|---|
| Existing FP32 ONNX | A55 CPU | Immutable reference |
| Raw-head FP32 ONNX or TFLite | CPU | Export/decoder parity |
| Static INT8 ONNX | A55 CPU | asimddp and memory-traffic benefit |
| Full INT8 TFLite, uncompiled | A55 CPU | Quantization-only reference |
| Vela INT8 TFLite with delegate | Ethos-U65 plus any fallback | Final NPU result |
All rows must use batch 1, 320x320, the same RGB/letterbox preprocessing, the same corpus, the same confidence and IoU thresholds and the same accuracy calculation. Record model hashes rather than relying on filenames.
Known risks and likely failure modes¶
- End-to-end detection head is not delegatable. Use the raw output head and CPU decode.
- Partial quantization. A small number of FP32 activations can prevent large regions from reaching the NPU; inspect the generated model and Vela report.
- Unsupported TFLite operators or tensor constraints. Replace/export them differently rather than accepting a large CPU island unnoticed.
- Incorrect input layout or color order. Current source frames are RGB; TFLite is likely NHWC while ONNX is NCHW.
- Incorrect scale/zero point. Always read tensor metadata and test saturation/range.
- Quantized output decoded as FP32. Dequantize before raw-head box decoding.
- Calibration under-represents cameras or target sizes. Use all cameras and the whole recorded time range with a saved manifest.
- Delegate loaded but little work delegated. Treat Vela operator coverage as a primary result.
- Too many NPU workers. A single accelerator may serialize requests while duplicating buffers and increasing contention; benchmark rather than assuming CPU topology rules.
- Model size mistaken for speed. INT8 usually reduces storage and bandwidth, but the real metric is complete fix latency.
- Permissions.
/dev/ethosu0is currently root-only. Add a deliberate udev/group rule before running DroneVision as an unprivileged service; do not broadly chmod the device. - Version coupling. Vela output, delegate, firmware and driver need compatible command stream versions. Keep the board's NXP stack together and capture versions in results.
- Nominal TOPS mistaken for delivered throughput. The 0.5-TOPS rating does not predict YOLO latency or include CPU fallback, transfer, preprocessing and postprocessing.
Immediate preparation checklist¶
- Pin/export the raw-head FP32 model from the existing
.ptweights. - Prove raw-head FP32 accuracy parity or improvement on all 337 frame sets.
- Create a representative calibration manifest (ready to commit/version).
- Produce full-integer TFLite and static INT8 ONNX artifacts separately.
- Record converter versions, commands, hashes and tensor metadata.
- Compile TFLite with Vela and archive the complete operator/fallback report.
- Implement and test a TFLite/Ethos-U
TensorRuntimebackend. - Confirm actual delegation before collecting performance numbers.
- Run accuracy before optimizing scheduling.
- Benchmark isolated inference, complete fixes and sustained operation.
A bundled INT8 MobileNet has now been compiled and executed successfully on the Ethos-U65, proving the board stack. No DroneVision INT8 model has yet been generated or compiled, and no DroneVision inference has yet run on the NPU. The existing FP32 CPU measurements remain the baseline against which those future results must be attributed.