From ce776396d8331904c1c762c306bd4d6ff1032b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Tue, 22 Sep 2026 13:33:01 +0200 Subject: [PATCH 01/16] feat(android): add the Qualcomm QNN backend Opt-in `qnn` backend: links libqnn_executorch_backend.so from the libs release and Qualcomm's runtime from Maven (com.qualcomm.qti:qnn-runtime 2.47.0). A QNN .pte targets one Hexagon version, so QNN variants resolve to the device's file through the new `qnnHtpArch` JSI value (ro.soc.model -> v69..v81), and QNN is only a DEFAULT candidate when that value is set. EfficientNetV2-S gains QNN_A16W8. nativeLibsVersion moves to 0.10.5. --- .cspell-wordlist.txt | 1 + .../app/classification/index.tsx | 5 + .../08-native-libraries.md | 47 +++++++++- .../__tests__/api/modelRegistry.test.ts | 13 ++- .../__tests__/api/modelVariants.test.ts | 43 +++++++++ .../__tests__/api/nativeLibsConfig.test.ts | 6 +- .../__tests__/support/fakeJsi.ts | 11 +++ .../android/CMakeLists.txt | 13 +++ .../android/build.gradle.kts | 17 +++- .../android/src/main/AndroidManifest.xml | 6 ++ .../rnexecutorch/RnExecutorchModule.kt | 24 ++++- .../cpp/core/install.cpp | 1 + .../cpp/core/utils.cpp | 93 ++++++++++++++----- .../react-native-executorch/cpp/core/utils.h | 10 ++ .../scripts/download-libs.js | 37 ++++++-- .../scripts/package-release-artifacts.sh | 5 + .../react-native-executorch/src/models.ts | 29 +++++- 17 files changed, 316 insertions(+), 45 deletions(-) diff --git a/.cspell-wordlist.txt b/.cspell-wordlist.txt index 56e970131f..9a90ab6a7b 100644 --- a/.cspell-wordlist.txt +++ b/.cspell-wordlist.txt @@ -6,6 +6,7 @@ executorch RNET libexecutorch libxnnpack +libqnn libvulkan xcframework metallib diff --git a/apps/computer-vision/app/classification/index.tsx b/apps/computer-vision/app/classification/index.tsx index e8e9b90eea..bc54d17017 100644 --- a/apps/computer-vision/app/classification/index.tsx +++ b/apps/computer-vision/app/classification/index.tsx @@ -22,6 +22,11 @@ const MODEL_OPTIONS: ModelOption[] = [ value: models.classification.EFFICIENTNET_V2_S.COREML_FP16, disabled: Platform.OS !== 'ios', }, + { + label: 'EfficientNetV2-S (QNN A16W8)', + value: models.classification.EFFICIENTNET_V2_S.QNN_A16W8, + disabled: Platform.OS !== 'android', + }, ]; function ClassificationContent() { diff --git a/docs/docs/03-core-and-advanced/08-native-libraries.md b/docs/docs/03-core-and-advanced/08-native-libraries.md index 74fc42bb54..ec115f13be 100644 --- a/docs/docs/03-core-and-advanced/08-native-libraries.md +++ b/docs/docs/03-core-and-advanced/08-native-libraries.md @@ -3,15 +3,25 @@ title: Native Libraries slug: /core-and-advanced/native-libraries description: 'How React Native ExecuTorch downloads, ships, and links native binaries on demand.' keywords: - [react native executorch, executorch, native libraries, backends, xnnpack, coreml, mlx, vulkan] + [ + react native executorch, + executorch, + native libraries, + backends, + xnnpack, + coreml, + mlx, + vulkan, + qnn, + ] --- React Native ExecuTorch ships the core runtime, hardware-accelerated backends -(XNNPACK, Core ML, MLX, Vulkan), and native third-party libraries (OpenCV, +(XNNPACK, Core ML, MLX, Vulkan, QNN), and native third-party libraries (OpenCV, phonemis) as **separate downloadable artifacts**. -By default, **everything is downloaded and enabled**, so no configuration is -required to get started. However, because on-device AI backends and vision +By default, **everything except QNN is downloaded and enabled**, so no +configuration is required to get started. However, because on-device AI backends and vision libraries add substantial binary weight, you can tailor exactly what gets pulled into your app. Declaring only the features or backends you use reduces install times, speeds up builds, and significantly shrinks the final app bundle. @@ -42,7 +52,7 @@ Add a `react-native-executorch` block to your `package.json`: | Option | Purpose | Accepted values | | ---------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `features` | High-level tasks — each automatically expands to the backends and libraries it needs | `"classification"`, `"imageEmbeddings"`, `"inpainting"`, `"instanceSegmentation"`, `"keypointDetection"`, `"llm"`, `"multimodalLLM"`, `"objectDetection"`, `"ocr"`, `"privacyFilter"`, `"segmentAnything"`, `"semanticSegmentation"`, `"speechToText"`, `"styleTransfer"`, `"textEmbeddings"`, `"textToImage"`, `"textToSpeech"`, `"tokenizer"`, `"vad"`, `"verticalOCR"` | -| `backends` | Hardware acceleration backends directly | `"xnnpack"`, `"coreml"`, `"mlx"`, `"vulkan"` | +| `backends` | Hardware acceleration backends directly | `"xnnpack"`, `"coreml"`, `"mlx"`, `"vulkan"`, `"qnn"` | | `libs` | Extra native C++ libraries | `"opencv"`, `"phonemis"` | The three lists are merged, so you can pair high-level `features` with specific `backends` or `libs`. Re-run your package manager install after editing. @@ -72,6 +82,7 @@ Hardware backends provide optimized execution kernels for specific processors an - **[Core ML](https://docs.pytorch.org/executorch/stable/backends/coreml/coreml-overview.html)** — Apple's framework for hardware-accelerated machine learning on Apple Silicon, targeting the Apple Neural Engine (ANE) and GPU. Supported on **iOS only**. - **[MLX](https://github.com/ml-explore/mlx)** — An array framework designed for efficient machine learning on Apple silicon via Metal compute shaders, used primarily for accelerated LLM generation. Supported on **iOS only** (physical device only, no simulator). - **[Vulkan](https://docs.pytorch.org/executorch/stable/backends/vulkan/vulkan-overview.html)** — Cross-platform 3D graphics and compute API, leveraging mobile GPUs on **Android only** for accelerated neural network inference and tensor compute operations. +- **[QNN](https://docs.pytorch.org/executorch/stable/backends-qualcomm.html)** — Qualcomm AI Engine Direct, running models on the Hexagon NPU of Snapdragon 8 Gen 1 and newer (SM8450, SM8475, SM8550, SM8650, SM8750, SM8845, SM8850). **Android arm64 only**, and **opt-in**: it is never enabled by default and no `feature` expands to it, so add `"qnn"` to `backends` explicitly. See [QNN](#qnn) below. ### Third-Party Libraries @@ -106,6 +117,29 @@ Specifying a task under `features` is shorthand: it automatically expands to the | `segmentAnything` | xnnpack, coreml, vulkan | opencv | | `tokenizer` | — | — | +## QNN + +Enabling `qnn` links the ExecuTorch QNN backend and adds Qualcomm's runtime +(`com.qualcomm.qti:qnn-runtime`) from Maven. A QNN model is compiled for one +Hexagon version, so each QNN variant is published once per version and the +registry resolves it to the device's file. On any other device, or when the +requirements below are not met, `DEFAULT` falls back to the next backend. + +The Hexagon DSP loads Qualcomm's skel libraries from disk, so the app has to +extract its native libraries. In `android/app/build.gradle`: + +```groovy +android { + packaging { + jniLibs { + useLegacyPackaging = true + // Not needed for the precompiled models the registry ships. + excludes += ['**/libQnnHtpPrepare.so', '**/libQnnGpu.so', '**/libQnnDsp*.so', '**/libQnnHtpV68*.so'] + } + } +} +``` + ## Binary size Approximate size each backend adds to a release `arm64` build: @@ -116,3 +150,6 @@ Approximate size each backend adds to a release `arm64` build: | `coreml` | — | +0.4 MB | | `mlx` | — | +6.0 MB | | `vulkan` | +10.3 MB | — | +| `qnn` | +92 MB¹ | — | + +¹ Uncompressed, with the excludes shown in [QNN](#qnn); 202 MB without them. diff --git a/packages/react-native-executorch/__tests__/api/modelRegistry.test.ts b/packages/react-native-executorch/__tests__/api/modelRegistry.test.ts index bbe17e37c9..e004128185 100644 --- a/packages/react-native-executorch/__tests__/api/modelRegistry.test.ts +++ b/packages/react-native-executorch/__tests__/api/modelRegistry.test.ts @@ -105,9 +105,16 @@ const namedVariants = (group: Node): Node[] => const BACKENDS = /^(xnnpack|coreml|mlx|qnn|vulkan)$/; // `spinquant` is a quantization recipe rather than a plain precision, but it is // what the published Llama builds are named after. -const PRECISIONS = /^(fp32|fp16|bf16|int8|int4|8da4w|8da8w|4w|dynamic|spinquant)$/; - -const basename = (url: string) => url.split('/').pop()!.replace('.pte', ''); +const PRECISIONS = /^(fp32|fp16|bf16|int8|int4|8da4w|8da8w|4w|a16w8|dynamic|spinquant)$/; + +// A QNN .pte targets one Hexagon version and carries it as a trailing `_vNN`, +// after the usual modelname_backend_precision. +const basename = (url: string) => + url + .split('/') + .pop()! + .replace('.pte', '') + .replace(/(_qnn_[a-z0-9]+)_v\d+$/, '$1'); /** The backend a `.pte` filename declares, or `undefined` when it declares none. */ const backendOf = (url: string): string | undefined => { diff --git a/packages/react-native-executorch/__tests__/api/modelVariants.test.ts b/packages/react-native-executorch/__tests__/api/modelVariants.test.ts index cb6eef3dc1..dde1363313 100644 --- a/packages/react-native-executorch/__tests__/api/modelVariants.test.ts +++ b/packages/react-native-executorch/__tests__/api/modelVariants.test.ts @@ -84,11 +84,13 @@ function registryFor(options: { os: 'ios' | 'android'; backends?: string[]; isEmulator?: boolean; + qnnHtpArch?: string; }): Node { fakeJsi.setRegisteredBackends( options.backends ?? ['XnnpackBackend', 'CoreMLBackend', 'MLXBackend', 'VulkanBackend'] ); fakeJsi.setIsEmulator(options.isEmulator ?? false); + fakeJsi.setQnnHtpArch(options.qnnHtpArch); jest.resetModules(); setPlatform(options.os); @@ -404,6 +406,47 @@ describe('deprecated variants', () => { }); }); +describe('QNN variants', () => { + const ANDROID_WITH_QNN = ['XnnpackBackend', 'VulkanBackend', 'QnnBackend']; + const efficientnet = (registry: Node): Node => + (registry.classification as Node).EFFICIENTNET_V2_S as Node; + + it('prefers QNN on Android when the backend is linked and the SoC is known', () => { + const registry = registryFor({ os: 'android', backends: ANDROID_WITH_QNN, qnnHtpArch: 'v75' }); + expect(defaultKeyOf(efficientnet(registry))).toBe('QNN_A16W8'); + }); + + it("resolves a QNN variant to the device's Hexagon version", () => { + const registry = registryFor({ os: 'android', backends: ANDROID_WITH_QNN, qnnHtpArch: 'v75' }); + expect(modelPathsOf(efficientnet(registry).DEFAULT as Node)).toMatch( + /\/qnn\/efficientnet_v2_s_qnn_a16w8_v75\.pte$/ + ); + }); + + it('skips QNN on a device that cannot run it, even with the backend linked', () => { + const registry = registryFor({ os: 'android', backends: ANDROID_WITH_QNN }); + expect(defaultKeyOf(efficientnet(registry))).toBe('XNNPACK_INT8'); + }); + + it('skips QNN when the app did not link the backend', () => { + const registry = registryFor({ os: 'android', qnnHtpArch: 'v81' }); + expect(defaultKeyOf(efficientnet(registry))).toBe('XNNPACK_INT8'); + }); + + it('never picks QNN on iOS', () => { + const registry = registryFor({ + os: 'ios', + backends: ['XnnpackBackend', 'CoreMLBackend', 'QnnBackend'], + qnnHtpArch: 'v81', + }); + expect( + defaultsOf(registry) + .filter(({ key }) => key?.startsWith('QNN')) + .map(({ label, key }) => `${label}: ${key}`) + ).toEqual([]); + }); +}); + describe('feature map', () => { // `models...DEFAULT` only reaches the accelerated export when // the app downloaded that backend, and `features` is the documented way to diff --git a/packages/react-native-executorch/__tests__/api/nativeLibsConfig.test.ts b/packages/react-native-executorch/__tests__/api/nativeLibsConfig.test.ts index 3b8d2dbf8c..4b81269a76 100644 --- a/packages/react-native-executorch/__tests__/api/nativeLibsConfig.test.ts +++ b/packages/react-native-executorch/__tests__/api/nativeLibsConfig.test.ts @@ -93,13 +93,15 @@ describe('native libs user config', () => { expect(findUserConfig().config).toBeUndefined(); }); - it('falls back to enabling everything when there is no block', () => { + it('falls back to enabling everything but opt-in qnn when there is no block', () => { writeManifest(root); const { readUserConfig, ALL_BACKENDS, ALL_LIBS } = loadWith(root); const resolved = readUserConfig(); - expect(new Set(resolved.backends)).toEqual(new Set(ALL_BACKENDS)); + expect(new Set(resolved.backends)).toEqual( + new Set(ALL_BACKENDS.filter((b: string) => b !== 'qnn')) + ); expect(new Set(resolved.libs)).toEqual(new Set(ALL_LIBS)); }); diff --git a/packages/react-native-executorch/__tests__/support/fakeJsi.ts b/packages/react-native-executorch/__tests__/support/fakeJsi.ts index 1c4bc7e5c8..a5602446a7 100644 --- a/packages/react-native-executorch/__tests__/support/fakeJsi.ts +++ b/packages/react-native-executorch/__tests__/support/fakeJsi.ts @@ -212,6 +212,7 @@ let registeredBackends: string[] = ['XnnpackBackend', 'CoreMLBackend']; const jsi = { isEmulator: false, + qnnHtpArch: undefined as string | undefined, createTensor: (shape: number[], dtype: DType) => new FakeTensor(dtype, shape), @@ -292,6 +293,15 @@ export const fakeJsi = { jsi.isEmulator = value; }, + /** + * Sets the Hexagon version the native installer would report for QNN. + * @param value The version (e.g. `'v81'`), or `undefined` for a device that + * cannot run QNN models. + */ + setQnnHtpArch(value: string | undefined): void { + jsi.qnnHtpArch = value; + }, + /** @returns Every `execute` call made so far, in order. */ executions(): readonly { path: string; methodName: string }[] { return executions; @@ -326,6 +336,7 @@ export const fakeJsi = { executions.length = 0; registeredBackends = ['XnnpackBackend', 'CoreMLBackend']; jsi.isEmulator = false; + jsi.qnnHtpArch = undefined; fakePhonemizer.reset(); llm.reset(); tensorTracker.reset(); diff --git a/packages/react-native-executorch/android/CMakeLists.txt b/packages/react-native-executorch/android/CMakeLists.txt index 56793ecd17..bbd4749b16 100644 --- a/packages/react-native-executorch/android/CMakeLists.txt +++ b/packages/react-native-executorch/android/CMakeLists.txt @@ -23,6 +23,7 @@ option(RNE_ENABLE_OPENCV "Compile + link OpenCV-dependent sources" ON) option(RNE_ENABLE_PHONEMIS "Compile phonemis (TTS) sources" ON) option(RNE_ENABLE_XNNPACK "Link the XNNPACK backend shared library" ON) option(RNE_ENABLE_VULKAN "Link the Vulkan backend shared library" ON) +option(RNE_ENABLE_QNN "Link the Qualcomm QNN backend shared library (arm64 only)" OFF) find_package(ReactAndroid REQUIRED CONFIG) @@ -169,6 +170,15 @@ if(RNE_ENABLE_VULKAN) IMPORTED_LOCATION "${LIBS_DIR}/executorch/${ANDROID_ABI}/libvulkan_executorch_backend.so") endif() +# ------- QNN backend (optional, arm64 only) ------- +# The Qualcomm runtime it dlopens at load time (libQnnHtp.so and the Hexagon +# skels) comes from the com.qualcomm.qti:qnn-runtime Maven artifact. +if(RNE_ENABLE_QNN AND ANDROID_ABI STREQUAL "arm64-v8a") + add_library(qnn_executorch_backend SHARED IMPORTED) + set_target_properties(qnn_executorch_backend PROPERTIES + IMPORTED_LOCATION "${LIBS_DIR}/executorch/${ANDROID_ABI}/libqnn_executorch_backend.so") +endif() + # ------- OpenCV (optional, static) ------- set(OPENCV_LINK_LIBS "") if(RNE_ENABLE_OPENCV) @@ -224,3 +234,6 @@ endif() if(RNE_ENABLE_VULKAN) target_link_libraries(${CMAKE_PROJECT_NAME} vulkan_executorch_backend) endif() +if(TARGET qnn_executorch_backend) + target_link_libraries(${CMAKE_PROJECT_NAME} qnn_executorch_backend) +endif() diff --git a/packages/react-native-executorch/android/build.gradle.kts b/packages/react-native-executorch/android/build.gradle.kts index 8dfea64165..84a887d577 100644 --- a/packages/react-native-executorch/android/build.gradle.kts +++ b/packages/react-native-executorch/android/build.gradle.kts @@ -52,6 +52,8 @@ fun rneBuildConfig(): Map<*, *> { val rneConfig = rneBuildConfig() fun rneFlag(key: String): String = if (rneConfig[key] != false) "ON" else "OFF" +// QNN is opt-in, so a config written before it existed (no key) means off. +val enableQnn = rneConfig["enableQnn"] == true /** * The prebuilt ExecuTorch runtime is not in the npm tarball - `package.json` @@ -127,7 +129,8 @@ android { "-DRNE_ENABLE_OPENCV=${rneFlag("enableOpencv")}", "-DRNE_ENABLE_PHONEMIS=${rneFlag("enablePhonemis")}", "-DRNE_ENABLE_XNNPACK=${rneFlag("enableXnnpack")}", - "-DRNE_ENABLE_VULKAN=${rneFlag("enableVulkan")}" + "-DRNE_ENABLE_VULKAN=${rneFlag("enableVulkan")}", + "-DRNE_ENABLE_QNN=${if (enableQnn) "ON" else "OFF"}" ) abiFilters.addAll(reactNativeArchitectures()) @@ -177,10 +180,11 @@ android { // user who provisions third-party/ by hand gets the same treatment. val backendLibs = mapOf( "enableXnnpack" to "libxnnpack_executorch_backend.so", - "enableVulkan" to "libvulkan_executorch_backend.so" + "enableVulkan" to "libvulkan_executorch_backend.so", + "enableQnn" to "libqnn_executorch_backend.so" ) for ((flag, soName) in backendLibs) { - if (rneConfig[flag] == false) { + if (rneConfig[flag] == false || (flag == "enableQnn" && !enableQnn)) { logger.lifecycle("[RnExecutorch] $flag is off; excluding $soName from the APK") jniLibs.excludes.add("**/$soName") } @@ -213,6 +217,13 @@ dependencies { // to third-party/android/libs/executorch.jar. implementation(files("../third-party/android/libs/executorch.jar")) + // Qualcomm's QNN runtime (libQnnHtp, libQnnSystem and the per-arch Hexagon + // stubs/skels) for the QNN backend. Its version must match the QAIRT SDK the + // backend and the .pte files were built with. + if (enableQnn) { + implementation("com.qualcomm.qti:qnn-runtime:2.47.0") + } + // Recommended for modern Kotlin Android development implementation("androidx.core:core-ktx:1.12.0") } diff --git a/packages/react-native-executorch/android/src/main/AndroidManifest.xml b/packages/react-native-executorch/android/src/main/AndroidManifest.xml index a2f47b6057..e37b3b120c 100644 --- a/packages/react-native-executorch/android/src/main/AndroidManifest.xml +++ b/packages/react-native-executorch/android/src/main/AndroidManifest.xml @@ -1,2 +1,8 @@ + + + + diff --git a/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/RnExecutorchModule.kt b/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/RnExecutorchModule.kt index 12ac5b8d5c..9dbca41d5a 100644 --- a/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/RnExecutorchModule.kt +++ b/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/RnExecutorchModule.kt @@ -1,19 +1,39 @@ package com.swmansion.rnexecutorch +import android.system.Os import com.facebook.react.bridge.JavaScriptContextHolder import com.facebook.react.bridge.ReactApplicationContext -class RnExecutorchModule(reactContext: ReactApplicationContext) : RnExecutorchSpec(reactContext) { - +class RnExecutorchModule( + reactContext: ReactApplicationContext, +) : RnExecutorchSpec(reactContext) { override fun getName(): String = NAME override fun install(): Boolean { val contextHolder: JavaScriptContextHolder = reactApplicationContext.javaScriptContextHolder ?: return false + exposeQnnSkels() nativeInstall(contextHolder.get()) return true } + // The Hexagon DSP loads the QNN skel libraries by path, searching + // ADSP_LIBRARY_PATH, so it has to name the app's native library dir before + // the first QNN model loads. The skels are only on disk when the app extracts + // its native libs (packaging.jniLibs.useLegacyPackaging = true); without them + // the variable stays unset and the JS side does not offer QNN variants. + private fun exposeQnnSkels() { + val libDir = reactApplicationContext.applicationInfo.nativeLibraryDir ?: return + val hasSkel = + java.io + .File(libDir) + .list() + ?.any { it.startsWith("libQnnHtpV") && it.endsWith("Skel.so") } == true + if (!hasSkel) return + val defaults = "/vendor/dsp/cdsp;/vendor/lib/rfsa/adsp;/system/lib/rfsa/adsp;/dsp" + Os.setenv("ADSP_LIBRARY_PATH", "$libDir;$defaults", true) + } + companion object { const val NAME = "RnExecutorch" diff --git a/packages/react-native-executorch/cpp/core/install.cpp b/packages/react-native-executorch/cpp/core/install.cpp index 0800664846..87b4fb8104 100644 --- a/packages/react-native-executorch/cpp/core/install.cpp +++ b/packages/react-native-executorch/cpp/core/install.cpp @@ -8,6 +8,7 @@ namespace rnexecutorch::core { void install(facebook::jsi::Runtime &rt, facebook::jsi::Object &module) { utils::install_getExecuTorchRegisteredBackends(rt, module); utils::install_isEmulator(rt, module); + utils::install_qnnHtpArch(rt, module); model::install_loadModel(rt, module); tensor::install_createTensor(rt, module); profiler::install_profiler(rt, module); diff --git a/packages/react-native-executorch/cpp/core/utils.cpp b/packages/react-native-executorch/cpp/core/utils.cpp index b0a0fd0f10..59d127a419 100644 --- a/packages/react-native-executorch/cpp/core/utils.cpp +++ b/packages/react-native-executorch/cpp/core/utils.cpp @@ -1,7 +1,12 @@ #include "utils.h" +#include +#include #include +#include #include +#include +#include #include #include @@ -18,6 +23,67 @@ namespace rnexecutorch::core::utils { namespace jsi = facebook::jsi; namespace { +#ifdef __ANDROID__ +std::string readProp(const char *key) { +#if __ANDROID_API__ >= 26 + const prop_info *pi = __system_property_find(key); + if (pi == nullptr) { + return ""; + } + std::string result; + __system_property_read_callback( + pi, + [](void *cookie, const char * /*name*/, const char *value, uint32_t /*serial*/) { + *static_cast(cookie) = value; + }, + &result); + return result; +#else + char value[PROP_VALUE_MAX] = {0}; + __system_property_get(key, value); + return {value}; +#endif +} +#endif + +// The Hexagon (HTP) version a QNN .pte has to be compiled for, from the SoC. +// A QNN context binary targets one HTP version, so the registry publishes one +// file per version and picks it from this. Only Snapdragon parts ExecuTorch's +// QNN backend knows (backends/qualcomm/serialization/qc_schema.py) are listed. +// Returns nothing off Qualcomm, on an unlisted SoC, or when the skel for that +// version is not where the DSP will look for it (ADSP_LIBRARY_PATH, set by the +// Android module), since a QNN model could not load then. +std::optional qnnHtpArch() { +#ifdef __ANDROID__ + static const std::unordered_map kSocToHtp = { + {"SM8450", 69}, + {"SM8475", 69}, + {"SM8550", 73}, + {"SM8650", 75}, + {"SM8750", 79}, + {"SM8845", 81}, + {"SM8850", 81}, + }; + const auto it = kSocToHtp.find(readProp("ro.soc.model")); + if (it == kSocToHtp.end()) { + return std::nullopt; + } + const char *adspPath = std::getenv("ADSP_LIBRARY_PATH"); + if (adspPath == nullptr) { + return std::nullopt; + } + const std::string_view paths{adspPath}; + const std::string firstDir{paths.substr(0, paths.find(';'))}; + std::error_code ec; + if (!std::filesystem::exists(std::format("{}/libQnnHtpV{}Skel.so", firstDir, it->second), ec)) { + return std::nullopt; + } + return std::format("v{}", it->second); +#else + return std::nullopt; +#endif +} + // Detects an Android emulator / iOS simulator. On Android no single property // covers every image, so we check three: the build fingerprint (`generic...` // for AOSP images), the hardware name (`goldfish`/`ranchu` are the QEMU @@ -27,27 +93,6 @@ namespace { // is known at compile time. bool isEmulator() { #ifdef __ANDROID__ - auto readProp = [](const char *key) -> std::string { -#if __ANDROID_API__ >= 26 - const prop_info *pi = __system_property_find(key); - if (pi == nullptr) { - return ""; - } - std::string result; - __system_property_read_callback( - pi, - [](void *cookie, const char * /*name*/, const char *value, uint32_t /*serial*/) { - *static_cast(cookie) = value; - }, - &result); - return result; -#else - char value[PROP_VALUE_MAX] = {0}; - __system_property_get(key, value); - return {value}; -#endif - }; - const auto startsWith = [](const std::string &value, const char *prefix) { return value.rfind(prefix, 0) == 0; }; @@ -105,4 +150,10 @@ void install_getExecuTorchRegisteredBackends(jsi::Runtime &rt, jsi::Object &modu void install_isEmulator(jsi::Runtime &rt, jsi::Object &module) { module.setProperty(rt, "isEmulator", jsi::Value(isEmulator())); } + +void install_qnnHtpArch(jsi::Runtime &rt, jsi::Object &module) { + const auto arch = qnnHtpArch(); + module.setProperty(rt, "qnnHtpArch", + arch ? jsi::Value(jsi::String::createFromUtf8(rt, *arch)) : jsi::Value::undefined()); +} } // namespace rnexecutorch::core::utils diff --git a/packages/react-native-executorch/cpp/core/utils.h b/packages/react-native-executorch/cpp/core/utils.h index ae352c7094..e45945fdf6 100644 --- a/packages/react-native-executorch/cpp/core/utils.h +++ b/packages/react-native-executorch/cpp/core/utils.h @@ -21,4 +21,14 @@ void install_getExecuTorchRegisteredBackends(facebook::jsi::Runtime &rt, faceboo * @param module The `__rnexecutorch_jsi__` module object to install onto. */ void install_isEmulator(facebook::jsi::Runtime &rt, facebook::jsi::Object &module); + +/** + * Installs `qnnHtpArch`, the Hexagon version (`"v69"` … `"v81"`) QNN models + * must be compiled for on this device, or `undefined` when the device cannot + * run them: not a known Snapdragon, or the matching skel is not reachable. + * + * @param rt The active JavaScript runtime. + * @param module The `__rnexecutorch_jsi__` module object to install onto. + */ +void install_qnnHtpArch(facebook::jsi::Runtime &rt, facebook::jsi::Object &module); } // namespace rnexecutorch::core::utils diff --git a/packages/react-native-executorch/scripts/download-libs.js b/packages/react-native-executorch/scripts/download-libs.js index 38b990f29c..dc42a3c575 100644 --- a/packages/react-native-executorch/scripts/download-libs.js +++ b/packages/react-native-executorch/scripts/download-libs.js @@ -23,6 +23,7 @@ * xnnpack-ios.tar.gz -- XnnpackBackend.xcframework (iOS) * vulkan-android-arm64-v8a.tar.gz -- libvulkan_executorch_backend.so (Android only) * vulkan-android-x86_64.tar.gz + * qnn-android-arm64-v8a.tar.gz -- libqnn_executorch_backend.so (Android arm64 only) * coreml-ios.tar.gz -- CoreMLBackend.xcframework (iOS only) * mlx-ios.tar.gz -- MLXBackend.xcframework + mlx.metallib (iOS only) * @@ -31,16 +32,17 @@ * * User configuration (in the app's package.json) — three optional arrays, all merged into a single set: * "react-native-executorch": { - * "backends": ["xnnpack", "coreml", "mlx", "vulkan"], + * "backends": ["xnnpack", "coreml", "mlx", "vulkan", "qnn"], * "libs": ["opencv", "phonemis"], * "features": ["llm", "textToSpeech", "objectDetection"] * } * * `features` is sugar — each one expands to a set of backends + libs via FEATURE_MAP below. - * If no `react-native-executorch` block is present, every backend and lib defaults to ON. + * If no `react-native-executorch` block is present, every backend and lib defaults to ON, + * except qnn, which is opt-in only (see below). * * Recognized values: - * backends: xnnpack, coreml (iOS), mlx (iOS), vulkan (Android) + * backends: xnnpack, coreml (iOS), mlx (iOS), vulkan (Android), qnn (Android arm64) * libs: opencv, phonemis * features: llm, multimodalLLM, speechToText, textToSpeech, vad, privacyFilter, * textEmbeddings, imageEmbeddings, @@ -56,6 +58,9 @@ * coreml iOS only — toggles CoreMLBackend.xcframework. * mlx iOS only — toggles MLXBackend.xcframework + mlx.metallib resource. * vulkan Android only — toggles libvulkan_executorch_backend.so. + * qnn Android arm64 only — toggles libqnn_executorch_backend.so and the Qualcomm + * runtime (Maven com.qualcomm.qti:qnn-runtime). Opt-in: the runtime adds tens of + * MB to the APK, so an app without a config block does not get it. * * Environment variables: * RNET_SKIP_DOWNLOAD=1 -- skip download entirely (for CI with pre-cached libs) @@ -112,7 +117,11 @@ const CACHE_DIR = process.env.RNET_LIBS_CACHE_DIR || DEFAULT_CACHE_DIR; // ---- User config ----------------------------------------------------------- -const ALL_BACKENDS = ['xnnpack', 'coreml', 'mlx', 'vulkan']; +const ALL_BACKENDS = ['xnnpack', 'coreml', 'mlx', 'vulkan', 'qnn']; +// What an app without a config block gets. qnn stays out: it pulls the +// Qualcomm runtime from Maven, which is far larger than any other backend and +// only pays off on Snapdragon devices. +const DEFAULT_BACKENDS = ALL_BACKENDS.filter((b) => b !== 'qnn'); const ALL_LIBS = ['opencv', 'phonemis']; // features -> { backends, libs } @@ -235,7 +244,11 @@ function findUserConfig() { } function readUserConfig() { - const allOn = () => ({ backends: [...ALL_BACKENDS], libs: [...ALL_LIBS], opencvPod: undefined }); + const allOn = () => ({ + backends: [...DEFAULT_BACKENDS], + libs: [...ALL_LIBS], + opencvPod: undefined, + }); const { config: rneConfig, manifest } = findUserConfig(); @@ -297,6 +310,7 @@ function writeBuildConfig({ backends, libs, opencvPod }) { enableCoreml: backends.includes('coreml'), enableMlx: backends.includes('mlx'), enableVulkan: backends.includes('vulkan'), + enableQnn: backends.includes('qnn'), }; // Which pod provides opencv2 on iOS. Only set when the app asks for a // specific one; otherwise the podspec picks, preferring an OpenCV the app @@ -325,6 +339,11 @@ function warnAboutPlatformAsymmetry({ backends }, targets) { '[react-native-executorch] mlx is enabled but the build targets only Android; the MLX backend is iOS-only and the flag has no effect here.' ); } + if (hasIos && !hasAndroid && backends.includes('qnn')) { + console.warn( + '[react-native-executorch] qnn is enabled but the build targets only iOS; the QNN backend is Android-only and the flag has no effect here.' + ); + } if (hasIos && !hasAndroid && backends.includes('vulkan')) { console.warn( '[react-native-executorch] vulkan is enabled but the build targets only iOS; the Vulkan backend is Android-only and the flag has no effect here.' @@ -402,6 +421,11 @@ function getArtifacts(targets, { backends, libs }) { if (backends.includes('vulkan') && target.startsWith('android')) { artifacts.push(makeArtifact(`vulkan-${target}`, destDir)); } + + // QNN targets Snapdragon's Hexagon NPU, so it is Android arm64 only + if (backends.includes('qnn') && target === 'android-arm64-v8a') { + artifacts.push(makeArtifact(`qnn-${target}`, destDir)); + } } return artifacts; @@ -527,6 +551,7 @@ const BACKEND_FILES = { android: { xnnpack: ['executorch/*/libxnnpack_executorch_backend.so'], vulkan: ['executorch/*/libvulkan_executorch_backend.so'], + qnn: ['executorch/*/libqnn_executorch_backend.so'], }, ios: { xnnpack: ['XnnpackBackend.xcframework'], @@ -651,7 +676,7 @@ async function main() { `[react-native-executorch] Backends: [${config.backends.join(', ') || '—'}]; Libs: [${config.libs.join(', ') || '—'}]` ); console.log( - `[react-native-executorch] Build flags: opencv=${buildConfig.enableOpencv}, phonemis=${buildConfig.enablePhonemis}, xnnpack=${buildConfig.enableXnnpack}, coreml=${buildConfig.enableCoreml}, mlx=${buildConfig.enableMlx}, vulkan=${buildConfig.enableVulkan}` + `[react-native-executorch] Build flags: opencv=${buildConfig.enableOpencv}, phonemis=${buildConfig.enablePhonemis}, xnnpack=${buildConfig.enableXnnpack}, coreml=${buildConfig.enableCoreml}, mlx=${buildConfig.enableMlx}, vulkan=${buildConfig.enableVulkan}, qnn=${buildConfig.enableQnn}` ); const targets = detectTargets(); diff --git a/packages/react-native-executorch/scripts/package-release-artifacts.sh b/packages/react-native-executorch/scripts/package-release-artifacts.sh index 0ebee9dea3..99b45b0f4d 100755 --- a/packages/react-native-executorch/scripts/package-release-artifacts.sh +++ b/packages/react-native-executorch/scripts/package-release-artifacts.sh @@ -16,6 +16,7 @@ # xnnpack-android-x86_64.tar.gz + .sha256 # vulkan-android-arm64-v8a.tar.gz + .sha256 # vulkan-android-x86_64.tar.gz + .sha256 +# qnn-android-arm64-v8a.tar.gz + .sha256 (arm64 only; Qualcomm runtime comes from Maven) # core-ios.tar.gz + .sha256 (ExecutorchLib.xcframework + libthreadpool_*.a) # xnnpack-ios.tar.gz + .sha256 # coreml-ios.tar.gz + .sha256 @@ -218,6 +219,10 @@ package_file "vulkan-android-arm64-v8a" \ package_file "vulkan-android-x86_64" \ "executorch/x86_64" "$ANDROID_LIBS/executorch/x86_64/libvulkan_executorch_backend.so" +# QNN runs on the Snapdragon Hexagon NPU, so there is no x86_64 build. +package_file "qnn-android-arm64-v8a" \ + "executorch/arm64-v8a" "$ANDROID_LIBS/executorch/arm64-v8a/libqnn_executorch_backend.so" + # ---- iOS -------------------------------------------------------------------- # Note: OpenCV for iOS is provided by CocoaPods (opencv-rne dependency). # No opencv-ios tarball is needed. diff --git a/packages/react-native-executorch/src/models.ts b/packages/react-native-executorch/src/models.ts index ad8074f9a2..d5232fd5a3 100644 --- a/packages/react-native-executorch/src/models.ts +++ b/packages/react-native-executorch/src/models.ts @@ -72,7 +72,7 @@ import { // that order pins one per platform — see the second argument of `variants`. /** Every backend the registry publishes for, spelled as the variant keys spell it. */ -const ALL_BACKENDS = ['xnnpack', 'coreml', 'mlx', 'vulkan'] as const; +const ALL_BACKENDS = ['xnnpack', 'coreml', 'mlx', 'vulkan', 'qnn'] as const; /** The backend prefix a variant key starts with. */ type BackendTag = (typeof ALL_BACKENDS)[number]; @@ -86,15 +86,25 @@ const PLATFORM: TargetPlatform = Platform.OS === 'ios' ? 'ios' : 'android'; // MLX or Vulkan once it has been shown to run better there, and XNNPACK is the // one backend every model exports to. Core ML sits above MLX only to make the // order deterministic; every group publishing both pins its winner explicitly. +// QNN leads on Android: it runs on the Hexagon NPU, and a model only gets a +// QNN export once it beats the GPU there. // // The iOS simulator links the Core ML backend but cannot run it: no Neural // Engine, and MPSGraph refuses the compiled models. MLX only ever ships a // device slice, so it has nothing to run there either. const BACKEND_ORDER: Record = { ios: rnexecutorchJsi.isEmulator === true ? ['xnnpack'] : ['coreml', 'mlx', 'xnnpack'], - android: ['vulkan', 'xnnpack'], + android: ['qnn', 'vulkan', 'xnnpack'], }; +/** + * The Hexagon version (`v69` … `v81`) this device's QNN models are compiled + * for, or `undefined` when it cannot run them. A QNN `.pte` targets a single + * Hexagon version, so QNN variants are published once per version and resolve + * to this device's file. + */ +const QNN_HTP_ARCH: string | undefined = rnexecutorchJsi.qnnHtpArch; + /** * The backends this platform may default to, best first. * @returns This platform's order, less every backend the binary was not linked @@ -111,7 +121,13 @@ function getCandidateBackends(): readonly BackendTag[] { if (registered.length === 0) return BACKEND_ORDER[PLATFORM]; const names = registered.map((name) => name.toLowerCase()); - return BACKEND_ORDER[PLATFORM].filter((tag) => names.some((name) => name.startsWith(tag))); + return BACKEND_ORDER[PLATFORM].filter( + (tag) => + names.some((name) => name.startsWith(tag)) && + // A linked QNN backend is not enough: the SoC also has to be one the + // published files target, with its Hexagon skel reachable. + (tag !== 'qnn' || QNN_HTP_ARCH !== undefined) + ); } const CANDIDATE_BACKENDS = getCandidateBackends(); @@ -261,6 +277,12 @@ const EFFICIENTNET_V2_S_COREML_FP16: ClassifierModel = { modelPath: `${BASE_URL}/EfficientNetV2-S-ExecuTorch/${VERSION_TAG}/coreml/efficientnet_v2_s_coreml_fp16.pte`, modelOpts: EFFICIENTNET_V2_S_OPTS, }; +// Resolves to this device's Hexagon version; off Snapdragon the v81 file stands +// in, and DEFAULT never picks it there. +const EFFICIENTNET_V2_S_QNN_A16W8: ClassifierModel = { + modelPath: `${BASE_URL}/EfficientNetV2-S-ExecuTorch/${VERSION_TAG}/qnn/efficientnet_v2_s_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: EFFICIENTNET_V2_S_OPTS, +}; // ============================================================================= // Style Transfer @@ -2527,6 +2549,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(EFFICIENTNET_V2_S_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: EFFICIENTNET_V2_S_COREML_FP16, + QNN_A16W8: EFFICIENTNET_V2_S_QNN_A16W8, }), }, From 8bd888c44aca9741c7222cabca464bb053b2e7dd Mon Sep 17 00:00:00 2001 From: Mateusz Sluszniak Date: Thu, 24 Sep 2026 14:45:17 +0200 Subject: [PATCH 02/16] feat(models): add the DeepLabV3-MobileNetV3 QNN export Returns a class index per pixel rather than 21 logit planes, so the segmenter takes its index-map path and skips the transpose and argmax over H*W*K floats. 66.55% mIoU on an S26 Ultra over 200 VOC2012 val images, against 66.56% for the XNNPACK int8 file, at 3.20 ms/inference. --- packages/react-native-executorch/src/models.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/react-native-executorch/src/models.ts b/packages/react-native-executorch/src/models.ts index d5232fd5a3..7ed98bd2e8 100644 --- a/packages/react-native-executorch/src/models.ts +++ b/packages/react-native-executorch/src/models.ts @@ -568,6 +568,13 @@ const DEEPLAB_V3_MOBILENET_V3_LARGE_COREML_FP16: SemanticSegmenterModel = { + modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_mobilenet_v3_large_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: DEEPLAB_V3_OPTS, +}; const FCN_OPTS = { labels: PASCAL_VOC_LABELS, @@ -2742,6 +2749,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(DEEPLAB_V3_MOBILENET_V3_LARGE_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: DEEPLAB_V3_MOBILENET_V3_LARGE_COREML_FP16, + QNN_A16W8: DEEPLAB_V3_MOBILENET_V3_LARGE_QNN_A16W8, }), /** * Fully Convolutional Network (FCN) semantic segmentation model with From acef48c7df1e365e09504e5cce909b9a41da8342 Mon Sep 17 00:00:00 2001 From: Mateusz Sluszniak Date: Fri, 25 Sep 2026 13:28:36 +0200 Subject: [PATCH 03/16] feat(apps): offer the DeepLabV3-MobileNetV3 QNN export in the CV demo --- apps/computer-vision/app/segmentation/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/computer-vision/app/segmentation/index.tsx b/apps/computer-vision/app/segmentation/index.tsx index 8222bfa60a..54c444a2fd 100644 --- a/apps/computer-vision/app/segmentation/index.tsx +++ b/apps/computer-vision/app/segmentation/index.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef } from 'react'; -import { View, Text, ScrollView } from 'react-native'; +import { View, Text, ScrollView, Platform } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { commonStyles, theme } from '../../theme'; import { @@ -39,6 +39,11 @@ const SEGMENTATION_OPTIONS: ModelOption[] = [ label: 'DeepLab V3 MobileNet V3 (INT8)', value: models.semanticSegmentation.DEEPLAB_V3_MOBILENET_V3_LARGE.XNNPACK_INT8, }, + { + label: 'DeepLab V3 MobileNet V3 (QNN A16W8)', + value: models.semanticSegmentation.DEEPLAB_V3_MOBILENET_V3_LARGE.QNN_A16W8, + disabled: Platform.OS !== 'android', + }, { label: 'FCN ResNet50 (INT8)', value: models.semanticSegmentation.FCN_RESNET50.XNNPACK_INT8, From 0def3dadcd40efb38a1291fec710778817ee59a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Fri, 25 Sep 2026 16:36:19 +0200 Subject: [PATCH 04/16] feat(models): add the DeepLabV3-ResNet50 QNN a16w8 variant 76.74% mIoU on 200 VOC2012 val images on an S26 Ultra, against 76.71% for the XNNPACK int8 file, at 20.1 ms/inference. --- packages/react-native-executorch/src/models.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/react-native-executorch/src/models.ts b/packages/react-native-executorch/src/models.ts index 7ed98bd2e8..eb1951a46c 100644 --- a/packages/react-native-executorch/src/models.ts +++ b/packages/react-native-executorch/src/models.ts @@ -544,6 +544,12 @@ const DEEPLAB_V3_RESNET50_COREML_FP16: SemanticSegmenterModel = modelPath: `${BASE_URL}/DeepLabV3-ExecuTorch/${VERSION_TAG}/coreml/deeplab_v3_resnet50_coreml_fp16.pte`, modelOpts: DEEPLAB_V3_OPTS, }; +// Same Hexagon-version resolution and index-map output as the MobileNetV3 QNN +// variant below. +const DEEPLAB_V3_RESNET50_QNN_A16W8: SemanticSegmenterModel = { + modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_resnet50_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: DEEPLAB_V3_OPTS, +}; const DEEPLAB_V3_RESNET101_XNNPACK_FP32: SemanticSegmenterModel = { modelPath: `${BASE_URL}/DeepLabV3-ExecuTorch/${VERSION_TAG}/xnnpack/deeplab_v3_resnet101_xnnpack_fp32.pte`, modelOpts: DEEPLAB_V3_OPTS, @@ -2725,6 +2731,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(DEEPLAB_V3_RESNET50_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: DEEPLAB_V3_RESNET50_COREML_FP16, + QNN_A16W8: DEEPLAB_V3_RESNET50_QNN_A16W8, }), /** * DeepLabV3 semantic segmentation model with ResNet-101 backbone (21 From 9c44ee051764419579534e2bd9e548262a4f7d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Fri, 25 Sep 2026 16:36:19 +0200 Subject: [PATCH 05/16] feat(apps): offer the DeepLabV3-ResNet50 QNN export in the CV demo --- apps/computer-vision/app/segmentation/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/computer-vision/app/segmentation/index.tsx b/apps/computer-vision/app/segmentation/index.tsx index 54c444a2fd..fbf979cba1 100644 --- a/apps/computer-vision/app/segmentation/index.tsx +++ b/apps/computer-vision/app/segmentation/index.tsx @@ -31,6 +31,11 @@ const SEGMENTATION_OPTIONS: ModelOption[] = [ label: 'DeepLab V3 ResNet50 (INT8)', value: models.semanticSegmentation.DEEPLAB_V3_RESNET50.XNNPACK_INT8, }, + { + label: 'DeepLab V3 ResNet50 (QNN A16W8)', + value: models.semanticSegmentation.DEEPLAB_V3_RESNET50.QNN_A16W8, + disabled: Platform.OS !== 'android', + }, { label: 'DeepLab V3 ResNet101 (INT8)', value: models.semanticSegmentation.DEEPLAB_V3_RESNET101.XNNPACK_INT8, From f542d8d590be24138e84dcd4294dc2ad87b770fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Fri, 25 Sep 2026 19:57:31 +0200 Subject: [PATCH 06/16] feat(models): add the DeepLabV3-ResNet101 and FCN QNN a16w8 variants On an S26 Ultra over 200 VOC2012 val images, against the XNNPACK int8 files: DeepLabV3-ResNet101 78.90% vs 78.82% mIoU, FCN-ResNet50 71.21% vs 71.36%, FCN-ResNet101 74.93% vs 74.99%. --- packages/react-native-executorch/src/models.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/react-native-executorch/src/models.ts b/packages/react-native-executorch/src/models.ts index eb1951a46c..a28c01c0e2 100644 --- a/packages/react-native-executorch/src/models.ts +++ b/packages/react-native-executorch/src/models.ts @@ -550,6 +550,10 @@ const DEEPLAB_V3_RESNET50_QNN_A16W8: SemanticSegmenterModel = { modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_resnet50_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: DEEPLAB_V3_OPTS, }; +const DEEPLAB_V3_RESNET101_QNN_A16W8: SemanticSegmenterModel = { + modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_resnet101_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: DEEPLAB_V3_OPTS, +}; const DEEPLAB_V3_RESNET101_XNNPACK_FP32: SemanticSegmenterModel = { modelPath: `${BASE_URL}/DeepLabV3-ExecuTorch/${VERSION_TAG}/xnnpack/deeplab_v3_resnet101_xnnpack_fp32.pte`, modelOpts: DEEPLAB_V3_OPTS, @@ -613,6 +617,16 @@ const FCN_RESNET101_COREML_FP16: SemanticSegmenterModel = { modelPath: `${BASE_URL}/FCN-ExecuTorch/${VERSION_TAG}/coreml/fcn_resnet101_coreml_fp16.pte`, modelOpts: FCN_OPTS, }; +// Same Hexagon-version resolution and index-map output as the DeepLabV3 QNN +// variants. +const FCN_RESNET50_QNN_A16W8: SemanticSegmenterModel = { + modelPath: `${BASE_URL}-fcn/${NEXT_VERSION_TAG}/qnn/fcn_resnet50_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: FCN_OPTS, +}; +const FCN_RESNET101_QNN_A16W8: SemanticSegmenterModel = { + modelPath: `${BASE_URL}-fcn/${NEXT_VERSION_TAG}/qnn/fcn_resnet101_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: FCN_OPTS, +}; // ============================================================================= // Object Detection @@ -2743,6 +2757,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(DEEPLAB_V3_RESNET101_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: DEEPLAB_V3_RESNET101_COREML_FP16, + QNN_A16W8: DEEPLAB_V3_RESNET101_QNN_A16W8, }), /** * DeepLabV3 semantic segmentation model with MobileNetV3-Large backbone (21 @@ -2767,6 +2782,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(FCN_RESNET50_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: FCN_RESNET50_COREML_FP16, + QNN_A16W8: FCN_RESNET50_QNN_A16W8, }), /** * Fully Convolutional Network (FCN) semantic segmentation model with @@ -2777,6 +2793,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(FCN_RESNET101_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: FCN_RESNET101_COREML_FP16, + QNN_A16W8: FCN_RESNET101_QNN_A16W8, }), }, From 6fedaa3edc11d4eacfd6f43d9df3963701116f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Fri, 25 Sep 2026 19:57:31 +0200 Subject: [PATCH 07/16] feat(apps): offer the DeepLabV3-ResNet101 and FCN QNN exports in the CV demo --- apps/computer-vision/app/segmentation/index.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/computer-vision/app/segmentation/index.tsx b/apps/computer-vision/app/segmentation/index.tsx index fbf979cba1..18604b454c 100644 --- a/apps/computer-vision/app/segmentation/index.tsx +++ b/apps/computer-vision/app/segmentation/index.tsx @@ -40,6 +40,11 @@ const SEGMENTATION_OPTIONS: ModelOption[] = [ label: 'DeepLab V3 ResNet101 (INT8)', value: models.semanticSegmentation.DEEPLAB_V3_RESNET101.XNNPACK_INT8, }, + { + label: 'DeepLab V3 ResNet101 (QNN A16W8)', + value: models.semanticSegmentation.DEEPLAB_V3_RESNET101.QNN_A16W8, + disabled: Platform.OS !== 'android', + }, { label: 'DeepLab V3 MobileNet V3 (INT8)', value: models.semanticSegmentation.DEEPLAB_V3_MOBILENET_V3_LARGE.XNNPACK_INT8, @@ -53,10 +58,20 @@ const SEGMENTATION_OPTIONS: ModelOption[] = [ label: 'FCN ResNet50 (INT8)', value: models.semanticSegmentation.FCN_RESNET50.XNNPACK_INT8, }, + { + label: 'FCN ResNet50 (QNN A16W8)', + value: models.semanticSegmentation.FCN_RESNET50.QNN_A16W8, + disabled: Platform.OS !== 'android', + }, { label: 'FCN ResNet101 (INT8)', value: models.semanticSegmentation.FCN_RESNET101.XNNPACK_INT8, }, + { + label: 'FCN ResNet101 (QNN A16W8)', + value: models.semanticSegmentation.FCN_RESNET101.QNN_A16W8, + disabled: Platform.OS !== 'android', + }, ]; function SegmentationContent() { From 842d5c282dcfdf3d3bb937ee30a251da34fb6a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Fri, 25 Sep 2026 22:21:27 +0200 Subject: [PATCH 08/16] feat(models): add the LR-ASPP MobileNetV3 QNN a16w8 variant 65.83% mIoU on an S26 Ultra over 200 VOC2012 val images, against 65.06% for the XNNPACK int8 file, at 2.06 ms/inference. --- packages/react-native-executorch/src/models.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/react-native-executorch/src/models.ts b/packages/react-native-executorch/src/models.ts index a28c01c0e2..6ea425c899 100644 --- a/packages/react-native-executorch/src/models.ts +++ b/packages/react-native-executorch/src/models.ts @@ -524,6 +524,12 @@ const LRASPP_MOBILENET_V3_LARGE_COREML_FP16: SemanticSegmenterModel = { + modelPath: `${BASE_URL}-lraspp/${NEXT_VERSION_TAG}/qnn/lraspp_mobilenet_v3_large_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: LRASPP_MOBILENET_V3_LARGE_OPTS, +}; const DEEPLAB_V3_OPTS = { labels: PASCAL_VOC_LABELS, @@ -2734,6 +2740,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(LRASPP_MOBILENET_V3_LARGE_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: LRASPP_MOBILENET_V3_LARGE_COREML_FP16, + QNN_A16W8: LRASPP_MOBILENET_V3_LARGE_QNN_A16W8, }), /** * DeepLabV3 semantic segmentation model with ResNet-50 backbone (21 From c2dfb9b2b4b8a4ce8dc16283176caa30d975d6aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Fri, 25 Sep 2026 22:21:27 +0200 Subject: [PATCH 09/16] feat(apps): offer the LR-ASPP QNN export in the CV demo --- apps/computer-vision/app/segmentation/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/computer-vision/app/segmentation/index.tsx b/apps/computer-vision/app/segmentation/index.tsx index 18604b454c..3f85372d61 100644 --- a/apps/computer-vision/app/segmentation/index.tsx +++ b/apps/computer-vision/app/segmentation/index.tsx @@ -27,6 +27,11 @@ const SEGMENTATION_OPTIONS: ModelOption[] = [ label: 'LRASPP MobileNet V3 (INT8)', value: models.semanticSegmentation.LRASPP_MOBILENET_V3_LARGE.XNNPACK_INT8, }, + { + label: 'LRASPP MobileNet V3 (QNN A16W8)', + value: models.semanticSegmentation.LRASPP_MOBILENET_V3_LARGE.QNN_A16W8, + disabled: Platform.OS !== 'android', + }, { label: 'DeepLab V3 ResNet50 (INT8)', value: models.semanticSegmentation.DEEPLAB_V3_RESNET50.XNNPACK_INT8, From 8b2d548c2988ac01ecd1ab291278684069972c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Mon, 19 Oct 2026 16:40:24 +0200 Subject: [PATCH 10/16] feat(models): switch the ResNet QNN segmentation variants to a8w8 DeepLabV3-R50/R101 and FCN-R50/R101 resolve QNN_A8W8 on every Hexagon version: about 2x faster at the same mIoU on an S26 Ultra (v81, 200 VOC2012 val). --- .../app/segmentation/index.tsx | 16 ++++++------- .../__tests__/api/modelRegistry.test.ts | 2 +- .../react-native-executorch/src/models.ts | 24 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/computer-vision/app/segmentation/index.tsx b/apps/computer-vision/app/segmentation/index.tsx index 3f85372d61..c85b3dfb24 100644 --- a/apps/computer-vision/app/segmentation/index.tsx +++ b/apps/computer-vision/app/segmentation/index.tsx @@ -37,8 +37,8 @@ const SEGMENTATION_OPTIONS: ModelOption[] = [ value: models.semanticSegmentation.DEEPLAB_V3_RESNET50.XNNPACK_INT8, }, { - label: 'DeepLab V3 ResNet50 (QNN A16W8)', - value: models.semanticSegmentation.DEEPLAB_V3_RESNET50.QNN_A16W8, + label: 'DeepLab V3 ResNet50 (QNN A8W8)', + value: models.semanticSegmentation.DEEPLAB_V3_RESNET50.QNN_A8W8, disabled: Platform.OS !== 'android', }, { @@ -46,8 +46,8 @@ const SEGMENTATION_OPTIONS: ModelOption[] = [ value: models.semanticSegmentation.DEEPLAB_V3_RESNET101.XNNPACK_INT8, }, { - label: 'DeepLab V3 ResNet101 (QNN A16W8)', - value: models.semanticSegmentation.DEEPLAB_V3_RESNET101.QNN_A16W8, + label: 'DeepLab V3 ResNet101 (QNN A8W8)', + value: models.semanticSegmentation.DEEPLAB_V3_RESNET101.QNN_A8W8, disabled: Platform.OS !== 'android', }, { @@ -64,8 +64,8 @@ const SEGMENTATION_OPTIONS: ModelOption[] = [ value: models.semanticSegmentation.FCN_RESNET50.XNNPACK_INT8, }, { - label: 'FCN ResNet50 (QNN A16W8)', - value: models.semanticSegmentation.FCN_RESNET50.QNN_A16W8, + label: 'FCN ResNet50 (QNN A8W8)', + value: models.semanticSegmentation.FCN_RESNET50.QNN_A8W8, disabled: Platform.OS !== 'android', }, { @@ -73,8 +73,8 @@ const SEGMENTATION_OPTIONS: ModelOption[] = [ value: models.semanticSegmentation.FCN_RESNET101.XNNPACK_INT8, }, { - label: 'FCN ResNet101 (QNN A16W8)', - value: models.semanticSegmentation.FCN_RESNET101.QNN_A16W8, + label: 'FCN ResNet101 (QNN A8W8)', + value: models.semanticSegmentation.FCN_RESNET101.QNN_A8W8, disabled: Platform.OS !== 'android', }, ]; diff --git a/packages/react-native-executorch/__tests__/api/modelRegistry.test.ts b/packages/react-native-executorch/__tests__/api/modelRegistry.test.ts index e004128185..b63083e840 100644 --- a/packages/react-native-executorch/__tests__/api/modelRegistry.test.ts +++ b/packages/react-native-executorch/__tests__/api/modelRegistry.test.ts @@ -105,7 +105,7 @@ const namedVariants = (group: Node): Node[] => const BACKENDS = /^(xnnpack|coreml|mlx|qnn|vulkan)$/; // `spinquant` is a quantization recipe rather than a plain precision, but it is // what the published Llama builds are named after. -const PRECISIONS = /^(fp32|fp16|bf16|int8|int4|8da4w|8da8w|4w|a16w8|dynamic|spinquant)$/; +const PRECISIONS = /^(fp32|fp16|bf16|int8|int4|8da4w|8da8w|4w|a16w8|a8w8|dynamic|spinquant)$/; // A QNN .pte targets one Hexagon version and carries it as a trailing `_vNN`, // after the usual modelname_backend_precision. diff --git a/packages/react-native-executorch/src/models.ts b/packages/react-native-executorch/src/models.ts index 6ea425c899..c9bffad13a 100644 --- a/packages/react-native-executorch/src/models.ts +++ b/packages/react-native-executorch/src/models.ts @@ -552,12 +552,12 @@ const DEEPLAB_V3_RESNET50_COREML_FP16: SemanticSegmenterModel = }; // Same Hexagon-version resolution and index-map output as the MobileNetV3 QNN // variant below. -const DEEPLAB_V3_RESNET50_QNN_A16W8: SemanticSegmenterModel = { - modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_resnet50_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, +const DEEPLAB_V3_RESNET50_QNN_A8W8: SemanticSegmenterModel = { + modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_resnet50_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: DEEPLAB_V3_OPTS, }; -const DEEPLAB_V3_RESNET101_QNN_A16W8: SemanticSegmenterModel = { - modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_resnet101_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, +const DEEPLAB_V3_RESNET101_QNN_A8W8: SemanticSegmenterModel = { + modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_resnet101_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: DEEPLAB_V3_OPTS, }; const DEEPLAB_V3_RESNET101_XNNPACK_FP32: SemanticSegmenterModel = { @@ -625,12 +625,12 @@ const FCN_RESNET101_COREML_FP16: SemanticSegmenterModel = { }; // Same Hexagon-version resolution and index-map output as the DeepLabV3 QNN // variants. -const FCN_RESNET50_QNN_A16W8: SemanticSegmenterModel = { - modelPath: `${BASE_URL}-fcn/${NEXT_VERSION_TAG}/qnn/fcn_resnet50_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, +const FCN_RESNET50_QNN_A8W8: SemanticSegmenterModel = { + modelPath: `${BASE_URL}-fcn/${NEXT_VERSION_TAG}/qnn/fcn_resnet50_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: FCN_OPTS, }; -const FCN_RESNET101_QNN_A16W8: SemanticSegmenterModel = { - modelPath: `${BASE_URL}-fcn/${NEXT_VERSION_TAG}/qnn/fcn_resnet101_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, +const FCN_RESNET101_QNN_A8W8: SemanticSegmenterModel = { + modelPath: `${BASE_URL}-fcn/${NEXT_VERSION_TAG}/qnn/fcn_resnet101_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: FCN_OPTS, }; @@ -2752,7 +2752,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(DEEPLAB_V3_RESNET50_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: DEEPLAB_V3_RESNET50_COREML_FP16, - QNN_A16W8: DEEPLAB_V3_RESNET50_QNN_A16W8, + QNN_A8W8: DEEPLAB_V3_RESNET50_QNN_A8W8, }), /** * DeepLabV3 semantic segmentation model with ResNet-101 backbone (21 @@ -2764,7 +2764,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(DEEPLAB_V3_RESNET101_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: DEEPLAB_V3_RESNET101_COREML_FP16, - QNN_A16W8: DEEPLAB_V3_RESNET101_QNN_A16W8, + QNN_A8W8: DEEPLAB_V3_RESNET101_QNN_A8W8, }), /** * DeepLabV3 semantic segmentation model with MobileNetV3-Large backbone (21 @@ -2789,7 +2789,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(FCN_RESNET50_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: FCN_RESNET50_COREML_FP16, - QNN_A16W8: FCN_RESNET50_QNN_A16W8, + QNN_A8W8: FCN_RESNET50_QNN_A8W8, }), /** * Fully Convolutional Network (FCN) semantic segmentation model with @@ -2800,7 +2800,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(FCN_RESNET101_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: FCN_RESNET101_COREML_FP16, - QNN_A16W8: FCN_RESNET101_QNN_A16W8, + QNN_A8W8: FCN_RESNET101_QNN_A8W8, }), }, From 5429bcb2814d175090ae19e2bb9e5dead834f8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Tue, 20 Oct 2026 11:40:58 +0200 Subject: [PATCH 11/16] feat(models): switch LR-ASPP and DeepLabV3-MBv3 QNN to a8w8, add EfficientNetV2-S QNN_A8W8 LR-ASPP and DeepLabV3-MBv3 resolve QNN_A8W8: about 2.2x faster at the same mIoU on an S26 Ultra (v81, 200 VOC2012 val). EfficientNetV2-S adds QNN_A8W8, 1.75x faster for 1.4 top-1; QNN_A16W8 stays its default. --- .../app/classification/index.tsx | 5 +++++ .../computer-vision/app/segmentation/index.tsx | 8 ++++---- packages/react-native-executorch/src/models.ts | 18 ++++++++++++------ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/apps/computer-vision/app/classification/index.tsx b/apps/computer-vision/app/classification/index.tsx index bc54d17017..703824649d 100644 --- a/apps/computer-vision/app/classification/index.tsx +++ b/apps/computer-vision/app/classification/index.tsx @@ -27,6 +27,11 @@ const MODEL_OPTIONS: ModelOption[] = [ value: models.classification.EFFICIENTNET_V2_S.QNN_A16W8, disabled: Platform.OS !== 'android', }, + { + label: 'EfficientNetV2-S (QNN A8W8)', + value: models.classification.EFFICIENTNET_V2_S.QNN_A8W8, + disabled: Platform.OS !== 'android', + }, ]; function ClassificationContent() { diff --git a/apps/computer-vision/app/segmentation/index.tsx b/apps/computer-vision/app/segmentation/index.tsx index c85b3dfb24..8a325df2eb 100644 --- a/apps/computer-vision/app/segmentation/index.tsx +++ b/apps/computer-vision/app/segmentation/index.tsx @@ -28,8 +28,8 @@ const SEGMENTATION_OPTIONS: ModelOption[] = [ value: models.semanticSegmentation.LRASPP_MOBILENET_V3_LARGE.XNNPACK_INT8, }, { - label: 'LRASPP MobileNet V3 (QNN A16W8)', - value: models.semanticSegmentation.LRASPP_MOBILENET_V3_LARGE.QNN_A16W8, + label: 'LRASPP MobileNet V3 (QNN A8W8)', + value: models.semanticSegmentation.LRASPP_MOBILENET_V3_LARGE.QNN_A8W8, disabled: Platform.OS !== 'android', }, { @@ -55,8 +55,8 @@ const SEGMENTATION_OPTIONS: ModelOption[] = [ value: models.semanticSegmentation.DEEPLAB_V3_MOBILENET_V3_LARGE.XNNPACK_INT8, }, { - label: 'DeepLab V3 MobileNet V3 (QNN A16W8)', - value: models.semanticSegmentation.DEEPLAB_V3_MOBILENET_V3_LARGE.QNN_A16W8, + label: 'DeepLab V3 MobileNet V3 (QNN A8W8)', + value: models.semanticSegmentation.DEEPLAB_V3_MOBILENET_V3_LARGE.QNN_A8W8, disabled: Platform.OS !== 'android', }, { diff --git a/packages/react-native-executorch/src/models.ts b/packages/react-native-executorch/src/models.ts index c9bffad13a..d7e0e5f5f6 100644 --- a/packages/react-native-executorch/src/models.ts +++ b/packages/react-native-executorch/src/models.ts @@ -283,6 +283,11 @@ const EFFICIENTNET_V2_S_QNN_A16W8: ClassifierModel = { modelPath: `${BASE_URL}/EfficientNetV2-S-ExecuTorch/${VERSION_TAG}/qnn/efficientnet_v2_s_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: EFFICIENTNET_V2_S_OPTS, }; +// 1.75x faster than QNN_A16W8 for 1.4 top-1 on an S26 Ultra, so not the default. +const EFFICIENTNET_V2_S_QNN_A8W8: ClassifierModel = { + modelPath: `${BASE_URL}-efficientnet-v2-s/${NEXT_VERSION_TAG}/qnn/efficientnet_v2_s_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: EFFICIENTNET_V2_S_OPTS, +}; // ============================================================================= // Style Transfer @@ -526,8 +531,8 @@ const LRASPP_MOBILENET_V3_LARGE_COREML_FP16: SemanticSegmenterModel = { - modelPath: `${BASE_URL}-lraspp/${NEXT_VERSION_TAG}/qnn/lraspp_mobilenet_v3_large_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, +const LRASPP_MOBILENET_V3_LARGE_QNN_A8W8: SemanticSegmenterModel = { + modelPath: `${BASE_URL}-lraspp/${NEXT_VERSION_TAG}/qnn/lraspp_mobilenet_v3_large_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: LRASPP_MOBILENET_V3_LARGE_OPTS, }; @@ -587,8 +592,8 @@ const DEEPLAB_V3_MOBILENET_V3_LARGE_COREML_FP16: SemanticSegmenterModel = { - modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_mobilenet_v3_large_qnn_a16w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, +const DEEPLAB_V3_MOBILENET_V3_LARGE_QNN_A8W8: SemanticSegmenterModel = { + modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_mobilenet_v3_large_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: DEEPLAB_V3_OPTS, }; @@ -2583,6 +2588,7 @@ export const models = { XNNPACK_FP32: deprecated(EFFICIENTNET_V2_S_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: EFFICIENTNET_V2_S_COREML_FP16, QNN_A16W8: EFFICIENTNET_V2_S_QNN_A16W8, + QNN_A8W8: EFFICIENTNET_V2_S_QNN_A8W8, }), }, @@ -2740,7 +2746,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(LRASPP_MOBILENET_V3_LARGE_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: LRASPP_MOBILENET_V3_LARGE_COREML_FP16, - QNN_A16W8: LRASPP_MOBILENET_V3_LARGE_QNN_A16W8, + QNN_A8W8: LRASPP_MOBILENET_V3_LARGE_QNN_A8W8, }), /** * DeepLabV3 semantic segmentation model with ResNet-50 backbone (21 @@ -2778,7 +2784,7 @@ export const models = { /** @deprecated Use `XNNPACK_INT8`. Removed in v1.0.0. */ XNNPACK_FP32: deprecated(DEEPLAB_V3_MOBILENET_V3_LARGE_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: DEEPLAB_V3_MOBILENET_V3_LARGE_COREML_FP16, - QNN_A16W8: DEEPLAB_V3_MOBILENET_V3_LARGE_QNN_A16W8, + QNN_A8W8: DEEPLAB_V3_MOBILENET_V3_LARGE_QNN_A8W8, }), /** * Fully Convolutional Network (FCN) semantic segmentation model with From 5604f811b7e3925f730b28fc24304be0b2425555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Tue, 27 Oct 2026 13:20:08 +0100 Subject: [PATCH 12/16] feat(models): add the YOLO26 QNN fp16 detection variants Every size and resolution, one file per Hexagon version. On an S26 Ultra each is within 0.6 mAP50-95 of fp32 over 100 COCO val images, and 5-11x faster than the Vulkan files at 640 (n 3.6 ms, x 21.9 ms). --- .../react-native-executorch/src/models.ts | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/packages/react-native-executorch/src/models.ts b/packages/react-native-executorch/src/models.ts index d7e0e5f5f6..330a1549bb 100644 --- a/packages/react-native-executorch/src/models.ts +++ b/packages/react-native-executorch/src/models.ts @@ -716,6 +716,12 @@ const YOLO26_NANO_512_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> = modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/n/xnnpack/yolo26n_512_xnnpack_int8.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +// fp16 on the HTP with TopK on the CPU; within 0.6 mAP50-95 of fp32 at every size +// and resolution on an S26 Ultra, and 5-11x faster than Vulkan there. +const YOLO26_NANO_384_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/n/qnn/yolo26n_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_NANO_512_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/n/xnnpack/yolo26n_512_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -732,6 +738,10 @@ const YOLO26_NANO_640_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> = modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/n/xnnpack/yolo26n_640_xnnpack_int8.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_NANO_512_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/n/qnn/yolo26n_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_NANO_640_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/n/xnnpack/yolo26n_640_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -744,6 +754,10 @@ const YOLO26_NANO_640_VULKAN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/n/vulkan/yolo26n_640_vulkan_fp16.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_NANO_640_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/n/qnn/yolo26n_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_SMALL_384_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/s/xnnpack/yolo26s_384_xnnpack_int8.pte`, @@ -765,6 +779,10 @@ const YOLO26_SMALL_512_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/s/xnnpack/yolo26s_512_xnnpack_int8.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_SMALL_384_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/s/qnn/yolo26s_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_SMALL_512_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/s/xnnpack/yolo26s_512_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -781,6 +799,10 @@ const YOLO26_SMALL_640_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/s/xnnpack/yolo26s_640_xnnpack_int8.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_SMALL_512_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/s/qnn/yolo26s_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_SMALL_640_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/s/xnnpack/yolo26s_640_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -793,6 +815,10 @@ const YOLO26_SMALL_640_VULKAN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/s/vulkan/yolo26s_640_vulkan_fp16.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_SMALL_640_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/s/qnn/yolo26s_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_MEDIUM_384_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/m/xnnpack/yolo26m_384_xnnpack_int8.pte`, @@ -814,6 +840,10 @@ const YOLO26_MEDIUM_512_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/m/xnnpack/yolo26m_512_xnnpack_int8.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_MEDIUM_384_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/m/qnn/yolo26m_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_MEDIUM_512_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/m/xnnpack/yolo26m_512_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -830,6 +860,10 @@ const YOLO26_MEDIUM_640_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/m/xnnpack/yolo26m_640_xnnpack_int8.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_MEDIUM_512_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/m/qnn/yolo26m_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_MEDIUM_640_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/m/xnnpack/yolo26m_640_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -842,6 +876,10 @@ const YOLO26_MEDIUM_640_VULKAN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/m/vulkan/yolo26m_640_vulkan_fp16.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_MEDIUM_640_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/m/qnn/yolo26m_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_LARGE_384_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/l/xnnpack/yolo26l_384_xnnpack_int8.pte`, @@ -863,6 +901,10 @@ const YOLO26_LARGE_512_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/l/xnnpack/yolo26l_512_xnnpack_int8.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_LARGE_384_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/l/qnn/yolo26l_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_LARGE_512_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/l/xnnpack/yolo26l_512_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -879,6 +921,10 @@ const YOLO26_LARGE_640_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/l/xnnpack/yolo26l_640_xnnpack_int8.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_LARGE_512_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/l/qnn/yolo26l_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_LARGE_640_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/l/xnnpack/yolo26l_640_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -891,6 +937,10 @@ const YOLO26_LARGE_640_VULKAN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/l/vulkan/yolo26l_640_vulkan_fp16.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_LARGE_640_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/l/qnn/yolo26l_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_XLARGE_384_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/x/xnnpack/yolo26x_384_xnnpack_int8.pte`, @@ -912,6 +962,10 @@ const YOLO26_XLARGE_512_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/x/xnnpack/yolo26x_512_xnnpack_int8.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_XLARGE_384_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/x/qnn/yolo26x_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_XLARGE_512_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/x/xnnpack/yolo26x_512_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -928,6 +982,10 @@ const YOLO26_XLARGE_640_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/x/xnnpack/yolo26x_640_xnnpack_int8.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_XLARGE_512_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/x/qnn/yolo26x_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; const YOLO26_XLARGE_640_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/x/xnnpack/yolo26x_640_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -940,6 +998,10 @@ const YOLO26_XLARGE_640_VULKAN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/x/vulkan/yolo26x_640_vulkan_fp16.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; +const YOLO26_XLARGE_640_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { + modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/x/qnn/yolo26x_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; // ============================================================================= // Keypoint Detection @@ -2853,6 +2915,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_NANO_384_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_NANO_384_COREML_FP16, VULKAN_FP16: YOLO26_NANO_384_VULKAN_FP16, + QNN_FP16: YOLO26_NANO_384_QNN_FP16, }), SIZE_512: variants({ XNNPACK_INT8: YOLO26_NANO_512_XNNPACK_INT8, @@ -2860,6 +2923,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_NANO_512_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_NANO_512_COREML_FP16, VULKAN_FP16: YOLO26_NANO_512_VULKAN_FP16, + QNN_FP16: YOLO26_NANO_512_QNN_FP16, }), SIZE_640: variants({ XNNPACK_INT8: YOLO26_NANO_640_XNNPACK_INT8, @@ -2867,6 +2931,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_NANO_640_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_NANO_640_COREML_FP16, VULKAN_FP16: YOLO26_NANO_640_VULKAN_FP16, + QNN_FP16: YOLO26_NANO_640_QNN_FP16, }), }), /** @@ -2880,6 +2945,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_SMALL_384_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_SMALL_384_COREML_FP16, VULKAN_FP16: YOLO26_SMALL_384_VULKAN_FP16, + QNN_FP16: YOLO26_SMALL_384_QNN_FP16, }), SIZE_512: variants({ XNNPACK_INT8: YOLO26_SMALL_512_XNNPACK_INT8, @@ -2887,6 +2953,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_SMALL_512_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_SMALL_512_COREML_FP16, VULKAN_FP16: YOLO26_SMALL_512_VULKAN_FP16, + QNN_FP16: YOLO26_SMALL_512_QNN_FP16, }), SIZE_640: variants({ XNNPACK_INT8: YOLO26_SMALL_640_XNNPACK_INT8, @@ -2894,6 +2961,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_SMALL_640_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_SMALL_640_COREML_FP16, VULKAN_FP16: YOLO26_SMALL_640_VULKAN_FP16, + QNN_FP16: YOLO26_SMALL_640_QNN_FP16, }), }), /** @@ -2907,6 +2975,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_MEDIUM_384_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_MEDIUM_384_COREML_FP16, VULKAN_FP16: YOLO26_MEDIUM_384_VULKAN_FP16, + QNN_FP16: YOLO26_MEDIUM_384_QNN_FP16, }), SIZE_512: variants({ XNNPACK_INT8: YOLO26_MEDIUM_512_XNNPACK_INT8, @@ -2914,6 +2983,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_MEDIUM_512_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_MEDIUM_512_COREML_FP16, VULKAN_FP16: YOLO26_MEDIUM_512_VULKAN_FP16, + QNN_FP16: YOLO26_MEDIUM_512_QNN_FP16, }), SIZE_640: variants({ XNNPACK_INT8: YOLO26_MEDIUM_640_XNNPACK_INT8, @@ -2921,6 +2991,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_MEDIUM_640_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_MEDIUM_640_COREML_FP16, VULKAN_FP16: YOLO26_MEDIUM_640_VULKAN_FP16, + QNN_FP16: YOLO26_MEDIUM_640_QNN_FP16, }), }), /** @@ -2933,6 +3004,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_LARGE_384_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_LARGE_384_COREML_FP16, VULKAN_FP16: YOLO26_LARGE_384_VULKAN_FP16, + QNN_FP16: YOLO26_LARGE_384_QNN_FP16, }), SIZE_512: variants({ XNNPACK_INT8: YOLO26_LARGE_512_XNNPACK_INT8, @@ -2940,6 +3012,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_LARGE_512_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_LARGE_512_COREML_FP16, VULKAN_FP16: YOLO26_LARGE_512_VULKAN_FP16, + QNN_FP16: YOLO26_LARGE_512_QNN_FP16, }), SIZE_640: variants({ XNNPACK_INT8: YOLO26_LARGE_640_XNNPACK_INT8, @@ -2947,6 +3020,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_LARGE_640_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_LARGE_640_COREML_FP16, VULKAN_FP16: YOLO26_LARGE_640_VULKAN_FP16, + QNN_FP16: YOLO26_LARGE_640_QNN_FP16, }), }), /** @@ -2960,6 +3034,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_XLARGE_384_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_XLARGE_384_COREML_FP16, VULKAN_FP16: YOLO26_XLARGE_384_VULKAN_FP16, + QNN_FP16: YOLO26_XLARGE_384_QNN_FP16, }), SIZE_512: variants({ XNNPACK_INT8: YOLO26_XLARGE_512_XNNPACK_INT8, @@ -2967,6 +3042,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_XLARGE_512_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_XLARGE_512_COREML_FP16, VULKAN_FP16: YOLO26_XLARGE_512_VULKAN_FP16, + QNN_FP16: YOLO26_XLARGE_512_QNN_FP16, }), SIZE_640: variants({ XNNPACK_INT8: YOLO26_XLARGE_640_XNNPACK_INT8, @@ -2974,6 +3050,7 @@ export const models = { XNNPACK_FP32: deprecated(YOLO26_XLARGE_640_XNNPACK_FP32, 'XNNPACK_INT8'), COREML_FP16: YOLO26_XLARGE_640_COREML_FP16, VULKAN_FP16: YOLO26_XLARGE_640_VULKAN_FP16, + QNN_FP16: YOLO26_XLARGE_640_QNN_FP16, }), }), }), From d7210f729ed5d959bdd42dc20ba7839de0a234d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Tue, 27 Oct 2026 13:20:08 +0100 Subject: [PATCH 13/16] feat(apps): offer the YOLO26 QNN exports in the CV demo --- apps/computer-vision/app/detection/index.tsx | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/apps/computer-vision/app/detection/index.tsx b/apps/computer-vision/app/detection/index.tsx index 0ecbc1012a..3e4479f9c3 100644 --- a/apps/computer-vision/app/detection/index.tsx +++ b/apps/computer-vision/app/detection/index.tsx @@ -51,6 +51,36 @@ const MODEL_OPTIONS: ModelOption[] = [ label: 'YOLO26 X-Large 640 (XNNPACK INT8)', value: models.objectDetection.YOLO26.XLARGE.SIZE_640.XNNPACK_INT8, }, + { + label: 'YOLO26 Nano 384 (QNN FP16)', + value: models.objectDetection.YOLO26.NANO.SIZE_384.QNN_FP16, + disabled: Platform.OS !== 'android', + }, + { + label: 'YOLO26 Nano 640 (QNN FP16)', + value: models.objectDetection.YOLO26.NANO.SIZE_640.QNN_FP16, + disabled: Platform.OS !== 'android', + }, + { + label: 'YOLO26 Small 640 (QNN FP16)', + value: models.objectDetection.YOLO26.SMALL.SIZE_640.QNN_FP16, + disabled: Platform.OS !== 'android', + }, + { + label: 'YOLO26 Medium 640 (QNN FP16)', + value: models.objectDetection.YOLO26.MEDIUM.SIZE_640.QNN_FP16, + disabled: Platform.OS !== 'android', + }, + { + label: 'YOLO26 Large 640 (QNN FP16)', + value: models.objectDetection.YOLO26.LARGE.SIZE_640.QNN_FP16, + disabled: Platform.OS !== 'android', + }, + { + label: 'YOLO26 X-Large 640 (QNN FP16)', + value: models.objectDetection.YOLO26.XLARGE.SIZE_640.QNN_FP16, + disabled: Platform.OS !== 'android', + }, ]; const VIEW_WIDTH = Dimensions.get('window').width - 32; From 10c541b0ac215f27a2ec19d61912a5ca5d512b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Wed, 28 Oct 2026 17:52:19 +0100 Subject: [PATCH 14/16] refactor: point QNN variants at the renamed repos and current VERSION_TAG --- .../react-native-executorch/src/models.ts | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/react-native-executorch/src/models.ts b/packages/react-native-executorch/src/models.ts index 330a1549bb..f3405991eb 100644 --- a/packages/react-native-executorch/src/models.ts +++ b/packages/react-native-executorch/src/models.ts @@ -285,7 +285,7 @@ const EFFICIENTNET_V2_S_QNN_A16W8: ClassifierModel = { }; // 1.75x faster than QNN_A16W8 for 1.4 top-1 on an S26 Ultra, so not the default. const EFFICIENTNET_V2_S_QNN_A8W8: ClassifierModel = { - modelPath: `${BASE_URL}-efficientnet-v2-s/${NEXT_VERSION_TAG}/qnn/efficientnet_v2_s_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/EfficientNetV2-S-ExecuTorch/${VERSION_TAG}/qnn/efficientnet_v2_s_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: EFFICIENTNET_V2_S_OPTS, }; @@ -532,7 +532,7 @@ const LRASPP_MOBILENET_V3_LARGE_COREML_FP16: SemanticSegmenterModel = { - modelPath: `${BASE_URL}-lraspp/${NEXT_VERSION_TAG}/qnn/lraspp_mobilenet_v3_large_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/LRASPP-MobileNetV3-Large-ExecuTorch/${VERSION_TAG}/qnn/lraspp_mobilenet_v3_large_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: LRASPP_MOBILENET_V3_LARGE_OPTS, }; @@ -558,11 +558,11 @@ const DEEPLAB_V3_RESNET50_COREML_FP16: SemanticSegmenterModel = // Same Hexagon-version resolution and index-map output as the MobileNetV3 QNN // variant below. const DEEPLAB_V3_RESNET50_QNN_A8W8: SemanticSegmenterModel = { - modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_resnet50_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/DeepLabV3-ExecuTorch/${VERSION_TAG}/qnn/deeplab_v3_resnet50_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: DEEPLAB_V3_OPTS, }; const DEEPLAB_V3_RESNET101_QNN_A8W8: SemanticSegmenterModel = { - modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_resnet101_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/DeepLabV3-ExecuTorch/${VERSION_TAG}/qnn/deeplab_v3_resnet101_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: DEEPLAB_V3_OPTS, }; const DEEPLAB_V3_RESNET101_XNNPACK_FP32: SemanticSegmenterModel = { @@ -593,7 +593,7 @@ const DEEPLAB_V3_MOBILENET_V3_LARGE_COREML_FP16: SemanticSegmenterModel = { - modelPath: `${BASE_URL}-deeplab-v3/${NEXT_VERSION_TAG}/qnn/deeplab_v3_mobilenet_v3_large_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/DeepLabV3-ExecuTorch/${VERSION_TAG}/qnn/deeplab_v3_mobilenet_v3_large_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: DEEPLAB_V3_OPTS, }; @@ -631,11 +631,11 @@ const FCN_RESNET101_COREML_FP16: SemanticSegmenterModel = { // Same Hexagon-version resolution and index-map output as the DeepLabV3 QNN // variants. const FCN_RESNET50_QNN_A8W8: SemanticSegmenterModel = { - modelPath: `${BASE_URL}-fcn/${NEXT_VERSION_TAG}/qnn/fcn_resnet50_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/FCN-ExecuTorch/${VERSION_TAG}/qnn/fcn_resnet50_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: FCN_OPTS, }; const FCN_RESNET101_QNN_A8W8: SemanticSegmenterModel = { - modelPath: `${BASE_URL}-fcn/${NEXT_VERSION_TAG}/qnn/fcn_resnet101_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/FCN-ExecuTorch/${VERSION_TAG}/qnn/fcn_resnet101_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: FCN_OPTS, }; @@ -719,7 +719,7 @@ const YOLO26_NANO_512_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> = // fp16 on the HTP with TopK on the CPU; within 0.6 mAP50-95 of fp32 at every size // and resolution on an S26 Ultra, and 5-11x faster than Vulkan there. const YOLO26_NANO_384_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/n/qnn/yolo26n_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/n/qnn/yolo26n_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_NANO_512_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { @@ -739,7 +739,7 @@ const YOLO26_NANO_640_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> = modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_NANO_512_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/n/qnn/yolo26n_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/n/qnn/yolo26n_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_NANO_640_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { @@ -755,7 +755,7 @@ const YOLO26_NANO_640_VULKAN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_NANO_640_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/n/qnn/yolo26n_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/n/qnn/yolo26n_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; @@ -780,7 +780,7 @@ const YOLO26_SMALL_512_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_SMALL_384_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/s/qnn/yolo26s_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/s/qnn/yolo26s_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_SMALL_512_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { @@ -800,7 +800,7 @@ const YOLO26_SMALL_640_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_SMALL_512_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/s/qnn/yolo26s_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/s/qnn/yolo26s_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_SMALL_640_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { @@ -816,7 +816,7 @@ const YOLO26_SMALL_640_VULKAN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_SMALL_640_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/s/qnn/yolo26s_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/s/qnn/yolo26s_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; @@ -841,7 +841,7 @@ const YOLO26_MEDIUM_512_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_MEDIUM_384_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/m/qnn/yolo26m_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/m/qnn/yolo26m_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_MEDIUM_512_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { @@ -861,7 +861,7 @@ const YOLO26_MEDIUM_640_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_MEDIUM_512_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/m/qnn/yolo26m_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/m/qnn/yolo26m_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_MEDIUM_640_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { @@ -877,7 +877,7 @@ const YOLO26_MEDIUM_640_VULKAN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_MEDIUM_640_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/m/qnn/yolo26m_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/m/qnn/yolo26m_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; @@ -902,7 +902,7 @@ const YOLO26_LARGE_512_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_LARGE_384_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/l/qnn/yolo26l_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/l/qnn/yolo26l_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_LARGE_512_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { @@ -922,7 +922,7 @@ const YOLO26_LARGE_640_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_LARGE_512_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/l/qnn/yolo26l_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/l/qnn/yolo26l_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_LARGE_640_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { @@ -938,7 +938,7 @@ const YOLO26_LARGE_640_VULKAN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_LARGE_640_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/l/qnn/yolo26l_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/l/qnn/yolo26l_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; @@ -963,7 +963,7 @@ const YOLO26_XLARGE_512_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_XLARGE_384_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/x/qnn/yolo26x_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/x/qnn/yolo26x_384_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_XLARGE_512_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { @@ -983,7 +983,7 @@ const YOLO26_XLARGE_640_XNNPACK_INT8: ObjectDetectorModel<'xyxy', CocoClassYolo> modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_XLARGE_512_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/x/qnn/yolo26x_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/x/qnn/yolo26x_512_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_XLARGE_640_XNNPACK_FP32: ObjectDetectorModel<'xyxy', CocoClassYolo> = { @@ -999,7 +999,7 @@ const YOLO26_XLARGE_640_VULKAN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> modelOpts: YOLO26_DETECTOR_OPTS, }; const YOLO26_XLARGE_640_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClassYolo> = { - modelPath: `${BASE_URL}-yolo26/${NEXT_VERSION_TAG}/x/qnn/yolo26x_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/x/qnn/yolo26x_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, modelOpts: YOLO26_DETECTOR_OPTS, }; From 533079b9ae2d7547444c0905bd821ed0cc2600bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Fri, 30 Oct 2026 15:40:18 +0100 Subject: [PATCH 15/16] feat(models): add the SSDLite320 MobileNetV3 QNN fp16 variant One file per Hexagon version. On an S26 Ultra: 22.32 mAP50-95 over 500 COCO val images (fp32 22.40) at 4.2 ms, 3x faster than XNNPACK. --- packages/react-native-executorch/src/models.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/react-native-executorch/src/models.ts b/packages/react-native-executorch/src/models.ts index f3405991eb..a3774a669d 100644 --- a/packages/react-native-executorch/src/models.ts +++ b/packages/react-native-executorch/src/models.ts @@ -659,6 +659,12 @@ const SSDLITE320_MOBILENET_V3_LARGE_COREML_FP16: ObjectDetectorModel<'xyxy', Coc modelPath: `${BASE_URL}/SSDlite320-MobileNetV3-Large-ExecuTorch/${VERSION_TAG}/coreml/ssdlite320_mobilenet_v3_large_coreml_fp16.pte`, modelOpts: SSDLITE320_MOBILENET_V3_LARGE_OPTS, }; +// fp16 on the HTP with TopK on the CPU; 22.32 mAP50-95 against 22.40 for fp32 on an +// S26 Ultra, and 3x faster than XNNPACK there. +const SSDLITE320_MOBILENET_V3_LARGE_QNN_FP16: ObjectDetectorModel<'xyxy', CocoClass> = { + modelPath: `${BASE_URL}/SSDlite320-MobileNetV3-Large-ExecuTorch/${VERSION_TAG}/qnn/ssdlite320_mobilenet_v3_large_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: SSDLITE320_MOBILENET_V3_LARGE_OPTS, +}; const RFDETR_NANO_DETECTOR_OPTS = { labels: COCO_CLASSES, @@ -2884,6 +2890,7 @@ export const models = { SSDLITE320_MOBILENET_V3_LARGE: variants({ XNNPACK_FP32: SSDLITE320_MOBILENET_V3_LARGE_XNNPACK_FP32, COREML_FP16: SSDLITE320_MOBILENET_V3_LARGE_COREML_FP16, + QNN_FP16: SSDLITE320_MOBILENET_V3_LARGE_QNN_FP16, }), /** * RF-DETR (Roboflow Detection Transformer) Nano variant trained on COCO From 04842eeacfef4af0625beb71313a694fb79ba2aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Fri, 30 Oct 2026 15:40:18 +0100 Subject: [PATCH 16/16] feat(apps): offer the SSDLite320 QNN export in the CV demo --- apps/computer-vision/app/detection/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/computer-vision/app/detection/index.tsx b/apps/computer-vision/app/detection/index.tsx index 3e4479f9c3..3965559822 100644 --- a/apps/computer-vision/app/detection/index.tsx +++ b/apps/computer-vision/app/detection/index.tsx @@ -18,6 +18,11 @@ const MODEL_OPTIONS: ModelOption[] = [ label: 'SSDLite 320 MobileNet V3 Large (XNNPACK FP32)', value: models.objectDetection.SSDLITE320_MOBILENET_V3_LARGE.XNNPACK_FP32, }, + { + label: 'SSDLite 320 MobileNet V3 Large (QNN FP16)', + value: models.objectDetection.SSDLITE320_MOBILENET_V3_LARGE.QNN_FP16, + disabled: Platform.OS !== 'android', + }, { label: 'RF-DETR Nano (XNNPACK INT8)', value: models.objectDetection.RFDETR_NANO.XNNPACK_INT8,