EmbeddingGemma 2: Multimodal Embeddings Across Text, Code, and Media
EmbeddingGemma 2 unifies text, code, images, video, and audio in one shared embedding space. This guide covers its architecture, benchmarks, and practical usage
Imagine building a search engine for your company’s knowledge base. Someone searches for a bug described in a Slack message, but the answer is buried in a code file. Another remembers a chart from a presentation but not its title. A third wants to find a video using a text description.
To make these searches work, an embedding model must understand the meaning of content and represent it as numerical vectors that can be compared. Google’s original EmbeddingGemma focused on compact text embeddings. EmbeddingGemma 2 takes this further by bringing text, code, images, video, and audio into one shared embedding space. It also lets developers choose which encoders to load and reduce vector dimensions to save storage.
But do these improvements translate into better retrieval and lower storage costs? Let’s explore the model and test its capabilities.
Key Features of EmbeddingGemma 2
| Specification | EmbeddingGemma 2 |
|---|---|
| Base model | Gemma 4 |
| License | Apache 2.0 |
| Output dimension | 768, truncatable to 512, 256, 128 |
| Context window | 8,192 tokens, shared across modalities |
| Modalities | Text, code, images, video, audio |
| Parameters | 270M text-only to 740M full multimodal |
| Released | October 6, 2026 |
Architecture of EmbeddingGemma 2
The usual way to search across mixed content is to run one model per content type and stitch the results together afterwards. That means separate indexes, separate score ranges, and no reliable way to compare a photo against a paragraph.
EmbeddingGemma 2 removes that problem by sending every content type through one backbone. Each modality gets its own front-end encoder, but the output always lands in the same 768-dimensional space.
Architecture diagram, Google Developers Blog.
3 Encoders
| Encoder | Size | Handles | Built on |
|---|---|---|---|
| Text and code | 270M, the base | Plain text and source code, up to 8,192 tokens | An adapted Gemma 4 decoder |
| Vision | +170M | Photos, charts, slides, PDF pages, and video frames | A dedicated vision encoder |
| Audio | +300M | Speech and ambient sound, fed in as raw audio | A dedicated audio encoder |
The text encoder is the base and is always present. Vision and audio are additions on top of it, which is where the four parameter counts come from.
Modular Loading

One checkpoint, four ways to load it.
You are not choosing between four different models. There is one set of weights, and you decide at load time which encoders get read into memory. Skipped encoders cost nothing, on disk or in RAM.
Because the output space is identical in all four cases, a query embedded with the 270M text setup can be matched against documents embedded with the full 740M model. Two practical consequences:
- Start text-only and add images later without re-embedding anything you already indexed.
- Run a small config on an edge device and a large one on a server, against the same index.
Pairing with Gemma 4
If you run Gemma 4 as the generator in an on-device RAG stack, the two models share a text tokenizer and the same audio encoder design. That overlap is loaded once rather than twice, which matters when the constraint is phone memory rather than server memory.
Benchmarks and Evaluation
What Google Published
| Benchmark | Result |
|---|---|
| MTEB (Code) | 14% higher than EmbeddingGemma 1 |
| Multilingual text | Retains EmbeddingGemma 1 accuracy |
| Image, video, audio retrieval | New capability, not present in version 1 |
Google published the MTEB (Code) gain as a percentage improvement rather than absolute scores, so there is no single number to compare against other models directly.
Quality Retention Under Truncation
| Dimension | Text and code | Image, video, speech | Storage per 1M vectors |
|---|---|---|---|
| 768d | baseline | baseline | 1,465 MB |
| 512d | no stated loss | no stated loss | 977 MB |
| 256d | near baseline | roughly 95% | 488 MB |
| 128d | roughly 90% | roughly 75% | 244 MB |
Storage figures assume bfloat16, and the media columns fall away faster than text does. Google flags 128d multimodal as the case to test before you ship it, and the 75% figure explains why.
What This Run Measured
Results from the five scripts below, on a small test corpus. These are measurements on one corpus, not benchmark scores.
| Measurement | Result |
|---|---|
| 256d versus 768d ranking | Identical top-1 and MRR |
| 128d ranking | Top-1 held, MRR fell from 0.667 to 0.656 |
| Mean similarity at 128d | Rose 12.6% versus 768d despite identical ranking |
| Image cross-modal margin | 0.1154 over the nearest wrong query |
| Audio cross-modal margin | 0.0137 over the nearest wrong query |
| Default output precision | float32, not bfloat16, so indexes are double the quoted size |
Getting Started
First install sentence-transformers and other relevant libraries:
pip install -U sentence-transformers[image,audio,video] transformers
Requires sentence-transformers 6.1.0 or later.
Step 0: Check Your Setup
Reports Python version, library version, available modalities, and hardware before you download 740M parameters.
File: 00_check_setup.py
import sys
import importlib
import shutil
def ok(b):
return "OK " if b else "-- "
print("=" * 66)
print("EmbeddingGemma 2 setup check")
print("=" * 66)
print(f"\nPython {sys.version.split()[0]} (3.9+ needed)")
# --- core library -------------------------------------------------
try:
import sentence_transformers
st_version = sentence_transformers.__version__
st_ok = tuple(int(x) for x in st_version.split(".")[:2]) >= (6, 1)
print(f"{ok(st_ok)}sentence-transformers {st_version} (6.1.0+ needed)")
except ImportError:
print("-- sentence-transformers not installed")
# --- optional modality dependencies -------------------------------
for pkg, label in [
("PIL", "Pillow (images)"),
("soundfile", "soundfile (audio)"),
("decord", "decord (video)"),
]:
try:
importlib.import_module(pkg)
print(f"OK {label}")
except ImportError:
print(f"-- {label} not found")
# --- hardware -----------------------------------------------------
try:
import torch
if torch.cuda.is_available():
print(f"OK CUDA {torch.version.cuda} ({torch.cuda.get_device_name(0)})")
else:
print("-- CUDA not available, will use CPU")
except ImportError:
print("-- torch not installed")
Step 1: Text and Code Retrieval
Embeds a small corpus of sentences and source-code snippets, runs four natural-language queries, and prints ranked results with cosine scores.
File: 01_text_code_retrieval.py
from sentence_transformers import SentenceTransformer
import torch
model = SentenceTransformer(
"google/embeddinggemma-2-text",
trust_remote_code=True,
)
corpus = [
"Machine learning is a subset of artificial intelligence.",
"def quicksort(arr): return arr if len(arr)<=1 else quicksort([x for x in arr[1:] if x<=arr[0]])+[arr[0]]+quicksort([x for x in arr[1:] if x>arr[0]])",
"Python supports multiple programming paradigms.",
"Neural networks are inspired by biological neural structures.",
"SELECT * FROM users WHERE active = 1 ORDER BY created_at DESC;",
"Docker containers provide isolated environments for applications.",
"The transformer architecture revolutionized natural language processing.",
"git commit -m 'feat: add multimodal embedding support'",
]
queries = [
"sorting algorithm implementation",
"database query for active users",
"AI and deep learning concepts",
"containerization technology",
]
corpus_embs = model.encode(corpus, convert_to_tensor=True)
query_embs = model.encode(queries, convert_to_tensor=True)
from sentence_transformers.util import cos_sim
scores = cos_sim(query_embs, corpus_embs)
for i, q in enumerate(queries):
ranked = scores[i].argsort(descending=True)
print(f"\nQuery: {q}")
for rank, idx in enumerate(ranked[:3], 1):
print(f" {rank}. [{scores[i][idx]:.4f}] {corpus[idx][:80]}")
Step 2: Cross-Modal Search
Embeds sample images and audio files alongside text queries, then retrieves the correct media item for each query using cosine similarity.
File: 02_cross_modal_search.py
from sentence_transformers import SentenceTransformer
from sentence_transformers.util import cos_sim
import requests
from PIL import Image
from io import BytesIO
import numpy as np
model = SentenceTransformer(
"google/embeddinggemma-2",
trust_remote_code=True,
)
# Load sample images from URLs
image_urls = [
"https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Bikesgray.jpg/640px-Bikesgray.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/280px-PNG_transparency_demonstration_1.png",
"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/Camponotus_flavomarginatus_ant.jpg/640px-Camponotus_flavomarginatus_ant.jpg",
]
images = []
for url in image_urls:
response = requests.get(url)
img = Image.open(BytesIO(response.content)).convert("RGB")
images.append(img)
image_labels = ["bicycles", "transparency demo", "ant"]
text_queries = [
"two wheeled vehicles parked outside",
"checkered pattern showing transparency",
"insect with six legs",
]
image_embs = model.encode(images, convert_to_tensor=True)
query_embs = model.encode(text_queries, convert_to_tensor=True)
scores = cos_sim(query_embs, image_embs)
print("Cross-modal image retrieval results:")
for i, query in enumerate(text_queries):
best = scores[i].argmax().item()
print(f"\nQuery: '{query}'")
for j, label in enumerate(image_labels):
marker = "<-- top match" if j == best else ""
print(f" [{scores[i][j]:.4f}] {label} {marker}")
Step 3: Matryoshka Truncation
Generates embeddings at 768, 512, 256, and 128 dimensions from a single forward pass and reports how ranking and mean similarity change at each size.
File: 03_matryoshka_truncation.py
from sentence_transformers import SentenceTransformer
from sentence_transformers.util import cos_sim
import torch
model = SentenceTransformer(
"google/embeddinggemma-2-text",
trust_remote_code=True,
)
corpus = [
"Machine learning algorithms learn patterns from data.",
"def binary_search(arr, target): lo, hi = 0, len(arr)-1 ...",
"Kubernetes orchestrates containerized applications at scale.",
"Transformers use self-attention to process sequences.",
"SELECT name, COUNT(*) FROM orders GROUP BY name;",
]
query = "how do neural networks process sequential data"
dimensions = [768, 512, 256, 128]
print(f"Query: {query}\n")
print(f"{'Dim':<6} {'Top-1 match':<45} {'Score':>6} {'MRR':>6}")
print("-" * 70)
for dim in dimensions:
trunc_model = SentenceTransformer(
"google/embeddinggemma-2-text",
trust_remote_code=True,
truncate_dim=dim,
)
q_emb = trunc_model.encode([query], convert_to_tensor=True)
c_embs = trunc_model.encode(corpus, convert_to_tensor=True)
scores = cos_sim(q_emb, c_embs)[0]
ranked = scores.argsort(descending=True)
# MRR: find rank of the "correct" answer (index 3, transformer sentence)
correct_idx = 3
rank_of_correct = (ranked == correct_idx).nonzero(as_tuple=True)[0].item() + 1
mrr = 1.0 / rank_of_correct
top_match = corpus[ranked[0]][:43]
top_score = scores[ranked[0]].item()
print(f"{dim:<6} {top_match:<45} {top_score:>6.4f} {mrr:>6.3f}")
Step 4: Code Search
Indexes a set of Python function definitions and retrieves the best match for plain-English descriptions of what each function does.
File: 04_code_search.py
from sentence_transformers import SentenceTransformer
from sentence_transformers.util import cos_sim
model = SentenceTransformer(
"google/embeddinggemma-2-text",
trust_remote_code=True,
)
code_corpus = [
"def bubble_sort(arr):\n n = len(arr)\n for i in range(n):\n for j in range(0, n-i-1):\n if arr[j] > arr[j+1]:\n arr[j], arr[j+1] = arr[j+1], arr[j]\n return arr",
"def fibonacci(n):\n if n <= 1:\n return n\n return fibonacci(n-1) + fibonacci(n-2)",
"def connect_db(host, port, dbname):\n conn = psycopg2.connect(host=host, port=port, dbname=dbname)\n return conn",
"class LinkedList:\n def __init__(self):\n self.head = None\n def append(self, data):\n new_node = Node(data)\n if not self.head:\n self.head = new_node\n return\n current = self.head\n while current.next:\n current = current.next\n current.next = new_node",
"def send_email(to, subject, body):\n msg = MIMEText(body)\n msg['Subject'] = subject\n msg['To'] = to\n smtp.sendmail(FROM, to, msg.as_string())",
]
nl_queries = [
"sort a list of numbers from smallest to largest",
"compute the nth number in a mathematical sequence",
"establish a connection to a PostgreSQL database",
"implement a singly linked list data structure",
"send an electronic mail message",
]
code_embs = model.encode(code_corpus, convert_to_tensor=True)
query_embs = model.encode(nl_queries, convert_to_tensor=True)
scores = cos_sim(query_embs, code_embs)
print("Natural language to code retrieval:\n")
correct = 0
for i, query in enumerate(nl_queries):
best = scores[i].argmax().item()
is_correct = best == i
if is_correct:
correct += 1
status = "CORRECT" if is_correct else f"WRONG (got {best})"
print(f"Query: {query}")
print(f" Match: [{scores[i][best]:.4f}] {code_corpus[best][:60]}...")
print(f" Status: {status}\n")
print(f"Accuracy: {correct}/{len(nl_queries)} ({100*correct/len(nl_queries):.0f}%)")
Choosing Your Configuration
| Use case | Configuration |
|---|---|
| Text and code only | 270M text encoder |
| Text plus images | 270M + 170M vision encoder |
| Full multimodal | 740M all encoders |
| Edge or memory-constrained | Text-only with 256d or 128d truncation |
| On-device RAG with Gemma 4 | Shared tokenizer and audio encoder reduce total memory load |
The modular design means you only pay for what you use. Start with the text encoder, validate retrieval quality on your corpus, then add vision or audio encoders only if your data requires them. For storage-sensitive deployments, 256d truncation retains near-baseline quality across all modalities at one-third the storage cost of full 768d vectors.
Conclusion
EmbeddingGemma 2 consolidates text, code, image, video, and audio retrieval into a single model with a shared output space. Its modular encoder design lets developers load only what they need, and Matryoshka truncation offers a practical path to reducing index size without a significant drop in retrieval quality. The 14% MTEB (Code) improvement over version 1 and near-lossless 256d truncation make it a strong candidate for mixed-content retrieval pipelines where both accuracy and storage efficiency matter.