Context: Jisho (Japanese dictionary app, Expo 55 / RN 0.83 / New Architecture) currently uses @react-native-ai/apple (0.12.0) from the callstackincubator/ai monorepo for on-device Apple Intelligence. Goal under evaluation: migrate to software-mansion/react-native-executorch to run Llama/Gemma locally.
TL;DR (opinion up front): A full migration is probably the wrong move. The most honest framing is that you are choosing between three things: (1) keep Apple-only, (2) add a second local backend alongside Apple, (3) replace Apple entirely. Option 2 is almost always the right answer, and callstackincubator/ai already supports it natively via @react-native-ai/llama or @react-native-ai/mlc — you do not need to leave the library at all. Also: Gemma is not on ExecuTorch's pre-built model list, Qwen is a significantly better Japanese LLM than Llama 3.2 at every size tier, and ExecuTorch's pre-built TTS (Kokoro) is English-only — no Japanese voices, no Japanese phonemizer — so for a Japanese dictionary, ExecuTorch's TTS story is actively worse than what you have today with Apple's Kyoko voice. TTS is a reason to stay, not to migrate.
- Library:
@react-native-ai/apple@0.12.0, part ofcallstackincubator/ai(MIT, ~1.3k★, active). - Backend: Apple Foundation Models via Vercel AI SDK v6 (
ai@6.0.146). - Usage surface (
providers/AppleAIProvider.tsx):generateObject({ model: apple(), schema: aiExampleSchemaArray, prompt })— structured JSON output using a Zod schema. Used for the 5-example generator.streamText({ model: apple(), messages, abortSignal, maxOutputTokens, temperature })— streaming chat + explanation.experimental_generateSpeech({ model: apple.speechModel(), text, voice, language })— base64 audio viaAVSpeechSynthesizer.AppleSpeech.getVoices()— voice enumeration, picks Kyoko for ja-JP.apple.isAvailable()— gates the whole provider.
- Runtime cost: 0 MB download. Models live in iOS. Requires iOS 26+ for text generation (iOS 17+ for embeddings, iOS 13+ for speech).
It is not an "Apple-only" wrapper. It's a multi-provider monorepo:
| Package | Backend | Platforms | Models |
|---|---|---|---|
@react-native-ai/apple |
Apple Foundation Models | iOS 26+ | Built-in, 0 download |
@react-native-ai/llama |
llama.rn (llama.cpp) |
iOS + Android | GGUF from HuggingFace (Llama 3.2, Qwen 2.5, SmolLM3, etc.) |
@react-native-ai/mlc |
MLC LLM | iOS + Android | Llama-3.2-3B, Phi-3-mini, Mistral-7B, Qwen2.5-1.5B |
@react-native-ai/dev-tools |
Rozenite / OpenTelemetry | — | profiling |
All providers plug into the same Vercel AI SDK surface (streamText, generateObject, embed, experimental_generateSpeech). This is the crucial fact: if the goal is "better local LLMs than Apple Intelligence," you can add @react-native-ai/llama and keep the exact code you already wrote — just swap model: apple() for a llama() factory in a settings-switched code path.
Software Mansion's declarative wrapper over Meta's ExecuTorch (PyTorch's on-device runtime, the same thing powering the "Private Mind" app on the App Store).
Current state:
- v0.8.3 (April 2026), MIT, ~1.4k★, 44 releases, active.
- Min versions: iOS 17.0, Android 13, New Architecture required (you have this).
- API is hook-based:
useLLM({ model: QWEN3_0_6B_QUANTIZED })→{ response, token, generate, sendMessage, interrupt, isReady, isGenerating, downloadProgress }. - Streaming works via
tokenCallback→ accumulatingresponsestate (imperative, React-state-driven; not AsyncIterable). - Tool use:
generate(messages, tools?: LLMTool[])+configure({ toolsConfig }). So function-calling is a first-class feature. - No
generateObject/ structured-output equivalent. You'd have to reimplement via prompt-engineering + JSON parsing + retries. This is a real regression from your current Zod-schema flow. - Covers far more than LLMs: OCR, Whisper STT, text embeddings, VAD, TTS, image classification, segmentation, VLMs.
Pre-built LLM catalog (on their HuggingFace org, constants in src/constants/modelUrls.ts):
| Family | Sizes | Quantizations |
|---|---|---|
| Llama 3.2 | 1B, 3B | bf16 / QLoRA / SpinQuant |
| Qwen 3 | 0.6B, 1.7B, 4B | bf16 / 8da4w (int4 weight + int8 activation) |
| Qwen 2.5 | 0.5B, 1.5B, 3B | bf16 / quantized |
| Phi-4-Mini | — | — |
| SmolLM-2 | — | — |
| Hammer 2.1 | 0.5B, 1.5B, 3B | bf16 / 8da4w |
| Bielik v3.0 | Polish | — |
| LFM 2.5 | 350M, 1.2B | — |
Gemma is NOT pre-packaged. You would need to export it yourself via optimum-executorch — achievable but non-trivial (PyTorch + Python export pipeline, .pte compilation, quantization tuning, runtime version pinning — "no forward compatibility guaranteed" is in the HF model cards).
Apple Foundation Models are English-biased. Fine for English UI copy; mediocre at generating idiomatic Japanese examples with furigana, and weak at nuanced grammatical explanations in Japanese. This is the single most defensible reason to consider moving.
How the candidate local models rank for Japanese (roughly, at ~1–3B scale):
- Qwen 2.5 / Qwen 3 — by far the strongest small-model Japanese. Alibaba trained heavily on CJK. Qwen2.5-1.5B or Qwen3-1.7B quantized is the realistic sweet spot for on-device Japanese generation.
- Phi-4-Mini — decent multilingual, weaker on Japanese idiom than Qwen.
- Gemma 2/3 (2B) — multilingual-capable but Japanese is not its strength; not pre-packaged.
- Llama 3.2 (1B/3B) — English-first; Japanese output is noticeably worse than Qwen at the same param count. Meta explicitly lists 8 supported languages and Japanese is not one of them.
If the whole goal is better Japanese, "Llama/Gemma" is the wrong shortlist. Qwen is the answer. And Qwen ships in both libraries: ExecuTorch has Qwen 3 0.6B/1.7B/4B and Qwen 2.5 0.5B/1.5B/3B; @react-native-ai/llama can load any Qwen GGUF.
Feature used in AppleAIProvider.tsx |
Apple (current) | ExecuTorch | @react-native-ai/llama |
|---|---|---|---|
generateObject w/ Zod schema |
✅ native | ❌ not supported — reimplement as prompt + JSON.parse + retry loop | ✅ via AI SDK (same code) |
streamText AsyncIterable |
✅ | ✅ via AI SDK (same code) | |
abortSignal |
✅ | ✅ interrupt() |
✅ |
maxOutputTokens, temperature |
✅ | ✅ via configure({ generationConfig }) |
✅ |
experimental_generateSpeech |
✅ AVSpeechSynthesizer | useTextToSpeech hook, model-based TTS (Kokoro etc.), not AVSpeech |
— (you'd keep Apple speech) |
| System prompt as first message | ✅ | ✅ | ✅ |
apple.isAvailable() gate |
✅ | N/A (downloads models instead) | N/A |
| Voice picker (Kyoko) | ✅ | ❌ — different TTS stack | — |
Net: ExecuTorch forces you to rewrite the structured-output path and the TTS path. @react-native-ai/llama drops in against 90% of your existing code.
This is the section that changes the decision if TTS quality is a driver.
@react-native-ai/apple → experimental_generateSpeech({ model: apple.speechModel(), text, voice: 'com.apple.voice.compact.ja-JP.Kyoko', language: 'ja-JP' }) → base64 WAV → expo-audio playback. The pickBestJaVoice helper prefers Kyoko → any non-Eloquence JA voice → any JA voice. This means users get the best available on-device Apple voice including iOS 17+ enhanced / premium neural voices (downloadable via iOS Settings → Accessibility → Spoken Content → Voices).
Quality: Kyoko Premium on iOS 17+ is genuinely very good JA TTS. Kyoko Enhanced is acceptable. Default compact Kyoko is OK. All are free, system-provided, and you already support voice selection via SystemVoice[] enumeration.
Only Kokoro-82M (KOKORO_SMALL, KOKORO_MEDIUM). The full voice list in src/constants/tts/voices.ts:
KOKORO_VOICE_AF_HEART → lang: 'en-us'
KOKORO_VOICE_AF_RIVER → lang: 'en-us'
KOKORO_VOICE_AF_SARAH → lang: 'en-us'
KOKORO_VOICE_AM_ADAM → lang: 'en-us'
KOKORO_VOICE_AM_MICHAEL → lang: 'en-us'
KOKORO_VOICE_AM_SANTA → lang: 'en-us'
KOKORO_VOICE_BF_EMMA → lang: 'en-gb'
KOKORO_VOICE_BM_DANIEL → lang: 'en-gb'
No ja- voices. No Japanese phonemizer*: only us_merged.json and gb_merged.json are bundled. Kokoro-82M upstream does support Japanese (voice IDs jf_alpha, jf_gongitsune, jf_nezumi, jf_tebukuro, jm_kumo) via the misaki G2P library — but Software Mansion has not packaged it. To run Japanese Kokoro you would have to:
- Export the
misakiJapanese G2P data to the exact JSON format the RN module expects. - Re-export the Kokoro model for Japanese token ranges (the pre-built
.pteis compiled with English token ranges baked in at export time). - Host your own weights bucket with compatible versioning.
- Track upstream
.ptecompatibility (recall the "no forward compat" warning).
This is a research project, not a library upgrade. It's the kind of thing you pitch to Software Mansion as a PR, not something you own in a dictionary app's main branch.
Nothing Japanese-capable either. The @react-native-ai/llama package is GGUF LLM inference only. The Vercel AI SDK surface exposes experimental_generateSpeech, but there is no TTS model loaded; calling it on a llama() model throws. So adding Qwen via @react-native-ai/llama doesn't buy you TTS — it just buys you LLM.
Almost nothing, honestly. The realistic options:
| Option | Status |
|---|---|
Apple AVSpeechSynthesizer + Kyoko Premium/Enhanced |
Works today, sounds good, free. iOS 13+. |
expo-speech |
Wraps the same AVSpeechSynthesizer. Same voices. |
| ExecuTorch + Kokoro-JA | Requires custom export work (above). Not shipped. |
| ExecuTorch + any other TTS model | Not packaged; useTextToSpeech is Kokoro-specific in types (KokoroVoiceExtras). |
llama.rn / @react-native-ai/llama |
No TTS at all. |
| VITS-JA / Style-Bert-VITS2 via ONNX Runtime | Not in either library. Would need a new native module. Heavy. |
| Cloud TTS (OpenAI / Google / ElevenLabs JA) | You already have this via getAiSound. Best quality, requires network + subscription. |
If TTS is part of your motivation for switching: switching hurts you. ExecuTorch's pre-built TTS doesn't speak Japanese, llama.rn has no TTS, and Apple's Kyoko Premium is genuinely competitive with any on-device neural TTS you could realistically ship in 2026 at zero download cost.
Keep AVSpeechSynthesizer for TTS no matter what you decide on the LLM side. Either:
- Via
@react-native-ai/apple(your current path — keep it), or - Directly via
expo-speech(already in your deps) if you drop the apple provider entirely.
The tiny argument for trying ExecuTorch Kokoro-JA would be for English readings in a JA→EN flow, but you already have that covered by Apple voices too.
- Apple: 0 MB added. All weights are system-provided.
- ExecuTorch: the runtime itself is meaningful native binary (C++ kernels per backend: XNNPACK CPU, Core ML delegate, MPS, Vulkan). Ballpark +20–40 MB IPA depending on enabled delegates. Models are downloaded on first use, not bundled — so initial IPA stays modest but first-run UX changes dramatically.
Rough numbers for on-device LLMs (quantized, fits-in-RAM tiers):
| Model | Disk (.pte quantized) | Peak RAM | Realistic devices |
|---|---|---|---|
| Qwen3-0.6B 8da4w | ~400 MB | ~1 GB | iPhone 12+ |
| Qwen3-1.7B 8da4w | ~1.1 GB | ~2.5 GB | iPhone 13+ |
| Llama-3.2-1B SpinQuant | ~1.1 GB | ~2 GB | iPhone 13+ |
| Qwen3-4B 8da4w / Llama-3.2-3B | ~2–2.5 GB | ~4–5 GB | iPhone 15 Pro+, and you need the "Increased Memory Limit" entitlement |
| Apple Foundation (3B distilled) | 0 (system) | shared w/ OS | iPhone 15 Pro / A17+ only anyway |
First launch with a local LLM means a multi-hundred-MB to multi-GB download over the user's network, with a progress UI, pause/resume, cellular warnings, disk-full handling, mid-download backgrounding, and eviction policy (what happens when the user switches models?). That is a feature, not a line item. useLLM gives you downloadProgress, but everything else is on you.
- Apple provider: iOS 26+ for generation. This is the real ceiling — most of your users on iPhone 13/14 on iOS 17/18 get nothing from Apple Intelligence today. If that is why you are reconsidering, say so clearly — that is a legitimate motivation.
- ExecuTorch: iOS 17+. Runs on ~every device sold in the last 5 years, with acceptable perf on A15+ chips.
- Apple Foundation Models: first-class ANE + GPU, co-designed with the model. You will not beat this on an A17/A18 device.
- ExecuTorch on iOS: supports Core ML delegate (ANE), MPS (GPU), XNNPACK (CPU). In practice Core ML delegate on small LLMs is still maturing. Expect 20–60 tok/s on a 0.6B–1.7B model on A17/A18, slower on older chips.
llama.rn(Metal) benchmarks similarly.
Nobody beats Apple for Apple's own model on Apple's own silicon. ExecuTorch wins when you need a specific model Apple doesn't provide or when you need to run on iOS 17/18 / older hardware.
| Dimension | callstackincubator/ai | react-native-executorch |
|---|---|---|
| Owner | Callstack (long-standing RN agency) | Software Mansion (long-standing RN agency, ships Reanimated, Screens, etc.) |
| Stars / forks | 1.26k / 50 | 1.41k / 69 |
| Open issues | 10 | 68 |
| Last push | 2026-03-24 | 2026-04-17 |
| License | MIT | MIT |
| Breaking-change posture | AI SDK v5→v6 jump required major version bump | "No forward compatibility guaranteed" for .pte files between runtime versions — models re-download on major upgrades |
| App you can point at | — | Private Mind (App Store, Google Play) |
Both are healthy. ExecuTorch has more open issues per star, which tracks with it doing more (OCR, Whisper, VLM, embeddings) and being closer to the bleeding edge. The .pte forward-compat caveat is worth internalizing: when you bump the library, users may redownload gigabytes.
Realistic work for a full replacement of @react-native-ai/apple:
- New provider (
ExecuTorchAIProvider.tsx) wrappinguseLLMand exposing the sameAIProviderValueshape. ~1 day. - Structured-output reimplementation for
generateExamples: prompt asking for JSON only, regex/JSON-repair, Zod parse, retry on failure. ~1 day including edge cases. - Streaming adapter: bridge
tokenCallbackstate updates to your existingonChunkcallback. ~2 hours. - Model download UX: progress, pause, retry, cellular warning, disk-space preflight, background-resume. Settings screen for model choice + delete. ~2–3 days if done properly; it is its own feature.
- TTS decision: either keep Apple
AVSpeechSynthesizerviaexpo-speech(works on iOS 13+, no AI needed), or adopt ExecuTorch'suseTextToSpeech. Keeping Apple is the right call here — yourpickBestJaVoicelogic is already solid. - Prompt tuning: your
JP_EXPLANATION_SYSTEM_PROMPTandEXAMPLES_PROMPTare tuned to Apple's model. Smaller models will need tighter, more explicit prompting and likely few-shot examples. - Evaluation harness: this is the one people skip and regret. Build a small JSON fixture of ~30–50 headwords and score example generation + explanation quality against Apple / Qwen / Llama before cutting over. ~1 day.
- Subscription gating rethink: today "local AI" is free (no model download), "remote" is premium. If local means 1.2 GB download, is that still the free tier? Business decision.
Realistic total: 1–2 weeks of real work, plus ongoing prompt/model retuning.
- Model choice: you pick Qwen 2.5/3, Phi-4-Mini, SmolLM-2, Llama 3.2. Qwen in particular unlocks much better Japanese quality.
- iOS 17+ reach: works on iPhone 11/12/13/14 where Apple Intelligence does not.
- Android parity: if you ever ship Android seriously, ExecuTorch works there. Apple provider does not.
- Full offline: truly offline, no dependency on OS-level Apple model availability.
- Broader capabilities: OCR (Tesseract/PaddleOCR-class), Whisper, text embeddings — all useful for a dictionary app (OCR for "look up from a photo", embeddings for semantic search over definitions).
- Tool use / function calling: first-class in
useLLM. Could power agentic dictionary features. - No silent empty-response bug: the
"Apple Intelligence is not available on the simulator"fallback you currently have disappears.
- No
generateObjectequivalent. Your Zod-schema flow becomes homemade JSON-mode-with-retries. Real regression, real maintenance cost. - Large first-run download (400 MB – 2 GB+ depending on model). Breaks "it just works" installs.
- RAM pressure. You ship a second premium tier (iPhone 15 Pro+) for the better models. Worse, crashes on 3–4GB-RAM devices if the user picks the wrong size.
.pteforward-compat gaps. Users re-download GBs on library upgrades. Needs a migration / version-pinning story.- Gemma requires custom export. "Llama/Gemma" is not a drop-in — Gemma means setting up
optimum-executorch, maintaining your own .pte files, rehosting them, versioning them. - Quality: Apple Intelligence at 3B-distilled-co-designed is roughly on par with Llama-3.2-3B quantized on English tasks. You are not trading up by default; you are trading specifically for Japanese capability and iOS 17 reach.
- Simulator: ExecuTorch LLMs on simulator are slow-to-impossible. Dev loop degrades.
- Bundle size +20–40 MB native binary.
- Rewriting working code —
providers/AppleAIProvider.tsxis tuned and works. Throwing it out is measurable lost work. - TTS regression if you try to switch away from Apple speech.
AVSpeechSynthesizerwith Kyoko is genuinely very good for JA.
- Zero migration. Your code ships today.
- Best possible Apple experience when available.
- Same AI SDK surface works across Apple / Llama / MLC — you can add
@react-native-ai/llamaas a second local backend without leaving the library.generateObjectcontinues to work. - Zero first-run download for Apple users.
- Maintained by Callstack, who know the RN AI space.
- iOS 26+ wall for generation. Big.
- Japanese quality ceiling set by Apple's model — not under your control.
- Android: basically nothing. If Android ships, Apple provider is dead weight.
- No model choice. You get what Apple gives you.
callstackincubator/ai is explicitly multi-provider. The lowest-risk path is:
- Keep
@react-native-ai/appleas-is. On iOS 26+, Apple Intelligence stays the default free-tier local path. - Add
@react-native-ai/llamafor iOS 17/18 users and Android, loading Qwen 2.5 1.5B Instruct GGUF Q4_K_M (~1 GB, strong Japanese). - Your
UnifiedAIProvidergets a thirdAIProviderType:"local-apple" | "local-qwen" | "remote", with gating based onapple.isAvailable()and model download state. - Keep TTS on Apple
AVSpeechSynthesizer(Kyoko) regardless of LLM backend. - Reuse all your existing
generateObject/streamTextcode. The model factory is the only thing that changes.
This gets you the 80% win (Japanese quality + iOS 17 reach) at ~30% of the cost of a full ExecuTorch migration, and you keep the Apple path for users who can use it.
- If the driver is "my users are on iOS 17/18 and Apple Intelligence is unavailable" → add
@react-native-ai/llamawith Qwen 2.5 1.5B GGUF. Don't migrate. 2–3 days of work. - If the driver is "Apple's Japanese is weak" → same answer. Qwen > Apple > Llama on Japanese at these sizes.
- If the driver is "I want Android parity" → same answer again, because
llama.rnworks on Android too. - If the driver is "I want better Japanese TTS" → do not migrate. ExecuTorch's pre-built TTS is Kokoro English-only.
@react-native-ai/llamahas no TTS. Apple Kyoko Premium (free, iOS 17+, downloadable via Settings) is the best realistic on-device JA TTS available to a React Native app in 2026. If Kyoko isn't good enough, the answer is cloud TTS (you already have this viagetAiSound), not a different local library. - If the driver is "I want features beyond LLM: OCR of Japanese photos, Whisper STT for listening practice, embeddings for semantic dictionary search" → then ExecuTorch is the right library for those things, because those capabilities don't exist in callstackincubator/ai. But in that case, mix: keep Apple for TTS and primary LLM, add ExecuTorch just for OCR/STT/embeddings. Don't let the OCR tail wag the LLM dog.
Given the actual Jisho feature set (dictionary search, word explanations, example sentences, JLPT reading passages), the OCR/STT case is genuinely plausible within 6–12 months. In that case the migration of those subsystems pays off. In the short term, the add-don't-replace option is strictly better than ripping out working Apple code — especially once you account for TTS.
yarn add @react-native-ai/llama llama.rn react-native-blob-util- Download Qwen2.5-1.5B-Instruct Q4_K_M GGUF on first use; store in app documents with version pinning.
- New factory:
const model = useMMKVString(SETTINGS_KEYS.LOCAL_MODEL) === 'qwen' ? llama({ modelPath }) : apple(). generateObject,streamText,abortSignal— all unchanged.- Add settings UI: "On-device model: Apple (requires iOS 26) / Qwen 1.5B (1 GB download, best Japanese)".
- Keep
pickBestJaVoice+AppleSpeechTTS exactly as it is.
yarn add react-native-executorch react-native-executorch-expo-resource-fetcher- Build
ExecuTorchAIProvider.tsxmirroringAppleAIProvider'sAIProviderValueshape. - Start with
QWEN3_1_7B_QUANTIZED— best Japanese-per-MB on offer. - Rebuild
generateExamplesas prompt +JSON.parse+ retry + Zod parse. - Bridge
tokenCallback→ existingonChunkcontract. - Build first-run model download UX (progress, cancel, retry, cellular guard, disk-space preflight).
- Decide on model management UX: single model, or switchable with eviction?
- Keep TTS on Apple
AVSpeechSynthesizerviaexpo-speech(or keep@react-native-ai/appleinstalled purely forAppleSpeech.getVoices()+apple.speechModel()). Do not adoptuseTextToSpeech— it is English-only Kokoro and has no Japanese phonemizer in the shipped package. If you strip@react-native-ai/apple, portpickBestJaVoiceto theexpo-speechvoice list. - Build a 30-headword eval fixture; compare Qwen 1.7B vs Apple vs current remote provider before cutover.
- Re-tune
JP_EXPLANATION_SYSTEM_PROMPTandEXAMPLES_PROMPTfor a smaller model (more explicit, few-shot, tighter token budgets).
Both libraries can coexist in one Expo app. If in 6–12 months you want photo-lookup (OCR), listening practice (Whisper), or semantic search (embeddings), the pragmatic stack is:
| Subsystem | Library |
|---|---|
| Chat + explanation LLM | @react-native-ai/apple on iOS 26+, @react-native-ai/llama (Qwen 1.5B GGUF) elsewhere |
| Structured output (examples) | same (Vercel AI SDK generateObject) |
| TTS | @react-native-ai/apple speech model or expo-speech (AVSpeechSynthesizer + Kyoko) |
| OCR for photo lookup | react-native-executorch — their OCR pipeline |
| STT for listening drills | react-native-executorch — Whisper |
| Embeddings for semantic dictionary search | react-native-executorch — text-embeddings |
This is heavier on native binary size than either library alone, but every subsystem uses the library that actually does it best. The LLM/TTS core stays on the path that works today, and ExecuTorch gets added only when/if the features that justify it ship.