Add embeddinggemma-300m
Adds google/embeddinggemma-300m as a built-in model, usable as ts/embeddinggemma-300m.
Blocked on typesense/typesense#3038. This model needs the
gemmatokenizer type added there; until that ships,model_type: "gemma"is rejected byvalidate_and_init_local_model. Opening this now so both halves can be reviewed together.
EmbeddingGemma is a 300M-parameter multilingual model trained on 100+ languages, at 768 dims — a third of the size of the multilingual-e5-large / bge-m3 class and the smallest capable multilingual option in the list.
Files
| File | Notes |
|---|---|
embeddinggemma-300m/model.onnx |
exported from google/embeddinggemma-300m |
embeddinggemma-300m/model.onnx_data |
external weights, 1.23 GB |
embeddinggemma-300m/tokenizer.model |
the Gemma SentencePiece vocab, unmodified |
embeddinggemma-300m/config.json |
model_type: gemma, with the model's own prompts as prefixes |
.gitattributes gains an LFS rule for embeddinggemma-300m/model.onnx_data, matching the existing multilingual-e5-large/model.onnx_data line.
About the export
EmbeddingGemma's sentence embedding is not the raw token output. Its pipeline is mean pooling → Dense(768→3072) → Dense(3072→768) → L2 normalise, and the Dense layers come after pooling, so they cannot be left to TextEmbedder::mean_pooling.
The graph therefore ends with the whole pipeline baked in, then broadcasts the finished 768-d vector across the sequence axis. Mean pooling over the attention mask returns that vector unchanged, so Typesense gets the real sentence embedding without any change to the pooling code. This is the same approach used for bge-m3 in #17.
One compatibility note for whoever regenerates this: torch.onnx.export produces IR version 10, and the pinned onnxruntime (rel-1.14.1) accepts at most IR 8. Setting ir_version = 8 before saving is enough — opset 18 itself is fine.
Prefixes
EmbeddingGemma ships asymmetric prompts. Mapped onto Typesense's prefixes, with the trailing space included since prefixes are concatenated raw:
indexing_prefix:"title: none | text: "(the model'sdocumentprompt)query_prefix:"task: search result | query: "(the model'squeryprompt)
Testing
The ONNX was loaded into typesense/typesense:31.0.rc14: the session builds, num_dim resolves to 768, documents index, and nothing is logged as an error. That build has no gemma tokenizer yet, so the full path was verified by feeding the ids produced by the compiled GemmaTokenizer from #3038 through this ONNX and applying TextEmbedder::mean_pooling:
- cosine 1.000000 against
sentence-transformerson 15 inputs — Latin, Chinese, Cyrillic, Arabic and Japanese text, emoji, mixed case, whitespace, numerics, and a 3603-token input exercising truncation. - cosine 1.000000 for a padded batch of 8, exercising
batch_encode. - cosine 1.000000 on the query path, comparing
query_prefix + qagainstprompt_name="query".
The document path lands at 0.991–0.997 rather than 1.000000. That is not the export: src/index.cpp:8348 appends " " after each field value, and removing that single trailing space gives exactly 1.000000. It applies to every model in this repo equally, so I have left it alone.
One note for maintainers
data_md5 is required for any model with external weights — without it download_public_model re-downloads the weights on every start — but the README's config table does not list it. Same note as on #17.
🤖 Generated with Claude Code
Correcting a bug in the config.json I first pushed here, and adding real verification.
Prefixes must not carry a trailing space. EmbedderManager::get_indexing_prefix and get_query_prefix append one themselves:
if(!val.empty()) {
val += " ";
}
I had copied EmbeddingGemma's prompts verbatim, including their trailing space, which produced "title: none | text: " with two spaces. Embeddings came out at cosine 0.983-0.997 against sentence-transformers instead of exact. Fixed in 21e7276 - the prefixes now end without a space, matching how the existing e5 entries are written ("passage:", not "passage: ").
Verified against a real build. I built typesense-server from typesense/typesense#3038 and ran it with these exact files in <data-dir>/models/embeddinggemma-300m/:
- collection creates,
num_dimresolves to 768 - embeddings match
sentence-transformersat cosine 1.000000 on six documents spanning Latin, Chinese and German text - cross-lingual search resolves correctly: "What is the capital of Japan?" ranks the Chinese sentence first at distance 0.411, "Wo ist der Hund?" ranks the German one at 0.559, "programozási nyelv" ranks the English Python sentence at 0.622
Worth noting for the README's config table: the trailing-space behaviour of the two prefix keys is not documented anywhere, and it is easy to get wrong in exactly the way I did.