Modelsgoogleembeddinggemma-2
providergoogle /

embeddinggemma-2

7 DZD/ 1M tokens

EmbeddingGemma 2 places text, code, images, video, and audio in one 768-dimensional vector space, so a text query can retrieve a photograph, a video clip, or a recording directly, with no separate model per modality. It carries 740 million parameters, but its vision and audio encoders load independently — a text-only deployment runs at 270 million. Against its predecessor, multilingual text quality is essentially unchanged while code retrieval rose by roughly fourteen percent, and every other modality is new. Vectors truncate from 768 down to 128 dimensions, though the cost of shrinking them depends heavily on what you are embedding.

PublicEmbeddingsMultimodalMatryoshkaMultilingualOn-Device
embeddinggemma-2
ArchitectureTransformer
Context Window8K

EmbeddingGemma 2

One vector space for text, code, images, video, and audio.

EmbeddingGemma 2


One Space, Four Modalities

Every input lands in the same 768-dimensional space — text (including code), images, video, and audio, and any combination of them.

Which is the capability, stated plainly: a text query can be compared directly against an image, a video clip, or an audio recording, and the nearest results are found by the same similarity function regardless of what kind of thing they are.

The conventional alternative is a model per modality — a text embedder, an image embedder, an audio embedder — each producing vectors in its own space. Those spaces do not line up, so cross-modal search needs an alignment step, a translation layer, or a separate index per modality.

Here there is one index. A product catalogue holding descriptions, photographs, and demo videos is searched by a single query against a single set of vectors.

And inputs can be combined. A single embedding can represent text interleaved with images, video, and audio together — a product listing with its description and photos, embedded as one item, and compared against a text-only query.


What Actually Improved

Read the benchmark table for what moved and what did not.

BenchmarkMetricEmbeddingGemma 2EmbeddingGemma 1
MTEB (multilingual, v2)Mean(Task)61.3661.15
MTEB (code, v1)Mean(Task), NDCG@1078.6868.76
MIEB (lite)Mean(TaskType)64.64—
MMEB v2 — ImageMean(Task), Hit@157.28—
MMEB v2 — VisDocMean(Task), NDCG@567.84—
MMEB v2 — VideoMean(Task), Hit@150.67—
MSEB — RetrievalMean(Task), MRR@1069.54—
MAEBMean(Task)49.39—

Multilingual text moved by two-tenths of a point. 61.15 to 61.36 — essentially unchanged.

Code moved by nearly ten points. 68.76 to 78.68, which is the roughly fourteen percent improvement the release describes.

Everything else is new. Six of the eight rows have no previous figure because the previous model did not accept those inputs.

Which tells you how to read the release. If your workload is multilingual text retrieval, this is a lateral move, not an upgrade. If it involves code, or any modality beyond text, it is a substantial one.


Selective Encoders

740 million parameters in total, but not all of them have to load.

ComponentParameters
Text backbone270M — 130M transformer + 140M embedder
Vision encoder170M
Audio encoder300M

The vision and audio encoders are independent components, loaded only when a deployment needs them.

Active modalitiesEffective size
Text only270M
Text and image440M
Text and audio570M
Full multimodal740M

A text-only deployment is a 270-million-parameter model. The multimodal capability costs nothing if you do not use it.

And the audio encoder is the largest single component — larger than the vision encoder and larger than the text transformer itself. A text-and-image pipeline that never loads it saves three hundred million parameters.


⚠️ Truncation Costs Depend on the Modality

The vectors support Matryoshka truncation from 768 down to 512, 256, or 128 dimensions. How much quality that costs varies sharply by what you are embedding.

DimensionStorageMTEB multiMTEB engMTEB codeMIEBMMEBMSEBMAEB
7681:161.3668.4678.6864.6459.0169.5449.39
5121:1.561.1768.4177.2464.3258.3869.1849.21
2561:360.4167.7876.1863.1356.2466.7648.91
1281:657.8965.6871.4159.0645.6556.7146.92

Down to 256 dimensions, the loss is small everywhere — three times less storage, with every column within a few points of full size.

At 128, the picture splits.

At 128 dimensionsDrop from 768
MTEB multilingual (text)about 6%
MTEB English (text)about 4%
MMEB (multimodal)about 23%
MSEB (audio retrieval)about 18%

Text survives aggressive truncation. Multimodal and audio retrieval do not.

Which is exactly the guidance on the model card: 128 dimensions is best suited to text-only workloads, and degrades multimodal quality substantially enough that it should be validated against your own data before you adopt it.

The practical rule: 256 for anything multimodal, 128 only if every item in your index is text.


The 8,192-Token Budget

Every modality draws from one shared context window, each at its own rate.

ModalityToken costMaximum in one input
Text1 token per subword8,192 tokens
Image280 tokens per image~29 images
Video140 tokens per frame~58 frames
Audio25 tokens per second~327 seconds

Audio reaches about five and a half minutes in a single input.

Video at the default sampling rate of one frame per second reaches just under a minute. The vision token budget is adjustable — from 70 to 1,120 soft tokens per image — and at the lower end, a single input can hold roughly 114 images or frames.

Those maximums assume one modality alone. Interleaved inputs share the same budget, so a description plus five photographs plus a short clip leaves less of each than any one would get on its own.

And the vision budget is a quality dial. More tokens per image captures more fine detail and costs more latency; fewer tokens fits more frames into one input.


Task Prefixes

The model was trained with short task instructions prepended to text. Using the right one improves quality; omitting it still works, with reduced precision.

Prefixes apply to text only. Images, video, and audio are passed without one.

Asymmetric tasks — different prefixes for queries and documents

Use caseQueryDocument
Web / document searchtask: search result | query: {query}title: {title} | text: {content}
Question answeringtask: question answering | query: {question}title: {title} | text: {passage}
Fact checkingtask: fact checking | query: {claim}title: {title} | text: {evidence}
Code searchtask: code retrieval | query: {query}title: {title or filename} | text: {code}

Symmetric tasks — the same prefix for every input compared

Use casePrefix
Classificationtask: classification | query: {content}
Clusteringtask: clustering | query: {content}
Sentence similaritytask: sentence similarity | query: {content}

Use title: none when a document has no title. The document format always carries a title slot.

The distinction between the two kinds matters. Retrieval compares a short question against long passages, and different prefixes tell the model which side of that comparison each input is on. Classification and similarity compare like with like, so every input gets the same instruction.


⚠️ Never Use float16

Stated directly on the model card, and the failure mode is the dangerous kind.

The model's activation range exceeds what float16 can represent. In float16 it returns NaN or silently degraded embeddings — rather than raising an error.

Use bfloat16 or float32. bfloat16 carries the same exponent range as float32 and halves memory; it is the recommended default on hardware that supports it. Use float32 elsewhere, including on most CPUs.

Through an API this is the provider's concern. It matters the moment you self-host — and because the failure is silent, a float16 deployment can serve plausible-looking vectors that quietly rank everything wrong.


Architecture

Total parameters740M
Layers24
Model dimension512
Hidden dimension2,048
AttentionGQA / MQA
Heads4
KV heads (local / global)2 / 1
Local : global attention5:1
Sliding window1,024 tokens
Vocabulary262,144
ActivationGated FFN with GELU
PoolingMean pooling
Projection512 → 768

The output is wider than the model. The network works in 512 dimensions internally, and a final projection expands mean-pooled representations to 768 — which is why 512 is also a natural truncation point that loses very little.

Five local attention layers for every global one, with a 1,024-token sliding window on the local layers — the same attention pattern the Gemma family uses to keep long inputs affordable.

Mean pooling averages the final token representations into one vector, rather than taking a single special token's output.


Specifications

Model IDgoogle/embeddinggemma-2
DeveloperGoogle DeepMind
Built onGemma 4
Parameters740M total — 270M text, 170M vision, 300M audio
Context window8,192 tokens, shared across modalities
Output dimension768
Truncation dimensions512, 256, 128
InputText (incl. code), images, video, audio, interleaved
OutputOne embedding per input
Languages100+ understood; trained on 140+
Training data cutoffJanuary 2025
Precisionbfloat16 or float32 — never float16
Audio inputMono, 16 kHz
Video sampling1 frame per second by default

Use is governed by the Gemma Prohibited Use Policy, which applies to every deployment.


Capabilities

CapabilityValue
input_typestext, image, video, audio
output_typesembedding
context_window8192 — shared across modalities
output_dimensions768 — truncatable to 512, 256, 128
mrl_supportYes
interleaved_inputYes — one embedding per mixed input
task_prefixesText only
endpoint/v1/embeddings
deterministicYes
requires_promptYes — input required

Using EmbeddingGemma 2 on DEVUP AI

Endpoint: POST https://api.devupai.com/v1/embeddings

Prepend the task prefix to the input text yourself. The prefix system is applied by name in some local libraries; through an OpenAI-compatible endpoint, it is part of the string you send.

Python

PYTHON
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["DEVUP_API_KEY"],
    base_url="https://api.devupai.com/v1",
)

response = client.embeddings.create(
    model="google/embeddinggemma-2",
    input=[
        "task: search result | query: What causes the northern lights?",
        "title: none | text: The northern lights are caused by charged particles from the sun.",
    ],
)

vectors = [item.embedding for item in response.data]
print(f"{len(vectors)} vectors of {len(vectors[0])} dimensions")
print(f"Tokens consumed: {response.usage.total_tokens}")

Node.js

JAVASCRIPT
import DevupAI from "devupai";

const client = new DevupAI({
  apiKey: process.env.DEVUP_API_KEY,
});

const response = await client.embeddings.create({
  model: "google/embeddinggemma-2",
  input: [
    "task: search result | query: What causes the northern lights?",
    "title: none | text: The northern lights are caused by charged particles from the sun.",
  ],
});

const vectors = response.data.map((item) => item.embedding);
console.log(`${vectors.length} vectors of ${vectors[0].length} dimensions`);

cURL

BASH
curl https://api.devupai.com/v1/embeddings \
  -H "Authorization: Bearer $DEVUP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/embeddinggemma-2",
    "input": "task: search result | query: What causes the northern lights?"
  }'

⚠️ The examples here are text. Image, video, audio, and interleaved inputs use a different request shape from plain text embeddings — confirm how your path accepts them with a test request before building a multimodal pipeline.


Retrieval

The asymmetric case — queries and documents carry different prefixes.

PYTHON
import numpy as np


def embed(texts: list[str]) -> np.ndarray:
    """Embed a batch and return one row per input."""
    response = client.embeddings.create(model="google/embeddinggemma-2", input=texts)
    return np.array([item.embedding for item in response.data])


def as_document(content: str, title: str | None = None) -> str:
    return f"title: {title or 'none'} | text: {content}"


def as_query(query: str) -> str:
    return f"task: search result | query: {query}"


CORPUS = [
    ("Refunds", "Refunds are issued to the original payment method within 14 days."),
    ("Delivery", "Orders above 5000 DZD qualify for free delivery nationwide."),
    ("Support hours", "Support is available Sunday through Thursday, 9am to 5pm."),
]

corpus_vectors = embed([as_document(text, title) for title, text in CORPUS])
query_vector = embed([as_query("How long until I get my money back?")])[0]

scores = corpus_vectors @ query_vector

for rank, index in enumerate(np.argsort(scores)[::-1], start=1):
    print(f"{rank}. [{scores[index]:.3f}] {CORPUS[index][1]}")

Include the real title when there is one. The document format reserves a slot for it, and a title is frequently the most informative part of a short passage.

Different prefixes on the two sides is the whole point. The query prefix tells the model it is looking at a question; the document prefix tells it it is looking at a passage that might answer one.


Code Search

Where the largest measured improvement landed.

PYTHON
def as_code(code: str, filename: str) -> str:
    return f"title: {filename} | text: {code}"


def as_code_query(query: str) -> str:
    return f"task: code retrieval | query: {query}"


files = {
    "billing/rounding.py": "def round_dzd(amount):\n    return round(amount, 2)",
    "billing/invoice.py": "def total(lines):\n    return sum(l.qty * l.price for l in lines)",
}

file_vectors = embed([as_code(code, name) for name, code in files.items()])
query_vector = embed([as_code_query("where are totals rounded to two decimals?")])[0]

best = list(files)[int(np.argmax(file_vectors @ query_vector))]
print(best)

The filename goes in the title slot. It is often the strongest signal of what a file does, and the format gives it somewhere to go.


Classification and Clustering

The symmetric case — every input gets the same prefix.

PYTHON
def as_classification(content: str) -> str:
    return f"task: classification | query: {content}"


LABELS = {
    "billing": "A question about a charge, a refund, an invoice, or a payment.",
    "delivery": "A question about shipping, a late order, or a missing parcel.",
    "technical": "A report that something in the app or website is not working.",
}

label_vectors = embed([as_classification(text) for text in LABELS.values()])


def classify(ticket: str) -> str:
    vector = embed([as_classification(ticket)])[0]
    return list(LABELS)[int(np.argmax(label_vectors @ vector))]


print(classify("I was charged twice for the same order"))

Both sides use the classification prefix. Symmetric tasks compare like with like, and mixing a query prefix with a document prefix here would describe a comparison that is not happening.


Truncating and Re-Normalising

Two rules from the model card, and the first one fails silently.

PYTHON
def truncate(vector: list[float], dimensions: int) -> np.ndarray:
    """Truncate a Matryoshka embedding and re-normalise it to unit length."""
    if dimensions not in (768, 512, 256, 128):
        raise ValueError(f"unsupported dimension {dimensions}")

    short = np.asarray(vector[:dimensions], dtype=np.float32)
    norm = np.linalg.norm(short)

    if norm == 0:
        raise ValueError("zero vector after truncation")

    return short / norm

Re-normalise after truncating. Slicing a unit-length vector does not preserve unit length, and cosine similarity computed as a dot product then produces plausible-looking scores that are wrong — with no error to tell you.

Queries and documents must share a dimension. A 768-dimensional query cannot be scored against a 128-dimensional index.

Use the supported dimensions only. 768, 512, 256, and 128 are the sizes the model was trained to make meaningful.


Choosing Your Dimension

The table above gives the published trade-offs; your own data gives the real ones. One embedding call covers every size, because truncation happens client-side.

PYTHON
def recall_at_k(corpus_raw, query_raw, expected, dimensions: int, k: int = 5) -> float:
    corpus = np.array([truncate(v, dimensions) for v in corpus_raw])
    queries = np.array([truncate(v, dimensions) for v in query_raw])

    hits = 0
    for query, target in zip(queries, expected):
        top = np.argsort(corpus @ query)[::-1][:k]
        hits += target in top
    return hits / len(queries)


corpus_raw = [item.embedding for item in client.embeddings.create(
    model="google/embeddinggemma-2", input=DOCUMENTS).data]
query_raw = [item.embedding for item in client.embeddings.create(
    model="google/embeddinggemma-2", input=QUERIES).data]

for dimensions in (768, 512, 256, 128):
    recall = recall_at_k(corpus_raw, query_raw, EXPECTED, dimensions)
    print(f"{dimensions:>4}d  recall@5 {recall:.3f}  storage {dimensions / 768:>5.1%}")

Four sizes from one embedding call. There is no reason to pick a dimension before running this.

Weight the result by modality. If your index is all text, 128 may hold up; if it contains images, video, or audio, the published numbers say it probably will not.

And the storage arithmetic is linear. Ten million vectors at 768 dimensions in float32 is about 31 GB; at 256 it is about 10 GB; at 128, about 5 GB.


No Safety Tuning

EmbeddingGemma 2 is a pre-trained embedding model. It does not undergo post-training alignment, safety tuning, or output-level moderation.

That is normal for an embedding model — it produces vectors, not text, so there is no output to moderate. Safety work during development focused on filtering the pre-training data.

The risk moves downstream. How the vectors are used — what gets retrieved, what gets classified as what, what gets clustered together — is where harm can occur, and that is the deployer's responsibility.

Retrieval filtering and fairness testing are the application-level safeguards the model card names. An index that surfaces content to users should filter what it returns, regardless of how semantically close it is.


Where It Fits

Cross-modal search — a text query over a mixed collection of documents, images, video, and audio, in one index.

Code search, where the measured improvement is largest.

Retrieval-augmented generation, as the first stage that finds what a language model then reads.

Classification and clustering of mixed-media content without a model per modality.

On-device and edge deployment — 270 million parameters for text-only, designed for phones and laptops.

Audio archives, searchable by spoken or written query, up to about five and a half minutes per input.

Not a generative model. It produces vectors, not text.

Not for long documents in one piece. 8,192 tokens is the shared limit; longer material needs splitting.

Not an upgrade for pure multilingual text retrieval. That benchmark barely moved from the previous model.


Practical Notes

Prepend the task prefix to text inputs yourself — through an API it is part of the string.

Use different prefixes for queries and documents in retrieval; the same prefix for classification and clustering.

Put the real title in the document's title slot, or title: none.

Pass images, video, and audio without a prefix.

Use 256 dimensions for anything multimodal; reserve 128 for text-only indexes.

Re-normalise after every truncation.

Keep queries and documents at the same dimension.

Run your own recall test across all four sizes before committing an index.

Never self-host in float16.

Confirm how your path accepts non-text inputs before building a multimodal pipeline.

Filter what your index returns — the model applies no moderation of its own.


Limitations

Multilingual text quality is essentially unchanged from the previous model — 61.15 to 61.36.

Truncation to 128 dimensions damages multimodal retrieval badly — about 23% on the multimodal benchmark and about 18% on audio retrieval, against roughly 6% for text.

8,192 tokens shared across every modality. About 29 images, 58 video frames, or five and a half minutes of audio at default settings — less of each when mixed.

float16 fails silently, returning NaN or degraded vectors rather than an error.

Language performance is uneven. The model understands 100+ languages but does not perform equally across them; measure yours.

Training data ends January 2025.

No safety tuning or moderation. Safeguards belong in the application.

Task prefixes matter. Omitting them still works, at reduced precision.

The Gemma Prohibited Use Policy applies to every deployment.