Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
ce77639
feat(android): add the Qualcomm QNN backend
msluszniak Sep 22, 2026
8bd888c
feat(models): add the DeepLabV3-MobileNetV3 QNN export
msluszniak Sep 24, 2026
acef48c
feat(apps): offer the DeepLabV3-MobileNetV3 QNN export in the CV demo
msluszniak Sep 25, 2026
0def3da
feat(models): add the DeepLabV3-ResNet50 QNN a16w8 variant
msluszniak Sep 25, 2026
9c44ee0
feat(apps): offer the DeepLabV3-ResNet50 QNN export in the CV demo
msluszniak Sep 25, 2026
f542d8d
feat(models): add the DeepLabV3-ResNet101 and FCN QNN a16w8 variants
msluszniak Sep 25, 2026
6fedaa3
feat(apps): offer the DeepLabV3-ResNet101 and FCN QNN exports in the …
msluszniak Sep 25, 2026
842d5c2
feat(models): add the LR-ASPP MobileNetV3 QNN a16w8 variant
msluszniak Sep 25, 2026
c2dfb9b
feat(apps): offer the LR-ASPP QNN export in the CV demo
msluszniak Sep 25, 2026
8b2d548
feat(models): switch the ResNet QNN segmentation variants to a8w8
msluszniak Oct 19, 2026
5429bcb
feat(models): switch LR-ASPP and DeepLabV3-MBv3 QNN to a8w8, add Effi…
msluszniak Oct 20, 2026
5604f81
feat(models): add the YOLO26 QNN fp16 detection variants
msluszniak Oct 27, 2026
d7210f7
feat(apps): offer the YOLO26 QNN exports in the CV demo
msluszniak Oct 27, 2026
10c541b
refactor: point QNN variants at the renamed repos and current VERSION…
msluszniak Oct 28, 2026
533079b
feat(models): add the SSDLite320 MobileNetV3 QNN fp16 variant
msluszniak Oct 30, 2026
04842ee
feat(apps): offer the SSDLite320 QNN export in the CV demo
msluszniak Oct 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cspell-wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ executorch
RNET
libexecutorch
libxnnpack
libqnn
libvulkan
xcframework
metallib
Expand Down
10 changes: 10 additions & 0 deletions apps/computer-vision/app/classification/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
35 changes: 35 additions & 0 deletions apps/computer-vision/app/detection/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
32 changes: 31 additions & 1 deletion apps/computer-vision/app/segmentation/index.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down
47 changes: 42 additions & 5 deletions docs/docs/03-core-and-advanced/08-native-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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.<task>.<MODEL>.DEFAULT` only reaches the accelerated export when
// the app downloaded that backend, and `features` is the documented way to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
});

Expand Down
11 changes: 11 additions & 0 deletions packages/react-native-executorch/__tests__/support/fakeJsi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
13 changes: 13 additions & 0 deletions packages/react-native-executorch/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Loading
Loading