diff --git a/packages/react-native-executorch/package.json b/packages/react-native-executorch/package.json index f8291c8639..4d6493fdfa 100644 --- a/packages/react-native-executorch/package.json +++ b/packages/react-native-executorch/package.json @@ -124,6 +124,7 @@ "@huggingface/jinja": "^0.5.0", "jsonrepair": "^3.12.0", "jsonschema": "^1.5.0", + "react-native-device-info": "^15.0.2", "zod": "^4.3.6" } } diff --git a/packages/react-native-executorch/src/constants/modelRegistry.ts b/packages/react-native-executorch/src/constants/modelRegistry.ts index c3cda78498..ed44fa3a68 100644 --- a/packages/react-native-executorch/src/constants/modelRegistry.ts +++ b/packages/react-native-executorch/src/constants/modelRegistry.ts @@ -1,4 +1,5 @@ import { Platform } from 'react-native'; +import { isEmulatorSync } from 'react-native-device-info'; import * as M from './modelUrls'; import * as OCR from './ocr/models'; import { symbols } from './ocr/symbols'; @@ -78,19 +79,32 @@ type ConfigOf = Extract< >; type BackendsOf = Extract; -const BACKEND_ORDER: Backend[] = ['xnnpack', 'coreml', 'mlx', 'vulkan', 'qnn']; +const PLATFORM_PREFERENCE: Partial> = { + ios: ['coreml', 'mlx', 'xnnpack'], + android: ['vulkan', 'qnn', 'xnnpack'], +}; -function firstBackend(variants: AnyVariantMap): Backend { - for (const b of BACKEND_ORDER) { - if (variants[b]) return b; +function applySimulatorPolicy( + backend: Backend, + variants: AnyVariantMap, + explicit: boolean +): Backend { + if (backend !== 'mlx' || Platform.OS !== 'ios' || !isEmulatorSync()) { + return backend; } + if (!explicit && variants.xnnpack) return 'xnnpack'; throw new RnExecutorchError( - RnExecutorchErrorCode.Internal, - 'Model variant map is empty.' + RnExecutorchErrorCode.InvalidConfig, + 'The MLX backend requires a physical iOS device and cannot run on the ' + + 'simulator.' + + (variants.xnnpack + ? " Pass `{ backend: 'xnnpack' }` (or omit `backend`) to run on the " + + 'simulator.' + : ' This model ships no simulator-compatible backend.') ); } -function resolveBackend( +function selectBackend( variants: AnyVariantMap, platformDefaults: PlatformDefaults | undefined, requested: Backend | undefined @@ -99,17 +113,32 @@ function resolveBackend( if (platformDefaults) { if (Platform.OS === 'ios' && platformDefaults.ios) return platformDefaults.ios; - if (Platform.OS === 'android' && platformDefaults.android) { + if (Platform.OS === 'android' && platformDefaults.android) return platformDefaults.android; - } if (platformDefaults.default) return platformDefaults.default; } - // Implicit platform default: prefer CoreML on iOS, XNNPACK on Android - // whenever the model ships that backend. Models can override via - // `platformDefaults`. - if (Platform.OS === 'ios' && variants.coreml) return 'coreml'; - if (Platform.OS === 'android' && variants.xnnpack) return 'xnnpack'; - return firstBackend(variants); + // Implicit platform default: walk the platform's preference order (iOS: + // coreml → mlx → xnnpack, Android: vulkan → qnn → xnnpack) and take the + // first backend the model ships. Models can override via `platformDefaults`. + const preference = PLATFORM_PREFERENCE[Platform.OS] ?? []; + for (const b of preference) { + if (variants[b]) return b; + } + // No backend the model ships can run on this platform. + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidConfig, + `This model ships no backend compatible with ${Platform.OS}.` + ); +} + +function resolveBackend( + variants: AnyVariantMap, + platformDefaults: PlatformDefaults | undefined, + requested: Backend | undefined +): Backend { + const explicit = requested !== undefined; + const backend = selectBackend(variants, platformDefaults, requested); + return applySimulatorPolicy(backend, variants, explicit); } function resolveCell(cell: BackendCell, quant: boolean): { modelName: string } { @@ -208,6 +237,29 @@ const GEMMA4_E2B_VARIANTS = { }, }; +const GEMMA4_E2B_MM_CONFIG = { + modelName: 'gemma4-e2b-multimodal' as const, + tokenizerSource: M.GEMMA4_E2B_TOKENIZER, + tokenizerConfigSource: M.GEMMA4_E2B_TOKENIZER_CONFIG, + capabilities: ['vision', 'audio'] as const, + audioConfig: { + samplesPerBlock: 7680, + tokensPerBlock: 12, + }, +}; + +const GEMMA4_E2B_MM_VARIANTS = { + mlx: { + base: { ...GEMMA4_E2B_MM_CONFIG, modelSource: M.GEMMA4_E2B_MLX_MM }, + }, + vulkan: { + base: { ...GEMMA4_E2B_MM_CONFIG, modelSource: M.GEMMA4_E2B_VULKAN_MM }, + }, + xnnpack: { + base: { ...GEMMA4_E2B_MM_CONFIG, modelSource: M.GEMMA4_E2B_XNNPACK_MM }, + }, +}; + const EFFICIENTNET_V2_S_VARIANTS = { xnnpack: { base: { @@ -531,7 +583,10 @@ export const models = { // pick a model by capability ("LLM") rather than by modality. lfm2_5_vl_1_6b: base(M.LFM2_5_VL_1_6B_QUANTIZED), lfm2_5_vl_450m: base(M.LFM2_5_VL_450M_QUANTIZED), - gemma4_e2b_multimodal: base(M.GEMMA4_E2B_MM), + gemma4_e2b_multimodal: variant(GEMMA4_E2B_MM_VARIANTS, { + ios: 'mlx', + android: 'vulkan', + }), }, classification: { efficientnet_v2_s: variant(EFFICIENTNET_V2_S_VARIANTS), diff --git a/packages/react-native-executorch/src/constants/modelUrls.ts b/packages/react-native-executorch/src/constants/modelUrls.ts index 8ff13f585c..2520d84520 100644 --- a/packages/react-native-executorch/src/constants/modelUrls.ts +++ b/packages/react-native-executorch/src/constants/modelUrls.ts @@ -133,37 +133,9 @@ export const GEMMA4_E2B_VULKAN_MODEL = `${GEMMA4_E2B_PREFIX}/vulkan/gemma_4_e2b_ export const GEMMA4_E2B_TOKENIZER = `${GEMMA4_E2B_PREFIX}/tokenizer.json`; export const GEMMA4_E2B_TOKENIZER_CONFIG = `${GEMMA4_E2B_PREFIX}/tokenizer_config.json`; -const GEMMA4_E2B_MODEL = - Platform.OS === `android` ? GEMMA4_E2B_VULKAN_MODEL : GEMMA4_E2B_MLX_MODEL; - -const GEMMA4_E2B_MLX_MM = `${URL_PREFIX}-gemma-4-multimodal/${PREVIOUS_VERSION_TAG}/e2b/mlx/gemma4_e2b_mlx_int4.pte`; -const GEMMA4_E2B_VULKAN_MM = `${URL_PREFIX}-gemma-4-multimodal/${PREVIOUS_VERSION_TAG}/e2b/vulkan/gemma_4_e2b_vulkan_8da4w.pte`; - -/** - * @category Models - LLM - */ -export const GEMMA4_E2B = { - modelName: 'gemma4-e2b', - modelSource: GEMMA4_E2B_MODEL, - tokenizerSource: GEMMA4_E2B_TOKENIZER, - tokenizerConfigSource: GEMMA4_E2B_TOKENIZER_CONFIG, -} as const; - -/** - * @category Models - LLM Multimodal - */ -export const GEMMA4_E2B_MM = { - modelName: 'gemma4-e2b-multimodal', - modelSource: - Platform.OS === `android` ? GEMMA4_E2B_VULKAN_MM : GEMMA4_E2B_MLX_MM, - tokenizerSource: GEMMA4_E2B_TOKENIZER, - tokenizerConfigSource: GEMMA4_E2B_TOKENIZER_CONFIG, - capabilities: ['vision', 'audio'], - audioConfig: { - samplesPerBlock: 7680, - tokensPerBlock: 12, - }, -} as const; +export const GEMMA4_E2B_MLX_MM = `${URL_PREFIX}-gemma-4-multimodal/${PREVIOUS_VERSION_TAG}/e2b/mlx/gemma4_e2b_mlx_int4.pte`; +export const GEMMA4_E2B_VULKAN_MM = `${URL_PREFIX}-gemma-4-multimodal/${PREVIOUS_VERSION_TAG}/e2b/vulkan/gemma_4_e2b_vulkan_8da4w.pte`; +export const GEMMA4_E2B_XNNPACK_MM = `${URL_PREFIX}-gemma-4-multimodal/${PREVIOUS_VERSION_TAG}/e2b/xnnpack/gemma_4_e2b_xnnpack_8da4w.pte`; /** * @category Models - LLM diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/Info.plist b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/Info.plist index 6a6c556899..aaba93b395 100644 --- a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/Info.plist +++ b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/Info.plist @@ -8,7 +8,7 @@ BinaryPath ExecutorchLib.framework/ExecutorchLib LibraryIdentifier - ios-arm64 + ios-arm64-simulator LibraryPath ExecutorchLib.framework SupportedArchitectures @@ -17,12 +17,14 @@ SupportedPlatform ios + SupportedPlatformVariant + simulator BinaryPath ExecutorchLib.framework/ExecutorchLib LibraryIdentifier - ios-arm64-simulator + ios-arm64 LibraryPath ExecutorchLib.framework SupportedArchitectures @@ -31,8 +33,6 @@ SupportedPlatform ios - SupportedPlatformVariant - simulator CFBundlePackageType diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_mlx_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_mlx_simulator.a deleted file mode 100644 index 59999e734d..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_mlx_simulator.a and /dev/null differ diff --git a/yarn.lock b/yarn.lock index 814f4334f2..36ccc4a04f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15066,6 +15066,7 @@ __metadata: react: "npm:19.2.3" react-native: "npm:0.85.3" react-native-builder-bob: "npm:^0.40.12" + react-native-device-info: "npm:^15.0.2" typescript: "npm:~5.9.2" zod: "npm:^4.3.6" peerDependencies: