Why are my SigLIP 2 text-to-image search results so bad?
Almost always because the query text is not prepared the way SigLIP 2 was trained: padded to exactly 64 tokens and lowercased. SigLIP pools its text embedding from the last position, so an unpadded query is pooled from the wrong token and its vector points almost anywhere. In our benchmark on Flickr30k, unpadded queries scored 0.74% recall@1; padding them correctly gave 73.86%, and lowercasing them as well gave 79.32%, against 80.7% in the SigLIP 2 paper.
Every number on this page comes from a run we measured on 2026-11-10 (text-to-image recall over all 5,000 Flickr30k test captions and 25,010 COCO 5k captions, with 95% bootstrap intervals), or from the source we link.
What SigLIP 2 expects from your text
Hugging Face's SigLIP 2 documentation lists the text preprocessing the model was trained with (Transformers docs):
The processor (AutoProcessor) applies both for you. The problems start when a pipeline calls the tokenizer directly, or passes padding="max_length" without max_length.
Why padding="max_length" can silently pad nothing
The fixed-resolution SigLIP 2 checkpoints, such as google/siglip2-base-patch16-224 and google/siglip2-so400m-patch14-384, declare the model type siglip and ship a Gemma tokenizer that sets no model_max_length. With no length to pad to, padding="max_length" pads nothing and truncation=True truncates nothing, and no warning is raised. Two symptoms follow:
1. Every query embedding is wrong. The model reads the last position of an unpadded sequence, which is a different token than the one it learned to pool. 2. Long captions fail. A caption over 64 tokens exceeds the text encoder's position count and is refused (1 of the 5,000 Flickr30k captions in our run).
The fix is to pass the length explicitly, or to read it from the model's own config (text_config.max_position_embeddings is 64 for these checkpoints) rather than from the tokenizer.
How much each fix is worth
Measured with SigLIP 2 B/16 at 224 px, text-to-image recall@1:
| Query text | Flickr30k 1k | COCO 5k |
|---|---|---|
| Unpadded | 0.74% | not run |
| Padded to 64 tokens | 73.86% | 48.73% |
| Padded and lowercased, punctuation removed | 79.32% | 53.09% |
| SigLIP 2 paper, Table 1 (arXiv 2502.14786) | 80.7% | 52.1% |
SigLIP 2 So/14 at 384 px moved the same way on Flickr30k: 79.10% padded, 83.44% padded and lowercased, against the paper's 85.7%. As a control, OpenAI CLIP B/16 through the same pipeline landed within 0.1 point of its published scores on both sets (62.20% and 33.00%), since its tokenizer declares its 77-token length and lowercases on its own.
How do I fix SigLIP 2 text embeddings?
Use the processor, or pass the training settings yourself:
from transformers import AutoModel, AutoProcessor model_id = "google/siglip2-base-patch16-224" model = AutoModel.from_pretrained(model_id).eval() processor = AutoProcessor.from_pretrained(model_id) texts = [t.lower() for t in ["A dog catching a frisbee", "Two people on a beach"]] inputs = processor(text=texts, padding="max_length", max_length=64, truncation=True, return_tensors="pt") text_vectors = model.get_text_features(**inputs) text_vectors = text_vectors / text_vectors.norm(dim=-1, keepdim=True)
Then check three things in your own pipeline:
How Mixpeek runs SigLIP 2
Mixpeek is a multimodal data warehouse: it embeds images and video frames where they sit in object storage and searches them through retrievers. SigLIP 2 models are in the model catalog, and a namespace can import other checkpoints from Hugging Face through the model registry.
The 0.74% row above was measured through Mixpeek's own Hugging Face import, which padded only checkpoints that declare the model type siglip2. The loader fix, which pads and truncates every query to the text encoder's position count, was merged on 2026-11-10. With it, the platform's rankings matched an exhaustive offline cosine search over the same vectors to two decimals. Lowercasing queries is left to the caller today, as in the code above.
Frequently Asked Questions
Why does SigLIP 2 need text padded to 64 tokens?
It was trained that way, and it pools its text embedding from the last position of the sequence. Hugging Face's documentation for SigLIP and SigLIP 2 says to pass padding="max_length" with max_length=64. An unpadded query is pooled from a different token, which in our test dropped Flickr30k recall@1 from 73.86% to 0.74%.
Does SigLIP 2 need lowercase text?
Yes. SigLIP 2 was trained on lowercased text, and its processor lowercases input by default. When we lowercased queries and removed punctuation ourselves, recall@1 rose by 4.4 to 5.5 points on Flickr30k and COCO for SigLIP 2 B/16.
Why does padding="max_length" not pad my SigLIP 2 queries?
The fixed-resolution SigLIP 2 checkpoints ship a tokenizer with no model_max_length, so there is no length to pad to. Pass max_length=64 explicitly, or read the length from the model config's text_config.max_position_embeddings.
Is SigLIP 2 better than CLIP for text-to-image search?
On the SigLIP 2 paper's benchmarks, yes: SigLIP 2 B/16 reports 80.7% recall@1 on Flickr30k against 62.1% for OpenAI CLIP B/16, and we measured 79.32% and 62.20% through one pipeline. The advantage holds only when SigLIP 2's text is padded and lowercased; unpadded, it scored far below CLIP in our run.
How do I know if my embedding pipeline has a preprocessing bug?
Score a public benchmark through it and compare with the model's paper. Recall within a few points means the pipeline is sound; a result near zero, or one that changes when you clear a cache, means queries are not being prepared the way the model was trained.