EmbeddingGemma 2 Similarity
Which of these captions actually fits the picture
- queries
- candidates
- scores
- best_label
- best_index
- best_score
This is the node that turns embeddings into an answer. Two EG2_EMBEDDINGS bundles go in, a table of cosine similarities comes out, and the single-value outputs tell you which candidate won for the first query. That "first query" detail is the thing everyone misses, so let's start there.
The shape of the maths
Every query is scored against every candidate. 5 queries × 5 candidates gives you a 5×5 grid, and all of it lands in the scores output as JSON - query labels, candidate labels, the full score matrix, plus a best field naming the winner for each query.
Then the three scalar-ish outputs, and here's the catch: best_label, best_index and best_score all describe query zero. Not the best pair overall, not a per-query list. The first one. This is deliberate - the author's framing is the common workflow, one image against N captions or one video against N descriptions, where there's only one query anyway and the naming is exactly right. The moment you encode five queries, those three outputs are quietly reporting on query one while your scores JSON holds all the answers. Read the JSON.
best_label is a string - and the labels are positional, generated by the encode nodes: image 3, audio 1, video, or the literal text of a line you typed for text embeddings. best_index is that label's position as an int, which is the one you can actually route: feed it to a Select node, or a switch, and pull the matching image out of a batch.
What you feed it
queries - the thing you're asking with. Usually Encode Text with task set to SearchQuery. One vector, ideally.
candidates - the pool. Five captions encoded as Document (or as the whole multi-line text in one go), a batch of images from Encode Images, or one video from Encode Video.
Both inputs are the same custom type, which means you can pick wrong without a type error - text embeddings in both slots works fine, you'll just get a mathematically valid comparison of the wrong things.
Two failure modes it will catch:
- Different dimensions. The node checks and raises with both lengths in the message. Encode your images at 256 and your text at 768 and you get a straight answer instead of a silent misalignment.
- Nothing to compare. An empty bundle errors before the maths.
Cosine is computed on L2-normalised vectors, so scores land between -1 and 1 and the absolute scale is comparable across runs. In practice, for EmbeddingGemma on a genuine match you're looking at clearly higher numbers than a non-match, and the ordering is the useful signal far more than the exact value. Don't set a "must be above 0.7" gate without checking the spread on your own data.
The workflow it's built for
Shipped example, workflows/gui/image_captions: input/example.png goes through Encode Images, five captions go through Encode Text as SearchQuery, Similarity scores them, and you get back which caption describes the picture and by how much. The README's screenshot is literally that - an image against five captions, and a video against five descriptions, run as a real queue.
The instruction-prefix pairing is what makes the scores mean anything, and what beginners get wrong. Text used to search should be SearchQuery; documents being searched should be Document; when you're comparing like with like - caption to caption, prompt to prompt - use the same similarity task on both sides (SentenceSimilarity, Clustering, Classification). Media encoders apply no prefix at all, so image and video sides need nothing. Mismatched tasks don't error. They just give you worse scores and no clue why.
There's a subtler trap in the Document path: if you fill in the title field on Encode Text, the node rewrites each item to title: {title} | text: {content} and uses no prompt name. That's the model card's format, and it's correct - but it means title'd documents and plain SearchQuery text aren't quite the same operation, so don't mix the two as if they were symmetric.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/hiroki-abe-58/ComfyUI-EmbeddingGemma2
python -m pip install -r ComfyUI-EmbeddingGemma2/requirements.txt
python ComfyUI-EmbeddingGemma2/tools/download_model.py --models-dir ../models/embeddinggemma
Or search EmbeddingGemma 2 for ComfyUI in Manager, restart, and open one of the shipped workflows to see the wiring before you build your own.
Note this node is the one part of the pack that doesn't need a model - pure tensor maths on whatever you hand it. Everything upstream does, though, and the pack is V3-API only, so an old ComfyUI shows you nothing at all.
Worth saying out loud
This is a good measuring instrument and a mediocre judge. Cosine similarity tells you whether the content matches - boat, snow, sunset, aerial - not which result is prettier or better composed. It gets you the semantic half of "score my generations automatically" and leaves the aesthetic half unsolved. Still a real improvement over clicking through thumbnails.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| queries | EG2_EMBEDDINGS | — | |
| candidates | EG2_EMBEDDINGS | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| scores | STRING | — |
| best_label | STRING | — |
| best_index | INT | — |
| best_score | FLOAT | — |