TAI BUI
Back to curriculum
BuildPython

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.

TIME: ~180 minutesPREREQUISITES: Phase 12 · 01 (ViT patches), Phase 7 (Transformers)

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.

Warm-up0/3
1. In a ViT, how does an image become the sequence of tokens that the transformer consumes?Warm-up
A photo box with an arrow to a dashed question-mark box, then an arrow to a transformer input panel showing a short sequence of tokens t1, t2, t3.
Typed schematic.
2. A transformer's attention applies softmax to a row of raw scores. What does that do to the row?Warm-up
Three raw score boxes 2.0, 1.0, 0.1 with an arrow labelled softmax to three dashed question-mark boxes.
Typed schematic, not captured output. Scores are illustrative.
3. Two vectors each have length 1. What does their dot product equal?Warm-up
A circle of radius 1 with two arrows a and b from the center, an arc between them marking the angle, a card saying both lengths are 1, and a dashed box asking for the dot product.
Typed schematic, not captured output.
Check: The Problem0/1
1. Why can an alt-text caption like 'my dog Max in the park' supervise an image encoder when it contains no class label?Explain
A web page showing a photo, its alt text my dog Max in the park, a class label column that is empty, and a dashed box asking what the encoder learns from this pair.
Typed schematic, not captured output.

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.

Check: The Concept0/27
1. What is InfoNCE as CLIP uses it?Define
A 4 by 4 grid of similarity values with image 1 to 4 as rows and caption 1 to 4 as columns, a note that it is a batch of 4 image-caption pairs, and a dashed box asking about the loss computed from S.
Values computed by running main.py's functions on its demo 1 embeddings. tau = 1.
2. What is the sigmoid loss that SigLIP uses?Define
A sigmoid curve from 0 to 1 over a logit axis, with four orange dots for the four entries of row 1 of the similarity matrix at tau 0.07, and a table of those entries with their sigmoid values.
Values computed by running main.py's functions on its demo 1 embeddings. tau = 0.07.
3. What does CLIP's temperature parameter tau control?Define
A chain of four boxes: cos of f of x and g of y, divide by tau, scores S, loss, with the divide by tau box outlined, a note that tau is a learned scalar starting at 0.07, and a dashed box asking what tau controls.
Typed schematic, not captured output.
4. What does zero-shot classification mean for a CLIP model?Define
A photo labelled photo to label, four class name chips cat, dog, bird and car, and a dashed box asking how a CLIP model gives the photo a zero-shot label.
Typed schematic, not captured output.
5. What is a prompt template in zero-shot CLIP?Define
A class name card golden retriever, an arrow through a dashed box asking what the text encoder is fed, then a text encoder box and a class vector box.
Typed schematic, not captured output.
6. What is a dual encoder in CLIP's design?Define
A photo and a caption card reading my dog Max in the park, arrows into a dashed question-mark box about the model that makes the pair comparable, and an arrow to a similarity score box.
Typed schematic, not captured output.
7. What is a hard negative in contrastive training?Define
A photo on the left and three caption cards on the right: my dog Max in the park labelled its own caption, and a dog running on a lawn and a bowl of pasta labelled captions of a different pair.
Typed example with illustrative values, not captured output.
8. What is a linear probe on a pretrained CLIP model?Define
A photo goes into the CLIP image encoder, then a features box, then a dashed question-mark box, then a cat or dog output, with a note that the target task has two classes with labeled photos.
Typed schematic, not captured output.
9. What does SigLIP 2's NaFlex feature let one checkpoint do that the original CLIP could not?Define
A timeline of three models, CLIP in 2021, SigLIP in 2023 and SigLIP 2 in 2025 with their arXiv numbers, a NaFlex tag pointing at SigLIP 2, and a dashed box asking what NaFlex lets one checkpoint do.
Years and arXiv numbers are from the lesson's reading list.
10. What is temperature scaling in CLIP's parametrization?Define
A chain from the initial value tau 0.07, to an optimizer updating a learned scalar, to S equals cos over tau, with a dashed box asking in what form that scalar is stored and updated.
Typed schematic, not captured output.
11. In CLIP's InfoNCE loss for a batch of N pairs, what serves as the negatives for a given image?Explain
Two columns, image 1 to 4 on the left with image 1 outlined and caption 1 to 4 on the right, with rows meaning pairs, and a dashed box asking for the negatives of image 1.
Typed schematic, not captured output.
12. Why do both towers normalize their outputs to unit length before the similarity matrix is built?Explain
Two encoder output cards, f of x equal to (3, 4) and g of y equal to (4, 3), each of length 5, arrows to a box saying both towers normalize to length 1, then to the similarity matrix S, and a dashed box asking why the normalization step is there.
Vectors are typed illustrative values.
13. Why does a low tau give CLIP's softmax a hard-negative-mining effect?Explain
Four horizontal bars for one image's cosines with captions: caption 1 (its pair) 0.9, caption 2 0.8, caption 3 0.3, caption 4 0.1, a note about softmax over the row, and a dashed box asking how a low tau treats the three negatives.
Typed example with illustrative values, not captured output.
14. Why does SigLIP's sigmoid pairwise loss scale better than softmax InfoNCE in distributed training?Explain
Four GPU boxes each holding a local slice of the batch, a note that SigLIP 2 trains at batch 32k to 512k, and a dashed box asking what GPUs must exchange for the sigmoid loss and for the softmax loss.
Typed schematic, not captured output. The GPU count is illustrative.
15. In SigLIP's pairwise loss, what role does the learned bias b play?Explain
A strip of the four row 1 logits at tau 0.07 (14.268, -0.024, 2.233, 1.388), a note that SigLIP learns a temperature tau and a bias b, and a dashed box asking about the role of b.
Values computed by running main.py's functions on its demo 1 embeddings. tau = 0.07.
16. How do the three usage regimes for a pretrained CLIP model compare?Explain
A box saying same pretrained checkpoint with arrows down to three boxes named zero-shot, linear probe and full finetuning, and a dashed box asking how they compare in-domain and on transfer.
Typed schematic, not captured output.
17. Which four levers do CLIP and SigLIP pretraining give you to shape the embedding space?Explain
A pipeline from web image-text pairs to image and text towers, an N by N similarity matrix and a contrastive loss, and a dashed box asking for the four levers that shape the embedding space.
Typed schematic, not captured output.
18. Roughly where does the zero-shot ImageNet ceiling for CLIP-class models sit, and what does SigLIP 2 reach?Explain
Two boxes, one for CLIP-class models (CLIP-G, OpenCLIP-G) and one for SigLIP 2, each asking for its ImageNet zero-shot top-1 accuracy, above an accuracy axis from 0 to 100 percent.
Typed schematic, not captured output.
19. What gain did the original CLIP paper report from averaging embeddings over 80 prompt templates per class?Explain
Three prompt strings for golden retriever, the third saying 80 templates in total, arrows to a text encoder and averaging box, then a single class vector, and a dashed box asking for the gain the CLIP paper reported on ImageNet zero-shot.
The prompt strings are typed examples.
20. You have 40 product photos, six category names and no labels or finetuning budget. What do you do with a pretrained CLIP model?Apply
A 4 by 10 grid of 40 blank photo tiles, six category name chips, a note that there are no labels and no finetuning budget, and a dashed box asking what to do with a pretrained CLIP model.
Typed schematic, not captured output. The six category names are not given in the question, so they are numbered.
21. A teammate says sigmoid loss throws away negatives, so batch size no longer matters for it. What is wrong with that claim?Analyze
A quote card with the teammate's claim that sigmoid loss throws away negatives so batch size no longer matters, boxes for N images, N captions and the sigmoid loss, and a dashed box asking what is wrong with the claim.
Typed schematic, not captured output.
22. During training the learned temperature keeps shrinking toward zero and the softmax becomes extremely sharp. Which part of CLIP's design is meant to prevent this?Analyze
A schematic line chart of the learned temperature tau falling toward zero over training steps.
Schematic curve, not measured. No values are implied.
23. A teammate switches from 'a photo of a {class}' to 'a picture of a {class}' and sees zero-shot accuracy move by about 3 points. They conclude the model is broken. What is the better reading?Analyze
Two template cards, a photo of a {class} and a picture of a {class}, with a switch arrow, a card saying zero-shot accuracy moves by about 3 points, a card with the teammate's conclusion that the model is broken, and a dashed box asking for a better reading.
Typed schematic, not captured output.
24. A team needs better in-domain accuracy than zero-shot gives, but other users share the same CLIP embedding space and it must not change. Which approach fits?Design
A box for a shared CLIP embedding space that must not change, with arrows to team A, team B and team C which use it as it is, and to our team which needs better in-domain accuracy than zero-shot gives, and a dashed box asking which approach fits both requirements.
Typed schematic, not captured output.
25. You must build image-text retrieval over product titles written in 40 languages. Which starting point fits best?Design
Product titles in 40 languages and product images flow to a dashed question-mark box for the starting model, then to a retrieval index.
Typed schematic, not captured output.
26. You are building an open VLM that must take documents with unusual aspect ratios and also feed dense features to segmentation. Which vision tower fits?Design
A layout with document pages of unusual aspect ratios feeding a dashed question-mark vision tower, which feeds a VLM text decoder and also sends dense features to a segmentation head.
Typed schematic, not captured output.
27. Your team has a 1.8B-pair web scrape that is roughly 90% noisy and no budget to clean it. Is contrastive pretraining still viable?Design
A bar representing a web scrape of 1.8B image-text pairs, mostly shaded as about 90 percent noisy (1.62B pairs) with a small 10 percent segment, a note about no cleaning budget, and a dashed box asking if contrastive pretraining is still viable.
Bar proportions drawn from the stem; 1.62B is computed from 90% of 1.8B.

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:

  1. similarity_matrix: normalize both sides and divide the cosine similarities by tau.
  2. infonce_loss: the symmetric row and column cross-entropy, using log-sum-exp for numerical stability.
  3. sigmoid_loss: the per-pair binary cross-entropy for comparison, with an optional bias argument.
  4. 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.

Check: Use It0/7
1. sigmoid_loss is called on a similarity matrix built from 6 aligned pairs. How many terms does it average over, and how many are positives?Apply
A blank 6 by 6 grid with image 1 to 6 as rows and caption 1 to 6 as columns, the call loss equals sigmoid_loss of S, and a dashed box asking how many terms are in the average and how many are positives.
Typed schematic, not captured output.
2. zero_shot_classify is called with an image vector that sits close to the 'dog' prototype and four class vectors. What does it return?Apply
A Python snippet defining four class vectors cat, dog, bird and car, a query image vector close to the dog prototype, and a call to zero_shot_classify, above a dashed box asking what result holds.
Typed schematic, not captured output. The vectors are seeded random in main.py.
3. On the four aligned pairs in the first demo, tau is raised from 0.07 to 1.0. How does the InfoNCE value change?Apply
A 4 by 4 grid of cosine values for the four aligned pairs in demo 1, a code box showing S equals cos over tau with tau going from 0.07 to 1.0, and a dashed box asking for the InfoNCE value.
Values computed by running main.py's functions on its demo 1 embeddings.
4. infonce_loss calls log_sum_exp on a row [1000.0, 1000.0]. What does it return, and why does it avoid overflow?Apply
A Python snippet setting row to [1000.0, 1000.0] and calling log_sum_exp on it, above a dashed box asking for the returned value and why it avoids overflow.
Typed schematic, not captured output.
5. sigmoid_loss is called on the demo's aligned matrix at tau=0.07 with bias=-10 instead of the default 0.0. What happens to the 0.9268 loss?Apply
The 4 by 4 matrix of demo 1 at tau 0.07 with diagonal entries near 14.27, and a code box with sigmoid_loss(S) giving 0.9268 and sigmoid_loss(S, bias=-10.0) with the value left as a question mark.
Values computed by running main.py's functions on its demo 1 embeddings. tau = 0.07.
6. In Use It, the demos print InfoNCE 0.0000 but sigmoid loss 0.9268 on the same aligned pairs at tau=0.07. What explains the gap?Analyze
A terminal window showing demo 1's header and its tau 0.07 line, InfoNCE 0.0000 and SigLIP 0.9268, with the other tau lines hidden, and a dashed box asking why the two numbers differ so much.
Terminal text captured from running main.py; other lines omitted.
7. Training InfoNCE stays high and never approaches the near-zero value seen on aligned pairs. Which data bug fits that symptom?Analyze
A schematic line chart of InfoNCE staying flat and high over training steps, well above a dashed orange line marking the near-zero value seen on aligned pairs, and a dashed box asking which data bug fits this symptom.
Schematic curve, not measured. No loss values are implied.

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.

Check: Ship It0/1
1. The zero-shot skill refuses to make claims about classes that are not in the prompt list. Why is that consistent with how zero-shot classification works?Explain
A three-stage card for the skill-clip-zero-shot skill: inputs of image paths and a target class list, a stage that builds prompts and embeds both sides, and outputs of top-1 and top-5 with scores, plus a rule card and a dashed box asking why the rule follows from how zero-shot works.
Stages are from the lesson's Ship It section.
Author's Note

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

  1. 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.

  2. SigLIP uses a bias parameter b in addition to temperature: S'[i,j] = S[i,j]/tau + b. What role does b play when the batch has a large class imbalance (many more negatives than positives per row)? Read SigLIP Section 3 (arXiv:2303.15343).

  3. Build a zero-shot classifier for cats vs dogs. Try two prompt templates: a photo of a {class} and a picture of a {class}. Measure accuracy on 100 test images. Does the ensemble of templates beat single?

  4. 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.

  5. 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

TermWhat people sayWhat 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

Code

Ships With