Skip to content

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

Draft
msluszniak wants to merge 2 commits intomainfrom
feat/on-demand-native-libs
Draft

feat: on-demand native lib download and optional feature splitting#1039
msluszniak wants to merge 2 commits intomainfrom
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 instead. Apps can opt out of features they don't need, skipping both the download and the native compilation. Each backend (XNNPACK, CoreML, Vulkan) ships as its own opt-in artifact with no platform asymmetry — every flag is meaningful where the backend exists.

User configuration in the app's package.json:

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

All extras default on; omit any to skip its artifact and disable its compilation. Each toggle drives a RNE_ENABLE_* flag through the podspec or build.gradle.

Extra iOS Android Description
opencv via opencv-rne CocoaPod static libopencv_*.a + KleidiCV HAL Computer-vision models
phonemizer force-loaded libphonemis.a static libphonemis.a Text-to-speech (Kokoro)
xnnpack force-loaded XnnpackBackend.xcframework separately-loaded libxnnpack_executorch_backend.so XNNPACK CPU delegate
coreml force-loaded CoreMLBackend.xcframework n/a Core ML delegate
vulkan n/a separately-loaded libvulkan_executorch_backend.so Vulkan GPU delegate

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: msluszniak/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).

Introduces a breaking change?

  • Yes
  • No

Type of change

  • New feature (change which adds functionality)

Tested on

  • iOS (simulator: iPhone 17 Pro / Xcode 26.4.1)
  • Android (Galaxy S24 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 extras splitting (drop XNNPACK; works the same on iOS and Android):

  1. In the app's package.json set "extras": ["opencv", "phonemizer", "coreml", "vulkan"]
  2. RNET_SKIP_DOWNLOAD=1 INIT_CWD=<app-root> node packages/react-native-executorch/scripts/download-libs.js
  3. Clean prebuild + run the app.
  4. Loading any XNNPACK-quantized .pte produces E ExecuTorch: Backend XnnpackBackend is not registered. — app stays alive.
  5. Switching back to "extras": ["opencv", "phonemizer", "xnnpack", "coreml", "vulkan"] restores XNNPACK inference.

Test Vulkan opt-in (Android-only):

  1. Add "vulkan" to extras, regenerate rne-build-config.json, rebuild.
  2. Run a Vulkan-exported model (e.g. yolo26n_vulkan_fp32_multi.pte) on a GPU-capable device — backend registers and inference runs.

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.

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.

2 participants