Models
Use OS-managed models, download open models at runtime, or register your own — all behind one API.
Overview
expo-ai-kit runs two kinds of model. Built-in models are provided and maintained by the OS — Apple Foundation Models on iOS and ML Kit on Android. Android may prepare its model on first use, but it is not bundled into your app. Downloadable models (Gemma, Qwen, Phi) are fetched at runtime via LiteRT-LM and run on both platforms. You switch between any of them with setModel().
Built-in vs Downloadable
| Built-in (OS) | Downloadable (LiteRT-LM) |
|---|---|
| OS-managed, zero app-size cost | 0.5–4 GB download, managed by you |
| OS-maintained & updated | Pinned version you control |
apple-fm (iOS), mlkit (Android) | Same model on iOS and Android |
The Catalog
The built-in registry ships a size ladder across three families. Each entry carries a license — check it before shipping a model to your users.
| id | Params | Download | License |
|---|---|---|---|
qwen3-0.6b | 0.6B | ~0.5 GB | Apache-2.0 |
qwen3-1.7b | 1.7B | ~2.1 GB | Apache-2.0 |
gemma-e2b | 2.3B | ~2.6 GB | Gemma |
qwen3-4b | 4B | ~2.7 GB | Apache-2.0 |
gemma-e4b | 4.5B | ~3.7 GB | Gemma |
phi-4-mini | 3.8B | ~3.9 GB | MIT |
Qwen3 runs on the CPU backend by default
Registry entries can pin a preferredBackend that setModel() uses when you don't pass one. The Qwen3 entries pin 'cpu': their GPU path is broken in the current LiteRT-LM runtime (device-verified — a native crash on Android and degenerate output on iOS), while CPU generates correctly on both platforms. An explicit setModel(id, { backend }) always wins. Qwen3 is also a thinking model — see stripThinking() if you call the raw sendMessage() (the orchestrated APIs and the AI SDK provider already handle its <think> blocks).
Download & Switch
Download a model (with progress), then activate it with setModel(). After that, every inference call — sendMessage, streamMessage, generateObject, generateText — uses it. unloadModel() reverts to the OS built-in.
import { downloadModel, setModel, unloadModel } from 'expo-ai-kit';
await downloadModel('qwen3-1.7b', {
onProgress: (p) => console.log(`${Math.round(p * 100)}%`),
});
await setModel('qwen3-1.7b', { generation: { temperature: 0.7 } });
// ...all inference now runs on Qwen3 1.7B
await unloadModel(); // back to the OS modelSampling is set at activation
Generation options (temperature, topK, …) are fixed when you call setModel(), not per request — LiteRT-LM builds the sampler when the model session is created.
Pick the Right Model
getDownloadableModels() returns the full catalog enriched with per-device status, size, license, and whether the device meets the model's RAM requirement. getRecommendedModel() returns the most capable model the current device can actually run, or null.
import { getDownloadableModels, getRecommendedModel } from 'expo-ai-kit';
const all = await getDownloadableModels();
all.forEach((m) => console.log(m.id, m.meetsRequirements, m.status, m.license));
const best = await getRecommendedModel();
if (best) await downloadModel(best.id);Status & Lifecycle
Each downloadable model reports a status:
not-downloaded— no file on diskdownloading— fetch in progressdownloaded— on disk but not loaded; survives app restartsloading— being loaded into memoryready— loaded and ready for inference
Downloads are integrity-checked with SHA-256. UsecancelDownload(id) to stop an active download anddeleteModel(id) to remove a downloaded model. Interrupted downloads restart from the beginning.
Bring Your Own Model
Not limited to the built-in list — register any LiteRT-LM model at runtime with registerModel(). Once registered, the id works with downloadModel / setModel / getDownloadableModels exactly like a built-in, and the download is still integrity-checked against the sha256 you provide.
import { registerModel, downloadModel, setModel } from 'expo-ai-kit';
registerModel({
id: 'qwen3-4b-custom',
name: 'Qwen3 4B',
parameterCount: '4B',
quantization: 'int4',
downloadUrl:
'https://huggingface.co/litert-community/Qwen3-4B/resolve/main/qwen3_4b_mixed_int4.litertlm',
sha256: 'f0794bc77efeaaf4f7af815f04c483b19b8f2ae4a102cef1b7b760a25848a18e',
sizeBytes: 2_659_057_664,
contextWindow: 4096,
minRamBytes: 3_000_000_000,
supportedPlatforms: ['ios', 'android'],
license: 'Apache-2.0',
});
await downloadModel('qwen3-4b-custom');
await setModel('qwen3-4b-custom');Re-register on each launch
Custom models live in memory — call registerModel() at startup every launch. The downloaded file persists on disk (keyed by id), so a model's downloaded status survives restarts once you re-register it. Curated and native ids are reserved; registerModel() rejects collisions.
fetchModelMetadata()
Rather than computing the sha256 and sizeBytes by hand, fetchModelMetadata() looks them up from a HuggingFace resolve URL.
import { fetchModelMetadata } from 'expo-ai-kit';
const { sha256, sizeBytes } = await fetchModelMetadata(
'https://huggingface.co/litert-community/Qwen3-4B/resolve/main/qwen3_4b_mixed_int4.litertlm',
);Trust note
It reads the hash from the same host you download from, so it only guards against transit corruption — not a maliciously changed upstream repo. For a real supply-chain guarantee, run it once at dev time and pin the returned sha256 in your source, exactly like the built-in registry does.