April 16, 2026
We’ve now built encoders for two modalities separately:
A natural question: are these the same kind of vector? If I have a BERT vector for “a photo of a dog” and a ViT vector for an actual dog image, is there any reason these should be similar?
Answering this question requires us to revisit something we talked about way back at the beginning of the semester: the manifold hypothesis.
Recall from our early discussions of representation learning:
The manifold hypothesis: real-world high-dimensional data doesn’t fill its space. It lies on (or near) a much lower-dimensional manifold embedded in that space.
Examples:
The job of a good encoder is to discover and parameterize this manifold. Take the high-dimensional input, map it to a low-dimensional representation that captures the structure of the data.
If both text and images obey the manifold hypothesis, then somewhere in BERT’s output space there’s a text manifold, and somewhere in ViT’s output space there’s an image manifold.
Both manifolds describe the same underlying world. The same set of concepts — dogs, cars, people, sunsets, sadness, motion — appear on both.
Does the geometric structure of the text manifold match the geometric structure of the image manifold?
If “dog” and “puppy” are close together on the text manifold, are pictures of dogs and pictures of puppies close together on the image manifold?
The answer is: yes, in general — because the underlying semantic structure is the same. Both manifolds reflect the way the world actually works.
Let’s be precise about what we mean by “embedding spaces have semantic meaning.”
In a useful embedding space:
Classic word2vec example:
\[\mathbf{v}_{\text{king}} - \mathbf{v}_{\text{man}} + \mathbf{v}_{\text{woman}} \approx \mathbf{v}_{\text{queen}}\]
This isn’t a quirk of word2vec. Any good embedding space exhibits this property because semantic relationships have geometric structure. Encoders that successfully discover the manifold preserve this structure.
Here’s the critical observation that students often miss:
The semantic meaning of an embedding space is invariant to certain transformations.
Specifically, if you apply a rotation, reflection, or rescaling to all embeddings in a space, the semantic relationships are preserved:
All of these properties are about relative positions of points. Rotating or reflecting the entire space doesn’t change which points are near each other or how they’re arranged.
So now we have:
Both manifolds capture the same underlying semantic structure. But each was discovered independently by its own encoder, which means each was placed in its own arbitrary coordinate system.
The text manifold and image manifold are like the same map of the world, drawn by two different cartographers who used different conventions:
To compare a text embedding with an image embedding, we need them in the same coordinate system.
We could try to fix this post-hoc:
This is the Procrustes problem from classical statistics. Given two sets of corresponding points, find the optimal rotation that aligns them.
But there are problems:
The deeper problem: independently trained encoders learn different aspects of the data.
BERT learns whatever structure helps it predict masked tokens. ViT learns whatever structure helps it classify images.
These objectives produce manifolds that are structurally different, not just rotated versions of each other.
The “dog” region in text-space might be characterized by syntactic features (“noun, animate, four-legged”), while the “dog” region in image-space is characterized by visual features (“furry, snouted, tail-having”). These regions can be similar in size and shape but oriented very differently in their respective high-dimensional spaces.
Here’s where CLIP’s brilliance becomes clear:
Don’t try to align two pre-existing manifolds. Train both encoders simultaneously so the manifolds emerge already aligned.
The “rotation” that aligns the two manifolds isn’t computed post-hoc. It’s baked into the encoders themselves through joint training.
The original transformer used cross-attention to bridge encoder (input language) and decoder (output language). Why not use cross-attention to bridge text and images?
Architecturally:
Encode the image patches with a vision encoder
Encode the text tokens with a text encoder
Add cross-attention layers where text attends to image (or vice versa)
Train end-to-end on some joint task
This is exactly how the original transformer handled machine translation. It would seem to be the “standard” answer to combining two modalities.
Three big issues at scale:
Problem 1: Coupling. With cross-attention, the model processes image and text together in every forward pass. You can’t encode an image without a text prompt. You can’t encode text without an image. There’s no separable “image embedding” or “text embedding” you can extract and reuse.
Problem 2: Computational cost. Cross-attention between \(N\) image patches and \(M\) text tokens costs \(O(NM)\) per layer. For 400M training pairs, this gets astronomically expensive.
Problem 3: It defeats the purpose. If we use cross-attention, we never produce the shared embedding space we wanted in the first place. We’d just produce joint representations of (image, text) pairs. That’s useful for some tasks but not what we’re after.
Imagine building an image search engine with 100 million images.
With cross-attention: for each query text, run the full cross-attention model against every image in the database. 100 million forward passes per query. Completely infeasible.
With separate encoders + shared space:
For most real-world applications — retrieval, classification, similarity, conditioning — embedding reusability is dramatically more valuable than the few percentage points of joint accuracy cross-attention provides.
A note on cross-attention: it isn’t gone forever in the multimodal world.
Stable Diffusion (which we’ll cover in the final lectures) uses CLIP’s text encoder to produce text embeddings, then uses cross-attention inside the U-Net to condition image generation on those embeddings.
That’s the best of both worlds:
The architectures aren’t competing. They’re complementary tools for different jobs.
CLIP uses two separate encoders:
Text encoder: a transformer (similar to GPT-2 or a small BERT)
Input: tokenized text
Output: a single embedding vector per text sample (typically the [EOS] token’s representation) Image encoder: a ViT (or ResNet in some variants)
Input: an image
Output: a single embedding vector per image (typically the [CLS] token’s representation)
Both outputs are passed through a linear projection head that maps them into a shared \(d\)-dimensional space (typically \(d = 512\)).
CLIP’s training data is massive and simple:
400 million (image, caption) pairs scraped from the web.
No class labels, no annotations, no careful curation. Just naturally occurring images with their associated text from the open internet.
Examples:
The key property: the pairs are natural. That natural co-occurrence signal is what CLIP exploits.
Here’s the clever loss function that makes alignment happen during training.
For a batch of \(N\) image-text pairs, encode all of them. Get \(N\) image embeddings \(\mathbf{i}_1, \ldots, \mathbf{i}_N\) and \(N\) text embeddings \(\mathbf{t}_1, \ldots, \mathbf{t}_N\). L2-normalize them all (project to unit sphere).
Compute the full similarity matrix:
\[\mathbf{S}_{jk} = \mathbf{i}_j \cdot \mathbf{t}_k / \tau\]
This is an \(N \times N\) matrix of cosine similarities, scaled by a learned temperature \(\tau\).
Apply softmax cross-entropy in both directions, treating the diagonal as the correct answer:
\[\mathcal{L}_{\text{CLIP}} = \frac{1}{2}\left[ \text{CE}(\mathbf{S}, \text{diag}) + \text{CE}(\mathbf{S}^T, \text{diag}) \right]\]
Key observations:
The CLIP loss isn’t an arbitrary construction — it’s an instance of a well-studied loss function called InfoNCE (Information Noise Contrastive Estimation), introduced by van den Oord et al. in 2018 for representation learning.
For a single anchor (image \(j\)) and its positive partner (text \(j\)) among \(N-1\) negatives:
\[\mathcal{L}_j = -\log \frac{\exp(\mathbf{i}_j \cdot \mathbf{t}_j / \tau)}{\sum_{k=1}^{N} \exp(\mathbf{i}_j \cdot \mathbf{t}_k / \tau)}\]
Minimizing the InfoNCE loss is equivalent to maximizing a lower bound on the mutual information between the two modalities.
\[\mathcal{L}_{\text{InfoNCE}} \approx -I(\mathbf{i}; \mathbf{t}) + \log N\]
Why does this matter?
This is why CLIP-style training works so well across modalities. We’re not just pulling matched pairs together — we’re maximizing the mutual information between modality-specific representations of the same concept.
Look at the InfoNCE loss again:
\[\mathcal{L}_{\text{InfoNCE}} \approx -I(\mathbf{i}; \mathbf{t}) + \log N\]
That \(\log N\) term is doing real work.
Notice: the loss isn’t normalized by batch size. We don’t divide by \(N\). Each batch contributes a full categorical cross-entropy, summed across all \(N\) anchors.
Why? Because the number of negatives is the source of learning signal:
The bound becomes tighter with more negatives:
\[I(\mathbf{i}; \mathbf{t}) \geq \log N - \mathcal{L}_{\text{InfoNCE}}\]
This is why CLIP’s original batch size was 32,768 — and why every subsequent contrastive paper obsesses over batch size. It’s not for computational efficiency. It’s because the negatives ARE the signal.
Practical implication: if you’re training a contrastive model and you can’t fit a large batch, use techniques like memory banks (MoCo) or distributed batches across GPUs to artificially increase the effective negative count.
CLIP doesn’t use a fixed temperature. It learns \(\tau\) as a parameter, jointly with the encoders. Specifically, CLIP parameterizes \(\log(1/\tau)\) as a free parameter and bounds it for stability.
Why learnable? The optimal sharpness changes during training:
The model learns to anneal \(\tau\) down over training, automatically adjusting its own “confidence.” No manual hyperparameter tuning, no scheduled annealing — the model figures it out.
Why we expect \(\tau\) to decrease during training:
Early on, the encoders barely know what they’re doing. If we used a low \(\tau\) from the start, gradients would be dominated by a few high-confidence (often wrong) predictions, and training would be unstable.
As training progresses, the embeddings genuinely become more discriminative. The model can afford to be more decisive. Lower \(\tau\) extracts more learning signal from each comparison by sharpening the distinction between the true match and the distractors.
Let’s connect this back to the manifold framing.
For a matched pair (image \(j\), caption \(j\)), the loss says: “these two embeddings should be close.” This pulls the corresponding points on the two manifolds toward each other.
For a mismatched pair (image \(j\), caption \(k \neq j\)), the loss says: “these two embeddings should NOT be close.” This pushes unrelated points on the two manifolds away from each other.
Over millions of batches and 400M pairs, this produces a remarkable effect:
The two manifolds co-evolve during training. They don’t start aligned — they become aligned because the loss function rewards alignment.
This is the moment to step back and appreciate what’s happening.
The only thing that creates cross-modal alignment is the loss function.
This is one of the most powerful patterns in modern ML: a clever loss function can do work that no amount of architectural innovation can do. The right training signal, at sufficient scale, produces emergent properties that aren’t designed into the architecture at all.
Two trained encoders, one shared embedding space.
import clip
model, preprocess = clip.load("ViT-B/32")
# Encode an image
image_embedding = model.encode_image(preprocess(image).unsqueeze(0))
# Encode text
text_tokens = clip.tokenize(["a photo of a dog", "a photo of a cat"])
text_embeddings = model.encode_text(text_tokens)
# Compare
similarity = image_embedding @ text_embeddings.TBoth outputs are in the same 512-dimensional space. Cosine similarity between them is semantically meaningful. Image and text live on the same manifold.
Classify an image without training any classifier.
Given an image and a set of candidate labels, compute similarity between the image embedding and each label’s text embedding. Pick the most similar.
def zero_shot_classify(image, candidate_labels, model):
image_emb = model.encode_image(image)
text_prompts = [f"a photo of a {label}" for label in candidate_labels]
text_embs = model.encode_text(clip.tokenize(text_prompts))
similarities = (image_emb @ text_embs.T).softmax(dim=-1)
return candidate_labels[similarities.argmax()]Geometrically: we’re asking “which point on the text manifold is closest to this point on the image manifold?” Because the manifolds are aligned, that text point names what’s in the image.
The original CLIP paper’s headline result:
For comparison: a ResNet-50 fine-tuned end-to-end on ImageNet achieves ~76% top-1 accuracy.
CLIP matches a fully-supervised ImageNet classifier with zero ImageNet training. The representations are that good.
Zero-shot accuracy depends heavily on how you phrase the class descriptions.
Compare:
This is the original “prompt engineering” — not for LLMs, but for CLIP.
Why does this matter? CLIP’s text encoder was trained on web captions. “A photo of a golden retriever” matches the distribution of training captions. Just “golden retriever” does not.
Geometrically: Adding “a photo of a…” moves the text embedding to where natural image captions actually live.
Ensembling across multiple prompts (averaging embeddings from several templates) gains 1-3% accuracy. This is finding the centroid of multiple nearby points on the manifold.
Text \(\to\) Image retrieval: given a text query, find matching images.
# Pre-compute embeddings for an image database (do this once)
image_embs = model.encode_image(all_images) # (N, 512)
# Encode query (do this for each query)
query_emb = model.encode_text(clip.tokenize("a red car on a beach"))
# Find top-k matches (very fast)
similarities = query_emb @ image_embs.T
top_k = similarities.topk(5)CLIP or CLIP-derivatives power nearly every modern image retrieval system: Google Images, Pinterest visual search, stock photo search, and many recommendation feeds.
Because CLIP learns a semantic geometry, embeddings support compositional queries:
Each additional descriptor shifts the embedding toward a more specific region of the manifold. The compositional structure of language gets reflected in the geometric structure of the embedding space.
This is emergent. Nobody told CLIP that “red car on a beach” should be the intersection of “red car” and “beach.” It learned this from seeing millions of natural image captions where compositional descriptions paired with compositionally-described scenes.
The manifold has internal structure that respects the compositional semantics of the modalities.
Remember from Lecture 20: we built a conditional VQ-VAE with categorical breed/color labels. It worked, but the conditioning signal was rigid — you could only condition on the exact labels in the training set.
With CLIP, we can use arbitrary text as a conditioning signal:
This is what DALL-E, Stable Diffusion, Midjourney, and every modern text-to-image model uses.
In Lecture 20, we built the conditional VQ-VAE:
In Lecture 24, we built the autoregressive prior:
We noted at the end of Lecture 24: the only thing missing from DALL-E 1 was a way to use arbitrary text as the conditioning signal.
Every piece you’ve built this semester plays a role:
You can now understand and build DALL-E 1 from first principles (multimodal AI until 2023). Every component, every loss function, every architectural decision.
DALL-E 1 was an important milestone but was quickly replaced by diffusion-based approaches (DALL-E 2, Stable Diffusion).
Limitations of autoregressive image generation over VQ-VAE tokens:
Diffusion solves these:
But CLIP remains essential. Every modern text-to-image model uses CLIP (or CLIP-derivatives) to encode text prompts. The conditioning mechanism didn’t change — only the image generator did.
CLIP is the part that didn’t get replaced. The shared embedding space is too useful to give up.
The CLIP pattern — separate encoders + shared embedding space — has spread far beyond text and images:
BLIP / BLIP-2: add a generation capability on top of CLIP-style alignment. BLIP jointly trains three heads — contrastive (ITC), matching (ITM), and language modeling (LM) — so one model can do retrieval AND captioning.
ImageBind (Meta, 2023): six modalities aligned to a common space — text, image, audio, depth, thermal, IMU. Alignment composes transitively: align everything to images, and you get all pairwise alignments for free.
Modern VLMs (GPT-4V, Claude with vision, Gemini): vision encoder + LLM, with visual features projected into the LLM’s token space. Same philosophy as CLIP but with generation built in.
The field is converging on a simple recipe:
Everything we’ve built this semester fits this pattern. Representation learning, alignment, and generation are three aspects of the same underlying framework.
Next two lectures: diffusion models. A completely different generative mechanism that replaces autoregressive token sampling with iterative denoising — but still uses CLIP embeddings as the conditioning signal. Stable Diffusion is what happens when you combine diffusion’s generative power with CLIP’s shared embedding space.