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

EmbeddingGemma 2: Multimodal Embeddings Across Text, Code, and Media

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

SpecificationEmbeddingGemma 2
Base modelGemma 4
LicenseApache 2.0
Output dimension768, truncatable to 512, 256, 128
Context window8,192 tokens, shared across modalities
ModalitiesText, code, images, video, audio
Parameters270M text-only to 740M full multimodal
ReleasedOctober 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.

Embedding Gemma 2 architecture Architecture diagram, Google Developers Blog.

3 Encoders

EncoderSizeHandlesBuilt on
Text and code270M, the basePlain text and source code, up to 8,192 tokensAn adapted Gemma 4 decoder
Vision+170MPhotos, charts, slides, PDF pages, and video framesA dedicated vision encoder
Audio+300MSpeech and ambient sound, fed in as raw audioA 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

multimodal similarity comparison

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

BenchmarkResult
MTEB (Code)14% higher than EmbeddingGemma 1
Multilingual textRetains EmbeddingGemma 1 accuracy
Image, video, audio retrievalNew 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

DimensionText and codeImage, video, speechStorage per 1M vectors
768dbaselinebaseline1,465 MB
512dno stated lossno stated loss977 MB
256dnear baselineroughly 95%488 MB
128droughly 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.

MeasurementResult
256d versus 768d rankingIdentical top-1 and MRR
128d rankingTop-1 held, MRR fell from 0.667 to 0.656
Mean similarity at 128dRose 12.6% versus 768d despite identical ranking
Image cross-modal margin0.1154 over the nearest wrong query
Audio cross-modal margin0.0137 over the nearest wrong query
Default output precisionfloat32, 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]}")

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}")

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 caseConfiguration
Text and code only270M text encoder
Text plus images270M + 170M vision encoder
Full multimodal740M all encoders
Edge or memory-constrainedText-only with 256d or 128d truncation
On-device RAG with Gemma 4Shared 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.