flutter_gemma supports Gemma 4, Gemma3n, FastVLM, Qwen2-VL, SmolVLM2,
LLaVA-OneVision, Gemma 3, FunctionGemma, Qwen3, Qwen 2.5, Phi-4 (incl. Phi-4 Mini
Reasoning), DeepSeek R1, SmolLM, SmolLM3 and more. Desktop platforms (macOS,
Windows, Linux) require the .litertlm model format.
Model file types#
Flutter Gemma supports different model file formats, grouped into two types based on how chat templates are handled.
Type 1: SDK-managed templates#
-
.taskfiles: MediaPipe-optimized format for mobile (Android/iOS) and web. -
.litertlmfiles: LiteRT-LM format for Android, iOS, Desktop, and web (early preview). Never handled by MediaPipe β the two formats go to different engines.
Chat templates are applied by the runtime rather than by your code β MediaPipe
for .task, LiteRT-LM for .litertlm β on every platform, iOS included.
Type 2: Manual template formatting#
.binfiles: standard binary format..tflitefiles: LiteRT format (formerly TensorFlow Lite).
Both formats require manual chat template formatting in your code.
Type 3: System OS models (no file)#
Gemini Nano (Android, via AICore / ML Kit GenAI), Apple Foundation Models
(iOS 26+/macOS), and Gemini Nano via the Chrome Prompt API (Web β desktop
Chrome/Edge) are built into the OS/browser β there is no model file to bundle
or download; the platform owns the weights. Add
flutter_gemma_builtin_ai, register BuiltInAiEngine(), and use
ModelFileType.builtIn. Availability is device-gated β Gemini Nano needs Pixel
9+/Galaxy S25+, and Apple FM needs Apple Intelligence enabled on iPhone
15 Pro+/M-series. On Android the package requires minSdk 26.
π See Built-in AI for the full setup, the availability
probe (BuiltInAi.availability() / BuiltInAi.ensureReady()), and fallback
guidance.
Format by platform#
| Format | Android | iOS | Web | Desktop | Use Case |
|---|---|---|---|---|---|
.task |
β | β | β | β | Older models (Gemma3n, Gemma 3, DeepSeek, Qwen 2.5, Phi-4) |
.litertlm |
β | β ΒΉ | β οΈ Β² | β | Newer models (Gemma 4, Qwen3, FastVLM + desktop for all) |
-web.task |
β | β | β | β | Web-specific builds (e.g. Gemma 4, Gemma3n) |
.bin |
β | β | β | β | Manual chat template formatting required |
.tflite |
β | β | β | β | Embeddings only (EmbeddingGemma, Gecko) |
ΒΉ iOS .litertlm runs on the FFI engine β vision and audio supported on physical
devices. The Simulator stays CPU-only because Metal sim has a 256 MB
single-allocation cap.
Β² Web .litertlm is an early preview via @litert-lm/core β text plus function calling. No
vision, audio, thinking or LoRA; see the feature matrix in
Troubleshooting. For full multimodal on web, use a
MediaPipe .task build.
Model capabilities#
| Model Family | Best For | Function Calling | Thinking Mode | Vision | Languages | Size |
|---|---|---|---|---|---|---|
| Gemma 4 E2B | Next-gen multimodal chat β text, image, audio | β | β ΒΉ | β | Multilingual | 2.4GB |
| Gemma 4 E4B | Next-gen multimodal chat β text, image, audio | β | β ΒΉ | β | Multilingual | 4.3GB |
| Gemma3n | On-device multimodal chat and image analysis | β | β | β | Multilingual | 3-6GB |
| FastVLM 0.5B | Fast vision-language inference | β | β | β | Multilingual | 0.5GB |
| Qwen2-VL 2B | Vision-language chat (image + text) | β | β | β | Multilingual | 1.8GB |
| SmolVLM2 500M | Compact vision-language model | β | β | β | Multilingual | 0.36GB |
| LLaVA-OneVision 0.5B | Compact vision-language model | β | β | β | Multilingual | 0.83GB |
| Phi-4 Mini | Advanced reasoning and instruction following | β | β | β | Multilingual | 3.9GB |
| Phi-4 Mini Reasoning | Step-by-step reasoning | β | β οΈ β‘ | β | Multilingual | 2.8GB |
| DeepSeek R1 | High-performance reasoning and code generation | β | β | β | Multilingual | 1.7GB |
| Qwen3 0.6B | Compact multilingual chat with function calling | β | β | β | Multilingual | 586MB |
| Qwen 2.5 | Strong multilingual chat and instruction following | β | β | β | Multilingual | 0.5-1.6GB |
| Gemma 3 1B | Balanced and efficient text generation | β | β | β | Multilingual | 0.5GB |
| Gemma 3 270M | Ideal for fine-tuning (LoRA) for specific tasks | β | β | β | Multilingual | 0.3GB |
| FunctionGemma 270M | Specialized for function calling on-device | β | β | β | Multilingual | 284MB |
| SmolLM 135M | Ultra-compact, resource-constrained devices | β | β | β | English | 135MB |
| LFM2.5 230M | Smallest entry; no HF token needed | β | β | β | Multilingual | 168MB |
| SmolLM3 3B | Multilingual small LLM with reasoning mode | β | β οΈ β‘ | β | Multilingual | 2.0GB |
| TranslateGemma 4B β | Single-shot 55-language translation | β | β | β | 55 languages | 2-4GB |
ΒΉ Gemma 4 Thinking Mode needs the native extraContext channel: Android, iOS
and Desktop only. Qwen3 and DeepSeek R1 reasoning is split out of the text by
flutter_gemma itself, so it works on every platform including Web.
β‘ Reasons, but emits no ThinkingResponse. These models run as
ModelType.general, which has no reasoning parser β their thinking blocks
arrive inside the answer as ordinary text and are not stripped. See
Thinking Mode.
Measured here on an M4 Pro (macOS, Metal), both published artifacts, same prompt and the same session code, backend the only variable:
| artifact | PreferredBackend.cpu | PreferredBackend.gpu |
|---|---|---|
int4-generic (2.0 GB, INT4 blockwise) | Guten Morgen | 997 <pad> tokens, nothing else |
dynamic_int8-generic (3.9 GB, INT8 channelwise) | Guten Morgen | 997 <pad> tokens, nothing else |
Nothing fails: the Metal engine is created, activeBackend reports gpu (so it
is not a silent fallback), generation runs and emits padding until it reaches the
context limit. A gemma-4-E2B-it.litertlm bundle on the same machine, the same code path and the same backend answers correctly with no padding, so this is not the Metal path in general. Earlier revisions of this page said the bundle crashed the LiteRT
GPU partitioner because its EMBEDDING_LOOKUP weights stay float32. Nothing in
the runs above crashes, so that description does not hold; the repository has
published these .litertlm artifacts since 2026-03-31, so it did not describe a
later change either. Reproducing across two different quantization recipes also
rules the bit width out.
Tracked at
LiteRT-LM#1748.
Use PreferredBackend.cpu for this model (β90 s prefill on a 4 B int4 bundle on
M-series Macs). If you are converting it yourself, AI Edge Quantizer is the
supported route today β the litert-lm quantization CLI announced in that thread
never shipped β with published recipes and Model Explorer for the layer regexes
(maintainer's pointers).
ModelType reference#
When installing models, specify the correct ModelType:
| Model Family | ModelType | Examples |
|---|---|---|
| Gemma 4 | ModelType.gemma4 | Gemma 4 E2B, Gemma 4 E4B (native function-call tokens) |
| Gemma 3 / Gemma3n | ModelType.gemmaIt | Gemma 3 1B, Gemma 3 270M, Gemma3n E2B/E4B |
| DeepSeek | ModelType.deepSeek | DeepSeek R1 |
| Qwen 2.5 | ModelType.qwen | Qwen 2.5 1.5B, Qwen 2.5 0.5B |
| Qwen 3 | ModelType.qwen3 | Qwen3 0.6B |
| Phi-4 | ModelType.phi | Phi-4 Mini (parses Phi's own tool-call markers) |
| FunctionGemma | ModelType.functionGemma | FunctionGemma 270M IT |
| General | ModelType.general | FastVLM 0.5B, SmolLM 135M, LFM2.5 230M, SmolLM3 3B, Phi-4 Mini Reasoning, Qwen2-VL 2B, SmolVLM2 500M, LLaVA-OneVision 0.5B |
Usage example:
// Gemma models
await FlutterGemma.installModel(modelType: ModelType.gemmaIt)
.fromNetwork(url).install();
// DeepSeek models
await FlutterGemma.installModel(modelType: ModelType.deepSeek)
.fromNetwork(url).install();
// Phi-4 (its own type β parses Phi's tool-call markers)
await FlutterGemma.installModel(modelType: ModelType.phi)
.fromNetwork(url).install();
Supported models & platforms#
| Model | Size | Desktop | Mobile | Web |
|---|---|---|---|---|
| Gemma 4 E2B | 2.4GB | β | β | β |
| Gemma 4 E4B | 4.3GB | β | β | β |
| Gemma3n E2B | 3.1GB | β | β | β |
| Gemma3n E4B | 6.5GB | β | β | β |
| FastVLM 0.5B | 0.5GB | β | β | β |
| Qwen2-VL 2B | 1.8GB | β | β | β |
| SmolVLM2 500M | 0.36GB | β | β | β |
| LLaVA-OneVision 0.5B | 0.83GB | β | β | β |
| Gemma-3 1B | 0.5GB | β | β | β |
| Gemma 3 270M | 0.3GB | β | β | β |
| FunctionGemma 270M | 284MB | β | β | β |
| Qwen3 0.6B | 586MB | β | β | β |
| Qwen 2.5 1.5B | 1.6GB | β | β | β |
| Qwen 2.5 0.5B | 0.5GB | β | β | β |
| SmolLM 135M | 135MB | β | β | β |
| LFM2.5 230M | 168MB | β | β | β |
| SmolLM3 3B | 2.0GB | β | β | β |
| Phi-4 Mini | 3.9GB | β | β | β |
| Phi-4 Mini Reasoning | 2.8GB | β | β | β |
| DeepSeek R1 | 1.7GB | β | β | β |
Installation sources#
// Network β .litertlm is the cross-platform default (Android/iOS/Desktop).
// For mobile-only or web-only apps you can substitute a .task URL β and drop
// the fileType, which defaults to ModelFileType.task.
await FlutterGemma.installModel(
modelType: ModelType.gemmaIt,
fileType: ModelFileType.litertlm,
)
.fromNetwork('https://example.com/model.litertlm', token: 'optional')
.install();
// Flutter assets
await FlutterGemma.installModel(
modelType: ModelType.gemmaIt,
fileType: ModelFileType.litertlm,
)
.fromAsset('assets/models/model.litertlm')
.install();
// Native bundle
await FlutterGemma.installModel(
modelType: ModelType.gemmaIt,
fileType: ModelFileType.litertlm,
)
.fromBundled('model.litertlm')
.install();
// External file (native only)
await FlutterGemma.installModel(
modelType: ModelType.gemmaIt,
fileType: ModelFileType.litertlm,
)
.fromFile('/path/to/model.litertlm')
.install();
Hugging Face#
Install a model straight from a Hugging Face repo. The resolver that reads a repo's deployment manifest ships with the engine, so registering the engine is enough β there is no separate resolver list to maintain.
One call (manifest-driven) β omit file, and fromHuggingFace(repo) resolves the repo's manifest (e.g. litertlm_manifest.json) at install time, installs the right revision-pinned variant for the device, and returns the manifest's overridable runtime defaults on the result:
// The engine carries its resolver β nothing else to register.
await FlutterGemma.initialize(inferenceEngines: [LiteRtLmEngine()]);
final install = await FlutterGemma.installModel(
modelType: ModelType.general, // fallback β the manifest overrides it
fileType: ModelFileType.litertlm, // selects the litertlm resolver
)
.fromHuggingFace('litert-community/Qwen3-4B-Thinking-2507')
.install();
final model = await FlutterGemma.getActiveModel(defaults: install.runtime);
// `minOutputTokens` is a FLOOR, not a cap β leave `maxOutputTokens` unset (or
// keep it >= `install.runtime?.minOutputTokens`). Passing the floor as the cap
// would truncate a reasoning model mid-thought.
final session = await model.createSession(
enableThinking: install.runtime?.isThinking ?? false,
);
.onnx repos install the whole ORT-GenAI directory β the ONNX resolver (carried by OnnxEngine) lists the repo, picks a CPU execution-provider folder (the bundled runtime is CPU-only; pin one with OnnxHuggingFaceResolver(variant: β¦)) and downloads every file in it; see ONNX. .builtIn repos surface that OS models have no Hugging Face file.
Explicit file β pass file, and fromHuggingFace(repo, file:) resolves β¦/resolve/<revision>/<file> and installs it directly, for any fileType (no manifest needed; the HF token is applied to huggingface.co automatically):
await FlutterGemma.installModel(
modelType: ModelType.gemmaIt,
fileType: ModelFileType.litertlm,
)
.fromHuggingFace('litert-community/Gemma3-1B-IT', file: 'model.litertlm', token: 'optional')
.install();
Inspect first β resolveHuggingFace(repo, fileType:) returns the resolved identity plus overridable runtime defaults WITHOUT installing, so you can inspect the variant, its notes, and defaults before committing:
final r = await FlutterGemma.resolveHuggingFace(
'litert-community/Qwen3-4B-Thinking-2507',
fileType: ModelFileType.litertlm,
);
// β¦ inspect r.file / r.notes / r.runtime β¦
await FlutterGemma.installModel(
modelType: r.modelType ?? ModelType.general,
fileType: r.fileType,
)
.fromNetwork(r.url) // r.url pins the resolver's revision
.install();
The resolver lives in the engine package β LitertlmManifestResolver in flutter_gemma_litertlm reads litertlm_manifest.json β and rides on the engine via HuggingFaceResolverSource, so initialize(inferenceEngines: [β¦]) auto-registers it. Pass initialize(huggingFaceResolvers: [...]) only to override an engine's default (e.g. LitertlmManifestResolver(revision: 'abc123') to pin a commit).
Source capabilities#
| Source Type | Platform | Progress | Resume | Authentication | Use Case |
|---|---|---|---|---|---|
| NetworkSource | All | β Detailed | β οΈ Server-dependent | β Supported | HuggingFace, CDNs, private servers |
| AssetSource | All | β οΈ End only | β No | β N/A | Models bundled in app assets |
| BundledSource | All | β οΈ End only | β No | β N/A | Native platform resources |
| FileSource | Native (no Web) | β οΈ End only | β No | β N/A | User-selected files (file picker) |
Android foreground service (large downloads)#
Android has a 9-minute background execution limit. A foreground service shows a notification and exempts the download from battery-optimization kills, and you have to ask for it β the default configures no notification, and the platform will not start the service without one, so file size alone changes nothing:
// DEFAULT β no foreground service; pass foreground: true to get one
await FlutterGemma.installModel(modelType: ModelType.gemmaIt)
.fromNetwork(url) // foreground: null
.install();
// Force foreground mode
await FlutterGemma.installModel(modelType: ModelType.gemmaIt)
.fromNetwork(url, foreground: true)
.install();
iOS uses native URLSession which handles long downloads automatically β no foreground service needed.
Cancelling downloads#
import 'package:flutter_gemma/core/model_management/cancel_token.dart';
final cancelToken = CancelToken();
final future = FlutterGemma.installModel(modelType: ModelType.gemmaIt)
.fromNetwork(url)
.withCancelToken(cancelToken)
.withProgress((progress) => print('Progress: $progress%'))
.install();
// Cancel from elsewhere (e.g. user pressed a cancel button)
cancelToken.cancel('User cancelled download');
try {
await future;
} catch (e) {
if (CancelToken.isCancel(e)) {
print('Download was cancelled by user');
}
}
CancelToken cancels all files in multi-file downloads (e.g. embedding model +
tokenizer), works on mobile + web, and throws DownloadCancelledException.
Speech models#
On-device speech via the opt-in flutter_gemma_speech package β
selectable, profile-driven pipelines (you pick the model with SttModelType /
TtsModelType). moonshine-tiny, Whisper, and Parakeet (STT) and
Matcha, Qwen3-TTS (multilingual), and Inflect-Nano-v2 (fast) TTS work
end-to-end today; kokoro / supertonic TTS voices are follow-ons. Native only (no
Web). See Speech for usage.
A VoiceSession also chains STT β LLM β TTS into a push-to-talk voice loop β
see Speech.
Speech-to-text
| Model | Input | Size | Status | Auth |
|---|---|---|---|---|
| moonshine-tiny | raw 16 kHz PCM | ~104 MB | β end-to-end | β |
| Whisper (tiny / base) | log-mel | β | β end-to-end | β |
| Parakeet (CTC 0.6B) | log-mel | β | β end-to-end | β |
Whisper is multilingual β the shipped checkpoints are the multilingual ones
(no .en suffix), so all 99 of Whisper's languages are available. Set a default
for the recognizer, override it per transcription, or both:
// Default for every transcription on this recognizer.
final stt = await FlutterGemma.getActiveStt(language: 'de');
final german = await stt.transcribe(germanPcm);
// One call in another language β same recognizer, nothing reloaded.
final french = await stt.transcribe(frenchPcm, language: 'fr');
Both are free: the language is one token in the decoder's seed prompt, and that prompt is rebuilt on every transcription. Switching languages never reloads the model or invalidates the recognizer you are holding.
The value is Whisper's own language code without the delimiters, and it defaults
to 'en'. It decides the OUTPUT language only β the weights understand the
audio either way, so asking for 'en' on German speech returns an English
translation rather than an error.
Moonshine and Parakeet have no language token in their decoder prompt and
reject the parameter with an ArgumentError rather than ignoring it; both
are English-only.
Text-to-speech
| Model | Output | Size | Status | Auth |
|---|---|---|---|---|
| Matcha | 16-bit PCM @ 22050 Hz | ~90 MB | β end-to-end | β |
Qwen3-TTS (10 langs + auto) | 16-bit PCM | ~1.9 GB | β end-to-end | β |
| Inflect-Nano-v2 (fast) | 16-bit PCM @ 24 kHz | ~8 MB | β end-to-end | β |
Text embedding models#
The LiteRT embedding models below generate 768-dimensional vectors. The numbers in names (64/256/512/1024/2048) indicate maximum input sequence length in tokens, not embedding dimension. (With the ONNX backend the dimension is model-dependent β e.g. all-MiniLM-L6-v2 is 384-dim.) See Embeddings & RAG for usage.
| Model | Parameters | Dimensions | Max Seq Length | Size | Auth Required |
|---|---|---|---|---|---|
| Gecko 64 | 110M | 768D | 64 tokens | 110MB | β |
| Gecko 256 | 110M | 768D | 256 tokens | 114MB | β |
| Gecko 512 | 110M | 768D | 512 tokens | 116MB | β |
| EmbeddingGemma 256 | 300M | 768D | 256 tokens | 179MB | β |
| EmbeddingGemma 512 | 300M | 768D | 512 tokens | 179MB | β |
| EmbeddingGemma 1024 | 300M | 768D | 1024 tokens | 183MB | β |
| EmbeddingGemma 2048 | 300M | 768D | 2048 tokens | 196MB | β |
Performance (Android Pixel 8):
- Gecko 64: ~109 ms/doc embedding, 130 ms search (fastest β 2.6Γ faster than EmbeddingGemma).
- EmbeddingGemma 256: ~286 ms/doc embedding, 342 ms search (more accurate β 300M vs 110M params).
SigLIP 2 text tower (ONNX, manual wiring)#
flutter_gemma_embeddings ships a SigLIP 2 text profile, for putting text
into the same space as SigLIP's vision tower β imageβtext retrieval rather than
document RAG. It is the only embedding profile here that is not installed
through installEmbedder().
Text in, vectors out β the plugin does not run the vision tower. You embed the image side elsewhere (or offline) and query it with vectors this profile produces.
Its convention differs from every model above: no leading BOS, a single trailing
<eos>, lowercased text, and a fixed 64-token width that lives in the token ids
because the int8 export carries no attention_mask. It also ignores the
TaskType prefix β the vision tower encodes an image with no prefix, so adding
one moves the text vector off the space the two towers share.
SigLIP 2 reuses the Gemma BPE vocabulary, so a tokenizer.json cannot be told
apart by its vocabulary alone, and the ONNX tokenizer loader
refuses such a file rather than reading it with Gemma's convention and
returning a plausible but wrong vector.
To tell whether an export is the one this profile expects, look at two blocks of
its tokenizer.json β the same two the refusal keys on:
"padding"declares a fixed width β"strategy": {"Fixed": β¦}, not"BatchLongest""post_processor"appends<eos>and prepends no<bos>
The refusal needs both, so there are three outcomes rather than two. A file
matching both is SigLIP 2's convention. One that prepends <bos> is an
EmbeddingGemma-family file and belongs on the profiles above. A file failing
either check β no padding block, "BatchLongest", or a post_processor
that is missing, empty, or does not end in a special token β is not classified
at all: it is read with Gemma's convention and nothing is raised.
So do not read the absence of an error as approval. A SigLIP 2 export that declares both blocks is caught, but one that dropped either β a re-export, or a tool that strips them β reaches the Gemma path and produces exactly the silently wrong vector this section warns about.
Wire it yourself:
import 'package:flutter_gemma_embeddings/embedding_tokenizer.dart'
show loadSiglipSentencePieceEmbeddingTokenizer;
and pass that as the tokenizer factory of the ForwardPassDescriptor you give
to CommonEmbeddingModel.create. That library is native-only. See the
flutter_gemma_embeddings README
for the full profile, and ONNX Runtime for why the factory
declines to guess.