Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions apps/computer-vision/app/classification/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,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() {
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 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,
},
{
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,
},
{
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,
},
{
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,
},
{
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() {
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"`, `"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 @@ -71,6 +81,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 @@ -103,6 +114,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 @@ -113,3 +147,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|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 @@ -79,11 +79,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 @@ -326,6 +328,47 @@ describe('variant selection rules', () => {
});
});

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
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 @@ -327,6 +327,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 @@ -434,6 +435,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 @@ -471,6 +481,7 @@ export const fakeJsi = {
runnerCalls.length = 0;
registeredBackends = ['XnnpackBackend', 'CoreMLBackend'];
jsi.isEmulator = false;
jsi.qnnHtpArch = undefined;
fakePhonemizer.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()
17 changes: 14 additions & 3 deletions packages/react-native-executorch/android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -126,7 +128,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())
Expand Down Expand Up @@ -176,10 +179,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")
}
Expand Down Expand Up @@ -212,6 +216,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")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<!-- The QNN backend reaches the Hexagon DSP through the vendor's FastRPC
client, which apps may only load once they declare it (Android 12+).
Optional, so devices without it still install. -->
<uses-native-library android:name="libcdsprpc.so" android:required="false" />
</application>
</manifest>
Loading
Loading