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..703824649d 100644 --- a/apps/computer-vision/app/classification/index.tsx +++ b/apps/computer-vision/app/classification/index.tsx @@ -22,6 +22,16 @@ 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', + }, + { + 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/detection/index.tsx b/apps/computer-vision/app/detection/index.tsx index 0ecbc1012a..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, @@ -51,6 +56,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; diff --git a/apps/computer-vision/app/segmentation/index.tsx b/apps/computer-vision/app/segmentation/index.tsx index 8222bfa60a..8a325df2eb 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 { @@ -27,26 +27,56 @@ const SEGMENTATION_OPTIONS: ModelOption[] = [ label: 'LRASPP MobileNet V3 (INT8)', value: models.semanticSegmentation.LRASPP_MOBILENET_V3_LARGE.XNNPACK_INT8, }, + { + label: 'LRASPP MobileNet V3 (QNN A8W8)', + value: models.semanticSegmentation.LRASPP_MOBILENET_V3_LARGE.QNN_A8W8, + disabled: Platform.OS !== 'android', + }, { label: 'DeepLab V3 ResNet50 (INT8)', value: models.semanticSegmentation.DEEPLAB_V3_RESNET50.XNNPACK_INT8, }, + { + label: 'DeepLab V3 ResNet50 (QNN A8W8)', + value: models.semanticSegmentation.DEEPLAB_V3_RESNET50.QNN_A8W8, + disabled: Platform.OS !== 'android', + }, { label: 'DeepLab V3 ResNet101 (INT8)', value: models.semanticSegmentation.DEEPLAB_V3_RESNET101.XNNPACK_INT8, }, + { + label: 'DeepLab V3 ResNet101 (QNN A8W8)', + value: models.semanticSegmentation.DEEPLAB_V3_RESNET101.QNN_A8W8, + disabled: Platform.OS !== 'android', + }, { label: 'DeepLab V3 MobileNet V3 (INT8)', value: models.semanticSegmentation.DEEPLAB_V3_MOBILENET_V3_LARGE.XNNPACK_INT8, }, + { + label: 'DeepLab V3 MobileNet V3 (QNN A8W8)', + value: models.semanticSegmentation.DEEPLAB_V3_MOBILENET_V3_LARGE.QNN_A8W8, + disabled: Platform.OS !== 'android', + }, { label: 'FCN ResNet50 (INT8)', value: models.semanticSegmentation.FCN_RESNET50.XNNPACK_INT8, }, + { + label: 'FCN ResNet50 (QNN A8W8)', + value: models.semanticSegmentation.FCN_RESNET50.QNN_A8W8, + disabled: Platform.OS !== 'android', + }, { label: 'FCN ResNet101 (INT8)', value: models.semanticSegmentation.FCN_RESNET101.XNNPACK_INT8, }, + { + label: 'FCN ResNet101 (QNN A8W8)', + value: models.semanticSegmentation.FCN_RESNET101.QNN_A8W8, + disabled: Platform.OS !== 'android', + }, ]; function SegmentationContent() { 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..b63083e840 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|a8w8|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..a3774a669d 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,17 @@ 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, +}; +// 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}/EfficientNetV2-S-ExecuTorch/${VERSION_TAG}/qnn/efficientnet_v2_s_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: EFFICIENTNET_V2_S_OPTS, +}; // ============================================================================= // Style Transfer @@ -502,6 +529,12 @@ const LRASPP_MOBILENET_V3_LARGE_COREML_FP16: SemanticSegmenterModel = { + 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, +}; const DEEPLAB_V3_OPTS = { labels: PASCAL_VOC_LABELS, @@ -522,6 +555,16 @@ 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_A8W8: SemanticSegmenterModel = { + 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}/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 = { modelPath: `${BASE_URL}/DeepLabV3-ExecuTorch/${VERSION_TAG}/xnnpack/deeplab_v3_resnet101_xnnpack_fp32.pte`, modelOpts: DEEPLAB_V3_OPTS, @@ -546,6 +589,13 @@ const DEEPLAB_V3_MOBILENET_V3_LARGE_COREML_FP16: SemanticSegmenterModel = { + modelPath: `${BASE_URL}/DeepLabV3-ExecuTorch/${VERSION_TAG}/qnn/deeplab_v3_mobilenet_v3_large_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: DEEPLAB_V3_OPTS, +}; const FCN_OPTS = { labels: PASCAL_VOC_LABELS, @@ -578,6 +628,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_A8W8: SemanticSegmenterModel = { + 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-ExecuTorch/${VERSION_TAG}/qnn/fcn_resnet101_qnn_a8w8_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: FCN_OPTS, +}; // ============================================================================= // Object Detection @@ -599,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, @@ -656,6 +722,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-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> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/n/xnnpack/yolo26n_512_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -672,6 +744,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-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> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/n/xnnpack/yolo26n_640_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -684,6 +760,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-ExecuTorch/${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`, @@ -705,6 +785,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-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> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/s/xnnpack/yolo26s_512_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -721,6 +805,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-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> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/s/xnnpack/yolo26s_640_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -733,6 +821,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-ExecuTorch/${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`, @@ -754,6 +846,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-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> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/m/xnnpack/yolo26m_512_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -770,6 +866,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-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> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/m/xnnpack/yolo26m_640_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -782,6 +882,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-ExecuTorch/${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`, @@ -803,6 +907,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-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> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/l/xnnpack/yolo26l_512_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -819,6 +927,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-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> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/l/xnnpack/yolo26l_640_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -831,6 +943,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-ExecuTorch/${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`, @@ -852,6 +968,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-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> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/x/xnnpack/yolo26x_512_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -868,6 +988,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-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> = { modelPath: `${BASE_URL}/YOLO26-ExecuTorch/${VERSION_TAG}/x/xnnpack/yolo26x_640_xnnpack_fp32.pte`, modelOpts: YOLO26_DETECTOR_OPTS, @@ -880,6 +1004,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-ExecuTorch/${VERSION_TAG}/x/qnn/yolo26x_640_qnn_fp16_${QNN_HTP_ARCH ?? 'v81'}.pte`, + modelOpts: YOLO26_DETECTOR_OPTS, +}; // ============================================================================= // Keypoint Detection @@ -2527,6 +2655,8 @@ 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, + QNN_A8W8: EFFICIENTNET_V2_S_QNN_A8W8, }), }, @@ -2684,6 +2814,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_A8W8: LRASPP_MOBILENET_V3_LARGE_QNN_A8W8, }), /** * DeepLabV3 semantic segmentation model with ResNet-50 backbone (21 @@ -2695,6 +2826,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_A8W8: DEEPLAB_V3_RESNET50_QNN_A8W8, }), /** * DeepLabV3 semantic segmentation model with ResNet-101 backbone (21 @@ -2706,6 +2838,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_A8W8: DEEPLAB_V3_RESNET101_QNN_A8W8, }), /** * DeepLabV3 semantic segmentation model with MobileNetV3-Large backbone (21 @@ -2719,6 +2852,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_A8W8: DEEPLAB_V3_MOBILENET_V3_LARGE_QNN_A8W8, }), /** * Fully Convolutional Network (FCN) semantic segmentation model with @@ -2729,6 +2863,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_A8W8: FCN_RESNET50_QNN_A8W8, }), /** * Fully Convolutional Network (FCN) semantic segmentation model with @@ -2739,6 +2874,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_A8W8: FCN_RESNET101_QNN_A8W8, }), }, @@ -2754,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 @@ -2785,6 +2922,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, @@ -2792,6 +2930,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, @@ -2799,6 +2938,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, }), }), /** @@ -2812,6 +2952,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, @@ -2819,6 +2960,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, @@ -2826,6 +2968,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, }), }), /** @@ -2839,6 +2982,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, @@ -2846,6 +2990,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, @@ -2853,6 +2998,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, }), }), /** @@ -2865,6 +3011,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, @@ -2872,6 +3019,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, @@ -2879,6 +3027,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, }), }), /** @@ -2892,6 +3041,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, @@ -2899,6 +3049,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, @@ -2906,6 +3057,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, }), }), }),