CLIP and Contrastive Vision-Language Pretraining
CLIP (2021) rests on one idea: put an image encoder and a text encoder in the same vector space, train them with a contrastive loss on 400M noisy web image-caption pairs, and skip class labels entirely. The trained space does zero-shot classification and image-text retrieval, and it serves as the vision tower in every 2026 VLM. SigLIP 2 (2025) swapped the softmax for a sigmoid and scaled past CLIP at lower cost. This lesson walks the math from InfoNCE to the sigmoid pairwise loss and implements both losses, plus a zero-shot classifier, in stdlib Python.
Learning Objectives
- Derive InfoNCE loss from mutual information and implement a numerically-stable vectorized version.
- Explain why sigmoid pairwise loss (SigLIP) scales to batch 32768+ without the all-gather overhead softmax demands.
- Run zero-shot ImageNet classification by constructing text templates (`a photo of a {class}`) and taking argmax over cosine similarity.
- Name the four levers CLIP / SigLIP pretraining gives you: batch size, temperature, prompt template, data quality.
The Problem
Before CLIP, vision was supervised. You collected a labeled dataset (ImageNet: 1.2M images, 1000 classes), trained a CNN, and shipped it. Labels are expensive, they skew toward whatever labelers can agree on, and they do not transfer to a new task without finetuning.
The web holds over a billion loosely labeled image-caption pairs for free. A picture of a golden retriever with alt text "my dog Max in the park" carries a supervisory signal, because the text describes the image. The open question is how to turn that into useful training.
CLIP treats it as a matching task. Given a batch of N images and N captions, learn to match each image to its own caption against N-1 distractors. The supervision is "these two things belong together; these N-1 do not." There are no class labels and no human annotation, only a contrastive loss.
The resulting embedding space does more than CLIP was trained for. ImageNet zero-shot works because "a photo of a cat" lands near pictures of cats that were never labeled as cats. That bet is why every 2026 VLM starts from a CLIP-style encoder.
The Concept
The dual encoder
CLIP has two towers:
- Image encoder
f: ViT or ResNet, outputs a D-dim vector per image. - Text encoder
g: small transformer, outputs a D-dim vector per caption.
Both towers normalize their outputs to unit length, so similarity is cos(f(x), g(y)) = f(x)^T g(y).
For a batch of N (image, caption) pairs, build the similarity matrix S of shape (N, N):
S[i, j] = cos(f(x_i), g(y_j)) / tau
where tau is a learned temperature (CLIP initializes to 0.07; learned in log-space).
InfoNCE loss
CLIP uses a symmetric cross-entropy over rows and columns:
loss_i2t = CE(S, labels=identity) # each image's positive is its own caption
loss_t2i = CE(S^T, labels=identity) # each caption's positive is its own image
loss = (loss_i2t + loss_t2i) / 2
This is InfoNCE. The softmax inside the cross-entropy pushes each image to match its own caption better than any other caption in the batch. The negatives are the other batch items, so a bigger batch means more negatives and a stronger signal. CLIP trained at batch 32k, and that scale matters.
Temperature
tau sets how sharp the softmax is. Low tau gives a sharp distribution with a hard-negative-mining effect. High tau gives a soft one where the whole batch contributes. CLIP learns log(1/tau) and clips it to prevent collapse. SigLIP 2 fixes the initial tau and uses a learned bias instead.
Why sigmoid scales better (SigLIP)
Softmax needs the whole similarity matrix in sync. In distributed training that means all-gathering the embeddings onto each replica before the softmax, which is quadratic in world size for communication.
SigLIP replaces the softmax with an element-wise sigmoid. For each pair (i, j) the loss is a binary classification of "is this the matching pair?" The diagonal is positive and everything else is negative. The loss is:
L = -1/N sum over (i, j) [ y_ij log sigmoid(S[i,j]) + (1-y_ij) log sigmoid(-S[i,j]) ]
y_ij = 1 if i == j, else 0. Each pair's loss is independent, so there is no all-gather: each GPU computes its local block and sums. SigLIP 2 scales to batch 32k-512k cheaply where CLIP would need proportionally more communication.
Zero-shot classification
Given N class names, for each class build a text template:
"a photo of a {class}"
Embed each template with the text encoder, embed your image with the image encoder, and take the argmax cosine similarity as the predicted class. Nothing is trained on the target classes.
Prompt templates matter. The original CLIP paper used 80 templates per class (plain, artistic, photo, painting, and so on) and averaged the embeddings, which bought +3 ImageNet points. Modern usage typically picks one or two templates.
Linear probes and finetuning
Zero-shot is the baseline. A linear probe (one linear layer trained on frozen CLIP features for your target classes) beats it on in-domain tasks. Full finetuning beats the linear probe in-domain but can hurt zero-shot transfer. Three regimes, three trade-offs.
SigLIP 2: NaFlex and dense features
SigLIP 2 (2025) adds four things:
- NaFlex: a single model handles variable aspect ratios and resolutions.
- Better dense features for segmentation and depth estimation, aimed at use as a frozen backbone in VLMs.
- Multilingual training on 100+ languages where CLIP was English-only.
- 1B param scale where CLIP topped out at 400M.
In 2026 open VLMs, SigLIP 2 SO400m/14 is the default vision tower. CLIP remains the default for pure image-text retrieval when the LAION-2B training distribution matches your query pattern.
ALIGN, BASIC, OpenCLIP, EVA-CLIP
ALIGN (Google, 2021) is the same idea as CLIP at 1.8B pairs, 90% noisy, and it showed that noisy data scales. OpenCLIP (LAION) is the open reproduction of CLIP on LAION-400M / 2B at multiple scales, and the go-to open checkpoint. EVA-CLIP initializes from masked image modeling and makes a strong backbone for VLMs. BASIC is Google's CLIP+ALIGN hybrid. Same family, different data and tuning.
The zero-shot ceiling
CLIP-class models cap around 76% ImageNet zero-shot (CLIP-G, OpenCLIP-G). Getting past that takes either much more data (SigLIP 2 gets 80%+) or architecture changes (supervised heads, more parameters). The benchmark is saturating. The value that matters is the embedding space downstream VLMs consume.
Use It
code/main.py is stdlib Python with no numpy and no torch. It does not train anything. It builds the pieces on synthetic embeddings so you can read the math:
similarity_matrix: normalize both sides and divide the cosine similarities bytau.infonce_loss: the symmetric row and column cross-entropy, using log-sum-exp for numerical stability.sigmoid_loss: the per-pair binary cross-entropy for comparison, with an optionalbiasargument.zero_shot_classify: cosine similarity against a set of class vectors, sorted so the top entry is the prediction.
Four demos print the results: InfoNCE on 4 aligned pairs at three temperatures, aligned versus misaligned pairs, a zero-shot ranking, and a prompt-template ensemble. The embeddings are seeded Gaussian vectors, so the absolute numbers are toy. What carries over is how each loss responds to alignment and to tau.
Ship It
This lesson produces outputs/skill-clip-zero-shot.md. Given a set of images (via path) and a list of target classes, it builds text prompts with the CLIP template, embeds both sides with a stated checkpoint (e.g., openai/clip-vit-large-patch14), and returns top-1 / top-5 predictions with similarity scores. The skill refuses to make claims about classes that are not in the prompt list.
I'd run main.py and then read it next to the loss formulas. It is short enough to hold in your head, and it makes one thing obvious that the math hides: nothing in it trains. Demos 1 to 3 score fixed synthetic embeddings, so you are looking at what the loss returns for a given alignment, not at a loss curve.
The output that surprised me is the first demo. On aligned pairs InfoNCE is about 0.0000 at tau=0.07 and 0.7748 at tau=1.0, so a small tau makes the softmax very confident about the diagonal. The sigmoid loss on the same pairs prints 0.9268 at tau=0.07, because sigmoid_loss defaults to bias=0.0 and the demos never pass one. With no bias, nothing pushes the off-diagonal logits negative, and those pairs are 12 of the 16 terms in that average. That is the argument for the learned bias in SigLIP (exercise 2 asks about its role). You can see the effect yourself by passing a negative bias to sigmoid_loss. Note also that the code averages over N*N pairs, where the formula above writes 1/N, so the absolute values are not directly comparable to the paper's.
The prompt-ensemble demo deserves skepticism too. It averages three seeded random vectors and prints a hard-coded line about gains on real benchmarks, so it shows the mechanics of ensembling and measures nothing. For a real number, do exercise 3 with an actual checkpoint.
Exercises
-
Implement InfoNCE for a batch of 4 pairs by hand. Construct the 4x4 similarity matrix, run softmax, pick out the diagonal, compute cross-entropy. Verify your Python implementation against this hand calculation.
-
SigLIP uses a bias parameter
bin addition to temperature:S'[i,j] = S[i,j]/tau + b. What role doesbplay when the batch has a large class imbalance (many more negatives than positives per row)? Read SigLIP Section 3 (arXiv:2303.15343). -
Build a zero-shot classifier for cats vs dogs. Try two prompt templates:
a photo of a {class}anda picture of a {class}. Measure accuracy on 100 test images. Does the ensemble of templates beat single? -
Compute the communication cost of softmax InfoNCE vs sigmoid pairwise for a 512-GPU run at batch 32k. Which scales as O(N), which as O(N^2)? Cite SigLIP Section 4.
-
Read the OpenCLIP scaling-laws paper (arXiv:2212.07143, Cherti et al.). Reproduce their conclusion for data scaling from the figures: at fixed model size, what is the log-linear relationship between ImageNet zero-shot accuracy and training data size?
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| InfoNCE | "Contrastive loss" | Cross-entropy over a batch's similarity matrix; each item's positive is its paired item, negatives are everything else |
| Sigmoid loss | "SigLIP loss" | Per-pair binary cross-entropy; no softmax, no all-gather, scales cheaply in distributed training |
| Temperature | "tau" | Scalar that scales logits before softmax/sigmoid; controls sharpness of the distribution |
| Zero-shot | "no-finetune classification" | Use text prompts to construct class embeddings and classify by cosine similarity; no training on target classes |
| Prompt template | "a photo of a ..." | Text scaffold around a class name; affects zero-shot accuracy by 1-5 points |
| Dual encoder | "Two-tower" | One image encoder + one text encoder, outputs in shared D-dim space |
| Hard negative | "Tough distractor" | A negative similar enough to the positive that the model has to work to separate them |
| Linear probe | "Frozen + one layer" | Train only a linear classifier on top of frozen features; measures feature quality |
| NaFlex | "Native flexible resolution" | SigLIP 2 capability to ingest images at any aspect ratio and resolution without resizing |
| Temperature scaling | "log-parametrized tau" | CLIP parametrizes log(1/tau) so gradients behave; clips to prevent collapse to near-zero tau |
Further Reading
- Radford et al. (2021). Learning Transferable Visual Models From Natural Language Supervision: the CLIP paper.
- Zhai et al. (2023). Sigmoid Loss for Language Image Pre-Training: SigLIP.
- Tschannen et al. (2025). SigLIP 2: multilingual + NaFlex.
- Jia et al. (2021). ALIGN: scale with noisy web data.
- Cherti et al. (2022). Reproducible scaling laws for contrastive language-image learning: OpenCLIP scaling laws.
Code
- main.py (python, 190 lines)