diff --git a/.gitignore b/.gitignore index d481899882..5c7b0308a0 100644 --- a/.gitignore +++ b/.gitignore @@ -98,6 +98,15 @@ docs/docs/06-api-reference/ # integration test model assets packages/react-native-executorch/common/rnexecutorch/tests/integration/assets/models/ +# release artifact staging dir (produced by scripts/package-release-artifacts.sh) +packages/react-native-executorch/dist-artifacts/ + +# on-demand native libs (downloaded at postinstall time, not committed) +packages/react-native-executorch/third-party/android/libs/ +packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ +packages/react-native-executorch/third-party/ios/libs/ +packages/react-native-executorch/rne-build-config.json + # custom *.tgz Makefile diff --git a/docs/docs/01-fundamentals/01-getting-started.md b/docs/docs/01-fundamentals/01-getting-started.md index 1ec3f29499..6d0db5702c 100644 --- a/docs/docs/01-fundamentals/01-getting-started.md +++ b/docs/docs/01-fundamentals/01-getting-started.md @@ -77,6 +77,42 @@ Installation is pretty straightforward, use your package manager of choice to in +### Configuring backends and extras + +On install, `react-native-executorch` runs a `postinstall` script that downloads prebuilt native libraries from the matching GitHub Release and unpacks them under `third-party/`. By default every optional feature is included — which keeps the app binary large. You can opt out of anything you don't need by adding an `extras` array to your app's `package.json`: + +```json +{ + "react-native-executorch": { + "extras": ["xnnpack", "coreml", "vulkan", "opencv", "phonemizer"] + } +} +``` + +If the `extras` key is omitted, all five features are enabled. To disable a feature, drop its name from the array. If you only need LLM inference with XNNPACK on iOS, for example, set `"extras": ["xnnpack"]`. + +| Extra | Platforms | What it enables | +| ------------ | ------------ | ------------------------------------------------------------- | +| `xnnpack` | iOS, Android | XNNPACK CPU backend (required for most quantized models) | +| `coreml` | iOS | Core ML backend (Apple Neural Engine / GPU acceleration) | +| `vulkan` | Android | Vulkan GPU backend | +| `opencv` | iOS, Android | Computer-vision models (classification, detection, OCR, etc.) | +| `phonemizer` | iOS, Android | Text-to-speech models | + +Source files and native libraries are excluded from compilation when an extra is disabled, so builds that only need LLMs can skip OpenCV and cut tens of megabytes off the final binary. + +The postinstall step honors a few environment variables: + +| Variable | Purpose | +| ---------------------- | ------------------------------------------------------------------------- | +| `RNET_SKIP_DOWNLOAD=1` | Skip the download entirely (for CI with pre-cached libraries). | +| `RNET_LIBS_CACHE_DIR` | Custom cache directory (default: `~/.cache/react-native-executorch/`). | +| `RNET_TARGET` | Force a specific target, e.g. `android-arm64-v8a` or `ios`. | +| `RNET_NO_X86_64=1` | Skip the Android x86_64 tarball (handy when only building for a device). | +| `GITHUB_TOKEN` | Required to access draft releases while iterating on a new version. | + +After changing `extras`, re-run `yarn install` (or the equivalent) so the postinstall script regenerates `rne-build-config.json` and re-extracts the right tarballs, then rebuild the native project. + :::warning Before using any other API, you must call `initExecutorch` with a resource fetcher adapter at the entry point of your app: diff --git a/packages/react-native-executorch/NATIVE_LIBS_PIPELINE.md b/packages/react-native-executorch/NATIVE_LIBS_PIPELINE.md new file mode 100644 index 0000000000..1d6e9eecb1 --- /dev/null +++ b/packages/react-native-executorch/NATIVE_LIBS_PIPELINE.md @@ -0,0 +1,168 @@ +# Native libraries pipeline + +This document describes how native dependencies (ExecuTorch runtime, backends, OpenCV, phonemizer) are produced, shipped, and stitched into an app build. It is intended for maintainers — the user-facing summary lives in `docs/docs/01-fundamentals/01-getting-started.md`. + +## High-level flow + +``` + ┌──────────────────────┐ ┌────────────────────────┐ ┌───────────────────────┐ + │ ExecuTorch fork │ ───▶ │ GitHub Release v │ ───▶ │ postinstall script │ + │ + our patches │ │ .tar.gz │ │ download-libs.js │ + │ (separate repo) │ │ .tar.gz.256 │ │ │ + └──────────────────────┘ └────────────────────────┘ └───────────┬───────────┘ + │ + ▼ + ┌───────────────────────┐ + │ third-party/android │ + │ third-party/ios │ + │ rne-build-config.json│ + └───────────┬───────────┘ + │ + ┌───────────────────────────┴────────────────────────────┐ + ▼ ▼ + ┌───────────────────────┐ ┌─────────────────────────┐ + │ android/build.gradle │ │ react-native-executorch │ + │ + CMakeLists.txt │ │ .podspec │ + │ -DRNE_ENABLE_* │ │ -DRNE_ENABLE_* │ + └───────────────────────┘ │ force_load xcframeworks │ + └─────────────────────────┘ +``` + +## Install-time: `scripts/download-libs.js` + +Runs at `postinstall`. Responsibilities: + +1. Read `react-native-executorch.extras` from the app's `package.json` (uses `INIT_CWD`). Defaults to `["opencv", "phonemizer", "xnnpack", "coreml", "vulkan"]`. +2. Write `rne-build-config.json` at the package root with boolean flags — this file is the single source of truth consumed by both the Gradle build and the podspec. +3. Detect targets (`ios` on macOS; always `android-arm64-v8a` and, unless `RNET_NO_X86_64` is set, `android-x86_64`). +4. For each target × enabled extra, fetch the corresponding `.tar.gz` from the GitHub Release tagged `v${PACKAGE_VERSION}`, verify the `.sha256`, and extract into `third-party/android/libs/` or `third-party/ios/`. +5. Cache validated tarballs under `~/.cache/react-native-executorch//` so subsequent installs skip the network. + +Environment overrides: `RNET_SKIP_DOWNLOAD`, `RNET_LIBS_CACHE_DIR`, `RNET_TARGET`, `RNET_BASE_URL` (useful with `python3 -m http.server` against `dist-artifacts/` for local iteration), `GITHUB_TOKEN` (needed for draft releases). + +The set of artifacts per target is defined in `getArtifacts()`: + +| Artifact name | Target | Produced by | Contents | +| ---------------------------------------- | ------- | ---------------------------------------- | ------------------------------------------------------------- | +| `core-android-arm64-v8a` | Android | ExecuTorch fork build | `libexecutorch.so` (backends baked in), headers | +| `core-android-x86_64` | Android | ExecuTorch fork build | x86_64 `libexecutorch.so` for the simulator | +| `core-ios` | iOS | `third-party/ios/ExecutorchLib/build.sh` | `ExecutorchLib.xcframework` | +| `xnnpack-ios` | iOS | `third-party/ios/ExecutorchLib/build.sh` | `XnnpackBackend.xcframework` | +| `coreml-ios` | iOS | `third-party/ios/ExecutorchLib/build.sh` | `CoreMLBackend.xcframework` | +| `xnnpack-android-*`, `vulkan-android-*` | Android | (currently baked into core) | Placeholder tarballs — backends are inside `libexecutorch.so` | +| `opencv-android-*` | Android | OpenCV release process | Static OpenCV + KleidiCV HAL | +| `phonemizer-android-*`, `phonemizer-ios` | both | phonemizer build | `libphonemis.a` (iOS: physical + simulator) | + +(`opencv-ios` is not a tarball — iOS consumes OpenCV through the `opencv-rne` CocoaPod.) + +## Build-time: Android + +`android/build.gradle` reads `rne-build-config.json` once and forwards the booleans to CMake: + +```groovy +"-DRNE_ENABLE_OPENCV=${rneBuildConfig.enableOpencv ? 'ON' : 'OFF'}", +"-DRNE_ENABLE_PHONEMIZER=${rneBuildConfig.enablePhonemizer ? 'ON' : 'OFF'}", +"-DRNE_ENABLE_XNNPACK=${rneBuildConfig.enableXnnpack ? 'ON' : 'OFF'}", +"-DRNE_ENABLE_VULKAN=${rneBuildConfig.enableVulkan ? 'ON' : 'OFF'}" +``` + +`android/CMakeLists.txt` and `android/src/main/cpp/CMakeLists.txt` respond by: + +- Adding `-DRNE_ENABLE_OPENCV` / `-DRNE_ENABLE_PHONEMIZER` compile definitions so C++ code can `#ifdef` around optional dependencies. +- Conditionally linking `libopencv_*.a`, KleidiCV HAL (arm64 only), and `libphonemis.a`. +- Always linking against the single prebuilt `libexecutorch.so` downloaded into `third-party/android/libs/executorch//`. + +**Backends (XNNPACK, Vulkan) are NOT separate shared libraries on Android.** They are compiled into `libexecutorch.so` during the ExecuTorch fork build. `RNE_ENABLE_XNNPACK`/`RNE_ENABLE_VULKAN` today act only as feature flags for C++ code paths that reference backend-specific APIs — they do not toggle native library linkage. This is a deliberate simplification (see the "Why backends differ" section below). + +## Build-time: iOS + +`react-native-executorch.podspec` reads the same `rne-build-config.json` and: + +- Excludes opencv/phonemizer C++ sources from compilation when those extras are off. +- Appends `-DRNE_ENABLE_*` to `OTHER_CPLUSPLUSFLAGS`. +- Assembles `OTHER_LDFLAGS[sdk=iphoneos*]` and `OTHER_LDFLAGS[sdk=iphonesimulator*]` with `-force_load` entries for each enabled backend xcframework. +- Declares `ExecutorchLib.xcframework` in `vendored_frameworks` but _not_ the backend xcframeworks — backend xcframeworks only live on the linker command line, never in the CocoaPods vendoring list (see next section for why). +- Adds `sqlite3` and the `CoreML` system framework to linkage only when Core ML is enabled. + +## Why backends differ between platforms + +ExecuTorch registers kernels statically via `__attribute__((constructor))` functions inside each backend's `.a`/`.so`. Two design points fall out of this: + +1. **Force-load is required.** Linkers drop unreferenced object files. The registrar symbols have no external users (they run as global constructors at load time), so a plain link keeps the backend library on disk but strips the registration symbols — and the app then fails with `Missing operator: ...` at inference. Every backend library must be force-loaded (`-force_load` on iOS, `--whole-archive` on Android, or `executorch_target_link_options_shared_lib(...)` in ExecuTorch's own CMake helpers). + +2. **A single copy of each CPU-kernel registration must exist.** Multiple backend libraries that each whole-archive-link `optimized_native_cpu_ops_lib` cause duplicate kernel-registration aborts (`error 22 EEXIST`) when both get force-loaded into the same process. + +On **iOS**, each backend ships as its own static xcframework (`XnnpackBackend.xcframework`, `CoreMLBackend.xcframework`). The podspec force-loads only the ones the user opted into, and `ExecutorchLib.xcframework` itself does not whole-archive the CPU ops — so there is no duplicate registration. + +On **Android**, the first iteration tried the same split (separate `libxnnpack_executorch_backend.so`, `libvulkan_executorch_backend.so`) and hit the duplicate-registration abort because the ExecuTorch Android build baked the CPU ops into each backend shared library. Rather than patch the ExecuTorch runtime, backends are now compiled into the single `libexecutorch.so` at ExecuTorch build time — matching the pre-split behavior. The `RNE_ENABLE_XNNPACK`/`RNE_ENABLE_VULKAN` CMake flags on the app side only gate optional C++ code paths; the native library linkage is whatever was baked in at ExecuTorch fork build time. + +This asymmetry is the main reason Android has a single `core-android-*` tarball while iOS has separate `core-ios`, `xnnpack-ios`, and `coreml-ios` tarballs. + +## Building artifacts from the ExecuTorch fork + +Patched sources live in a separate repo (see `executorch/` in the maintainer's machine, typically checked out next to `react-native-executorch/`). The key patch we carry: + +- `extension/android/CMakeLists.txt` — add `vulkan_schema` next to `vulkan_backend` in the link libraries list so Vulkan backend builds correctly. + +### iOS + +From inside `packages/react-native-executorch/third-party/ios/ExecutorchLib/`: + +```bash +./build.sh +``` + +The script drives Xcode to archive the Obj-C++ wrapper for device and simulator, then uses `xcodebuild -create-xcframework` to produce: + +- `output/ExecutorchLib.xcframework` — the high-level wrapper + ExecuTorch core + baked-in CPU ops. +- `output/XnnpackBackend.xcframework` — repackaged from `third-party/ios/libs/executorch/libbackend_xnnpack_{ios,simulator}.a`. +- `output/CoreMLBackend.xcframework` — repackaged from `libbackend_coreml_{ios,simulator}.a`. + +Producing the underlying `.a` files (executorch + backend static libs for both slices) is a separate step inside the ExecuTorch fork, outside the scope of this script — run the fork's iOS build instructions with XNNPACK and Core ML enabled, then drop the resulting `.a` files into `third-party/ios/libs/executorch/` before invoking `build.sh`. + +CocoaPods constraint: inside an xcframework, the library file name must be identical across slices, which is why `build.sh` copies each slice into a temp directory and renames before calling `-create-xcframework`. Do not skip this step. + +### Android + +Build the ExecuTorch fork with the Android JNI target, enabling the backends you want baked in: + +```bash +# from the executorch fork +cmake -S . -B cmake-out-android-arm64 \ + -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \ + -DANDROID_ABI=arm64-v8a \ + -DEXECUTORCH_BUILD_XNNPACK=ON \ + -DEXECUTORCH_BUILD_VULKAN=ON \ + -DEXECUTORCH_BUILD_KERNELS_OPTIMIZED=ON \ + -DEXECUTORCH_BUILD_EXTENSION_MODULE=ON \ + -DEXECUTORCH_BUILD_EXTENSION_TENSOR=ON \ + # ... the full set of flags the fork expects; see the fork's build script +cmake --build cmake-out-android-arm64 -j +``` + +The resulting `libexecutorch_jni.so` (renamed to `libexecutorch.so` in our layout) goes to `third-party/android/libs/executorch/arm64-v8a/`. Repeat for `x86_64`. The headers copied into `third-party/include/` must match the fork commit that produced the binary — a mismatch shows up as runtime `dlopen`/symbol errors. + +### Packaging for a release + +For each `` tarball: + +```bash +tar -czf .tar.gz -C . +sha256sum .tar.gz > .tar.gz.sha256 # or shasum -a 256 +``` + +Staging-dir layout must mirror the destination (`download-libs.js` extracts with `tar -xzf` into `third-party/android/libs/` or `third-party/ios/` without any path stripping). So `core-android-arm64-v8a.tar.gz` contains a top-level `executorch/arm64-v8a/libexecutorch.so`, `cpuinfo/arm64-v8a/libcpuinfo.a`, etc. + +Upload every `.tar.gz` **and** its `.tar.gz.sha256` as release assets under the `v` tag on GitHub. Publishing the release (out of draft) makes them fetchable anonymously; until then, consumers need `GITHUB_TOKEN` with `repo:read`. + +### Iterating locally + +Drop built artifacts (plus `.sha256` files) into `packages/react-native-executorch/dist-artifacts/`, then run a static server and point the script at it: + +```bash +cd packages/react-native-executorch/dist-artifacts +python3 -m http.server 8080 & +RNET_BASE_URL=http://localhost:8080 yarn install +``` + +This skips GitHub entirely and re-extracts from the local tarballs — the same checksum verification still runs, so stale caches still get rejected. diff --git a/packages/react-native-executorch/android/CMakeLists.txt b/packages/react-native-executorch/android/CMakeLists.txt index ddc7ab4126..9443024ebf 100644 --- a/packages/react-native-executorch/android/CMakeLists.txt +++ b/packages/react-native-executorch/android/CMakeLists.txt @@ -24,6 +24,12 @@ set(LIBS_DIR "${CMAKE_SOURCE_DIR}/../third-party/android/libs") set(TOKENIZERS_DIR "${CMAKE_SOURCE_DIR}/../third-party/include/executorch/extension/llm/tokenizers/include") set(INCLUDE_DIR "${CMAKE_SOURCE_DIR}/../third-party/include") +# Optional feature flags — driven by user config in package.json, passed via gradle cmake arguments +option(RNE_ENABLE_OPENCV "Enable OpenCV-dependent computer vision features" ON) +option(RNE_ENABLE_PHONEMIZER "Enable Phonemizer-dependent TTS features" ON) +option(RNE_ENABLE_XNNPACK "Load the XNNPACK backend shared library" ON) +option(RNE_ENABLE_VULKAN "Load the Vulkan backend shared library" ON) + # Treat third-party headers as system headers to suppress deprecation warnings include_directories(SYSTEM "${INCLUDE_DIR}") diff --git a/packages/react-native-executorch/android/build.gradle b/packages/react-native-executorch/android/build.gradle index 5b1cfd2973..3dbae2efdb 100644 --- a/packages/react-native-executorch/android/build.gradle +++ b/packages/react-native-executorch/android/build.gradle @@ -1,5 +1,22 @@ import org.apache.tools.ant.taskdefs.condition.Os +// Read the generated build config written by the postinstall script. +// Falls back to enabling everything if the file doesn't exist (e.g. during CI +// when libs are pre-cached and the postinstall script skipped writing config). +def getRneBuildConfig() { + def configFile = new File("${project.projectDir}/../rne-build-config.json") + if (configFile.exists()) { + try { + return new groovy.json.JsonSlurper().parse(configFile) + } catch (e) { + logger.warn("[RnExecutorch] Failed to parse rne-build-config.json: ${e.message}. Defaulting to all features enabled.") + } + } + return [enableOpencv: true, enablePhonemizer: true, enableXnnpack: true, enableCoreml: true, enableVulkan: true] +} + +def rneBuildConfig = getRneBuildConfig() + buildscript { ext { agp_version = '8.4.2' @@ -122,7 +139,11 @@ android { "-DREACT_NATIVE_DIR=${toPlatformFileString(reactNativeRootDir.path)}", "-DBUILD_DIR=${project.buildDir}", "-DANDROID_TOOLCHAIN=clang", - "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", + "-DRNE_ENABLE_OPENCV=${rneBuildConfig.enableOpencv ? 'ON' : 'OFF'}", + "-DRNE_ENABLE_PHONEMIZER=${rneBuildConfig.enablePhonemizer ? 'ON' : 'OFF'}", + "-DRNE_ENABLE_XNNPACK=${rneBuildConfig.enableXnnpack ? 'ON' : 'OFF'}", + "-DRNE_ENABLE_VULKAN=${rneBuildConfig.enableVulkan ? 'ON' : 'OFF'}" } } } diff --git a/packages/react-native-executorch/android/libs/classes.jar b/packages/react-native-executorch/android/libs/classes.jar index 3b00d51231..6a50b05849 100644 Binary files a/packages/react-native-executorch/android/libs/classes.jar and b/packages/react-native-executorch/android/libs/classes.jar differ diff --git a/packages/react-native-executorch/android/src/main/cpp/CMakeLists.txt b/packages/react-native-executorch/android/src/main/cpp/CMakeLists.txt index d7bd1fa870..299f0e4ea8 100644 --- a/packages/react-native-executorch/android/src/main/cpp/CMakeLists.txt +++ b/packages/react-native-executorch/android/src/main/cpp/CMakeLists.txt @@ -1,12 +1,74 @@ cmake_minimum_required(VERSION 3.13) file(GLOB_RECURSE ANDROID_CPP_SOURCES CONFIGURE_DEPENDS "${ANDROID_CPP_DIR}/*.cpp") -file(GLOB_RECURSE COMMON_CPP_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/*.cpp") -file(GLOB_RECURSE COMMON_C_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/*.c") + +# --- Source separation --- +# Glob all common sources, then separate opencv-dependent and phonemizer-dependent +# files so they can be conditionally included based on feature flags. + +file(GLOB_RECURSE ALL_COMMON_CPP_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/*.cpp") +file(GLOB_RECURSE ALL_COMMON_C_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/*.c") + +# Exclude test sources unconditionally file(GLOB_RECURSE TEST_CPP_SOURCES "${COMMON_CPP_DIR}/rnexecutorch/tests/*.cpp") -list(REMOVE_ITEM COMMON_CPP_SOURCES ${TEST_CPP_SOURCES}) +list(REMOVE_ITEM ALL_COMMON_CPP_SOURCES ${TEST_CPP_SOURCES}) + +# OpenCV-dependent sources: CV models + frame utilities + image processing +file(GLOB_RECURSE OPENCV_CPP_SOURCES CONFIGURE_DEPENDS + "${COMMON_CPP_DIR}/rnexecutorch/models/classification/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/object_detection/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/semantic_segmentation/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/instance_segmentation/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/style_transfer/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/ocr/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/vertical_ocr/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/embeddings/image/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/text_to_image/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/VisionModel.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/data_processing/ImageProcessing.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/utils/FrameExtractor.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/utils/FrameProcessor.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/utils/FrameTransform.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/utils/computer_vision/*.cpp" + "${COMMON_CPP_DIR}/runner/encoders/vision_encoder.cpp" + "${COMMON_CPP_DIR}/runner/multimodal_prefiller.cpp" + "${COMMON_CPP_DIR}/runner/multimodal_runner.cpp" +) + +# Phonemizer-dependent sources: Kokoro TTS (only user of phonemis) +file(GLOB_RECURSE PHONEMIZER_CPP_SOURCES CONFIGURE_DEPENDS + "${COMMON_CPP_DIR}/rnexecutorch/models/text_to_speech/*.cpp" +) + +# Core = everything minus optional sources +set(CORE_COMMON_CPP_SOURCES ${ALL_COMMON_CPP_SOURCES}) +list(REMOVE_ITEM CORE_COMMON_CPP_SOURCES ${OPENCV_CPP_SOURCES} ${PHONEMIZER_CPP_SOURCES}) + +# Build final source list +set(ENABLED_COMMON_SOURCES ${CORE_COMMON_CPP_SOURCES}) + +if(RNE_ENABLE_OPENCV) + list(APPEND ENABLED_COMMON_SOURCES ${OPENCV_CPP_SOURCES}) +endif() + +if(RNE_ENABLE_PHONEMIZER) + list(APPEND ENABLED_COMMON_SOURCES ${PHONEMIZER_CPP_SOURCES}) +endif() + +add_library(react-native-executorch SHARED + ${ANDROID_CPP_SOURCES} + ${ENABLED_COMMON_SOURCES} + ${ALL_COMMON_C_SOURCES} +) -add_library(react-native-executorch SHARED ${ANDROID_CPP_SOURCES} ${COMMON_CPP_SOURCES} ${COMMON_C_SOURCES}) +# Propagate feature flags as preprocessor defines so C++ code can guard includes +if(RNE_ENABLE_OPENCV) + target_compile_definitions(react-native-executorch PRIVATE RNE_ENABLE_OPENCV) +endif() + +if(RNE_ENABLE_PHONEMIZER) + target_compile_definitions(react-native-executorch PRIVATE RNE_ENABLE_PHONEMIZER) +endif() find_package(ReactAndroid REQUIRED CONFIG) find_package(fbjni REQUIRED CONFIG) @@ -34,64 +96,54 @@ set(RN_VERSION_LINK_LIBRARIES ReactAndroid::reactnative ) -# Dependencies: - -# ------- Executorch ------- +# ------- Executorch (always required) ------- add_library(executorch SHARED IMPORTED) set_target_properties(executorch PROPERTIES IMPORTED_LOCATION "${LIBS_DIR}/executorch/${ANDROID_ABI}/libexecutorch.so") +# Backends (XNNPACK, Vulkan) are baked into libexecutorch.so on Android due to +# ExecuTorch's static kernel registration design — separate backend .so files +# caused duplicate kernel registration aborts. Enable/disable happens at +# libexecutorch.so build time, not at runtime link time. if(ANDROID_ABI STREQUAL "arm64-v8a") target_compile_definitions(react-native-executorch PRIVATE ARCH_ARM64) +endif() - # ------- pthreadpool ------- - add_library(pthreadpool SHARED IMPORTED) - - set_target_properties(pthreadpool PROPERTIES - IMPORTED_LOCATION "${LIBS_DIR}/pthreadpool/${ANDROID_ABI}/libpthreadpool.so") +# pthreadpool and cpuinfo are statically linked into libexecutorch.so, +# no separate shared libraries needed. - # ------- cpuinfo ------- - add_library(cpuinfo SHARED IMPORTED) +# ------- OpenCV (optional) ------- - set_target_properties(cpuinfo PROPERTIES - IMPORTED_LOCATION "${LIBS_DIR}/cpuinfo/${ANDROID_ABI}/libcpuinfo.so") - set(EXECUTORCH_LIBS - "pthreadpool" - "cpuinfo" +if(RNE_ENABLE_OPENCV) + set(OPENCV_LINK_LIBS + "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_core.a" + "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_features2d.a" + "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_highgui.a" + "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_imgproc.a" + "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_photo.a" + "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_video.a" ) -endif() -# ------- OpenCV ------- + if(ANDROID_ABI STREQUAL "arm64-v8a") + list(APPEND OPENCV_LINK_LIBS + "${LIBS_DIR}/opencv-third-party/${ANDROID_ABI}/libkleidicv_hal.a" + "${LIBS_DIR}/opencv-third-party/${ANDROID_ABI}/libkleidicv_thread.a" + "${LIBS_DIR}/opencv-third-party/${ANDROID_ABI}/libkleidicv.a" + ) + endif() +endif() -set(OPENCV_LIBS - "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_core.a" - "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_features2d.a" - "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_highgui.a" - "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_imgproc.a" - "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_photo.a" - "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_video.a" -) +# ------- Phonemizer (optional) ------- -if(ANDROID_ABI STREQUAL "arm64-v8a") - set(OPENCV_THIRD_PARTY_LIBS - "${LIBS_DIR}/opencv-third-party/${ANDROID_ABI}/libkleidicv_hal.a" - "${LIBS_DIR}/opencv-third-party/${ANDROID_ABI}/libkleidicv_thread.a" - "${LIBS_DIR}/opencv-third-party/${ANDROID_ABI}/libkleidicv.a" +if(RNE_ENABLE_PHONEMIZER) + set(PHONEMIZER_LINK_LIBS + "${LIBS_DIR}/phonemis/${ANDROID_ABI}/libphonemis.a" ) -elseif(ANDROID_ABI STREQUAL "x86_64") - set(OPENCV_THIRD_PARTY_LIBS "") endif() - -# ------- phonemis ------- - -set(PHONEMIS_LIBS - "${LIBS_DIR}/phonemis/${ANDROID_ABI}/libphonemis.a" -) - # -------------- target_link_options(react-native-executorch PRIVATE -fopenmp -static-openmp) @@ -100,10 +152,8 @@ target_link_libraries( react-native-executorch ${LINK_LIBRARIES} ${RN_VERSION_LINK_LIBRARIES} - ${OPENCV_LIBS} - ${OPENCV_THIRD_PARTY_LIBS} - ${PHONEMIS_LIBS} + ${OPENCV_LINK_LIBS} + ${PHONEMIZER_LINK_LIBS} executorch - ${EXECUTORCH_LIBS} z ) diff --git a/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/ETInstaller.kt b/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/ETInstaller.kt index acc43c0a9e..a0fccd4dca 100644 --- a/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/ETInstaller.kt +++ b/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/ETInstaller.kt @@ -49,6 +49,8 @@ class ETInstaller( init { try { + // XNNPACK and Vulkan backends are baked into libexecutorch.so on Android + // (enabled at build time via rne-build-config.json). System.loadLibrary("executorch") System.loadLibrary("react-native-executorch") val jsCallInvokerHolder = reactContext.jsCallInvokerHolder as CallInvokerHolderImpl diff --git a/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp b/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp index da75ab951c..294d0080ba 100644 --- a/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp +++ b/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp @@ -2,22 +2,28 @@ #include #include +#include +#include +#include +#include +#include +#include + +#ifdef RNE_ENABLE_OPENCV #include #include -#include #include -#include #include #include #include -#include #include #include -#include #include -#include -#include -#include +#endif + +#ifdef RNE_ENABLE_PHONEMIZER +#include +#endif #if defined(__ANDROID__) && defined(__aarch64__) #include @@ -40,6 +46,7 @@ void RnExecutorchInstaller::injectJSIBindings( jsiRuntime->global().setProperty(*jsiRuntime, "__rne_isEmulator", jsi::Value(isEmulator)); +#ifdef RNE_ENABLE_OPENCV jsiRuntime->global().setProperty( *jsiRuntime, "loadStyleTransfer", RnExecutorchInstaller::loadModel( @@ -72,6 +79,7 @@ void RnExecutorchInstaller::injectJSIBindings( RnExecutorchInstaller::loadModel< models::object_detection::ObjectDetection>(jsiRuntime, jsCallInvoker, "loadObjectDetection")); +#endif // RNE_ENABLE_OPENCV jsiRuntime->global().setProperty( *jsiRuntime, "loadExecutorchModule", @@ -83,10 +91,12 @@ void RnExecutorchInstaller::injectJSIBindings( RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadTokenizerModule")); +#ifdef RNE_ENABLE_OPENCV jsiRuntime->global().setProperty( *jsiRuntime, "loadImageEmbeddings", RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadImageEmbeddings")); +#endif // RNE_ENABLE_OPENCV jsiRuntime->global().setProperty( *jsiRuntime, "loadTextEmbeddings", @@ -98,6 +108,7 @@ void RnExecutorchInstaller::injectJSIBindings( RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadLLM")); +#ifdef RNE_ENABLE_OPENCV jsiRuntime->global().setProperty( *jsiRuntime, "loadOCR", RnExecutorchInstaller::loadModel( @@ -107,16 +118,19 @@ void RnExecutorchInstaller::injectJSIBindings( *jsiRuntime, "loadVerticalOCR", RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadVerticalOCR")); +#endif // RNE_ENABLE_OPENCV jsiRuntime->global().setProperty( *jsiRuntime, "loadSpeechToText", RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadSpeechToText")); +#ifdef RNE_ENABLE_PHONEMIZER jsiRuntime->global().setProperty( *jsiRuntime, "loadTextToSpeechKokoro", RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadTextToSpeechKokoro")); +#endif // RNE_ENABLE_PHONEMIZER jsiRuntime->global().setProperty( *jsiRuntime, "loadVAD", diff --git a/packages/react-native-executorch/common/rnexecutorch/models/llm/LLM.cpp b/packages/react-native-executorch/common/rnexecutorch/models/llm/LLM.cpp index 64e94c2ff0..642201d799 100644 --- a/packages/react-native-executorch/common/rnexecutorch/models/llm/LLM.cpp +++ b/packages/react-native-executorch/common/rnexecutorch/models/llm/LLM.cpp @@ -6,9 +6,11 @@ #include #include #include +#include +#ifdef RNE_ENABLE_OPENCV #include #include -#include +#endif namespace rnexecutorch::models::llm { namespace llm = ::executorch::extension::llm; @@ -22,10 +24,8 @@ LLM::LLM(const std::string &modelSource, const std::string &tokenizerSource, std::shared_ptr callInvoker) : BaseModel(modelSource, callInvoker, Module::LoadMode::File) { - if (capabilities.empty()) { - runner_ = - std::make_unique(std::move(module_), tokenizerSource); - } else { +#ifdef RNE_ENABLE_OPENCV + if (!capabilities.empty()) { std::map> encoders; for (const auto &cap : capabilities) { if (cap == "vision") { @@ -35,7 +35,13 @@ LLM::LLM(const std::string &modelSource, const std::string &tokenizerSource, } runner_ = std::make_unique( std::move(module_), tokenizerSource, std::move(encoders)); + } else { +#endif + runner_ = + std::make_unique(std::move(module_), tokenizerSource); +#ifdef RNE_ENABLE_OPENCV } +#endif auto loadResult = runner_->load(); if (loadResult != Error::Ok) { diff --git a/packages/react-native-executorch/common/rnexecutorch/threads/GlobalThreadPool.h b/packages/react-native-executorch/common/rnexecutorch/threads/GlobalThreadPool.h index 50025eeeb7..ad955b29f4 100644 --- a/packages/react-native-executorch/common/rnexecutorch/threads/GlobalThreadPool.h +++ b/packages/react-native-executorch/common/rnexecutorch/threads/GlobalThreadPool.h @@ -4,7 +4,9 @@ #include #include #include +#ifdef RNE_ENABLE_OPENCV #include +#endif #include #include #include @@ -41,7 +43,9 @@ class GlobalThreadPool { config); // Disable OpenCV's internal threading to prevent it from overriding our // thread pool configuration, which would cause degraded performance +#ifdef RNE_ENABLE_OPENCV cv::setNumThreads(0); +#endif }); } diff --git a/packages/react-native-executorch/package.json b/packages/react-native-executorch/package.json index c1825e393f..b8961b85c3 100644 --- a/packages/react-native-executorch/package.json +++ b/packages/react-native-executorch/package.json @@ -18,8 +18,10 @@ "*.podspec", "third-party/include", "third-party", + "!third-party/android/libs", "!third-party/ios/ExecutorchLib", - "!third-party/ios/libs/executorch", + "!third-party/ios/ExecutorchLib.xcframework", + "!third-party/ios/libs", "!ios/build", "!android/build", "!android/gradle", @@ -38,7 +40,8 @@ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", "prepare": "bob build", "prepack": "cp ../../README.md ./README.md", - "postpack": "rm ./README.md" + "postpack": "rm ./README.md", + "postinstall": "node scripts/download-libs.js" }, "keywords": [ "react-native", diff --git a/packages/react-native-executorch/react-native-executorch.podspec b/packages/react-native-executorch/react-native-executorch.podspec index 4094d8815d..76b2034f8a 100644 --- a/packages/react-native-executorch/react-native-executorch.podspec +++ b/packages/react-native-executorch/react-native-executorch.podspec @@ -2,6 +2,23 @@ require "json" package = JSON.parse(File.read(File.join(__dir__, "package.json"))) +# Read the build config written by the postinstall script. +# Falls back to all features enabled if the file doesn't exist. +rne_build_config_path = File.join(__dir__, "rne-build-config.json") +if File.exist?(rne_build_config_path) + require "json" + rne_build_config = JSON.parse(File.read(rne_build_config_path)) + enable_opencv = rne_build_config["enableOpencv"] != false + enable_phonemizer = rne_build_config["enablePhonemizer"] != false + enable_xnnpack = rne_build_config["enableXnnpack"] != false + enable_coreml = rne_build_config["enableCoreml"] != false +else + enable_opencv = true + enable_phonemizer = true + enable_xnnpack = true + enable_coreml = true +end + Pod::Spec.new do |s| s.name = "react-native-executorch" s.version = package["version"] @@ -13,29 +30,90 @@ Pod::Spec.new do |s| s.platforms = { :ios => min_ios_version_supported } s.source = { :git => "https://github.com/software-mansion/react-native-executorch.git", :tag => "#{s.version}" } - pthreadpool_binaries_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/libs/pthreadpool', __dir__) - cpuinfo_binaries_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/libs/cpuinfo', __dir__) - phonemis_binaries_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/libs/phonemis', __dir__) + cpuinfo_binaries_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/libs/cpuinfo', __dir__) + phonemis_binaries_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/libs/phonemis', __dir__) - s.user_target_xcconfig = { - "HEADER_SEARCH_PATHS" => "$(PODS_TARGET_SRCROOT)/third-party/include", + # --- Core sources (always compiled) --- + opencv_source_dirs = [ + "common/rnexecutorch/models/classification", + "common/rnexecutorch/models/object_detection", + "common/rnexecutorch/models/semantic_segmentation", + "common/rnexecutorch/models/instance_segmentation", + "common/rnexecutorch/models/style_transfer", + "common/rnexecutorch/models/ocr", + "common/rnexecutorch/models/vertical_ocr", + "common/rnexecutorch/models/embeddings/image", + "common/rnexecutorch/models/text_to_image", + "common/rnexecutorch/utils/computer_vision", + ] + opencv_source_files = opencv_source_dirs.map { |d| "#{d}/**/*.{cpp,c,h,hpp}" } + opencv_source_files += [ + "common/rnexecutorch/models/VisionModel.{cpp,h}", + "common/rnexecutorch/data_processing/ImageProcessing.{cpp,h}", + "common/rnexecutorch/utils/FrameExtractor.{cpp,h}", + "common/rnexecutorch/utils/FrameProcessor.{cpp,h}", + "common/rnexecutorch/utils/FrameTransform.{cpp,h}", + ] + + phonemizer_source_files = [ + "common/rnexecutorch/models/text_to_speech/**/*.{cpp,c,h,hpp}", + ] + + s.source_files = [ + "ios/**/*.{m,mm,h}", + "common/**/*.{cpp,c,h,hpp}", + ] + + exclude_files = [ + "common/rnexecutorch/tests/**/*.{cpp}", + "common/rnexecutorch/jsi/*.{h,hpp}", + ] + exclude_files += opencv_source_files unless enable_opencv + exclude_files += phonemizer_source_files unless enable_phonemizer + s.exclude_files = exclude_files + + # --- Preprocessor flags --- + extra_compiler_flags = [] + extra_compiler_flags << "-DRNE_ENABLE_OPENCV" if enable_opencv + extra_compiler_flags << "-DRNE_ENABLE_PHONEMIZER" if enable_phonemizer + extra_compiler_flags << "-DRNE_ENABLE_XNNPACK" if enable_xnnpack + extra_compiler_flags << "-DRNE_ENABLE_COREML" if enable_coreml + + # --- Link flags --- + physical_ldflags = [ + '$(inherited)', + "\"#{pthreadpool_binaries_path}/physical-arm64-release/libpthreadpool.a\"", + "\"#{cpuinfo_binaries_path}/libcpuinfo.a\"", + ] + simulator_ldflags = [ + '$(inherited)', + "\"#{pthreadpool_binaries_path}/simulator-arm64-debug/libpthreadpool.a\"", + "\"#{cpuinfo_binaries_path}/libcpuinfo.a\"", + ] + + if enable_phonemizer + physical_ldflags << "\"#{phonemis_binaries_path}/physical-arm64-release/libphonemis.a\"" + simulator_ldflags << "\"#{phonemis_binaries_path}/simulator-arm64-debug/libphonemis.a\"" + end - "OTHER_LDFLAGS[sdk=iphoneos*]" => [ - '$(inherited)', - "\"#{pthreadpool_binaries_path}/physical-arm64-release/libpthreadpool.a\"", - "\"#{cpuinfo_binaries_path}/libcpuinfo.a\"", - "\"#{phonemis_binaries_path}/physical-arm64-release/libphonemis.a\"", + xnnpack_xcframework_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/XnnpackBackend.xcframework', __dir__) + coreml_xcframework_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/CoreMLBackend.xcframework', __dir__) - ].join(' '), + if enable_xnnpack + physical_ldflags << "-force_load \"#{xnnpack_xcframework_path}/ios-arm64/libXnnpackBackend.a\"" + simulator_ldflags << "-force_load \"#{xnnpack_xcframework_path}/ios-arm64-simulator/libXnnpackBackend.a\"" + end - "OTHER_LDFLAGS[sdk=iphonesimulator*]" => [ - '$(inherited)', - "\"#{pthreadpool_binaries_path}/simulator-arm64-debug/libpthreadpool.a\"", - "\"#{cpuinfo_binaries_path}/libcpuinfo.a\"", - "\"#{phonemis_binaries_path}/simulator-arm64-debug/libphonemis.a\"", - ].join(' '), + if enable_coreml + physical_ldflags << "-force_load \"#{coreml_xcframework_path}/ios-arm64/libCoreMLBackend.a\"" + simulator_ldflags << "-force_load \"#{coreml_xcframework_path}/ios-arm64-simulator/libCoreMLBackend.a\"" + end + s.user_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "$(PODS_TARGET_SRCROOT)/third-party/include", + "OTHER_LDFLAGS[sdk=iphoneos*]" => physical_ldflags.join(' '), + "OTHER_LDFLAGS[sdk=iphonesimulator*]" => simulator_ldflags.join(' '), 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'x86_64', } @@ -47,32 +125,28 @@ Pod::Spec.new do |s| '"$(PODS_TARGET_SRCROOT)/third-party/include" '+ '"$(PODS_TARGET_SRCROOT)/common" ', "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", + "OTHER_CPLUSPLUSFLAGS" => extra_compiler_flags.join(' '), 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'x86_64', } - s.source_files = [ - "ios/**/*.{m,mm,h}", - "common/**/*.{cpp,c,h,hpp}", - ] + libs = ["z"] + libs << "sqlite3" if enable_coreml + s.libraries = libs + + system_frameworks = ["Accelerate"] + system_frameworks << "CoreML" if enable_coreml + s.frameworks = system_frameworks + + # Backend xcframeworks are linked via force_load in OTHER_LDFLAGS (needed to + # preserve __attribute__((constructor)) registrations). Only ExecutorchLib goes + # in vendored_frameworks to avoid duplicate symbol errors. + s.ios.vendored_frameworks = ["third-party/ios/ExecutorchLib.xcframework"] - s.libraries = "z" - s.ios.vendored_frameworks = "third-party/ios/ExecutorchLib.xcframework" - # Exclude file with tests to not introduce gtest dependency. - # Do not include the headers from common/rnexecutorch/jsi/ as source files. - # Xcode/Cocoapods leaks them to other pods that an app also depends on, so if - # another pod includes a header with the same name without a path by - # #include "Header.h" we get a conflict. Here, headers in jsi/ collide with - # react-native-skia. The headers are preserved by preserve_paths and - # then made available by HEADER_SEARCH_PATHS. - s.exclude_files = [ - "common/rnexecutorch/tests/**/*.{cpp}", - "common/rnexecutorch/jsi/*.{h,hpp}" - ] s.header_mappings_dir = "common/rnexecutorch" s.header_dir = "rnexecutorch" s.preserve_paths = "common/rnexecutorch/jsi/*.{h,hpp}" - s.dependency "opencv-rne", "~> 4.11.0" + s.dependency "opencv-rne", "~> 4.11.0" if enable_opencv install_modules_dependencies(s) end diff --git a/packages/react-native-executorch/scripts/download-libs.js b/packages/react-native-executorch/scripts/download-libs.js new file mode 100644 index 0000000000..ed2f5a6f37 --- /dev/null +++ b/packages/react-native-executorch/scripts/download-libs.js @@ -0,0 +1,308 @@ +/** + * On-demand native library downloader + * + * Runs at postinstall time. Downloads prebuilt native artifacts from GitHub Releases + * and extracts them into third-party/ so the existing CMakeLists.txt / podspec + * can find them at build time without any other changes. + * + * Artifact layout on GitHub Releases (per version tag, e.g. v0.9.0): + * + * core-android-arm64-v8a.tar.gz -- executorch, pthreadpool, cpuinfo for arm64 + * core-android-x86_64.tar.gz -- executorch for x86_64 + * core-ios.tar.gz -- ExecutorchLib.xcframework (without xnnpack/coreml) + * opencv-android-arm64-v8a.tar.gz -- OpenCV for arm64 + * opencv-android-x86_64.tar.gz -- OpenCV for x86_64 + * opencv-ios.tar.gz -- OpenCV xcframework + * phonemizer-android-arm64-v8a.tar.gz + * phonemizer-android-x86_64.tar.gz + * phonemizer-ios.tar.gz + * xnnpack-android-arm64-v8a.tar.gz -- libxnnpack_executorch_backend.so + * xnnpack-android-x86_64.tar.gz + * xnnpack-ios.tar.gz -- XnnpackBackend.xcframework + * coreml-ios.tar.gz -- CoreMLBackend.xcframework (iOS only) + * + * Each tarball extracts into third-party/android/libs/ or third-party/ios/ + * preserving the existing directory structure so CMakeLists/podspec need no changes. + * + * User configuration (in the app's package.json): + * "react-native-executorch": { + * "extras": ["opencv", "phonemizer", "xnnpack", "coreml"] // default: all enabled + * } + * + * Environment variables: + * RNET_SKIP_DOWNLOAD=1 -- skip download entirely (for CI with pre-cached libs) + * RNET_LIBS_CACHE_DIR=/path -- use custom cache dir instead of default + * RNET_TARGET=android-arm64 -- force specific target (skip auto-detection) + * RNET_BASE_URL=http://localhost:8080 -- override base URL (useful for local testing: + * cd dist-artifacts && python3 -m http.server 8080) + * GITHUB_TOKEN=ghp_xxx -- GitHub token for accessing draft releases + */ + +'use strict'; + +const https = require('https'); +const http = require('http'); +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +// ---- Config ---------------------------------------------------------------- + +const PACKAGE_VERSION = require('../package.json').version; +const GITHUB_REPO = 'software-mansion/react-native-executorch'; +const BASE_URL = + process.env.RNET_BASE_URL || + `https://github.com/${GITHUB_REPO}/releases/download/v${PACKAGE_VERSION}`; + +const PACKAGE_ROOT = path.resolve(__dirname, '..'); +const THIRD_PARTY_DIR = path.join(PACKAGE_ROOT, 'third-party'); + +const DEFAULT_CACHE_DIR = path.join( + require('os').homedir(), + '.cache', + 'react-native-executorch', + PACKAGE_VERSION +); +const CACHE_DIR = process.env.RNET_LIBS_CACHE_DIR || DEFAULT_CACHE_DIR; + +// ---- User config ----------------------------------------------------------- + +function readUserExtras() { + // npm/yarn set INIT_CWD to the directory where install was invoked (project root) + const projectRoot = + process.env.INIT_CWD || process.env.npm_config_local_prefix; + if (!projectRoot) { + console.warn( + '[react-native-executorch] Could not determine project root, enabling all extras.' + ); + return ['opencv', 'phonemizer']; + } + + const userPackageJsonPath = path.join(projectRoot, 'package.json'); + try { + const userPackageJson = JSON.parse( + fs.readFileSync(userPackageJsonPath, 'utf8') + ); + const rneConfig = userPackageJson['react-native-executorch'] || {}; + return ( + rneConfig.extras ?? [ + 'opencv', + 'phonemizer', + 'xnnpack', + 'coreml', + 'vulkan', + ] + ); + } catch { + console.warn( + '[react-native-executorch] Could not read app package.json, enabling all extras.' + ); + return ['opencv', 'phonemizer', 'xnnpack', 'coreml', 'vulkan']; + } +} + +function writeBuildConfig(extras) { + const config = { + enableOpencv: extras.includes('opencv'), + enablePhonemizer: extras.includes('phonemizer'), + enableXnnpack: extras.includes('xnnpack'), + enableCoreml: extras.includes('coreml'), + enableVulkan: extras.includes('vulkan'), + }; + fs.writeFileSync( + path.join(PACKAGE_ROOT, 'rne-build-config.json'), + JSON.stringify(config, null, 2) + ); + return config; +} + +// ---- Target detection ------------------------------------------------------ + +function detectTargets() { + if (process.env.RNET_TARGET) { + return [process.env.RNET_TARGET]; + } + + const targets = []; + if (process.platform === 'darwin') { + targets.push('ios'); + } + targets.push('android-arm64-v8a'); + if (!process.env.RNET_NO_X86_64) { + targets.push('android-x86_64'); + } + return targets; +} + +// ---- Artifact metadata ----------------------------------------------------- + +// Core artifacts are always downloaded; optional ones only if the extra is enabled. +function getArtifacts(targets, extras) { + const artifacts = []; + + for (const target of targets) { + const destDir = target.startsWith('android') + ? path.join(THIRD_PARTY_DIR, 'android', 'libs') + : path.join(THIRD_PARTY_DIR, 'ios'); + + // Core is always needed + artifacts.push(makeArtifact(`core-${target}`, destDir)); + + // iOS OpenCV is provided via CocoaPods (opencv-rne dependency), not a tarball + if (extras.includes('opencv') && target !== 'ios') { + artifacts.push(makeArtifact(`opencv-${target}`, destDir)); + } + + if (extras.includes('phonemizer')) { + artifacts.push(makeArtifact(`phonemizer-${target}`, destDir)); + } + + if (extras.includes('xnnpack')) { + artifacts.push(makeArtifact(`xnnpack-${target}`, destDir)); + } + + // CoreML is iOS only + if (extras.includes('coreml') && target === 'ios') { + artifacts.push(makeArtifact(`coreml-${target}`, destDir)); + } + + // Vulkan is Android only + if (extras.includes('vulkan') && target.startsWith('android')) { + artifacts.push(makeArtifact(`vulkan-${target}`, destDir)); + } + } + + return artifacts; +} + +function makeArtifact(name, destDir) { + return { + name, + url: `${BASE_URL}/${name}.tar.gz`, + checksumUrl: `${BASE_URL}/${name}.tar.gz.sha256`, + destDir, + cacheFile: path.join(CACHE_DIR, `${name}.tar.gz`), + cacheChecksumFile: path.join(CACHE_DIR, `${name}.tar.gz.sha256`), + }; +} + +// ---- Helpers --------------------------------------------------------------- + +function ensureDir(dir) { + fs.mkdirSync(dir, { recursive: true }); +} + +function download(url, dest) { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(dest); + const get = (url) => { + const client = url.startsWith('http://') ? http : https; + const headers = {}; + if (process.env.GITHUB_TOKEN && !url.startsWith('http://')) { + headers['Authorization'] = `Bearer ${process.env.GITHUB_TOKEN}`; + } + client.get(url, { headers }, (res) => { + if (res.statusCode === 301 || res.statusCode === 302) { + return get(res.headers.location); + } + if (res.statusCode !== 200) { + return reject(new Error(`HTTP ${res.statusCode} for ${url}`)); + } + res.pipe(file); + file.on('finish', () => file.close(resolve)); + }); + }; + get(url); + file.on('error', (err) => { + fs.unlinkSync(dest); + reject(err); + }); + }); +} + +function sha256(filePath) { + const result = execSync( + `sha256sum "${filePath}" || shasum -a 256 "${filePath}"` + ); + return result.toString().split(' ')[0].trim(); +} + +function isCacheValid(artifact) { + if (!fs.existsSync(artifact.cacheFile)) return false; + if (!fs.existsSync(artifact.cacheChecksumFile)) return false; + const expectedChecksum = fs + .readFileSync(artifact.cacheChecksumFile, 'utf8') + .trim(); + const actualChecksum = sha256(artifact.cacheFile); + return expectedChecksum === actualChecksum; +} + +function extract(tarball, destDir) { + ensureDir(destDir); + execSync(`tar -xzf "${tarball}" -C "${destDir}"`); +} + +// ---- Main ------------------------------------------------------------------ + +async function main() { + if (process.env.RNET_SKIP_DOWNLOAD) { + console.log( + '[react-native-executorch] Skipping native lib download (RNET_SKIP_DOWNLOAD set)' + ); + // Still write build config so the native build knows what features are enabled + const extras = readUserExtras(); + writeBuildConfig(extras); + return; + } + + const extras = readUserExtras(); + const buildConfig = writeBuildConfig(extras); + console.log( + `[react-native-executorch] Features: opencv=${buildConfig.enableOpencv}, phonemizer=${buildConfig.enablePhonemizer}, xnnpack=${buildConfig.enableXnnpack}, coreml=${buildConfig.enableCoreml}, vulkan=${buildConfig.enableVulkan}` + ); + + const targets = detectTargets(); + const artifacts = getArtifacts(targets, extras); + + ensureDir(CACHE_DIR); + + for (const artifact of artifacts) { + console.log(`[react-native-executorch] Preparing ${artifact.name}...`); + + if (isCacheValid(artifact)) { + console.log(` ✓ Cache hit, skipping download`); + } else { + console.log(` ↓ Downloading ${artifact.url}`); + await download(artifact.checksumUrl, artifact.cacheChecksumFile); + await download(artifact.url, artifact.cacheFile); + + const expectedChecksum = fs + .readFileSync(artifact.cacheChecksumFile, 'utf8') + .trim(); + const actualChecksum = sha256(artifact.cacheFile); + if (expectedChecksum !== actualChecksum) { + throw new Error( + `Checksum mismatch for ${artifact.name}: expected ${expectedChecksum}, got ${actualChecksum}` + ); + } + console.log(` ✓ Downloaded and verified`); + } + + console.log(` ↓ Extracting to ${artifact.destDir}`); + extract(artifact.cacheFile, artifact.destDir); + console.log(` ✓ Done`); + } + + console.log('[react-native-executorch] Native libs ready.'); +} + +main().catch((err) => { + console.error( + '[react-native-executorch] Failed to download native libs:', + err.message + ); + console.error( + ' You can set RNET_SKIP_DOWNLOAD=1 to skip and provide libs manually.' + ); + process.exit(1); +}); diff --git a/packages/react-native-executorch/scripts/package-release-artifacts.sh b/packages/react-native-executorch/scripts/package-release-artifacts.sh new file mode 100755 index 0000000000..f375e542a8 --- /dev/null +++ b/packages/react-native-executorch/scripts/package-release-artifacts.sh @@ -0,0 +1,201 @@ +#!/usr/bin/env bash +# package-release-artifacts.sh +# +# Packages the currently committed native libs into release artifact tarballs +# ready to be uploaded to GitHub Releases. +# +# Run from the package root (packages/react-native-executorch/): +# ./scripts/package-release-artifacts.sh +# +# Output: dist-artifacts/ +# core-android-arm64-v8a.tar.gz + .sha256 +# core-android-x86_64.tar.gz + .sha256 +# opencv-android-arm64-v8a.tar.gz + .sha256 +# opencv-android-x86_64.tar.gz + .sha256 +# phonemizer-android-arm64-v8a.tar.gz + .sha256 +# phonemizer-android-x86_64.tar.gz + .sha256 +# xnnpack-android-arm64-v8a.tar.gz + .sha256 +# xnnpack-android-x86_64.tar.gz + .sha256 +# core-ios.tar.gz + .sha256 +# phonemizer-ios.tar.gz + .sha256 +# xnnpack-ios.tar.gz + .sha256 +# coreml-ios.tar.gz + .sha256 +# +# Note: iOS OpenCV is provided via CocoaPods (opencv-rne), not a tarball. +# +# Testing the download flow +# ------------------------- +# Option A — local HTTP server (no GitHub needed): +# cd dist-artifacts && python3 -m http.server 8080 +# RNET_BASE_URL=http://localhost:8080 INIT_CWD= node scripts/download-libs.js +# +# Option B — GitHub pre-release: +# gh release create v0.9.0-libs-test --prerelease --title "libs test" \ +# --notes "Test release, will be deleted." \ +# --repo software-mansion/react-native-executorch +# gh release upload v0.9.0-libs-test dist-artifacts/* \ +# --repo software-mansion/react-native-executorch +# RNET_BASE_URL=https://github.com/software-mansion/react-native-executorch/releases/download/v0.9.0-libs-test \ +# INIT_CWD= node scripts/download-libs.js +# # cleanup: +# gh release delete v0.9.0-libs-test --repo software-mansion/react-native-executorch --yes + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PACKAGE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +ANDROID_LIBS="$PACKAGE_ROOT/third-party/android/libs" +IOS_DIR="$PACKAGE_ROOT/third-party/ios" +OUT="$PACKAGE_ROOT/dist-artifacts" + +VERSION=$(node -p "require('$PACKAGE_ROOT/package.json').version") + +echo "Packaging release artifacts for v$VERSION" +mkdir -p "$OUT" + +# ---- Helpers ---------------------------------------------------------------- + +package() { + local name=$1 + local src_dir=$2 + local out_file="$OUT/$name.tar.gz" + + echo " → $name" + + if [ ! -d "$src_dir" ]; then + echo " ✗ Source directory not found: $src_dir" >&2 + exit 1 + fi + + tar -czf "$out_file" -C "$src_dir" . + shasum -a 256 "$out_file" | awk '{print $1}' > "$out_file.sha256" + echo " ✓ $(du -sh "$out_file" | cut -f1)" +} + +# Packages a single file into a tarball, placing it at the given relative path. +# package_file +package_file() { + local name=$1 + local rel_path=$2 # directory path inside the tarball + local src_file=$3 # full path to the source file + local out_file="$OUT/$name.tar.gz" + local tmp + tmp=$(mktemp -d) + + echo " → $name" + + if [ ! -f "$src_file" ]; then + echo " ✗ Source file not found: $src_file" >&2 + rm -rf "$tmp" + exit 1 + fi + + mkdir -p "$tmp/$rel_path" + cp "$src_file" "$tmp/$rel_path/" + + tar -czf "$out_file" -C "$tmp" . + shasum -a 256 "$out_file" | awk '{print $1}' > "$out_file.sha256" + echo " ✓ $(du -sh "$out_file" | cut -f1)" + rm -rf "$tmp" +} + +# Packages multiple source directories into a single tarball by staging them +# into a temp directory first, preserving relative paths. +package_merged() { + local name=$1 + shift + local out_file="$OUT/$name.tar.gz" + local tmp + tmp=$(mktemp -d) + + echo " → $name" + + while [[ $# -gt 0 ]]; do + local rel_path=$1 # relative path inside the tarball + local src=$2 # source directory to copy from + shift 2 + + if [ ! -d "$src" ]; then + echo " ✗ Source directory not found: $src" >&2 + rm -rf "$tmp" + exit 1 + fi + + mkdir -p "$tmp/$rel_path" + cp -r "$src/." "$tmp/$rel_path/" + done + + tar -czf "$out_file" -C "$tmp" . + shasum -a 256 "$out_file" | awk '{print $1}' > "$out_file.sha256" + echo " ✓ $(du -sh "$out_file" | cut -f1)" + rm -rf "$tmp" +} + +# ---- Android ---------------------------------------------------------------- + +echo "" +echo "Android:" + +package_merged "core-android-arm64-v8a" \ + "executorch/arm64-v8a" "$ANDROID_LIBS/executorch/arm64-v8a" \ + "pthreadpool/arm64-v8a" "$ANDROID_LIBS/pthreadpool/arm64-v8a" \ + "cpuinfo/arm64-v8a" "$ANDROID_LIBS/cpuinfo/arm64-v8a" + +package_merged "core-android-x86_64" \ + "executorch/x86_64" "$ANDROID_LIBS/executorch/x86_64" + +package_merged "opencv-android-arm64-v8a" \ + "opencv/arm64-v8a" "$ANDROID_LIBS/opencv/arm64-v8a" \ + "opencv-third-party/arm64-v8a" "$ANDROID_LIBS/opencv-third-party/arm64-v8a" + +package_merged "opencv-android-x86_64" \ + "opencv/x86_64" "$ANDROID_LIBS/opencv/x86_64" + +package_merged "phonemizer-android-arm64-v8a" \ + "phonemis/arm64-v8a" "$ANDROID_LIBS/phonemis/arm64-v8a" + +package_merged "phonemizer-android-x86_64" \ + "phonemis/x86_64" "$ANDROID_LIBS/phonemis/x86_64" + +package_file "xnnpack-android-arm64-v8a" \ + "executorch/arm64-v8a" "$ANDROID_LIBS/executorch/arm64-v8a/libxnnpack_executorch_backend.so" + +package_file "xnnpack-android-x86_64" \ + "executorch/x86_64" "$ANDROID_LIBS/executorch/x86_64/libxnnpack_executorch_backend.so" + +package_file "vulkan-android-arm64-v8a" \ + "executorch/arm64-v8a" "$ANDROID_LIBS/executorch/arm64-v8a/libvulkan_executorch_backend.so" + +package_file "vulkan-android-x86_64" \ + "executorch/x86_64" "$ANDROID_LIBS/executorch/x86_64/libvulkan_executorch_backend.so" + +# ---- iOS -------------------------------------------------------------------- +# Note: OpenCV for iOS is provided by CocoaPods (opencv-rne dependency). +# No opencv-ios tarball is needed. + +echo "" +echo "iOS:" + +package_merged "core-ios" \ + "ExecutorchLib.xcframework" "$IOS_DIR/ExecutorchLib.xcframework" \ + "libs/executorch" "$IOS_DIR/libs/executorch" \ + "libs/pthreadpool" "$IOS_DIR/libs/pthreadpool" \ + "libs/cpuinfo" "$IOS_DIR/libs/cpuinfo" + +package_merged "phonemizer-ios" \ + "libs/phonemis" "$IOS_DIR/libs/phonemis" + +package_merged "xnnpack-ios" \ + "XnnpackBackend.xcframework" "$IOS_DIR/XnnpackBackend.xcframework" + +package_merged "coreml-ios" \ + "CoreMLBackend.xcframework" "$IOS_DIR/CoreMLBackend.xcframework" + +# ---- Summary ---------------------------------------------------------------- + +echo "" +echo "Done. Artifacts written to dist-artifacts/:" +ls -lh "$OUT" +echo "" +echo "Upload these files to the GitHub Release for v$VERSION:" +echo " https://github.com/software-mansion/react-native-executorch/releases/tag/v$VERSION" diff --git a/packages/react-native-executorch/src/index.ts b/packages/react-native-executorch/src/index.ts index 3f6b9fc4ce..34fc71a592 100644 --- a/packages/react-native-executorch/src/index.ts +++ b/packages/react-native-executorch/src/index.ts @@ -110,25 +110,21 @@ declare global { } // eslint-disable no-var -if ( - global.loadStyleTransfer == null || - global.loadSemanticSegmentation == null || - global.loadInstanceSegmentation == null || - global.loadTextToImage == null || - global.loadExecutorchModule == null || - global.loadClassification == null || - global.loadObjectDetection == null || - global.loadTokenizerModule == null || - global.loadTextEmbeddings == null || - global.loadImageEmbeddings == null || - global.loadVAD == null || - global.loadLLM == null || - global.loadSpeechToText == null || - global.loadTextToSpeechKokoro == null || - global.loadOCR == null || - global.loadVerticalOCR == null || - global.__rne_isEmulator == null -) { +// Core globals are always installed regardless of which extras are enabled. +// Optional globals (opencv/phonemizer) may be absent if the library was built +// without those features — calling them at that point throws a runtime error +// from the native side with a clear message. +const CORE_GLOBALS = [ + 'loadExecutorchModule', + 'loadTokenizerModule', + 'loadLLM', + 'loadSpeechToText', + 'loadTextEmbeddings', + 'loadVAD', + '__rne_isEmulator', +] as const; + +if (CORE_GLOBALS.some((name) => global[name] == null)) { if (!ETInstallerNativeModule) { throw new Error( `Failed to install react-native-executorch: The native module could not be found.` diff --git a/packages/react-native-executorch/third-party/android/libs/executorch/arm64-v8a/libexecutorch.so b/packages/react-native-executorch/third-party/android/libs/executorch/arm64-v8a/libexecutorch.so old mode 100644 new mode 100755 index 413f42448e..0d69892b7f Binary files a/packages/react-native-executorch/third-party/android/libs/executorch/arm64-v8a/libexecutorch.so and b/packages/react-native-executorch/third-party/android/libs/executorch/arm64-v8a/libexecutorch.so differ diff --git a/packages/react-native-executorch/third-party/android/libs/executorch/x86_64/libexecutorch.so b/packages/react-native-executorch/third-party/android/libs/executorch/x86_64/libexecutorch.so index c96d61aa68..0d48a37e8e 100644 Binary files a/packages/react-native-executorch/third-party/android/libs/executorch/x86_64/libexecutorch.so and b/packages/react-native-executorch/third-party/android/libs/executorch/x86_64/libexecutorch.so differ diff --git a/packages/react-native-executorch/third-party/android/libs/executorch/x86_64/libxnnpack_executorch_backend.so b/packages/react-native-executorch/third-party/android/libs/executorch/x86_64/libxnnpack_executorch_backend.so new file mode 100755 index 0000000000..3f26ba7b30 Binary files /dev/null and b/packages/react-native-executorch/third-party/android/libs/executorch/x86_64/libxnnpack_executorch_backend.so differ diff --git a/packages/react-native-executorch/third-party/include/executorch/extension/module/module.h b/packages/react-native-executorch/third-party/include/executorch/extension/module/module.h index 4a45182bab..278f996ed2 100644 --- a/packages/react-native-executorch/third-party/include/executorch/extension/module/module.h +++ b/packages/react-native-executorch/third-party/include/executorch/extension/module/module.h @@ -29,6 +29,7 @@ using ET_RUNTIME_NAMESPACE::Method; using ET_RUNTIME_NAMESPACE::MethodMeta; using ET_RUNTIME_NAMESPACE::NamedDataMap; using ET_RUNTIME_NAMESPACE::Program; +using runtime::LoadBackendOptionsMap; class ExecuTorchJni; @@ -59,12 +60,28 @@ class Module { * @param[in] file_path The path to the ExecuTorch program file to load. * @param[in] load_mode The loading mode to use. * @param[in] event_tracer A EventTracer used for tracking and logging events. + * @param[in] share_memory_arenas When true, all methods loaded by this Module + * share the same memory-planned buffers for mem_id=1 (activation memory) + * and mem_id=2 (shared mutable buffer memory), sized to the max + * across all methods. mem_id>2 indicates a custom memory plan, and those + * receive fresh memory buffers. share_memory_arenas is required for models + * exported with share_mutable_buffers=true, where methods access shared + * mutable state (e.g., set/get state). When enabled, outputs from one method + * may be invalidated by executing another method, since their output tensors + * can alias the same underlying buffer. Consume or copy outputs before + * calling execute again. NOTE: This class is not thread-safe and performs + * no internal synchronization. Calling execute concurrently on the same + * Module instance from multiple threads is unsafe, regardless of whether + * share_memory_arenas is true or false. When share_memory_arenas is true, + * methods may overwrite each other's data in the shared memory arenas, + * increasing aliasing and the risk of unintended overwrites. */ explicit Module( const std::string &file_path, const LoadMode load_mode = LoadMode::File, std::unique_ptr event_tracer = nullptr, std::unique_ptr memory_allocator = nullptr, - std::unique_ptr temp_allocator = nullptr); + std::unique_ptr temp_allocator = nullptr, + bool share_memory_arenas = false); /** * Constructs an instance by loading a program from a file with specified @@ -74,13 +91,16 @@ class Module { * @param[in] data_map_path The path to a .ptd file. * @param[in] load_mode The loading mode to use. * @param[in] event_tracer A EventTracer used for tracking and logging events. + * @param[in] share_memory_arenas When true, all methods loaded by this Module + * share a single set of memory-planned buffers. */ explicit Module( const std::string &file_path, const std::string &data_map_path, const LoadMode load_mode = LoadMode::File, std::unique_ptr event_tracer = nullptr, std::unique_ptr memory_allocator = nullptr, - std::unique_ptr temp_allocator = nullptr); + std::unique_ptr temp_allocator = nullptr, + bool share_memory_arenas = false); /** * Constructs an instance by loading a program from a file with specified @@ -90,13 +110,16 @@ class Module { * @param[in] data_files The path to one or more .ptd file/s. * @param[in] load_mode The loading mode to use. * @param[in] event_tracer A EventTracer used for tracking and logging events. + * @param[in] share_memory_arenas When true, all methods loaded by this Module + * share a single set of memory-planned buffers. */ explicit Module( const std::string &file_path, std::vector data_files, const LoadMode load_mode = LoadMode::File, std::unique_ptr event_tracer = nullptr, std::unique_ptr memory_allocator = nullptr, - std::unique_ptr temp_allocator = nullptr); + std::unique_ptr temp_allocator = nullptr, + bool share_memory_arenas = false); /** * Constructs an instance with the provided data loader and memory allocator. @@ -107,13 +130,16 @@ class Module { * temporary data during kernel or delegate execution. * @param[in] event_tracer A EventTracer used for tracking and logging events. * @param[in] data_map_loader A DataLoader used for loading external weights. + * @param[in] share_memory_arenas When true, all methods loaded by this Module + * share a single set of memory-planned buffers. */ explicit Module( std::unique_ptr data_loader, std::unique_ptr memory_allocator = nullptr, std::unique_ptr temp_allocator = nullptr, std::unique_ptr event_tracer = nullptr, - std::unique_ptr data_map_loader = nullptr); + std::unique_ptr data_map_loader = nullptr, + bool share_memory_arenas = false); /** * Constructs an instance using an existing shared program. @@ -125,13 +151,16 @@ class Module { * temporary data. * @param[in] event_tracer A EventTracer used for tracking and logging events. * @param[in] data_map_loader A DataLoader used for loading external weights. + * @param[in] share_memory_arenas When true, all methods loaded by this Module + * share a single set of memory-planned buffers. */ explicit Module( std::shared_ptr program, std::unique_ptr memory_allocator = nullptr, std::unique_ptr temp_allocator = nullptr, std::unique_ptr event_tracer = nullptr, - std::unique_ptr data_map_loader = nullptr); + std::unique_ptr data_map_loader = nullptr, + bool share_memory_arenas = false); Module(const Module &) = delete; Module &operator=(const Module &) = delete; @@ -150,6 +179,22 @@ class Module { load(const Program::Verification verification = Program::Verification::Minimal); + /** + * Loads the program with per-delegate runtime options. + * + * @param[in] backend_options A LoadBackendOptionsMap containing per-delegate + * load-time configuration options. The caller must ensure this object + * outlives any methods loaded with these options. + * @param[in] verification The type of verification to do before returning + * success. + * + * @returns An Error to indicate success or failure of the loading process. + */ + ET_NODISCARD virtual runtime::Error + load(const LoadBackendOptionsMap &backend_options, + const Program::Verification verification = + Program::Verification::Minimal); + /** * Checks if the program is loaded. * @@ -200,12 +245,13 @@ class Module { runtime::Error load_method(const std::string &method_name, runtime::HierarchicalAllocator *planned_memory = nullptr, - torch::executor::EventTracer *event_tracer = nullptr); + torch::executor::EventTracer *event_tracer = nullptr, + const LoadBackendOptionsMap *backend_options = nullptr); ET_DEPRECATED ET_NODISCARD runtime::Error inline load_method( const std::string &method_name, torch::executor::EventTracer *event_tracer) { - return load_method(method_name, nullptr, event_tracer); + return load_method(method_name, nullptr, event_tracer, nullptr); } /** @@ -247,13 +293,15 @@ class Module { */ ET_NODISCARD inline runtime::Error load_forward(runtime::HierarchicalAllocator *planned_memory = nullptr, - torch::executor::EventTracer *event_tracer = nullptr) { - return load_method("forward", planned_memory, event_tracer); + torch::executor::EventTracer *event_tracer = nullptr, + const LoadBackendOptionsMap *backend_options = nullptr) { + return load_method("forward", planned_memory, event_tracer, + backend_options); } ET_DEPRECATED ET_NODISCARD inline runtime::Error load_forward(torch::executor::EventTracer *event_tracer) { - return load_forward(nullptr, event_tracer); + return load_forward(nullptr, event_tracer, nullptr); } /** @@ -612,10 +660,22 @@ class Module { } private: - struct MethodHolder { + struct PlannedMemory { std::vector> planned_buffers; std::vector> planned_spans; std::unique_ptr planned_memory; + }; + std::unique_ptr + make_planned_memory(const std::vector &buffer_sizes); + std::unique_ptr make_planned_memory_with_shared_arenas( + const std::vector &buffer_sizes, + std::vector> &shared_arenas); + runtime::Result> + get_mem_planned_buffer_sizes(const std::string &method_name); + runtime::Result> get_max_mem_planned_buffer_sizes(); + + struct MethodHolder { + std::unique_ptr planned_memory; std::unique_ptr memory_manager; std::unique_ptr method; }; @@ -631,7 +691,13 @@ class Module { std::vector> data_map_loaders_; std::vector> named_data_maps_; std::unique_ptr merged_data_map_; + std::vector> shared_arenas_; ET_DEPRECATED std::vector debug_buffer_; + const LoadBackendOptionsMap *backend_options_ = nullptr; + bool share_memory_arenas_; + + ET_NODISCARD runtime::Error + load_internal(const Program::Verification verification); protected: std::unordered_map methods_; diff --git a/packages/react-native-executorch/third-party/include/executorch/runtime/backend/backend_options_map.h b/packages/react-native-executorch/third-party/include/executorch/runtime/backend/backend_options_map.h new file mode 100644 index 0000000000..8ae9543c29 --- /dev/null +++ b/packages/react-native-executorch/third-party/include/executorch/runtime/backend/backend_options_map.h @@ -0,0 +1,185 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +#include + +namespace executorch { +namespace runtime { + +/** + * Maps backend IDs to their load-time options. + * + * This class is used to provide per-delegate configuration at Module::load() + * time. Users can set options for multiple backends, and the runtime will + * route the appropriate options to each backend during initialization. + * + * Example usage: + * @code + * BackendOptions<4> coreml_opts; + * coreml_opts.set_option("compute_unit", "cpu_and_gpu"); + * + * LoadBackendOptionsMap map; + * map.set_options("CoreMLBackend", coreml_opts.view()); + * + * // Later, during backend init: + * auto opts = map.get_options("CoreMLBackend"); + * @endcode + * + * Note: This class does NOT take ownership of the option spans. The caller + * must ensure that the BackendOptions objects outlive the LoadBackendOptionsMap + * and any loaded models that use it. + */ +class LoadBackendOptionsMap final { +public: + /** + * Default constructor - creates an empty map. + */ + LoadBackendOptionsMap() : size_(0) { + for (size_t i = 0; i < kMaxBackends; ++i) { + entries_[i].backend_id[0] = '\0'; + } + } + + /** + * Sets options for a specific backend. + * + * If options for the given backend_id already exist, they will be replaced. + * + * @param backend_id The backend identifier (e.g., "CoreMLBackend", + * "XNNPACKBackend"). Must not be null or empty. + * @param options Span of BackendOption to associate with this backend. + * The span's underlying data must outlive this map and any + * models loaded with it. + * @return Error::Ok on success. + * Error::InvalidArgument if backend_id is null/empty or max backends + * exceeded. + */ + Error set_options(const char *backend_id, Span options) { + if (backend_id == nullptr || backend_id[0] == '\0') { + return Error::InvalidArgument; + } + + return set_options_impl(backend_id, options); + } + + /** + * Sets options from a backend options builder. + * + * This convenience overload accepts any builder type that provides + * backend_id() and view() methods, allowing simpler usage: + * + * @code + * ExampleBackendOptions opts; + * opts.setNumThreads(4).setEnableOptimization(true); + * map.set_options(opts); + * @endcode + * + * @param builder A backend options builder with backend_id() and view() + * methods. + * @return Error::Ok on success, Error::InvalidArgument on failure. + */ + template Error set_options(Builder &builder) { + return set_options_impl(builder.backend_id(), builder.view()); + } + +private: + Error set_options_impl(const char *backend_id, Span options) { + // Check if backend already exists and update it + for (size_t i = 0; i < size_; ++i) { + if (std::strcmp(entries_[i].backend_id, backend_id) == 0) { + entries_[i].options = options; + return Error::Ok; + } + } + + // Add new entry if space available + if (size_ >= kMaxBackends) { + return Error::InvalidArgument; + } + + const size_t id_len = std::strlen(backend_id); + if (id_len >= kMaxBackendIdLength) { + return Error::InvalidArgument; + } + std::memcpy(entries_[size_].backend_id, backend_id, id_len); + entries_[size_].backend_id[id_len] = '\0'; + entries_[size_].options = options; + ++size_; + + return Error::Ok; + } + +public: + /** + * Gets options for a specific backend. + * + * @param backend_id The backend identifier to look up. + * @return Span of options for this backend, or an empty span if the backend + * has no options configured or backend_id is null. + */ + Span get_options(const char *backend_id) const { + if (backend_id == nullptr) { + return Span(nullptr, static_cast(0)); + } + + for (size_t i = 0; i < size_; ++i) { + if (std::strcmp(entries_[i].backend_id, backend_id) == 0) { + return Span(entries_[i].options.data(), + entries_[i].options.size()); + } + } + + return Span(nullptr, static_cast(0)); + } + + /** + * Checks if options have been configured for a specific backend. + * + * @param backend_id The backend identifier to check. + * @return true if options are set for this backend, false otherwise. + */ + bool has_options(const char *backend_id) const { + if (backend_id == nullptr) { + return false; + } + + for (size_t i = 0; i < size_; ++i) { + if (std::strcmp(entries_[i].backend_id, backend_id) == 0) { + return true; + } + } + + return false; + } + + /** + * Returns the number of backends with configured options. + */ + size_t size() const { return size_; } + +private: + static constexpr size_t kMaxBackends = 8; + static constexpr size_t kMaxBackendIdLength = 64; + + struct Entry { + char backend_id[kMaxBackendIdLength]; + Span options; + }; + + Entry entries_[kMaxBackends]; + size_t size_; +}; + +} // namespace runtime +} // namespace executorch diff --git a/packages/react-native-executorch/third-party/include/executorch/runtime/executor/program.h b/packages/react-native-executorch/third-party/include/executorch/runtime/executor/program.h index 56262d309f..59ae346f36 100644 --- a/packages/react-native-executorch/third-party/include/executorch/runtime/executor/program.h +++ b/packages/react-native-executorch/third-party/include/executorch/runtime/executor/program.h @@ -8,10 +8,10 @@ #pragma once -#include #include #include +#include #include #include #include @@ -58,10 +58,15 @@ class Program final { */ Minimal, /** + * When ET_ENABLE_PROGRAM_VERIFICATION is enabled, * Do full verification of the data, ensuring that internal pointers are * self-consistent and that the data has not been truncated or obviously * corrupted. May not catch all types of corruption, but should guard * against illegal memory operations during parsing. + * Also performs additional semantic validation such as: + * - Tensor numel overflow checks (ensuring size calculations don't + * overflow) + * - List element type validation * * Will have higher runtime overhead, scaling with the complexity of the * proram data. @@ -133,13 +138,17 @@ class Program final { * @param[in] event_tracer The event tracer to use for this method run. * @param[in] named_data_map An optional map of {name, blob} used to resolve * data that is external to the PTE, if any. + * @param[in] backend_options An optional map of per-backend load-time options + * (RuntimeSpecs). Each backend will receive its corresponding options + * during initialization. * * @returns The loaded method on success, or an error on failure. */ Result load_method(const char *method_name, MemoryManager *memory_manager, EventTracer *event_tracer = nullptr, - const NamedDataMap *named_data_map = nullptr) const; + const NamedDataMap *named_data_map = nullptr, + const LoadBackendOptionsMap *backend_options = nullptr) const; /** * Gathers metadata for the named method. diff --git a/packages/react-native-executorch/third-party/ios/CoreMLBackend.xcframework/Info.plist b/packages/react-native-executorch/third-party/ios/CoreMLBackend.xcframework/Info.plist new file mode 100644 index 0000000000..2308ef14c2 --- /dev/null +++ b/packages/react-native-executorch/third-party/ios/CoreMLBackend.xcframework/Info.plist @@ -0,0 +1,43 @@ + + + + + AvailableLibraries + + + BinaryPath + libCoreMLBackend.a + LibraryIdentifier + ios-arm64 + LibraryPath + libCoreMLBackend.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + BinaryPath + libCoreMLBackend.a + LibraryIdentifier + ios-arm64-simulator + LibraryPath + libCoreMLBackend.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/packages/react-native-executorch/third-party/ios/CoreMLBackend.xcframework/ios-arm64-simulator/libCoreMLBackend.a b/packages/react-native-executorch/third-party/ios/CoreMLBackend.xcframework/ios-arm64-simulator/libCoreMLBackend.a new file mode 100644 index 0000000000..7a76b4e1e8 Binary files /dev/null and b/packages/react-native-executorch/third-party/ios/CoreMLBackend.xcframework/ios-arm64-simulator/libCoreMLBackend.a differ diff --git a/packages/react-native-executorch/third-party/ios/CoreMLBackend.xcframework/ios-arm64/libCoreMLBackend.a b/packages/react-native-executorch/third-party/ios/CoreMLBackend.xcframework/ios-arm64/libCoreMLBackend.a new file mode 100644 index 0000000000..78c51f62a3 Binary files /dev/null and b/packages/react-native-executorch/third-party/ios/CoreMLBackend.xcframework/ios-arm64/libCoreMLBackend.a differ diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/ExecutorchLib b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/ExecutorchLib index 719ff9d4eb..b64f917f74 100755 Binary files a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/ExecutorchLib and b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/ExecutorchLib differ diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/Info.plist b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/Info.plist index bd0373672c..5cfe659411 100644 Binary files a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/Info.plist and b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/Info.plist differ diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/ExecutorchLib b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/ExecutorchLib index 6d77e0cfe8..e2738d0335 100755 Binary files a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/ExecutorchLib and b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/ExecutorchLib differ diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/Info.plist b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/Info.plist index 2372838d49..00b2e6fed7 100644 Binary files a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/Info.plist and b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/Info.plist differ diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib/ExecutorchLib.xcodeproj/project.pbxproj b/packages/react-native-executorch/third-party/ios/ExecutorchLib/ExecutorchLib.xcodeproj/project.pbxproj index a4d139dcaa..a7c0ce85bc 100644 --- a/packages/react-native-executorch/third-party/ios/ExecutorchLib/ExecutorchLib.xcodeproj/project.pbxproj +++ b/packages/react-native-executorch/third-party/ios/ExecutorchLib/ExecutorchLib.xcodeproj/project.pbxproj @@ -334,13 +334,10 @@ "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_llm_ios.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_torchao_ios.a", + "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkleidiai_ios.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_quantized_ios.a", "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_xnnpack_ios.a", - "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_coreml_ios.a", - "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_mps_ios.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libexecutorch_ios.a", @@ -357,13 +354,10 @@ "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_llm_simulator.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_torchao_simulator.a", + "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkleidiai_simulator.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_quantized_simulator.a", "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_xnnpack_simulator.a", - "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_coreml_simulator.a", - "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_mps_simulator.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libexecutorch_simulator.a", @@ -426,13 +420,10 @@ "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_llm_ios.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_torchao_ios.a", + "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkleidiai_ios.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_quantized_ios.a", "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_xnnpack_ios.a", - "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_coreml_ios.a", - "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_mps_ios.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libexecutorch_ios.a", @@ -449,13 +440,10 @@ "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_llm_simulator.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_torchao_simulator.a", + "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkleidiai_simulator.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_quantized_simulator.a", "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_xnnpack_simulator.a", - "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_coreml_simulator.a", - "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_mps_simulator.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libexecutorch_simulator.a", diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib/build.sh b/packages/react-native-executorch/third-party/ios/ExecutorchLib/build.sh index 0a410e237d..bc4191f5f1 100755 --- a/packages/react-native-executorch/third-party/ios/ExecutorchLib/build.sh +++ b/packages/react-native-executorch/third-party/ios/ExecutorchLib/build.sh @@ -1,21 +1,30 @@ #!/bin/bash -# Builds ExecutorchLib.xcframework for iOS and iOS Simulator +# Builds ExecutorchLib.xcframework for iOS and iOS Simulator, plus separate +# static xcframeworks for optional backends (xnnpack, coreml). # # This script: # 1. Cleans previous builds # 2. Archives the framework for iOS device (arm64) # 3. Archives the framework for iOS Simulator (arm64) # 4. Combines both archives into a single .xcframework +# 5. Creates XnnpackBackend.xcframework from the backend .a files +# 6. Creates CoreMLBackend.xcframework from the backend .a files # -# Output: ./output/ExecutorchLib.xcframework +# Output: +# ./output/ExecutorchLib.xcframework +# ./output/XnnpackBackend.xcframework +# ./output/CoreMLBackend.xcframework # # Usage: ./build.sh +set -euo pipefail + # --- Configuration --- -PROJECT_NAME="ExecutorchLib" # Replace with your Xcode project name -SCHEME_NAME="ExecutorchLib" # Replace with your scheme name -OUTPUT_FOLDER="output" # Choose your desired output folder +PROJECT_NAME="ExecutorchLib" +SCHEME_NAME="ExecutorchLib" +OUTPUT_FOLDER="output" +LIBS_DIR="$(pwd)/../../../third-party/ios/libs/executorch" # --- Derived Variables --- BUILD_FOLDER="build" @@ -27,6 +36,7 @@ XCFRAMEWORK_PATH="$OUTPUT_FOLDER/$XCFRAMEWORK_NAME" # --- Script --- rm -rf "$BUILD_FOLDER" "$OUTPUT_FOLDER" +mkdir -p "$OUTPUT_FOLDER" xcodebuild clean -project "$PROJECT_NAME.xcodeproj" -scheme "$SCHEME_NAME" @@ -54,3 +64,28 @@ xcodebuild -create-xcframework \ -framework "$ARCHIVE_PATH_IOS.xcarchive/Products/Library/Frameworks/$FRAMEWORK_NAME" \ -framework "$ARCHIVE_PATH_SIMULATOR.xcarchive/Products/Library/Frameworks/$FRAMEWORK_NAME" \ -output "$XCFRAMEWORK_PATH" + +# --- Build XnnpackBackend.xcframework --- +# CocoaPods requires matching binary names across slices +STAGING=$(mktemp -d) +mkdir -p "$STAGING/ios" "$STAGING/sim" +cp "$LIBS_DIR/libbackend_xnnpack_ios.a" "$STAGING/ios/libXnnpackBackend.a" +cp "$LIBS_DIR/libbackend_xnnpack_simulator.a" "$STAGING/sim/libXnnpackBackend.a" +xcodebuild -create-xcframework \ + -library "$STAGING/ios/libXnnpackBackend.a" \ + -library "$STAGING/sim/libXnnpackBackend.a" \ + -output "$OUTPUT_FOLDER/XnnpackBackend.xcframework" + +# --- Build CoreMLBackend.xcframework --- +cp "$LIBS_DIR/libbackend_coreml_ios.a" "$STAGING/ios/libCoreMLBackend.a" +cp "$LIBS_DIR/libbackend_coreml_simulator.a" "$STAGING/sim/libCoreMLBackend.a" +xcodebuild -create-xcframework \ + -library "$STAGING/ios/libCoreMLBackend.a" \ + -library "$STAGING/sim/libCoreMLBackend.a" \ + -output "$OUTPUT_FOLDER/CoreMLBackend.xcframework" +rm -rf "$STAGING" + +echo "Done! Output:" +echo " $OUTPUT_FOLDER/ExecutorchLib.xcframework" +echo " $OUTPUT_FOLDER/XnnpackBackend.xcframework" +echo " $OUTPUT_FOLDER/CoreMLBackend.xcframework" diff --git a/packages/react-native-executorch/third-party/ios/XnnpackBackend.xcframework/Info.plist b/packages/react-native-executorch/third-party/ios/XnnpackBackend.xcframework/Info.plist new file mode 100644 index 0000000000..65a82b47a3 --- /dev/null +++ b/packages/react-native-executorch/third-party/ios/XnnpackBackend.xcframework/Info.plist @@ -0,0 +1,43 @@ + + + + + AvailableLibraries + + + BinaryPath + libXnnpackBackend.a + LibraryIdentifier + ios-arm64-simulator + LibraryPath + libXnnpackBackend.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + BinaryPath + libXnnpackBackend.a + LibraryIdentifier + ios-arm64 + LibraryPath + libXnnpackBackend.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/packages/react-native-executorch/third-party/ios/XnnpackBackend.xcframework/ios-arm64-simulator/libXnnpackBackend.a b/packages/react-native-executorch/third-party/ios/XnnpackBackend.xcframework/ios-arm64-simulator/libXnnpackBackend.a new file mode 100644 index 0000000000..7e1645cc84 Binary files /dev/null and b/packages/react-native-executorch/third-party/ios/XnnpackBackend.xcframework/ios-arm64-simulator/libXnnpackBackend.a differ diff --git a/packages/react-native-executorch/third-party/ios/XnnpackBackend.xcframework/ios-arm64/libXnnpackBackend.a b/packages/react-native-executorch/third-party/ios/XnnpackBackend.xcframework/ios-arm64/libXnnpackBackend.a new file mode 100644 index 0000000000..002d402482 Binary files /dev/null and b/packages/react-native-executorch/third-party/ios/XnnpackBackend.xcframework/ios-arm64/libXnnpackBackend.a differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_ios.a index a7370915ab..456edfc637 100644 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_ios.a and b/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_ios.a differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_ios.a index b7b10d5c18..0bb6f5e5fd 100644 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_ios.a and b/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_ios.a differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_simulator.a index 99d30b8aae..c7504e7723 100644 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_simulator.a and b/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_simulator.a differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_simulator.a index b7059624ea..68f8f09d9a 100644 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_simulator.a and b/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_simulator.a differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libkleidiai_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libkleidiai_ios.a new file mode 100644 index 0000000000..0bc8d26486 Binary files /dev/null and b/packages/react-native-executorch/third-party/ios/libs/executorch/libkleidiai_ios.a differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libkleidiai_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libkleidiai_simulator.a new file mode 100644 index 0000000000..6d4467856b Binary files /dev/null and b/packages/react-native-executorch/third-party/ios/libs/executorch/libkleidiai_simulator.a differ diff --git a/packages/react-native-executorch/third-party/ios/libs/pthreadpool/physical-arm64-release/libpthreadpool.a b/packages/react-native-executorch/third-party/ios/libs/pthreadpool/physical-arm64-release/libpthreadpool.a index 652afff7bf..ae5bb40bc4 100644 Binary files a/packages/react-native-executorch/third-party/ios/libs/pthreadpool/physical-arm64-release/libpthreadpool.a and b/packages/react-native-executorch/third-party/ios/libs/pthreadpool/physical-arm64-release/libpthreadpool.a differ diff --git a/packages/react-native-executorch/third-party/ios/libs/pthreadpool/simulator-arm64-debug/libpthreadpool.a b/packages/react-native-executorch/third-party/ios/libs/pthreadpool/simulator-arm64-debug/libpthreadpool.a index e3c3053a15..727ade6eb8 100644 Binary files a/packages/react-native-executorch/third-party/ios/libs/pthreadpool/simulator-arm64-debug/libpthreadpool.a and b/packages/react-native-executorch/third-party/ios/libs/pthreadpool/simulator-arm64-debug/libpthreadpool.a differ