hexagon_tpu

Elixir bindings for the Qualcomm Hexagon NPU (HTP) via QNN (Qualcomm AI Engine Direct), targeting the Radxa Dragon Q6A (QCS6490, HTP v68) running Nerves with nerves_system_dragon_q6a.

Nx tensors in, Nx tensors out; quantization handled automatically from the model's own metadata.

{:ok, model} = HexagonTpu.load_model("/data/mobilenet_v2.bin")
{:ok, [scores]} = HexagonTpu.predict(model, [image_tensor]) # f32 in, f32 out

How it works

Requirements

On device (provided by nerves_system_dragon_q6a with BR2_PACKAGE_QAIRT_RUNTIME=y):

Build host: QNN_SDK_ROOT pointing at an extracted QAIRT SDK (2.42.0.251225). The provided devenv.nix fetches it. Consumers of the published Hex package get a precompiled NIF and don't need the SDK.

Usage on host (development)

The QNN CPU backend (libQnnCpu.so, x86-64) allows exercising the runtime lifecycle and context introspection without hardware:

{:ok, rt} = HexagonTpu.Runtime.create(
lib_path: Path.join(System.get_env("QNN_HOST_LIB_DIR"), "libQnnCpu.so"),
system_lib_path: Path.join(System.get_env("QNN_HOST_LIB_DIR"), "libQnnSystem.so")
)

Run tests: mix test (pure), mix test --include qnn_host (needs devenv).

Quantization

QNN stores affine quant params as float = (q + offset) * scale; HexagonTpu.TensorInfo normalizes to the conventional zero_point = -offset. Graph.execute/3 quantizes f32 inputs and dequantizes outputs automatically (quantize: :none / dequantize: :none opt out for zero-overhead pre-quantized pipelines).

Observability & guards

Leak-guard tests (test/stats_test.exs) assert alive counts return to baseline across create/close and GC-only cycles.

Releases

Pushing a v* tag runs .github/workflows/release-precompiled.yml: builds the dragon_q6a NIF with the Nerves toolchain, uploads the precompiled tarball + checksum.exs to the GitHub release, and publishes to Hex (requires the HEX_API_KEY secret).

License

Apache-2.0. The QAIRT SDK and its runtime libraries are Qualcomm proprietary — this repository neither vendors nor redistributes them.