flutter_gemma_embeddings
The embedding tokenizers for flutter_gemma: Gemma SentencePiece and BERT-family WordPiece, plus the task-type prefixing and the routing that picks between them. Android, iOS, macOS, Linux, Windows, Web.
Since 2.2.0 this is all it is. The seam an engine implements, the
background-isolate worker and the pooling moved into flutter_gemma itself, so
an engine package can implement embeddings without depending on this one — and
no engine does. What lives here is the part that cannot move: the tokenizer
implementations, which pull dart_sentencepiece_tokenizer and must stay out of
core's dart2wasm-clean graph.
Your app registers them, beside the backend that consumes them:
await FlutterGemma.initialize(
embeddingBackends: [LiteRtEmbeddingBackend()], // from an engine package
embeddingTokenizers: [GemmaEmbeddingTokenizers()],
);
Teach your AI assistant this package
dart run skills@ get --all
Installs the agent skills flutter_gemma bundles — this package depends on it, so they come with it. One of them, flutter-gemma-rag, covers embedding models, the vector stores, and metadata filters.
Usage
import 'package:flutter_gemma/flutter_gemma.dart';
import 'package:flutter_gemma_embeddings/flutter_gemma_embeddings.dart';
import 'package:flutter_gemma_litertlm/flutter_gemma_litertlm.dart';
await FlutterGemma.initialize(
embeddingBackends: [LiteRtEmbeddingBackend()],
embeddingTokenizers: [GemmaEmbeddingTokenizers()],
);
Two lists, because they answer different questions. The backend is the engine that turns token ids into a vector; the tokenizer is what turns text into those ids, and which one a model needs is a property of the MODEL — EmbeddingGemma is SentencePiece whether LiteRT or ONNX Runtime runs it. Keeping them apart is why neither engine package depends on this one, and why an app that never embeds anything resolves neither.
Forget the second list and the first embedding throws a StateError naming the
package to add — it never silently falls back to a tokenizer with the wrong
convention.
LiteRtEmbeddingBackend provides the embedding model used by the auto-embedding
RAG methods (addDocument / searchSimilar) and by createEmbeddingModel. Pair
it with a vector store from flutter_gemma_rag_sqlite or
flutter_gemma_rag_qdrant.
Web setup
The web embedding bundle moved to flutter_gemma_litertlm in its 1.8.0 — it is
LiteRT.js, and it belongs with the package named after it. See
flutter_gemma_litertlm's web setup.
This package has no web assets of its own: on web its tokenizers run only for
backends that tokenize in Dart (WordPiece), while the LiteRT web arm tokenizes
inside sentencepiece.js.
Platforms
| Platform | Support |
|---|---|
| Android / iOS | ✅ (via flutter_gemma_litertlm's FFI backend) |
| macOS / Linux / Windows | ✅ (via flutter_gemma_litertlm's FFI backend) |
| Web | ✅ (via flutter_gemma_litertlm's LiteRT.js backend, CDN) |
This package itself is pure Dart with no native/FFI code — the concrete backend (and its native library) is owned by whichever engine package you add.
Tokenizer profiles
Three adapters, picked by the model you load — a model's special-token convention is not negotiable, and using the wrong one corrupts the vector silently rather than failing:
| profile | convention | loader |
|---|---|---|
| Gemma (SentencePiece) | BOS 2, EOS 1, TaskType prefix | loadGemmaSentencePieceEmbeddingTokenizer |
| WordPiece (BERT / MiniLM) | [CLS] … [SEP] |
WordPieceEmbeddingTokenizer.fromJsonString / .fromPath |
| SigLIP2 text tower | no BOS, one trailing EOS, lowercased, fixed 64-token width | loadSiglipSentencePieceEmbeddingTokenizer |
SigLIP2's ONNX export carries no attention_mask, and its head does not pool
over the sequence at all — Siglip2TextModel takes last_hidden_state[:, -1, :],
the LAST position, which with right-padding is a pad token. So the 64-token width
has to live in the ids: the adapter pads (and truncates) to exactly 64 itself
rather than leaving it to the forward pass, and the pad id is first-order rather
than cosmetic.
WordPieceEmbeddingTokenizer.fromPath reads from disk and is native only;
on web it throws UnsupportedError — fetch the tokenizer.json yourself there
and use fromJsonString.
Known limitation: the SigLIP2 profile is not selected automatically
The tokenizer router here has three outcomes — WordPiece, a SigLIP2 refusal, or Gemma.
A SigLIP2 tokenizer.json is BPE, so it would fall through to the Gemma
adapter, which injects BOS, skips the lowercasing and does not pad to 64: every
id in range, nothing thrown, and a vector that is quietly the wrong point in the
embedding space. The loader therefore refuses such a file rather than
embedding it wrongly.
Until a profile selector lands, reach the adapter by building the
ForwardPassDescriptor yourself, with
loadSiglipSentencePieceEmbeddingTokenizer as its tokenizer factory:
import 'package:flutter_gemma_embeddings/embedding_tokenizer.dart'
show loadSiglipSentencePieceEmbeddingTokenizer;
That library is native-only, which is why it sits outside the package barrel.
How the file is recognised. SigLIP2 and Gemma share a vocabulary, but their
tokenizer.json files differ where it counts: SigLIP2 declares
"padding": {"strategy": {"Fixed": 64}, "pad_id": 0} and a post_processor of
[Sequence A, <eos>], while a Gemma tokenizer has "padding": null and
[<bos>, Sequence A]. isSiglip2TokenizerJson
(package:flutter_gemma_embeddings/tokenizer_convention.dart — web-safe, so an
engine's web arm can apply the same rule) reads exactly those two blocks, and
requires both: plenty of models declare one alone.
What the tokenizer loaders return
Both loaders return a tokenizer with any padding and truncation the file
declares switched OFF: encode() gives back bare content, never a fixed-width
row. Width and terminators belong to the profile — encodeForSiglipEmbedding
applies SigLIP2's 64-token rule itself — and to the forward pass, which owns the
engine's compiled seqLen.
This matters if you build a ForwardPassDescriptor by hand: do not expect the
file's "padding": {"strategy": {"Fixed": 64}} to have been applied for you.
The loaders throw StateError if the resolved dart_sentencepiece_tokenizer
accepts the calls that disable those settings and leaves them set anyway. That
is a check on the tokenizer's config, not a guarantee about encode()'s output.
The task-type prefix
TaskType has two values and both prefixes are non-empty, and
generateEmbedding defaults to TaskType.retrievalQuery — so no caller can
embed without one. That is the intended contract for Gemma/Gecko.
It is not for a CLIP-family tower, whose vision side encodes an image with no prefix at all, so the SigLIP2 profile drops it. Honoring one would put query and document of the same string on two different points, both off the space the two towers exist to share.
WordPieceEmbeddingTokenizer (MiniLM and other BERT-style models) still
concatenates the prefix. Those models are not trained with EmbeddingGemma's
prefixes either; making that configurable belongs with the profile selector.
Building a new engine backend
Implement EmbeddingForwardPass (load/run/close/outputDimension/
inputSequenceLength) for your engine, expose a top-level factory tear-off
for it, and build an EmbeddingBackendProvider that calls
CommonEmbeddingModel.create(descriptor: ForwardPassDescriptor(...), tokenizerPath: ...).
Declare EmbeddingOutputContract.pooledFinal if your engine's forward pass
already returns the final embedding, or .tokenLevel if it returns raw
per-token hidden states for this package's meanPoolAndNormalize to pool.
See flutter_gemma_litertlm's lib/src/embedding/ for a worked example.
Troubleshooting
dlopen / "library not found" (libLiteRtLm)
flutter_gemma_litertlm is the sole owner of the shared native library and
bundles it via its build hook. A stale Native-Assets cache after a native
version bump can leave the library unbundled, surfacing as an opaque dlopen
"no such file" on the first embedding call. Fix with a clean rebuild:
flutter clean
rm -rf ~/Library/Caches/flutter_gemma/native # macOS
rm -rf ~/.cache/flutter_gemma/native # Linux
# Windows: rmdir /s "%LOCALAPPDATA%\flutter_gemma\native" (path may vary)
flutter pub get
Libraries
- embedding_tokenizer
- Public, native-only export of the Gemma SentencePiece EmbeddingTokenizer
adapter (
loadGemmaSentencePieceEmbeddingTokenizer,loadEmbeddingTokenizer,encodeForEmbedding) for native-only leaves that need to import it from a file that is itself native-only (never reached on web) — engine packages' embedding backends (e.g.flutter_gemma_litertlm'sLiteRtEmbeddingBackend). - flutter_gemma_embeddings
- The embedding tokenizers for flutter_gemma.
- tokenizer_convention
- Public export of the
tokenizer.jsonconvention reader, for engine packages that route a tokenizer file to an adapter and need to know which convention the file declares before building anything. - wordpiece_embedding_tokenizer
- Public, native-only export of WordPieceEmbeddingTokenizer for
native-only leaves that need to import it from a file that is itself
native-only (never reached on web) — engine packages' embedding backends
(e.g.
flutter_gemma_onnx's ONNX tokenizer loader, which sniffs atokenizer.jsonand routes to either this WordPiece adapter or the Gemma SentencePiece one inembedding_tokenizer.dart).