Skip to content

feat: on-demand native lib download and optional feature splitting#1039

Open
msluszniak wants to merge 10 commits into
mainfrom
feat/on-demand-native-libs
Open

feat: on-demand native lib download and optional feature splitting#1039
msluszniak wants to merge 10 commits into
mainfrom
feat/on-demand-native-libs

Conversation

@msluszniak
Copy link
Copy Markdown
Member

@msluszniak msluszniak commented Mar 31, 2026

Description

Removes prebuilt native binaries from the npm tarball and downloads them at postinstall from GitHub Releases. Apps declare exactly what they need under react-native-executorch in package.json — three optional arrays, all merged into a single set:

{
  "react-native-executorch": {
    "backends": ["xnnpack", "coreml", "vulkan"],
    "libs":     ["opencv", "phonemizer"],
    "features": ["llm", "textToSpeech", "objectDetection"]
  }
}
  • features is the friendly opt-in: list the use* hooks you'll use and the postinstall expands each one to its required backends + libs (per FEATURE_MAP).
  • backends / libs are the precise opt-in.
  • Omitting the block enables everything (largest install, lowest friction).
  • Mixing the three arrays unions their effects.

The 18 features track the documented use* hooks one-for-one; each row's backend list is the union of what at least one model in that family ships today (sourced from src/constants/modelRegistry.ts).

Component iOS Android
xnnpack backend XnnpackBackend.xcframework force-loaded separately-loaded libxnnpack_executorch_backend.so
coreml backend CoreMLBackend.xcframework force-loaded n/a
vulkan backend n/a separately-loaded libvulkan_executorch_backend.so
opencv lib via opencv-rne CocoaPod static libopencv_*.a + KleidiCV HAL (arm64)
phonemizer lib compiled from in-tree source (third-party/common/phonemis submodule) same submodule via CMake add_subdirectory

Each Android backend .so links only <backend>_backend (--whole-archive) + <backend>_schema + executorch_core — no CPU kernel registries — so loading multiple side-by-side does not trigger duplicate kernel registration. Supporting executorch fork branch: software-mansion-labs/executorch@ms/separate-backends (EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED + EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED switches, a custom_ops fix to stop the transitive XNNPACK link from leaking into libexecutorch_jni.so, and a flatcc -Werror workaround for Apple clang 21).

Demo apps in this PR declare their actual feature set:

  • bare-rn["llm"]
  • llm["llm", "multimodalLLM", "privacyFilter"]
  • speech["llm", "speechToText", "textToSpeech", "vad"]
  • text-embeddings["textEmbeddings", "imageEmbeddings"]
  • computer-vision["classification", "imageEmbeddings", "instanceSegmentation", "ocr", "objectDetection", "poseEstimation", "semanticSegmentation", "styleTransfer", "textEmbeddings", "textToImage", "verticalOCR"]

Introduces a breaking change?

  • Yes
  • No

Removes the legacy extras field. Apps that set it get an install-time error pointing at the new shape.

Type of change

  • New feature (change which adds functionality)

Tested on

  • iOS (physical iPhone SE 3rd gen / Xcode 26.4.1)
  • Android (Galaxy S26 Ultra)

Testing instructions

Test the download flow:

cd packages/react-native-executorch
rm -rf third-party/android/libs third-party/ios/ExecutorchLib.xcframework third-party/ios/libs
rm -rf ~/.cache/react-native-executorch/0.9.0
RNET_BASE_URL=https://github.com/software-mansion/react-native-executorch/releases/download/v0.9.0-libs-test \
  INIT_CWD=<app-root> \
  node scripts/download-libs.js

Test feature opt-in (drop opencv / coreml by listing only LLM-shaped features):

  1. In the app's package.json set "features": ["llm"].
  2. yarn installrne-build-config.json should show enableOpencv:false, enableCoreml:false, enableXnnpack:true.
  3. Clean prebuild + run the app. LLM inference works; loading a CoreML-only model now produces a Backend CoreMLBackend is not registered error rather than crashing.

Test phonemizer opt-out:

  1. In the app's package.json set "features": ["textEmbeddings"] (no TTS).
  2. yarn install (regenerates rne-build-config.json), then pod install / regenerate android/.
  3. The phonemis submodule is not entered by either CMake or the podspec — TTS sources are excluded from compilation.

Test that legacy extras is rejected:

  1. In any app's package.json set "extras": ["opencv"].
  2. yarn install fails with an explicit migration error pointing at backends, libs, features.

Related issues

Builds toward pytorch/executorch#10457 (hot-pluggable Android backends).

Checklist

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the documentation accordingly
  • My changes generate no new warnings

@msluszniak msluszniak self-assigned this Mar 31, 2026
@msluszniak msluszniak added refactoring feature PRs that implement a new feature labels Mar 31, 2026
@msluszniak msluszniak marked this pull request as draft March 31, 2026 20:10
@msluszniak msluszniak marked this pull request as draft March 31, 2026 20:10
@msluszniak
Copy link
Copy Markdown
Member Author

TODO: separate xnnpack and coreml backends to separate libs, so they can be opt-out the same way as opencv etc.

@msluszniak
Copy link
Copy Markdown
Member Author

Adding support for vulkan is in progress

@msluszniak msluszniak force-pushed the feat/on-demand-native-libs branch 3 times, most recently from e69584e to 5aa72cc Compare May 8, 2026 09:29
@msluszniak
Copy link
Copy Markdown
Member Author

msluszniak commented May 8, 2026

Status — verified working state

PR is in a tested, working state with a clear opt-in/opt-out model for every backend except XNNPACK on Android (which stays baked into libexecutorch.so).

Backend iOS Android
XNNPACK separable (XnnpackBackend.xcframework, force-loaded when xnnpack extra is on) always on — baked into libexecutorch.so; xnnpack extra has no effect on Android (postinstall warns)
CoreML separable (CoreMLBackend.xcframework, force-loaded when coreml extra is on) n/a
Vulkan n/a separable (libvulkan_executorch_backend.so, separately loaded when vulkan extra is on)

Verified on a Galaxy S26 Ultra and an iPhone 17 Pro simulator (Xcode 26.4.1):

  • Android XNNPACK YOLO inference: works.
  • Android Vulkan opt-in (YOLO26N exported with Vulkan delegate): works — libvulkan_executorch_backend.so packaged into the APK and loaded as a linker dependency of libreact-native-executorch.so.
  • Android Vulkan opt-out: .so absent from APK, runtime emits E ExecuTorch: Backend VulkanBackend is not registered. and the app stays alive (no crash).
  • iOS XNNPACK opt-out: dylib link drops -force_load XnnpackBackend.xcframework, app dylib has 0 XNNPACK symbols / 11 CoreML symbols, XNNPACK model fails gracefully without crashing.

Supporting executorch fork branch: msluszniak/executorch@ms/separate-backends (EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED switch + Apple-clang-21 flatcc workaround).

Next work

Make XNNPACK separable on Android too — apply the same QNN-style EXECUTORCH_BUILD_*_BACKEND_SHARED pattern from the Vulkan commit to xnnpack_backend, link the resulting libxnnpack_executorch_backend.so only against xnnpack_backend + executorch_core + the XNNPACK third-party libs, and audit the link line to make sure no kernel registration libs (optimized_native_cpu_ops_lib, custom_ops, quantized_ops_lib, register_prim_ops) leak in. Once that holds, the xnnpack extra becomes meaningful on both platforms and the post-install Android warning can go away. Tracking towards pytorch/executorch#10457.

@msluszniak msluszniak force-pushed the feat/on-demand-native-libs branch from 5aa72cc to eb14dbe Compare May 8, 2026 10:34
@msluszniak
Copy link
Copy Markdown
Member Author

msluszniak commented May 8, 2026

Update — XNNPACK on Android is now separable too

Every backend is now opt-in symmetrically across both platforms:

Backend iOS Android
XNNPACK XnnpackBackend.xcframework (force-loaded when xnnpack extra is on) libxnnpack_executorch_backend.so (separately loaded when xnnpack extra is on)
CoreML CoreMLBackend.xcframework (force-loaded when coreml extra is on) n/a
Vulkan n/a libvulkan_executorch_backend.so (separately loaded when vulkan extra is on)

Two changes on the executorch fork (msluszniak/executorch@ms/separate-backends):

  1. New EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED switch mirrors the Vulkan one. When ON, xnnpack_backend is no longer whole-archive-linked into libexecutorch_jni.so; instead libxnnpack_executorch_backend.so is built linking only xnnpack_backend (--whole-archive) + the XNNPACK third-party libs (XNNPACK, pthreadpool, cpuinfo, xnnpack-microkernels-prod, kleidiai) + executorch_core + log. No CPU-kernel-registration archives leak in.
  2. extension/llm/custom_ops/CMakeLists.txt drops the (transitive) PUBLIC link to xnnpack_backend when the new switch is ON. custom_ops doesn't actually call into XNNPACK in its sources — that link was purely transitive — but combined with WHOLE_ARCHIVE custom_ops in extension/android/CMakeLists.txt it was dragging XNNCompiler / XNNExecutor into libexecutorch_jni.so and would have caused the duplicate-registration crash when both shared libs loaded.

Verified on the same Galaxy S26 Ultra:

  • libxnnpack_executorch_backend.so (~2.5 MB stripped) — only _GLOBAL__sub_I_XNNPACKBackend.cpp. No RegisterCodegen*, op_sdpa, register_prim_ops, regex_lookahead static initializers leaked in. 0 aten::* strings.
  • libexecutorch.so shrank from 13 MB → 11.6 MB (XNNPACK no longer baked).
  • Extras [xnnpack, vulkan] enabled: both backends register, both YOLO XNNPACK and YOLO26N Vulkan run. No RegistrationAlreadyRegistered (0x16). Confirms the duplicate-kernel issue we were avoiding by keeping XNNPACK baked is fully gone.
  • Extras [vulkan] only (XNNPACK off): APK contains no libxnnpack_executorch_backend.so. Loading any XNNPACK-quantized .pte produces E ExecuTorch: Backend XnnpackBackend is not registered. and the app stays alive — clean failure mode, identical to the iOS XNNPACK opt-out path. Vulkan still works in the same build.

The download-libs.js platform-asymmetry warning for the xnnpack extra is removed. xnnpack-android-* tarballs are produced by package-release-artifacts.sh. Docs updated.

This finishes the work tracked above. PR is ready for review.

The npm tarball ships without prebuilt native binaries — they are
downloaded from GitHub Releases at postinstall and extracted into
third-party/, where the existing CMake / podspec configurations pick them
up unchanged. Apps can opt out of features they don't need to skip both
the download and the native compilation:

    "react-native-executorch": {
      "extras": ["opencv", "phonemizer", "xnnpack", "coreml", "vulkan"]
    }

Defaults to all enabled. Each extra trims one or more artifacts and toggles
a corresponding RNE_ENABLE_* CMake / podspec flag, dropping its sources from
compilation and its libraries from the final link.

Per-platform behavior:
- opencv      Android + iOS (iOS provided via opencv-rne CocoaPod)
- phonemizer  Android + iOS
- xnnpack     iOS-only as a force-loaded XnnpackBackend.xcframework;
              baked into libexecutorch.so on Android
- coreml      iOS-only as a force-loaded CoreMLBackend.xcframework
- vulkan      Android-only as a separately-loaded
              libvulkan_executorch_backend.so

Vulkan ships as its own shared library (mirroring the QNN backend pattern)
so its load-time backend registration runs only when the user opts in. The
.so links only against vulkan_backend + vulkan_schema + executorch_core,
not the CPU kernel registries, so it does not cause duplicate kernel
registration when loaded alongside libexecutorch.so.
@msluszniak msluszniak force-pushed the feat/on-demand-native-libs branch from eb14dbe to ef7ab0e Compare May 8, 2026 11:01
Bring NATIVE_LIBS_PIPELINE.md in sync with the current
msluszniak/executorch@ms/separate-backends tip:

- Pin SHA bumped from 1a5c0f267 to bd24ac7681.
- Patch list expanded to cover all 10 commits on the fork branch:
  the original 4 (version-script removal, vulkan-shared, flatcc-Werror,
  xnnpack-shared) plus the 6 added in this round (tokenizers submodule
  switched to software-mansion-labs/pytorch-tokenizers@build,
  build_android_library.sh forwards BACKEND_SHARED env vars to cmake,
  XNNWeightsCache null-ptr fix, ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES,
  iOS create_frameworks.sh keeps merged .a files, iOS CMakePresets
  disable XNNPACK_ENABLE_ARM_SME{,2}).
- iOS build section rewritten as a three-stage flow (fork .a build →
  stage into RNE → repackage via ExecutorchLib/build.sh).
@kirklandsign
Copy link
Copy Markdown

Thanks @msluszniak . Mind working on small stack of PRs so it's easier to review?

@msluszniak
Copy link
Copy Markdown
Member Author

@kirklandsign this particular PR solely address React Native ExecuTorch repo, but what is probably instresting for you is a not-yet-published PR from https://github.com/msluszniak/executorch/tree/@ms/separate-backends branch to executorch. For this one, for sure I can proceed with a series of small PRs :)). I just want to make sure changes are aligned with RNE repo first. I just need to check if iOS works the same way as Android.

@msluszniak msluszniak marked this pull request as ready for review May 11, 2026 12:09
@msluszniak msluszniak marked this pull request as draft May 11, 2026 12:10
msluszniak and others added 8 commits May 11, 2026 18:46
…steps

The fork branch grew an 11th patch on top of bd24ac7681 to address the
flatccrt build failure on Apple clang 21 inside the iOS / macOS presets
(separate from the host-side flatcc_ep fix). Bump the pinned SHA to
3ce953dbde73035e733442f99c082f5b6fedff5b and add the patch to the list.

Rewrite the iOS build section so a fresh machine can reproduce the
artifacts exactly:

- Drop the install_executorch.sh step: torch_pin.py points at a
  pruned nightly. Pin torch==2.11.0 directly + install requirements-dev
  + certifi/zstd (Buck2 resolver) instead.
- Switch from a bare 'create_frameworks.sh' invocation to
  'build_apple_frameworks.sh --Release', which orchestrates the cmake
  builds and calls create_frameworks.sh with the required flags.
- Document the harmless 'no binary artifact for *_debug.xcframework'
  Swift Package error at the end of --Release runs.
- Spell out the exact per-slice .a file list to stage into RNE.
- Call out that libkleidiai_*.a and the non-executorch prebuilts
  (cpuinfo / pthreadpool / phonemis) are kept as-is, not rebuilt.
- Add the iOS 26.4 simulator NSURLSession regression as a callout so
  future maintainers don't lose another day chasing it.
…-libs

# Conflicts:
#	packages/react-native-executorch/android/src/main/cpp/CMakeLists.txt
#	packages/react-native-executorch/react-native-executorch.podspec
#	packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/Info.plist
#	packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/Info.plist
- Three optional arrays in package.json: `backends`, `libs`, `features` (all merged).
- `features` is sugar: each one expands to (backends, libs) via FEATURE_MAP
  matching the documented use* hooks (16 features, e.g. `llm`, `textToSpeech`,
  `objectDetection`).
- Backends and libs are validated; unknown values throw with a list of supported ones.
- Legacy `extras` is rejected with a migration error.
- Defaults stay all-on when no config is given.
- ModelHostObject.h: gate TextToSpeech include + Kokoro if-constexpr block with
  RNE_ENABLE_PHONEMIZER — unblocks builds when the phonemizer lib is disabled.
…ps' features

- FEATURE_MAP rebuilt from src/constants/modelRegistry.ts. Removed guessed
  backends. LLMs / TTS / VAD / OCR / pose / sem-seg / textEmbeddings / textToImage
  are xnnpack-only; classification / objectDetection / instanceSegmentation /
  styleTransfer / speechToText / segmentAnything also ship coreml.
  No model family currently ships a vulkan variant.
- Added privacyFilter feature.
- Demo apps now declare their actual feature set:
  - bare-rn:         [llm]
  - llm:             [llm, multimodalLLM, privacyFilter]
  - speech:          [llm, speechToText, textToSpeech, vad]
  - text-embeddings: [textEmbeddings, imageEmbeddings]
  - computer-vision: [classification, imageEmbeddings, instanceSegmentation,
                      ocr, objectDetection, poseEstimation, semanticSegmentation,
                      styleTransfer, textEmbeddings, textToImage, verticalOCR]
@msluszniak msluszniak marked this pull request as ready for review May 27, 2026 15:58
@msluszniak
Copy link
Copy Markdown
Member Author

PR is ready for review. Right now, I'm testing all demo apps on both iOS and Android.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature PRs that implement a new feature refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Modularization based on backends/usecase(llm/cv) Try to dynamically fetch prebuilt libs instead of packaging them to RNET

2 participants