← CLIP Series Reading Guide
Deep Learning · Architectures · CLIP (Contrastive Language–Image Pre-training) Chapter 04 / 4

4. Zero-Shot Classification, Prompt Ensembling, the Modality Gap & Modern CLIP Frontiers (SigLIP, OpenCLIP, MetaCLIP)

Once CLIP's Image Encoder and Text Encoder are pre-trained, how do we turn them into a classifier for a brand-new dataset without a single labeled training image? Page 2 of our handwritten OneNote notes illustrates the exact recipe: convert the dataset's class names (plane, car, dog, …, bird) into natural language prompts ("A photo of a {object}"), encode all prompts once with the Text Transformer to build a zero-shot weight matrix, and pick whichever prompt vector has the highest cosine similarity with the test image! In this final chapter, we trace that zero-shot pipeline, show why 80-prompt ensembling adds $+3.5\%$ to $+5\%$ on ImageNet at zero extra test-time compute, examine the geometric Modality Gap, and derive Google's pairwise sigmoid successor SigLIP.

Three-panel diagram showing (1) Zero-Shot Prompt Classifier and 80-template embedding ensembling, (2) The Modality Gap separating Image and Text cones on the unit hypersphere, and (3) CLIP Softmax vs SigLIP Pairwise Sigmoid loss.
Figure 1: Left: Compiling $K$ class names and $M=80$ prompt templates into a cached zero-shot linear classifier $\mathbf{W}_{\text{ZS}}$. Center: The Modality Gap ($\|\bar{\mathbf{I}}_e - \bar{\mathbf{T}}_e\|_2 \approx 0.82$) separating vision and text cones on $\mathbb{S}^{d_e-1}$. Right: Replacing global $N \times N$ Softmax with SigLIP's independent pairwise logistic loss.

1. Compiling a Zero-Shot Classifier from Label Text (Study Notes)

Look at our handwritten diagram from Page 2 of the OneNote study notes below:

Handwritten OneNote diagram showing Step 2 Create dataset classifier from label text: a list of classes plane, car, dog, bird mapped into A photo of a {object}, passed through the Transformer text encoder to produce T_1 through T_N, and a test image dog passed through a ResNet or ViT image encoder to produce I_1, scored via dot products I_1 T_1, I_1 T_2, I_1 T_3 to select A photo of dog.
Figure 2 (Study Notes — Page 2): Constructing a zero-shot dataset classifier from class labels (plane, car, dog, …, bird) using prompt templates and scoring a test image dog via cosine dot products $[I_1 T_1 \mid I_1 T_2 \mid I_1 T_3 \dots]$.

Let us formalize the two-step zero-shot inference procedure shown in Figure 2 for any target dataset with $K$ class names $\{c_1, c_2, \dots, c_K\}$:

  1. Offline Classifier Compilation (Run Once per Dataset): Insert each class name $c_k$ into a natural language template such as $\text{prompt}(c_k) = \text{“A photo of a \{c\_k\}.”}$, encode all $K$ strings with the Text Transformer, and stack the resulting unit vectors as columns of a zero-shot classification matrix $\mathbf{W}_{\text{ZS}} \in \mathbb{R}^{d_e \times K}$:
    $$\mathbf{t}_k = \frac{g_\phi(\text{prompt}(c_k))\,W_t}{\bigl\|g_\phi(\text{prompt}(c_k))\,W_t\bigr\|_2} \in \mathbb{S}^{d_e-1}, \qquad \mathbf{W}_{\text{ZS}} = \bigl[\,\mathbf{t}_1 \;\big|\; \mathbf{t}_2 \;\big|\; \dots \;\big|\; \mathbf{t}_K\,\bigr].$$
    Notice that $\mathbf{W}_{\text{ZS}}$ has the exact same shape ($d_e \times K$) as a supervised linear classification head! And because $\mathbf{W}_{\text{ZS}}$ depends only on the $K$ class names, it is cached once in memory—the Text Transformer is never executed during test-image inference.
  2. Test-Time Image Prediction (Zero Labeled Training Examples): Given any test image $x$, encode and $\ell_2$-normalize it into $\mathbf{I}_1 \in \mathbb{S}^{d_e-1}$, multiply by $\mathbf{W}_{\text{ZS}}$, and select the class with maximum cosine similarity:
    $$\hat{y}(x) = \arg\max_{k \in \{1, \dots, K\}} \bigl(\mathbf{I}_1^\top \mathbf{t}_k\bigr), \qquad P(y = k \mid x) = \frac{\exp\!\bigl(e^t\,\mathbf{I}_1^\top \mathbf{t}_k\bigr)}{\sum_{j=1}^{K} \exp\!\bigl(e^t\,\mathbf{I}_1^\top \mathbf{t}_j\bigr)}.$$

2. Why Prompt Engineering & 80-Template Ensembling Add $+3.5\%$ to $+5\%$

Why did we write "A photo of a {object}" in our OneNote diagram instead of just passing the bare word "dog" or "crane" into the text encoder? Radford et al. identified two failure modes of bare single-word labels:

Zero-Cost Embedding-Space Ensembling Across $M = 80$ Templates

On ImageNet, a single target class $c_k$ might appear as a blurry photo, a close-up, a dark photo, a cropped photo, or a sketch. OpenAI curated $M = 80$ diverse context templates (e.g., "a bad photo of a {}", "a photo of the large {}", "a close-up photo of a {}", "a bright photo of a {}"). Instead of running $80$ separate classifier heads at test time, linearity of the inner product $\mathbf{I}_1^\top \mathbf{t}$ allows us to average and re-normalize the $80$ text embeddings for class $k$ into a single unit vector $\bar{\mathbf{t}}_k$ offline:

$$\tilde{\mathbf{t}}_k = \frac{1}{M}\sum_{m=1}^{M} \mathbf{T}_e^{(k, m)}, \qquad \bar{\mathbf{t}}_k = \frac{\tilde{\mathbf{t}}_k}{\|\tilde{\mathbf{t}}_k\|_2} \in \mathbb{S}^{d_e-1}.$$

Combined with domain prompt engineering, 80-template embedding ensembling improves ImageNet zero-shot accuracy by nearly $+5\%$ (matching a ResNet with $4\times$ more compute) while costing zero extra FLOPs per test image!

3. The Modality Gap: Why Image and Text Vectors Live in Separate Cones

One might assume that after contrastive training on $400\text{M}$ pairs, a dog image embedding $\mathbf{I}_e^{(i)}$ and its caption embedding $\mathbf{T}_e^{(i)}$ coincide at the exact same point on the unit hypersphere ($\mathbf{I}_e^{(i)} \approx \mathbf{T}_e^{(i)}$). In a landmark NeurIPS 2022 paper (Mind the Gap), Liang et al. showed that this is false:

4. Beyond Softmax: Pairwise Sigmoid Loss in SigLIP (Zhai et al., 2023)

In Chapter 2, we saw that CLIP's symmetric InfoNCE loss computes row-wise and column-wise softmax normalization across the entire global batch of $N = 32{,}768$ pairs:

$$P_{I \to T}(j \mid i) = \frac{\exp(e^t\,\mathbf{I}_i \cdot \mathbf{T}_j)}{\sum_{k=1}^{N} \exp(e^t\,\mathbf{I}_i \cdot \mathbf{T}_k)}.$$

Computing the global denominator $\sum_{k=1}^N \exp(e^t\,\mathbf{I}_i \cdot \mathbf{T}_k)$ across $D$ distributed accelerators requires an expensive all-to-all all_gather of embeddings and materializing an $N \times N$ logit matrix in memory. Worse, softmax couples every sample in the batch: a single corrupted or outlier pair changes the denominator for all $N$ items.

SigLIP (Sigmoid Loss for Language Image Pre-Training, Zhai et al., ICCV 2023) eliminates global softmax normalization entirely. It treats every pair $(i, j) \in \{1, \dots, N\}^2$ as an independent binary logistic regression problem with label $z_{ij} = +1$ on the diagonal ($i = j$) and $z_{ij} = -1$ off the diagonal ($i \neq j$):

$$\mathcal{L}_{\text{SigLIP}} = \frac{1}{N} \sum_{i=1}^{N} \sum_{j=1}^{N} \ln\!\Bigl(1 + \exp\!\bigl(-z_{ij}\,(e^t\,\mathbf{I}_i \cdot \mathbf{T}_j + b)\bigr)\Bigr), \qquad z_{ij} = \begin{cases} +1 & \text{if } i = j \\ -1 & \text{if } i \neq j \end{cases}.$$
Structural comparison between OpenAI CLIP (Symmetric Softmax InfoNCE) and Google SigLIP (Pairwise Sigmoid Loss).
Property CLIP (Symmetric Softmax InfoNCE) SigLIP (Pairwise Sigmoid Loss)
Loss Formulation Row softmax ($I \to T$) + Column softmax ($T \to I$) $N^2$ independent binary logistic losses $\ln(1 + e^{-z_{ij}(e^t S_{ij} + b)})$
Global Normalization Requires row & column partition sums $\sum_{k=1}^N e^{L_{ik}}$ None — every $(i, j)$ pair is decoupled
Learnable Logit Parameters Log-temperature $t = \ln(1/\tau)$ ($t_0 = \ln(1/0.07)$) Log-temperature $t$ ($t_0 = \ln 10$) + negative prior bias $b$ ($b_0 = -10$)
Optimal Batch Size Sweet Spot $32{,}768\text{–}65{,}536$ (degrades at small $N < 4\text{k}$) Strong at $16{,}384\text{–}32{,}768$; stable even at $N = 4{,}096$ or $1\text{M}$

5. Runnable Python Verification: 80-Prompt Ensembling & SigLIP Loss

import math

def l2_norm(v: list[float]) -> list[float]:
    s = math.sqrt(sum(x * x for x in v))
    return [x / s for x in v]

def dot(a: list[float], b: list[float]) -> float:
    return sum(x * y for x, y in zip(a, b))

# 1. Verify Zero-Cost Prompt Ensembling (averaging M prompt embeddings offline)
dog_prompts = [
    l2_norm([0.88, 0.32, -0.05]),  # "a photo of a dog, a type of pet."
    l2_norm([0.84, 0.38, -0.02]),  # "a close-up photo of the dog."
    l2_norm([0.91, 0.27, -0.08]),  # "a bright photo of a dog."
]
mean_dog = [sum(p[d] for p in dog_prompts) / len(dog_prompts) for d in range(3)]
w_dog_ensembled = l2_norm(mean_dog)

# 2. Verify SigLIP Pairwise Binary Logistic Loss with b = -10 initialization
def siglip_loss(I_e: list[list[float]], T_e: list[list[float]], t: float, b: float) -> float:
    n = len(I_e)
    scale = math.exp(t)
    total = 0.0
    for i in range(n):
        for j in range(n):
            z_ij = 1.0 if i == j else -1.0
            logit = scale * dot(I_e[i], T_e[j]) + b
            # Numerically stable softplus: ln(1 + exp(-z_ij * logit))
            x = -z_ij * logit
            total += x + math.log1p(math.exp(-x)) if x > 0 else math.log1p(math.exp(x))
    return total / n

I_batch = [l2_norm([0.95, 0.15]), l2_norm([0.10, 0.98])]
T_batch = [l2_norm([0.93, 0.18]), l2_norm([0.14, 0.96])]
loss_siglip = siglip_loss(I_batch, T_batch, t=math.log(10.0), b=-5.0)
print("Ensembled dog classifier weight:", [round(x, 4) for x in w_dog_ensembled])
print(f"SigLIP 2x2 batch loss: {loss_siglip:.4f}")

6. Domain Adaptation, Fine-Tuning Regimes & Production Two-Tower Indexing

When deploying CLIP or SigLIP in a specialized domain (such as e-commerce fashion attributes, medical radiology, or satellite imagery), zero-shot representations may miss fine-grained domain distinctions. Adapting a pretrained dual-tower encoder requires careful control of four factors:

  1. False-Negative Collision & Multi-Positive Objectives: In domain catalogs, multiple items in a minibatch often share identical or near-identical captions (e.g., two black leather boots). Standard diagonal InfoNCE penalizes valid off-diagonal matches as negatives; production pipelines either deduplicate batch concepts or use a multi-positive contrastive / sigmoid objective where $z_{ij} = +1$ for all pairs sharing the same product/concept ID.
  2. Micro-Batch Accumulation vs. Global Contrastive Denominators: Naive gradient accumulation over small micro-batches does not reproduce a large-batch InfoNCE denominator unless embeddings are cached (GradCache) or decoupled via SigLIP's pairwise sigmoid loss.
  3. Index–Encoder Version Coupling: Whenever the vision or text projection weights change, the geometry of $\mathbb{S}^{d_e-1}$ rotates. A query tower from checkpoint $v_2$ cannot query an ANN index built with item vectors from checkpoint $v_1$ unless backward-compatible projection alignment (e.g., Shen et al., Backward-Compatible Training) is enforced; otherwise, the entire item index must be rebuilt atomically with the encoder rollout.
Comparison of parameter adaptation budgets when fine-tuning a pretrained CLIP / SigLIP dual-tower model on domain retrieval datasets.
Adaptation Regime Trainable Parameters Effect on Representation Geometry Key Engineering Trade-off
Projection-Only Tuning $W_i, W_t$ (or linear adapter) Rotates/re-weights existing features on $\mathbb{S}^{d_e-1}$ Fastest & resists catastrophic forgetting, but cannot learn new visual primitives.
Locked-Image Tuning (LiT)Zhai et al. (CVPR 2022) Freeze Vision Tower; train Text Tower Aligns domain text vocabulary to frozen visual features Preserves pretrained visual generality; existing image ANN index remains valid!
LoRA / Partial Unfreezing Low-rank adapters or top $2\text{–}4$ blocks Adapts high-level domain attributes & composition Strong accuracy-to-VRAM ratio; requires full item index rebuild on deployment.
Full End-to-End Fine-Tuning+ WiSE-FT weight-space ensembling All Vision & Text Tower weights Rebuilds patch-level and token-level representations Highest in-domain ceiling on large corpora; interpolate weights $\theta_{\text{WiSE}} = (1-\alpha)\theta_0 + \alpha\theta_{\text{ft}}$ to preserve out-of-distribution robustness.