Models

Use OS-managed models, download open models at runtime, or register your own — all behind one API.

iOSAndroid

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 cost0.5–4 GB download, managed by you
OS-maintained & updatedPinned 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.

idParamsDownloadLicense
qwen3-0.6b0.6B~0.5 GBApache-2.0
qwen3-1.7b1.7B~2.1 GBApache-2.0
gemma-e2b2.3B~2.6 GBGemma
qwen3-4b4B~2.7 GBApache-2.0
gemma-e4b4.5B~3.7 GBGemma
phi-4-mini3.8B~3.9 GBMIT

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.

setup.tstypescript
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 model

Sampling 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.

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 disk
  • downloading — fetch in progress
  • downloaded — on disk but not loaded; survives app restarts
  • loading — being loaded into memory
  • ready — 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.

App.tsxtypescript
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.