Contrastive learning and InfoNCE
At the end of §7.1 we hit a wall. A pretrained BERT knows a great deal about language, but its vectors are bad at the one thing a search engine needs, putting texts that mean the same thing close together. What we do have is an enormous supply of pairs that should be close. A question and its accepted answer. A title and its article. A search query and the page people clicked. Two paraphrases. How do we turn pairs into a training signal for geometry?
The obvious idea is to pull each pair together. That fails in a way that's worth seeing, and the fix (push everything else apart, in a very particular way) gives us InfoNCE, the loss behind DPR, SimCSE, CLIP, SimCLR, the sentence-transformers library, and every modern embedding model. By the end of this section you'll have derived it, proved what it estimates, implemented its gradient and its symmetric CLIP form, and know exactly what the temperature does, including what happens when the model learns it.
Pulling pairs together is not enough
Suppose an encoder maps each text to a unit vector , and we minimize the average squared distance between the two members of each positive pair ,
There's a perfect solution, and it's useless. Map every input to the same vector. The loss is zero, and every query is equally similar to every document. This is called representation collapse, and any objective that only pulls must somehow forbid it.
The classical fix is the triplet loss with a margin , which Sentence-BERT (Reimers & Gurevych, 2019) used with . For an anchor , a positive , and one negative ,
In plain words, the positive must be closer than the negative by at least the margin. It works, but each example teaches the model about a single negative, and once a negative is beaten by the margin it contributes no gradient at all. We'd like every example to contrast against many negatives at once, and to weigh them by how confusable they are. That's what InfoNCE does.
InfoNCE turns similarity into classification
Take a batch of positive pairs , for example questions and their answer passages. Encode and normalize everything, , and form the similarity logits
where is the temperature. Now treat each row as a -way classification problem. "Which of these passages is mine?" The correct answer for row is column , so the loss is the cross-entropy with the diagonal as the target,
In plain words, each query should score its own passage higher than every other passage in the batch, and the loss is how surprised a softmax over those scores is by the right answer.
The other passages in the batch are called in-batch negatives, and they're the reason the loss is so practical. We already had to encode them as positives for their own queries, so the negatives cost nothing extra except the dot products in , which is tiny next to running the encoder. A batch of 1,024 pairs gives every query 1,023 negatives for free.
Let's work one by hand, small enough to check on paper. Two queries and two passages have cosines
Row 1 gives its own passage the probability , a loss of . Row 2 gives , a loss of . InfoNCE is the mean, . With two candidates, each row's softmax is a sigmoid of the gap between the positive logit and the negative one, so the loss depends only on differences of cosines, magnified by . Query 2 is the weak one, because its positive beats its negative by only in cosine.
You've seen this loss twice before. It's the row half of the CLIP loss sketched in §1.8 (CLIP averages it with the same loss on columns, which we derive in full below), and it's a softmax version of word2vec's negative sampling from §1.5. The name comes from van den Oord, Li and Vinyals (2018), who introduced it for contrastive predictive coding. The "NCE" refers to noise-contrastive estimation, and the "Info" to a link with mutual information that we'll prove shortly.
A quick sanity check gives two numbers worth memorizing. With a random encoder and a temperature near 1, all logits are roughly equal, so every row's softmax is close to uniform and the loss is about . A perfect encoder drives it toward zero. With , training starts near .
At the random encoder sits right at , but notice that even identical pairs can't get the loss anywhere near zero. At the identical pairs reach zero, while random pairs land well above . Their cosines spread by about (the high-dimensional fact from §1.3), the temperature magnifies that spread twentyfold, and a confidently wrong softmax costs more than a uniform one. We'll come back to both effects when we study the temperature.
The gradient is observed minus expected
Let's derive the gradient, because it tells you what the loss does to the embeddings, and because "implement InfoNCE with its backward pass" is a common interview question. Write applied to each row, so is the probability the model assigns to passage for query . From the softmax cross-entropy gradient (§0.2),
Each depends on and through , so the chain rule gives
In matrix form, with , these are and .
In the hand example , so , with rows and . Every row sums to zero, because a softmax row sums to one. And the weak query 2 gets the larger entries, so the gradient spends its effort where the ranking is least certain.
In plain words, a gradient step moves each query toward its own passage and away from the softmax-weighted average of all passages in the batch, the "observed minus expected" pattern of word2vec. Each passage gets pushes from every query, which is easy to forget in an implementation.
One more step. The vectors were normalized, , and the Jacobian of normalization is
In plain words, normalization removes the part of the gradient that would only change the vector's length, since length doesn't affect the loss. The gradient with respect to an unnormalized vector is always perpendicular to it.
∂ Why the normalization Jacobian is I minus q-hat q-hat-transpose, over the norm
Write , so . The quotient rule on gives
This matrix is symmetric, so the chain rule gives the formula in the text. The matrix is the projection onto the plane perpendicular to , which is why .
InfoNCE loss and gradient with in-batch negatives
Implement infonce_grad(Q, P, tau) for unnormalized query embeddings Q and passage embeddings P, both (B, d). Normalize the rows, compute the InfoNCE loss above (rows are queries, the diagonal is the target), and return (loss, dQ, dP), the gradients with respect to the unnormalized Q and P. Vectorize everything and use a stable log-softmax.
What InfoNCE estimates, a lower bound on mutual information
Why would classifying "which passage is mine" produce good embeddings? Here's the precise answer. Minimizing InfoNCE maximizes a lower bound on the mutual information between the two views,
where is the number of candidates in each classification (here ) and mutual information (§0.5) is , the expected log of how much seeing raises the probability of . This bound is a theorem, not a heuristic, and we'll prove it in full because the proof explains three practical facts at once.
Set up the classification game precisely. Draw a context (the query). Pick a secret index uniformly from . Draw (the true passage) and every other independently (passages unrelated to ). A model with a score function , also called the critic, guesses the index with , and is exactly InfoNCE.
∂ Proof that log N minus InfoNCE is at most the mutual information
Write and the density ratio .
Step 1, the true posterior over the index. The joint density of the game is . Dividing by its sum over ,
Step 2, cross-entropy is at least entropy. For any model , , because the gap is an expected KL divergence, which is never negative. So and
Step 3, the index can't carry more information than the pair. Using Step 1, and ,
The first term is because is a draw from . For the second, call . Under a "null" game where all are drawn from , independent of , the density of is . The real game's density, summed over , is that times (from Step 1). So is a likelihood ratio, and . Therefore .
Chaining the two inequalities gives . ∎
Equality in Step 2 needs , so the best possible critic is , the log density ratio up to anything that depends only on . This argument follows Poole et al. (2019), who make the original derivation of van den Oord et al. (2018) rigorous.
Three consequences follow, and all three show up in practice.
- The optimal critic is a log density ratio. A trained bi-encoder's score is trying to approximate plus a constant per query (which the softmax over candidates ignores), and that log ratio is exactly the pointwise mutual information of §1.4. Word2vec factorized PMI implicitly (§1.5), and so does a contrastive embedder.
- The bound can never exceed , since . With a batch of 512, InfoNCE can certify at most nats of shared information, however good the encoder. This is one clean reason larger batches help.
- Negatives must come from . The proof assumed the other candidates are independent draws from the marginal. In-batch negatives are approximately that. Hard negatives, which we'll add soon, deliberately violate it. That's fine for learning embeddings (we care about the ranking, not the MI estimate), but it means you shouldn't read the loss as an MI estimate once you mine negatives.
Let's watch the bound behave with a case where the true mutual information is known. If is standard Gaussian in dimensions and , then , and we can use the optimal critic exactly.
With little shared information (0.83 nats), a few dozen candidates already make the bound nearly tight. With a lot (6.64 nats), the estimate is pinned just under the ceiling for small , and even at it reaches only 5.40, short of both the ceiling (6.24) and the truth. The bound is a Monte Carlo average, so expect wobble in the second decimal. The lesson for practice is that when two views share a lot of information, a small batch can't express it, and that's exactly the regime of text retrieval, where a query pins down its answer very precisely.
The InfoNCE estimate from a critic matrix
Implement nce_mi_estimate(S), where S is an (N, N) matrix of critic scores with S[i, j] = s(x_j, y_i) and the positives on the diagonal. Return in nats, computed stably. Then check with your own eyes that it never exceeds .
Temperature decides which negatives matter
The temperature looks like a minor knob. It isn't. It controls both how confident the model is allowed to be and which negatives get the gradient.
Confidence. With unit vectors, every cosine lies in , so every logit lies in . At , even a perfect encoder (positive cosine 1, every negative cosine ) can give its positive at most percent probability in a batch of 256. The loss can't go below about 3.6, and the gradient never stops pulling. A small temperature removes that ceiling. Typical values are 0.05 (SimCSE), 0.01 (E5 and GTE), 0.02 (the default in BGE's public fine-tuning scripts), and CLIP learns starting from 0.07.
Which negatives. Look again at the gradient on query . Negative pushes it with weight , where . At small , the softmax concentrates almost all of that weight on the few negatives with the highest cosine, the hard negatives. At large , the weights flatten and every negative gets a similar push. Wang and Liu (2021) call this the hardness-aware property of the contrastive loss, and they show a trade-off. Low temperatures spread embeddings more uniformly but become intolerant of near-duplicates, pushing apart negatives that are actually semantically close.
Hard negatives and bigger batches
Random in-batch negatives quickly become easy. Once the model can tell a question about tax law from a passage about volcanoes, those negatives give almost no gradient, which is the temperature argument again. Two levers keep the task hard.
Hard negatives. Add, for each query, passages that look relevant but aren't. Dense Passage Retrieval (DPR, Karpukhin et al., 2020) added one BM25-retrieved passage per question that matched many question words but didn't contain the answer, and shared all of them across the batch, so each query saw in-batch negatives plus hard negatives. Later work mined negatives with the retriever itself as it trained (ANCE, Xiong et al., 2021), or with a stronger cross-encoder. With hard negatives, the logit matrix becomes for hard negatives per query.
Bigger batches. More negatives raise the ceiling and make it likelier that a few hard ones show up by chance. In E5's ablation (Wang et al., 2022), growing the contrastive pretraining batch from 1K to 8K to 32K raised the average nDCG@10 on six BEIR datasets from 45.8 to 50.2 to 51.6. Since encoder activations dominate memory, big batches are built with tricks. You can all-gather embeddings across GPUs so every device scores against the whole global batch (§10.2), cache gradients with respect to embeddings and re-run the encoder in small chunks (GradCache, Gao et al., 2021), or keep a queue of recent embeddings as extra negatives, an idea MoCo popularized for images.
InfoNCE with shared hard negatives and duplicate masking
Implement hard_negative_loss(Q, P, Nh, tau, pos_ids, neg_ids).
QandPare(B, d)query and positive embeddings, andNhis(B, k, d), withkhard negatives per query. Normalize all of them.- Every query scores against all candidates, the
Bpositives followed by allB * khard negatives (flattened in row order), so the logits are(B, B + B*k). Rowi's target is columni. pos_ids(B,)andneg_ids(B, k)are document ids. Any candidate other than columniwhose id equalspos_ids[i]is a duplicate of the true answer and must be removed from rowi's softmax (set its logit to minus infinity).- Return the mean cross-entropy over rows, computed stably.
The symmetric loss and a learned temperature, as in CLIP
Everything so far scored one direction. Each query picks its passage out of the batch. CLIP (Radford et al., 2021), which §1.8 sketched, trains an image encoder and a text encoder together, and it needs both directions at test time, finding the caption for an image and finding the image for a caption. So it scores both. Let the rows of be the normalized image embeddings and the rows of the normalized caption embeddings, with the same logits . The row loss is InfoNCE as before. The column loss asks each caption to pick its own image out of the images,
In plain words, the same logit matrix is read twice, once as classification problems over its rows and once over its columns, and the two cross-entropies are averaged. The only difference between the directions is where the softmax normalizes.
Back to the hand example. Column 1 compares with , so caption 1 picks image 1 with probability and loss . Column 2 compares with , the same gap, so it has the same loss. , and . The two views disagree about what is hard. In the rows, query 2 is the weak one, with a cosine gap of 0.2. In the columns, both captions have a gap of 0.4, because each caption is compared with the other image's cosine to it (0.4 for caption 1, 0.2 for caption 2).
The gradient. Write for the softmax taken down each column, . The column loss is a cross-entropy over column , so the same "p minus y" rule gives . Adding the halves,
In plain words, it is the one-directional gradient with the row softmax replaced by the average of the row and column softmaxes. Rows of no longer sum to zero, but rows of and columns of still do, which is a quick test for an implementation. The normalization Jacobian from above then carries these gradients back to the raw embeddings unchanged.
Learning the temperature. CLIP doesn't tune by hand. It learns a scalar with , so . The exponential keeps the scale positive, and it makes the derivative clean, . By the chain rule, . Split it into the two halves and use ,
In plain words, the temperature's gradient is how far the softmax-weighted average logit sits from the positive logit, averaged over rows and columns. When positives score above that average, which is what a working encoder does, the gradient is negative, so gradient descent raises and sharpens the softmax. In the hand example the row terms are and and both column terms are , so .
That has a sharp consequence. If every positive is the largest logit in its row and its column, a larger scale always lowers the loss, all the way to zero, so the learned temperature runs off toward zero and the logits blow up. Only the mistakes push back, because a row whose positive is beaten pays more as the softmax sharpens. So the learned settles where the model's confidence matches its error rate, and CLIP adds a guard. It starts at and clips the scale at 100, which means . Watch the loss as a function of the scale for three batches of different difficulty.
The clean batch, where every image already finds its caption, keeps getting cheaper as the scale grows and would push the temperature to zero without the clip. The middle batch has its best scale near 30, where its 7 percent of mistakes start to cost more than its confident hits save. The hard batch wants a small scale, because a confident softmax is mostly confidently wrong. A learned temperature tracks this as training makes the task easier, and the clip at 100 is what stops it in the late, nearly separable phase.
At real scale. CLIP trained on 400 million image and caption pairs with a batch of 32,768. Its logit matrix has entries, 4.3 GB in float32, and the two softmaxes and their gradients need several times that. Nobody materializes it in one place. Each GPU gathers every embedding in the global batch (they're small, ), computes only the rows for its own images and the columns for its own captions, and its share of the matrix shrinks by the number of GPUs. With 256 GPUs, each holds two slices of , about 34 MB.
A different fix avoids the batch-wide softmax altogether. SigLIP (Zhai et al., 2023) scores every entry of the matrix as its own binary problem, with and off the diagonal, a learned bias (initialized at , since each positive faces negatives), and initialized at 10. Every term needs only its own pair, so no row or column sum has to be gathered, which is the story of the sigmoid losses that followed CLIP.
The CLIP loss with its gradients and a learned temperature
Implement clip_loss_grad(Qn, Pn, t, max_scale=100.0). Qn and Pn are (B, d) arrays of image and caption embeddings whose rows are already unit length (use them as given, don't renormalize), and t is the log of the logit scale, the above. The scale is the smaller of and max_scale, and the logits are the scale times Qn @ Pn.T.
Return (loss, dQn, dPn, dt), the symmetric loss and its gradients with respect to Qn, Pn, and t. When the scale is clipped ( max_scale), dt is 0. Use stable log-softmaxes along both axes, and no Python loops.
Sentence-BERT and the bi-encoder
Now we can say exactly what Sentence-BERT (SBERT, Reimers & Gurevych, 2019) contributed. It ran BERT on each sentence separately, mean-pooled the token vectors into one embedding (mean beat [CLS] in their ablations), and fine-tuned the shared encoder on pairs so that cosine similarity became meaningful. The architecture with two weight-sharing towers is called a siamese network, and the general pattern, where query and document are embedded independently and compared by a dot product, is called a bi-encoder.
The paper's motivating number is worth reproducing. To find the most similar pair among 10,000 sentences, a cross-encoder (BERT reading both sentences together) needs a forward pass for every pair.
SBERT measured about 5 seconds for the embeddings, against 65 hours for the cross-encoder. That gap is the reason bi-encoders exist. A bi-encoder can embed a corpus once, offline, and answer each query with one forward pass plus a nearest-neighbor search (§7.4). The price is that query and document never see each other inside the network, so subtle interactions ("not", word order, which entity does what) are harder to capture. We'll recover some of that accuracy with cross-encoder reranking in §7.5.
SBERT itself trained on natural language inference data (570,000 SNLI pairs and 430,000 MultiNLI pairs) with a softmax classifier over the features . The sentence-transformers library that grew from it soon made in-batch InfoNCE, under the name multiple negatives ranking loss, the default, because it works better for retrieval. SimCSE (Gao, Yao & Chen, 2021) then showed two neat things with InfoNCE at . Without any labels, you can make a positive pair by passing the same sentence through the encoder twice, because dropout makes the two embeddings slightly different. With NLI labels, entailment pairs make good positives, and contradictions make good hard negatives.
Alignment and uniformity
What does a good embedding space look like? Wang and Isola (2020) gave a clean answer with two measurable properties, for unit vectors on the hypersphere.
Alignment. Positive pairs should be close,
Uniformity. The embeddings of all inputs should spread out over the sphere, measured by a log average Gaussian potential between random pairs, with by default,
In plain words, alignment asks that pairs agree, uniformity asks that everything else disagree, and lower is better for both. A collapsed encoder has perfect alignment (0) and the worst possible uniformity (0, since every distance is zero). The uniform distribution on the sphere minimizes uniformity.
The link to InfoNCE is a theorem. As the number of negatives grows, InfoNCE minus converges to an alignment term plus a uniformity-style term,
∂ InfoNCE splits into alignment and uniformity
With one positive and negatives, write and . Then
Every exponent is at most , so the term goes to 0, and by the law of large numbers for each . Everything is bounded, so the limit passes inside the expectation, which gives the formula.
Now use from §1.3. The first term is , which is alignment up to scale and a constant. In the second, , the same Gaussian potential as the uniformity loss with , with the log taken per anchor. Wang and Isola prove that if perfectly aligned encoders exist, the uniform distribution on the sphere minimizes this term.
So the temperature is literally the width of the repulsion kernel. Small means a large , a sharp kernel, and repulsion dominated by each point's nearest neighbors, exactly the hard-negative focus we found from the gradient.
This also explains a puzzle from §1.9. Raw BERT and GPT-2 vectors are anisotropic, crowded into a narrow cone where random pairs have high cosine. That's a terrible uniformity score. Contrastive training fixes it directly, and SimCSE showed its gains on semantic similarity come with much better uniformity at nearly unchanged alignment.
Let's verify the whole story on synthetic data. Each example has a hidden 6-dimensional "meaning", and we observe two noisy 32-dimensional views of it. We train a linear encoder (with a bias, so collapse is possible) twice, once with InfoNCE and once with only the pulling loss.
The pull-only encoder reaches a perfect alignment score and a uniformity of zero, which is collapse. It learned to make its bias huge, so every input lands on the same point, and its recall@1 is that of a blind guess (1 in 500). InfoNCE pays a little alignment for a lot of uniformity, and that's what makes retrieval work.
Measure alignment and uniformity
Implement alignment_uniformity(Za, Zb, t=2.0). Za and Zb are (n, d) embeddings of positive pairs (row i of each). Normalize the rows first. Return (align, uniform), where align is the mean of over pairs, and uniform is of the mean of over all distinct pairs of rows of Za, computed with a stable log-mean-exp.
In PyTorch
In PyTorch, InfoNCE is one call to cross_entropy with arange targets. Here it is with a learnable temperature (parameterized as CLIP does, by its log inverse, and clamped), shared hard negatives, and duplicate masking.
To get a global batch across GPUs, gather the embeddings before computing the logits. torch.distributed.nn.functional.all_gather keeps the gradient flowing back to each device's own encoder, which the plain all_gather does not.
| loss | negatives per example | what it optimizes | where you'll meet it |
|---|---|---|---|
| triplet with margin | 1 | positive closer than negative by a margin | early SBERT, face recognition |
| InfoNCE (softmax) | in-batch, plus hard negatives | a lower bound on mutual information, alignment plus uniformity | DPR, SimCSE, E5, BGE, CLIP |
| symmetric InfoNCE | rows and columns | both directions of retrieval | CLIP, many text embedders |
| pairwise sigmoid | every pair as a binary problem | real-or-fake per pair, no batch normalizer | SigLIP-style image and text models |
The last row matters for multimodal models. Replacing the batch softmax with an independent sigmoid on each pair removes the need for a global normalizer, so the batch size no longer changes what the loss means, which is the idea behind the sigmoid loss of SigLIP. And contrastive learning for images, where the two views are random crops and color changes of one photo, follows the same mathematics in SimCLR and MoCo.
Read more
- van den Oord, Li & Vinyals (2018). Representation Learning with Contrastive Predictive Coding. The InfoNCE loss and its link to mutual information.
- Poole et al. (2019). On Variational Bounds of Mutual Information. A rigorous treatment of the InfoNCE bound and its log N ceiling.
- Wang & Isola (2020). Understanding Contrastive Representation Learning through Alignment and Uniformity on the Hypersphere. The two properties, their metrics, and the asymptotic theorem.
- Wang & Liu (2021). Understanding the Behaviour of Contrastive Loss. Temperature as a hardness knob, and the uniformity-tolerance dilemma.
- Radford et al. (2021). Learning Transferable Visual Models From Natural Language Supervision (CLIP). The symmetric loss, the learned and clipped temperature, and the sharded similarity matrix at a batch of 32,768.
- Reimers & Gurevych (2019). Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. Siamese encoders, pooling ablations, and the 65 hours versus 5 seconds comparison.
- Karpukhin et al. (2020). Dense Passage Retrieval for Open-Domain Question Answering. In-batch negatives plus BM25 hard negatives, beating BM25 by a wide margin.
- Gao, Yao & Chen (2021). SimCSE: Simple Contrastive Learning of Sentence Embeddings. Dropout as augmentation, NLI hard negatives, and the uniformity analysis.