Blog / Coding tips

EmbeddingGemma 2 Tutorial: Run Google's New Open Embedding Model Locally in Python (Tested Semantic Search)

Cover image for EmbeddingGemma 2 Tutorial: Run Google's New Open Embedding Model Locally in Python (Tested Semantic Search)

EmbeddingGemma 2 is Google DeepMind's new open embedding model. It turns text (including code), images, video and audio into 768-number vectors in one shared space, so you can search across them by meaning. It has 740M parameters in total, but you can load only the 270M text part. It's released under Apache 2.0 and runs on a laptop CPU. In Python, install sentence-transformers, load google/embeddinggemma-2, and call model.encode() with the right prompt name.

Google announced it on 6 October 2026, and it quickly became one of the most-liked models on Hugging Face, with over 1,800 likes and more than 115,000 downloads by 12 October. This tutorial goes beyond the model card's quick start: I installed it in a fresh virtual environment on Linux, hit two missing-dependency errors (fixes below), and built a small tested semantic search.

What is EmbeddingGemma 2?

From the EmbeddingGemma 2 model card and Google's launch post:

  • Multimodal: text, images, video and audio, and mixtures of them, map into a single 768-dimensional vector space.
  • Modular size: 740M parameters in total, made up of a 270M text model (130M transformer plus 140M embedder) plus optional encoders for vision (170M) and audio (300M).
  • Multilingual and code-aware: understands 100+ languages.
  • Matryoshka Representation Learning (MRL): vectors can be shortened to 512, 256 or 128 dimensions.
  • 8K context: 8,192 tokens shared across all inputs.
  • Licence: Apache 2.0, and it's built on the Gemma 4 architecture.

An embedding model doesn't generate text. It produces vectors you can compare: similar meanings give vectors that point in similar directions. That's the engine behind semantic search, RAG (retrieval-augmented generation), clustering, duplicate detection and recommendations.

EmbeddingGemma 2 vs EmbeddingGemma 1

The original EmbeddingGemma handled text only. According to the model card's benchmark table:

  • Multilingual text (MTEB v2): 61.36, roughly the same as EmbeddingGemma 1's 61.15.
  • Code (MTEB code v1): 78.68, up from 68.76. That's the biggest measurable gain.
  • New modalities: image, video, visual document and audio benchmarks, which the first model didn't support at all.

If you only need text search, the main reasons to upgrade are better code retrieval and the longer 8K context window. If you need to search images, audio or video with text, it's a new capability.

Installing EmbeddingGemma 2 in Python

I used a fresh virtual environment with Python 3.13 on Linux, CPU only. The model card says:

pip install -U sentence-transformers transformers

That installed sentence-transformers 6.1.0, transformers 5.19.0 and torch 2.14.1 for me. But loading the model then failed, even for text-only use:

ImportError: EmbeddingGemma2Processor requires the PIL library but it was not found in your environment. You can install it with pip:
`pip install pillow`.
...
To install the required dependencies, run:
pip install -U "sentence-transformers[image]"

After installing Pillow, the next attempt failed on another import:

ModuleNotFoundError: No module named 'torchvision'

The model's processor imports the Gemma 4 image processing code, which needs both packages even if you never pass an image. The working install was:

python -m venv .venv
source .venv/bin/activate
python -m pip install -U sentence-transformers transformers pillow torchvision

With Pillow 12.3.0 and torchvision 0.29.1 added, everything loaded. If you see other "No module named" errors, my guide to ModuleNotFoundError in Python explains how to check you're installing into the right environment.

The first load downloads the weights from Hugging Face. On my machine, the cached model took 1.5 GB of disk space.

Loading only the text model

The vision and audio encoders are optional. The model card shows how to skip them with config_kwargs, which reduces the effective size to 270M parameters:

from sentence_transformers import SentenceTransformer
import torch

model = SentenceTransformer(
    "google/embeddinggemma-2",
    config_kwargs={"vision_config": None, "audio_config": None},
    model_kwargs={"torch_dtype": torch.float32},
)
print(sum(p.numel() for p in model.parameters()))

My run printed 271,002,624 parameters, matching the card's 270M text-only figure, and loaded in about 11 seconds on CPU. For text and images only, pass just {"audio_config": None} (440M); for text and audio, {"vision_config": None} (570M).

Don't use float16

The model card warns that EmbeddingGemma 2's activations exceed the range of float16, and in that format "the model returns NaN or silently degraded embeddings rather than raising an error". Use bfloat16 on hardware that supports it, or float32 elsewhere, which includes most CPUs.

Your first semantic search (tested)

The model is trained with short task prefixes. sentence-transformers applies them for you through prompt_name. For search, use SearchQuery for the query and Document for the texts you're searching:

docs = [
    "To remove 20% VAT from a price, divide it by 1.2.",
    "Use git stash pop to reapply your most recent stash and drop it.",
    "A passkey is a FIDO credential that replaces a password with a key pair.",
    "Python raises ModuleNotFoundError when it cannot find an imported package.",
]
query = "how do I take VAT off a price"
q = model.encode(query, prompt_name="SearchQuery")
d = model.encode(docs, prompt_name="Document")
print("dims:", q.shape, d.shape)
scores = model.similarity(q, d)[0]
for s, doc in sorted(zip(scores.tolist(), docs), reverse=True):
    print(f"{s:.3f}  {doc}")

Output:

dims: (768,) (4, 768)
0.884  To remove 20% VAT from a price, divide it by 1.2.
0.613  Use git stash pop to reapply your most recent stash and drop it.
0.570  Python raises ModuleNotFoundError when it cannot find an imported package.
0.538  A passkey is a FIDO credential that replaces a password with a key pair.

The query shares only the word "VAT" with the top result. "Take VAT off" and "remove VAT" match by meaning, which is the point of embeddings. The VAT sentence comes from my guide on how to calculate VAT in the UK.

Which prompt names should I use?

The model card lists task prefixes. Here are the ones you'll use most, with the text each one adds:

  • SearchQuery: task: search result | query: (pair with Document for the corpus)
  • Document: title: none | text:
  • QuestionAnswering: task: question answering | query:
  • FactChecking: task: fact checking | query:
  • CodeRetrieval: task: code retrieval | query:
  • Classification, Clustering, SentenceSimilarity: symmetric tasks, so apply the same prompt to every input

Printing sorted(model.prompts) on my install also listed names like Retrieval, STS, Reranking and Summarization.

For retrieval, the query and documents get different prefixes ("asymmetric"). For similarity or clustering, every input gets the same prefix. If your documents have real titles, the card recommends formatting them yourself as title: {title} | text: {content} instead of using prompt_name="Document", which always uses title: none.

Shrinking vectors with Matryoshka (truncate_dim)

Storing 768 floats per document adds up. Because of MRL training, you can keep only the first 512, 256 or 128 numbers. The model card stresses two rules: re-normalise after truncating, and use the same dimension for queries and documents. sentence-transformers handles both:

q256 = model.encode(query, prompt_name="SearchQuery", truncate_dim=256, normalize_embeddings=True)
d256 = model.encode(docs, prompt_name="Document", truncate_dim=256, normalize_embeddings=True)
print("256d:", q256.shape, [round(x, 3) for x in model.similarity(q256, d256)[0].tolist()])
256d: (256,) [0.894, 0.616, 0.551, 0.585]

At 256 dimensions (a third of the storage), the VAT document is still clearly first. The card's benchmarks show the cost: MTEB English drops from 68.46 at 768d to 67.78 at 256d, and 65.68 at 128d. Google describes quality as close to lossless down to 256 dimensions, and warns that 128d "degrades multimodal quality substantially", so test 128d on your own data first.

Code search with CodeRetrieval

The biggest benchmark gain was in code retrieval, so I tried a quick natural-language search over three snippets:

code = [
    "def add_vat(net): return round(net*1.2, 2)",
    "function debounce(fn, ms) { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); }; }",
    "with open('data.csv') as f: rows = list(csv.reader(f))",
]
q = model.encode("wait until the user stops typing before calling a function", prompt_name="CodeRetrieval")
d = model.encode([f"title: snippet | text: {c}" for c in code])
print([round(x, 3) for x in model.similarity(q, d)[0].tolist()])
[0.649, 0.761, 0.645]

The debounce function wins, even though the query never says "debounce", "timeout" or "JavaScript". If the concept is new to you, my JavaScript debounce function example explains it. With real codebases, use file names as titles (title: utils.py | text: ...), as the model card suggests.

Multimodal inputs: images, video and audio

With the full model loaded, you can embed media and compare it with text. The model card's example interleaves text and media using placeholder tokens:

emb = model.encode({
    "text": "Waterproof running shoes. <|image|> Grip test on wet rock: <|video|>",
    "image": ["shoe.jpg"],
    "video": "demo.mp4",
})

I only tested text in this tutorial. The card's budgets are useful when planning: an image costs 280 tokens by default (about 29 images per 8K context), a video frame 140 tokens at 1 frame per second, and audio 25 tokens per second (about 327 seconds). Audio should be mono at 16 kHz, and media inputs don't take a task prefix.

Where to run EmbeddingGemma 2

Google's launch post lists many runtimes besides sentence-transformers: transformers, MLX, vLLM, llama.cpp, SGLang, Ollama and LM Studio for serving, transformers.js and WebGPU for browsers, and MediaPipe and LiteRT for mobile apps. GGUF versions for llama.cpp-style tools also appeared on Hugging Face within days of release. For storing vectors, a dedicated vector database helps once you pass a few thousand documents; for small projects, a NumPy array saved to disk is enough.

Tips for using EmbeddingGemma 2 well

  • Always use the prompts. The card says omitting them still works but reduces precision.
  • Embed documents once and save them. Only queries need encoding at search time.
  • Keep your data local. Running the model yourself means documents never leave your machine, which matters for private notes or company code.
  • Be careful with API keys if you do use hosted models. Don't put them in front-end code.
  • Check its training cutoff: the card says its pre-training data runs to January 2025.

FAQ

What is EmbeddingGemma 2?

An open embedding model from Google DeepMind, released in October 2026. It turns text, code, images, video and audio into 768-dimensional vectors in one shared space, for search, RAG, classification and clustering.

How do I use EmbeddingGemma 2 in Python?

Install sentence-transformers, transformers, pillow and torchvision, load SentenceTransformer("google/embeddinggemma-2"), then call model.encode() with prompt_name="SearchQuery" for queries and "Document" for documents.

Can I use EmbeddingGemma 2 for text only?

Yes. Pass config_kwargs={"vision_config": None, "audio_config": None} when loading. That loads only the 270M-parameter text model, which my test confirmed at 271,002,624 parameters.

Why do I get "EmbeddingGemma2Processor requires the PIL library"?

The model's processor imports image-processing code even for text-only use. Install Pillow, and torchvision too, since the next error is otherwise "No module named 'torchvision'".

Is EmbeddingGemma 2 free for commercial use?

It's released under the Apache 2.0 licence, which allows commercial use. Deployments must also follow the Gemma Prohibited Use Policy, according to the model card.

What does truncate_dim do in EmbeddingGemma 2?

It keeps only the first 512, 256 or 128 numbers of each vector to save storage. Use normalize_embeddings=True with it, and the same dimension for queries and documents.

Does EmbeddingGemma 2 run on CPU?

Yes. It loaded in about 11 seconds and encoded short texts quickly on a normal CPU in my test. Use float32 on CPU and never float16, which the model card says can produce NaN results.

How is EmbeddingGemma 2 better than the first version?

It adds images, video and audio, quadruples the context to 8K tokens, and scores 78.68 on the MTEB code benchmark compared with 68.76, while matching the first model on multilingual text.

// note

How to read this note.

This is a learning note from studying the web. It is one small topic, written so I can remember it. It is not a course and not a claim that I have finished the subject.

If a sentence is wrong, say so from the contact page and name this title. Drafts never appear here. Related notes, when they exist, are other published posts, and the same sample rule applies to each of them.

ModuleNotFoundError: No module named in Python: 10 Causes and Fixes (pip, venv, VS Code)
Coding tips

ModuleNotFoundError: No module named in Python: 10 Causes and Fixes (pip, venv, VS Code)

Python raises ModuleNotFoundError when it can’t find a module in the interpreter that’s running your code. The 10 causes I reproduced, from installing with the wrong pip to cv2/bs4/yaml package names, venvs, VS Code, Jupyter, local imports and externally-managed-environment, each with the exact fix.

October 12, 2026 · 13 min read · 2 views

Python Lazy Imports: How the New lazy Keyword Works in Python 3.15
Coding tips

Python Lazy Imports: How the New lazy Keyword Works in Python 3.15

Python 3.15 adds the lazy soft keyword: lazy import json loads the module only when you first use it. Tested examples of the startup saving, where lazy isn’t allowed, how errors appear, -X lazy_imports=all, filters and __lazy_modules__.

October 12, 2026 · 11 min read · 0 views