Six years of work on one deceptively simple question — and the answer turned out to have a security bulletin attached to it.
You train a ResNet-50 on CIFAR-10. It reaches 93.2% on the test set. Then you change one line — torch.manual_seed(1) becomes torch.manual_seed(2) — and train it again. Same data. Same augmentations. Same architecture, same optimiser, same schedule, same number of steps. It reaches 93.3%.
Now open both checkpoints and diff the weights. Every single number is different. Not close-but-different: the first convolutional filter of network A looks nothing like the first convolutional filter of network B. The cosine similarity between corresponding weight vectors is essentially zero, layer after layer, all 23 million parameters of it.
So here is the question this whole lesson exists to answer. Did those two networks learn the same thing?
Before we look for a better tool, let us be certain the obvious one is genuinely broken. It is, and the reason is worth doing by hand, because it is the first of three symmetries that will structure the entire chapter after this one.
Take the smallest network that can make the point. Input x in R2, one hidden layer of two ReLU units — a unit that outputs max(0, its input), so it passes positives through unchanged and clamps negatives to zero — and one linear output.
Network A has w1 = (2, −1), w2 = (−1, 3), and output weights v = (5, 7). Feed it x = (1, 1):
Network B has exactly the same two hidden units, written down in the other order: w′1 = (−1, 3), w′2 = (2, −1), and output weights v′ = (7, 5). Same input:
Identical output. Not approximately — exactly, on every input, forever. The two networks compute the same function with different numbers in them.
Now measure them in weight space. The squared Euclidean distance between the two parameter vectors:
A distance of 7.6 between two networks that are the same network. Weight-space geometry does not measure functional identity; it measures an accident of labelling.
This symmetry is not a curiosity. An entire research line — Entezari et al. on permutation invariance and linear mode connectivity, and Ainsworth et al.’s Git Re-Basin — is about undoing it: find the permutation that best aligns network B to network A, apply it, and suddenly the straight line in weight space between them has no loss barrier. The networks were in the same valley the whole time, wearing different name tags.
Permutation is the discrete symmetry. There is a continuous one as well, and it is arguably worse.
ReLU is positively homogeneous: for any c > 0, ReLU(c z) = c ReLU(z). Both sides are cz when z > 0 and 0 otherwise. So scale a hidden unit’s incoming weights up by c and its outgoing weight down by c, and the two cancel exactly.
Take our network A and scale unit 1 by c = 10: w1 = (2, −1) becomes (20, −10), and v1 = 5 becomes 0.5. On x = (1, 1):
Because c ranges over all positive reals, this is not p! discrete copies but a continuous manifold of them. The set of weight vectors computing a given function is infinite, and unbounded: you can push a network arbitrarily far in weight space without changing a single output. Any weight-space metric is measuring a coordinate on that manifold.
If the weights are the wrong object, what is the right one? The answer that the whole field converged on: the activations. Fix a set of inputs, push them through both networks, and compare what comes out of a given layer.
Concretely. Choose a probe set of n inputs — say 10,000 CIFAR-10 test images. Pick a layer. Record the activation of that layer for every probe input, flatten or spatially average-pool it to a vector of length p, and stack the results:
Do the same for network B at some layer, giving Y ∈ Rn×q. Note the shapes: n is shared (both networks saw the same probes, in the same order) but p and q need not be. Layer 3 of a wide net might have 1024 channels while layer 3 of a narrow one has 128. Any comparison tool that requires p = q is already too fragile to be useful.
None of this is theoretical. Here is the actual code that produces the object every subsequent chapter operates on, with every shape annotated, because the shape decisions change the answer.
python — build the activation matrix X for one layerimport torch feats = {} def hook(name): def fn(mod, inp, out): # conv layer out: (batch, C, H, W). We need (batch, p). # Spatial average pooling is the standard choice: it makes the # comparison translation-invariant and keeps p = C rather than C*H*W. if out.dim() == 4: out = out.mean(dim=(2, 3)) # (batch, C) feats.setdefault(name, []).append(out.detach().cpu()) return fn for name, mod in model.named_modules(): if isinstance(mod, torch.nn.ReLU): mod.register_forward_hook(hook(name)) with torch.no_grad(): for xb, _ in probe_loader: # SAME loader, SAME order, for both nets model(xb.cuda()) X = {k: torch.cat(v).numpy() for k, v in feats.items()} # X["layer3.5.relu"].shape -> (10000, 1024) n probes x p features
| Decision | Options | What it changes |
|---|---|---|
| Where to tap | Post-ReLU, post-BatchNorm, pre-residual-add | Post-ReLU is the convention. Pre-activation values have a different (unclipped) spectrum and give systematically different numbers |
| How to reduce space | Average-pool to (n, C); flatten to (n, C·H·W); centre-crop | Pooling discards spatial layout, which is usually what you want for a layer-identity question and exactly wrong for a localisation question |
| Probe count n | 512 — 50,000 | Too few and the estimate is noise; too many and the n×n Gram matrix stops fitting in memory. Chapter 2 has the cost model |
| Precision | fp16 capture, fp32 or fp64 accumulate | Gram entries are products of large numbers summed over p; fp16 accumulation loses real precision on wide layers |
“Are these representations the same?” is really three questions, and conflating them causes most of the confusion in this literature. Separate them now and the next nine chapters will stay clean.
| Question | What it asks | The tool | Chapter |
|---|---|---|---|
| Information | Is the same information present, in any form at all? | Train a probe and see what it can decode | touched in 3 |
| Geometry | Is the same arrangement of examples present — are the same pairs close and the same pairs far? | A similarity index: CKA | 2, 3 |
| Interchangeability | Can I unplug one and plug in the other and have the system still work? | Model stitching | 4 |
These come apart in both directions and it is important to see how. A representation can carry all the information you need while placing it in a geometry that no downstream layer can read — information present, geometry mismatched. And two layers can have near-identical geometry and still refuse to be swapped, because geometry does not constrain the parts of the representation that carry no variance but that the next layer happens to depend on. Chapter 4 has an explicit example of the second failure.
Here is the design constraint for any similarity measure, stated once, precisely. We want a number sim(X, Y) that is:
That last line is the whole reason CKA exists rather than the older tools. Chapter 1 makes it a number.
One shortcut deserves killing before it takes root. Our two seeds scored 93.2% and 93.3%. Does that settle it?
No, and the reason generalises. Accuracy is a one-dimensional projection of a system whose behaviour lives in a much larger space. Two models can match on aggregate accuracy while:
| Differing in | Consequence you would care about |
|---|---|
| Which examples they get wrong | An ensemble of two models with disjoint errors is far better than either; an ensemble of two models with identical errors is worthless. Same accuracy, opposite value |
| Calibration | One is usable for thresholded decisions, the other is not |
| Robustness to distribution shift | They diverge the moment they leave the test set |
| Which features they rely on | One breaks when a spurious cue disappears; the other does not |
| Transferability of intermediate layers | The whole point of pretraining is what the middle of the network learned, and accuracy on the pretraining task barely constrains it |
“Same accuracy” is a statement about one number. “Same representation” is a statement about an n×p matrix. The second implies useful things about the first; the first implies almost nothing about the second. That asymmetry is why this whole line of work exists.
Six papers, one thread. The thread is: we built a ruler, we used it, we found something we did not expect, and then someone weaponised what we found.
| Chapters | What happens |
|---|---|
| 1–3 | Build the ruler. Why the naive rulers fail, then CKA derived from HSIC with every number worked by hand, then what CKA actually revealed about deep networks — and the ways CKA itself is untrustworthy. |
| 4 | The stronger test. Stitching: don’t score the geometry, swap the halves and see if the network still works. A behavioural test with no free parameters to argue about. |
| 5 | The finding. As models get bigger and better they converge — toward each other, and across modalities. The Platonic Representation Hypothesis and the arguments for and against it. |
| 6–7 | The consequence. If the geometry is shared, the map between two embedding spaces is recoverable from a pile of unpaired vectors. Do it by hand in 2-D, then read what happens at scale — and what it means for the vector database you are running right now. |
| 8–9 | What to actually do about it, what the hypothesis does not license, and the cheat sheet. |
We now have the right object: two activation matrices X ∈ Rn×p and Y ∈ Rn×q over a shared probe set. The temptation is to reach for something familiar — correlate the columns, take a distance, run CCA. This chapter is a catalogue of what happens when you do, worked in numbers small enough to check on paper.
We will use one running pair of matrices for the rest of the lesson. Three probe examples, two features. Here is network A’s representation:
Three rows, one per probe input; two columns, one per feature. Column means are (4+1+1)/3 = 2 and (1+4+1)/3 = 2, so the centered version — subtract each column’s mean, which is what every one of these methods does first — is:
And here is network B’s representation of the same three probes. B is a hypothetical second network that learned exactly the same thing, but wrote it down in a rotated basis at three times the scale. Specifically, B’s features are A’s features rotated 90° — feature 1 of B is what feature 2 of A was, negated, and so on — then multiplied by 3:
Check one row so you trust the rest. Row 1 of X̄ is (2, −1). Right-multiplying a row vector by R gives (2·0 + (−1)·(−1), 2·1 + (−1)·0) = (1, 2). Times 3: (3, 6). That is row 1 of Ȳ. Good.
The most natural thing in the world: neuron 1 of A ought to look like neuron 1 of B. Correlate them, do the same for neuron 2, average.
Column 1 of X̄ is (2, −1, −1). Column 1 of Ȳ is (3, −6, 3). Both are already mean-zero, so the correlation is just the cosine:
Column 2 of X̄ is (−1, 2, −1); column 2 of Ȳ is (6, −3, −3):
Average the two: (0.5 + (−0.5))/2 = 0.000. The metric reports that these two representations are completely unrelated. They are identical.
And look at what it missed. Correlate column 1 of X̄ against column 2 of Ȳ:
A perfect match, sitting one column over. The metric was looking at the wrong pair because it assumed the two networks agreed on which neuron gets which index — the exact assumption Chapter 0 demolished.
Work the 45° case out properly, because “it drops toward zero” deserves a number. Suppose A’s two features are uncorrelated with equal variance 1, and B’s basis is A’s rotated by 45°, so B’s first neuron computes (a + b)/√2. Then
So the best any neuron of A can do against any neuron of B is 0.707, and the metric reports “70% similar” for two representations that are, again, identical. Now generalise: a rotation that mixes p features evenly gives each of B’s neurons a 1/√p share of each of A’s, so the best-match correlation falls as
At realistic layer widths the metric returns essentially zero for identical representations. The failure is not marginal; it scales away to nothing.
A reasonable objection at this point: why should rotation invariance be required? ReLU is not rotation-equivariant, so a rotated representation is not literally interchangeable inside the network. The answer is that the invariance requirement is not about the representation, it is about what reads the representation.
| What consumes the layer | Absorbs a permutation? | Absorbs a rotation? | Absorbs a global scale? |
|---|---|---|---|
| A linear layer / 1×1 conv | Yes | Yes — it can compose the inverse rotation into its own weights at no cost | Yes |
| A linear probe you train | Yes | Yes — same argument | Yes |
| Cosine-similarity retrieval | Yes | Yes — cosine is rotation-invariant by definition | Yes |
| BatchNorm | Yes | No — it normalises per channel, so the basis matters | Yes |
| ReLU applied directly | Yes | No — the positive orthant is basis-dependent | Yes (positively homogeneous) |
| A human reading neuron 47 | No | No | No |
Read the first three rows. Every question we actually ask of a representation — can a probe decode this, can this be retrieved against, can the next linear stage use it — is answered identically for X and XQ. That is why rotation invariance is the right requirement even though the network itself is not rotation-equivariant: we are measuring the representation’s usefulness, and usefulness is mediated by things that absorb rotations.
Even simpler: ||X̄ − Ȳ||F, the Frobenius distance — square every entry of the difference, sum, take the root.
This is worse than useless, and not only because it is non-zero for identical representations. It requires p = q — you cannot subtract a 1024-column matrix from a 128-column one at all — and it is not invariant to anything. Multiply Y by 10 and the distance grows tenfold. There is no interpretable scale: is 10.95 large? Compared to what?
The serious pre-2019 answer was canonical correlation analysis (CCA), and its deep-learning descendants SVCCA and PWCCA. CCA finds the pair of directions — one in X’s feature space, one in Y’s — whose projections correlate most, records that correlation, then finds the next such pair orthogonal to the first, and so on. The mean of those canonical correlations is the similarity score.
CCA passes all three of our tests. Permutation: yes, it optimises over all directions. Rotation: yes. Scale: yes. On our X and Y it returns exactly 1. So far so good.
The problem is that it passes too much. CCA is invariant to any invertible linear transformation of either representation. Any. And that is fatal, which the following worked example makes concrete.
What should the answer be? Something well below 1. We will be able to compute it exactly by the end of the next chapter — CKA gives 0.707 here, because it weights each direction by how much variance actually rides on it. Hold that number; we will earn it.
It is worth seeing where the excess invariance enters, because it is one line of the algorithm and it is the same line that makes CCA well-behaved in its original statistical setting.
CCA begins by whitening both representations: replace X̄ with X̄(X̄TX̄)−1/2, which is the version of X whose feature covariance is the identity. Every direction is rescaled to unit variance. Then the canonical correlations are the singular values of QXTQY, where Q denotes the whitened (orthonormal) basis of each column space.
In classical statistics that is exactly right: you are asking whether two sets of variables are related, and the units of each variable are arbitrary. In representation analysis it is exactly wrong: the variance a direction carries is not an arbitrary unit, it is a statement about how much of the representation lives there.
And after whitening, only the subspace spanned by each representation survives. Which produces the following, on the numbers we already have:
This example is small enough to be a little unfair — with n = 3 and p = 2 the subspaces are forced to coincide. But the failure it exhibits does not go away at scale; it just becomes harder to see. Whenever p is comparable to n, two representations of full rank occupy overlapping subspaces and CCA saturates near 1 for structural reasons, telling you nothing.
There is a second, practical reason CCA struggles: it needs n > p to be well-conditioned. With p = 2048 features you need thousands of probe examples before the estimate stops being noise, and modern layers are wider than that. SVCCA patches this by first truncating to the top singular directions, which helps, and PWCCA patches it further by weighting canonical directions by how much of the representation they explain — which is, notably, a step in exactly the direction CKA takes wholesale.
The last approach worth dismantling is the one an experienced practitioner reaches for: forget metrics, train a linear map from A’s representation to B’s and see how well it fits.
This is a genuinely good instinct — it is the seed of model stitching, which is Chapter 4 and which is the best tool in the lesson. But as a similarity index it has three specific problems worth naming, because each one shapes how Chapter 4 has to be set up.
| Problem | What goes wrong |
|---|---|
| It saturates | If X̄ has full rank p and n > p is not comfortably satisfied, T can fit Ȳ essentially perfectly regardless of what either representation contains. R2 = 1 for reasons that are about shape, not content — the same disease as CCA, arriving by a different route |
| It is asymmetric | R2(A → B) and R2(B → A) differ, so it is not a metric at all. That asymmetry is informative — Chapter 4 mines it — but it means you cannot put the number in a symmetric heatmap and read a diagonal off it |
| It measures reconstruction, not use | Fitting Ȳ well means recovering the directions of Ȳ that carry variance. The directions B’s next layer actually depends on may carry very little. A high R2 is compatible with a stitched network that does not work |
Here is every metric so far against every symmetry. The rightmost column is the one that separates the survivors.
| Metric | Permute columns | Rotate basis | Isotropic scale | Invertible map (must be NO) |
|---|---|---|---|---|
| Same-index correlation | No | No | Yes | No ✓ |
| Best-match correlation | Yes | No | Yes | No ✓ |
| Frobenius distance | No | No | No | No ✓ |
| CCA / SVCCA | Yes | Yes | Yes | Yes ✗ |
| Linear CKA | Yes | Yes | Yes | No ✓ |
Only one row has four green cells. That row is the next chapter. But before we build it, play with the failures.
Eight probe points live in network A’s 2-D representation space, on the left. On the right, network B’s space — the same representation after whatever transforms you switch on. The three meters below score A against B. Turn on permute and rotate and watch same-index correlation collapse to noise while CKA sits at 1.000, because nothing about the representation has actually changed. Then hit real change, which genuinely reshuffles which probes are near which, and watch CKA finally move — proving it is not just always returning 1.
Two things to notice while you play. First, the point cloud on the right visibly moves under permute and rotate — the picture changes completely — while the pattern of which points are near which does not. That pattern is the invariant we are chasing. Second, once real change is on, the picture may look barely different but CKA drops sharply, because a probe that used to be an outlier is now in the middle. Visual similarity and representational similarity are not the same thing, and the metric is right where your eye is wrong.
Let us name what all three symmetries leave alone. Permuting columns, rotating the basis, and uniform scaling all act on the features. None of them touches the relationship between two rows — between two probe examples — except by an overall constant.
Precisely: the inner product between probe i and probe j. Under a rotation X → XR with RTR = I:
Under a permutation, which is a special orthogonal matrix, the same thing. Under scaling by c, every inner product is multiplied by c2 — a single global constant that any sensible normalisation will divide out.
We ended Chapter 1 with a plan: describe a representation by the table of relationships between probe examples, then compare the two tables. This chapter turns that plan into a formula, and then computes the formula three times by hand on numbers you can verify with a pencil.
Given the centered representation X̄ ∈ Rn×p, the Gram matrix is
Its size is n×n — determined by how many probes you chose, not by how many features the layer has. That is what lets us compare a 128-channel layer to a 2048-channel one: both become 10000×10000 tables of the same shape.
Compute K for our running X̄ = [(2,−1); (−1,2); (−1,−1)]. Six distinct entries, by symmetry:
Read it as a story. Probes 1 and 2 sit at opposite ends of the representation (K12 = −4, strongly negative). Probe 3 is a mild counterweight to both. The diagonal says probes 1 and 2 are far from the centre (5, 5) and probe 3 is closer in (2).
We now have two n×n tables, K from network A and L from network B, and we want a number saying how much they agree. The right frame is not “distance between tables” but covariance: when K says two probes are unusually similar, does L agree?
That statistic has a name from the kernel-methods literature: the Hilbert-Schmidt Independence Criterion, or HSIC. Gretton et al. introduced it in 2005 as a test for statistical independence between two random variables, and its empirical estimator is
Unpack it left to right. H is the centering matrix: multiplying by it subtracts row and column means. K H L H is therefore “the centered version of K, matrix-multiplied by the centered version of L.” And the trace of a product of two symmetric matrices is exactly their entrywise inner product:
So with our already-centered Gram matrices, HSIC is nothing more mysterious than: multiply the two tables entry by entry and add everything up. The (n−1)2 out front is the same normalisation you see in a sample covariance, and — crucially — it will cancel completely in the next step.
A dot product is not yet a similarity score — it grows if you scale either side. Divide by the lengths, exactly as you would to turn a dot product into a cosine:
The (n−1)−2 factors cancel top and bottom, leaving the form we will actually compute with:
That is Centered Kernel Alignment. It is bounded in [0, 1] for positive semi-definite kernels, it equals 1 exactly when L is a positive multiple of K, and — the whole point — it inherits every invariance of the Gram matrix. Rotate X and K does not move. Scale X by c and K scales by c2, which the denominator removes.
Chapter 0 set four requirements. Here they are, discharged, each in two lines. This is the part that makes CKA a construction rather than a heuristic.
Invariance to orthogonal transforms (which includes permutations). Let X̄′ = X̄Q with QTQ = I. Then
The Gram matrix is literally unchanged — not approximately, not up to a constant. Every downstream quantity therefore also unchanged. A permutation matrix satisfies QTQ = I, so relabelling neurons is a special case and costs nothing.
Invariance to isotropic scale. Let X̄′ = cX̄. Then K′ = c2K, so
Invariance to translation. Adding a constant vector b to every row of X leaves X̄ unchanged, because the column means shift by exactly b and the centering subtracts them. This is why centering is not a preprocessing nicety — it is what buys translation invariance, and it is why the “C” in CKA is load-bearing.
Non-invariance to general invertible maps. Let X̄′ = X̄A with A invertible but not orthogonal. Then K′ = X̄AATX̄T, and AAT ≠ I, so K′ is not a multiple of K. The Gram matrix genuinely changes, and CKA genuinely notices. The anisotropic caricature from Chapter 1 is exactly this case, and we compute its value at the end of this chapter.
| Transformation of X | Effect on K | Effect on CKA |
|---|---|---|
| Permute columns | K → K | None |
| Rotate / reflect basis | K → K | None |
| Scale all features by c | K → c2K | None |
| Add a constant to every row | K → K (after centering) | None |
| Scale one direction by c | K genuinely changes | Changes — correctly |
| Drop a low-variance direction | K changes slightly | Changes slightly — and that is the blind spot of Chapter 3 |
| Permute the rows (probes) of X only | K → PKPT | Changes — correctly. Probe order must match between the two networks |
shuffle=False and fix the seed.Our unit test. X and Y are the same representation rotated 90° and scaled by 3, so CKA must return exactly 1.
Ȳ = [(3, 6); (−6, −3); (3, −3)]. Its Gram matrix L:
Every entry is exactly nine times the corresponding entry of K. Which is not luck: the rotation left inner products untouched and the factor of 3 squared into 9. Now the cosine:
Exactly one. The metric passed the unit test that killed same-index correlation (which said 0.000) and Frobenius distance (which said 10.95). And it passed it for a structural reason, not a numerical accident: proportional Gram matrices are the fixed point of the whole construction.
For reference in what follows, ||K||F2 = 25 + 16 + 1 + 16 + 25 + 1 + 1 + 1 + 4 = 90, so ||K||F = √90 ≈ 9.4868.
A metric that always returns 1 is also useless. Here is a third network, C, whose representation of the same three probes is genuinely different:
Its Gram matrix M:
Compare the stories. In A, probes 1 and 2 are the two extremes (K12 = −4, the most negative entry) and probe 3 sits between them. In C, probes 1 and 2 are orthogonal (M12 = 0) and probe 3 is the outlier that both point away from (M13 = M23 = −2). Different structure, so we expect a middling score.
The entrywise product, term by term:
Exactly 0.6. Same structure in some respects, genuinely different in others — which is what a similarity index is for.
Everything above went through n×n Gram matrices. There is an equivalent route through p×p feature covariances, and knowing both is the difference between a CKA implementation that runs and one that runs out of memory.
Start from ⟨K, L⟩F = tr(X̄X̄T ȲȲT). Trace is invariant under cyclic rotation of its argument, so move X̄T from the front to the back:
And the same move on the norms gives ||K||F = ||X̄TX̄||F. So:
Verify it on X and Z. First the cross term, Z̄TX̄, a 2×2:
Same 36/60 = 0.6. Two completely different computational paths, one number.
| Form | Intermediate size | Cost | Use when |
|---|---|---|---|
| Gram (n×n) | n2 | O(n2p) | Few probes, wide layers. n = 512 probes, p = 8192 channels |
| Cross-covariance (p×q) | pq | O(npq) | Many probes, narrow layers. n = 100,000 probes, p = 256 |
One more derivation, and it is the one that explains both CKA’s strength and its most serious weakness. Write X̄ in its singular value decomposition, X̄ = UXSXVXT, and likewise for Ȳ. Substituting into the cross-covariance form and cancelling the orthogonal V factors leaves
In words: CKA measures how well the principal directions of the two representations line up, weighted by the product of the variance each direction carries. That is precisely the property CCA was missing. CCA gave every canonical direction an equal vote, so a direction carrying 0.0001 of the variance counted as much as one carrying 10000. CKA weights each agreement by how much it matters.
Verify the identity on our example, because it looks abstract until you see it in integers. The eigenvalues and eigenvectors of K (with the constant direction always giving eigenvalue 0, since the rows sum to zero):
And for M — check that the same two vectors are its eigenvectors too:
The two representations agree perfectly on which directions exist — the inner-product terms ⟨ui, uj⟩2 are 1 when i = j and 0 otherwise — and disagree completely about which one is important. A puts 9 units of variance on direction 1 and 3 on direction 2; C puts 2 and 6. Plug in:
Three independent routes to 36/60 = 0.6. And now we can settle the debt from Chapter 1: the anisotropic caricature with λX = (10000, 0.0001) and λY = (1, 1) on the same directions gives
CCA said 1.000. CKA says 0.707. The debt is paid.
python — linear CKA, both formsimport numpy as np def center(X): # center the FEATURES (columns). This is what makes the Gram matrix # double-centered for free — see the note above. return X - X.mean(axis=0, keepdims=True) def cka_gram(X, Y): """O(n^2 p). Use when n (probes) is smaller than p (features).""" X, Y = center(X), center(Y) K, L = X @ X.T, Y @ Y.T # (n, n) each return (K * L).sum() / (np.linalg.norm(K) * np.linalg.norm(L)) def cka_feat(X, Y): """O(n p q). Use when p, q are smaller than n. Same number.""" X, Y = center(X), center(Y) num = np.linalg.norm(Y.T @ X) ** 2 # (q, p) intermediate den = np.linalg.norm(X.T @ X) * np.linalg.norm(Y.T @ Y) return num / den X = np.array([[4., 1], [1, 4], [1, 1]]) Z = np.array([[3., 3], [3, 1], [0, 2]]) print(cka_gram(X, Z), cka_feat(X, Z)) # 0.6 0.6
The line people get wrong is the first one. X.mean(axis=0) centers the columns — each feature across probes. Centering axis=1 instead subtracts each probe’s own mean activation, which is a completely different (and generally wrong) operation that silently produces plausible-looking numbers. If your CKA heatmap has a suspiciously uniform diagonal band, check that axis first.
Nothing above required K to be X̄X̄T. HSIC is defined for any positive semi-definite kernel, so you can substitute an RBF kernel — one that measures similarity by distance rather than by inner product — and every step still goes through:
This makes CKA sensitive to nonlinear structure: two representations related by a nonlinear warp can score high under RBF CKA and low under linear CKA. The bandwidth σ is the new knob, conventionally set as a fraction α of the median pairwise distance, with α around 0.2–0.8.
The honest report from the original paper is that it rarely matters. RBF CKA and linear CKA give very similar layer-correspondence heatmaps across a range of bandwidths. That is a useful negative result: it says the interesting structure in these representations is already visible to a linear kernel, and it means you can use the cheap form without apologising. Reach for RBF only when you have a specific reason to suspect a nonlinear relationship, and report α when you do.
The estimator we derived, HSIC = tr(KHLH)/(n−1)2, is biased upward. Song et al.’s unbiased version works on the kernel matrices with their diagonals zeroed — write K̃ for K with zeros on the diagonal — and reads
Ugly, but every term is a matrix product you already have. The reason it matters is practical: accumulate numerator and denominator across minibatches separately and divide once at the end,
and the result converges to the full-batch value as the number of batches grows, independent of batch size. Averaging per-batch CKA values instead — the obvious thing, and the wrong thing — does not converge to anything in particular, because a ratio of averages is not the average of ratios.
Ten probe points, network A on the left and network B on the right, with their Gram matrices drawn beneath as heatmaps (warm = probes aligned, cool = probes opposed). The geometry match slider morphs B from “a rotated copy of A” (1.0) to “an unrelated arrangement” (0.0). The PC1 spike slider adds one shared dominant direction to both. Push the spike up and watch CKA climb toward 1.000 while the geometry-match slider stays where you left it — that is the failure mode Chapter 3 is about, visible in one control.
Press Unrelated + big PC1 and read the two numbers under the heatmaps. The full CKA reads around 0.94; the CKA computed after removing the top principal direction from both reads 0.00. Both are true statements about the same pair of representations, and only one of them is usually reported.
A ruler is only interesting for what it measures. Kornblith, Norouzi, Lee and Hinton did not publish CKA as a mathematical object; they published it because when they pointed it at real networks it showed them things the older tools had been hiding. This chapter is the findings, followed — because this lesson does not sell — by the four ways the ruler itself is untrustworthy.
The sanity check any similarity index must pass: take two networks with identical architecture trained from different random seeds, compute similarity between every layer of A and every layer of B, and draw the resulting matrix as a heatmap. If the index works, layer i of A should be most similar to layer i of B, giving a bright diagonal.
CKA produces that diagonal, crisply, across depths. CCA and its variants, on the same networks and the same probe set, produce a washed-out blob — they cannot reliably tell you that layer 5 of A corresponds to layer 5 of B rather than layer 9.
Make it a number. Model two independently trained networks as agreeing on their top direction and disagreeing on the rest. Say each has Gram eigenvalues (50, 5, 1, 0.2, 0.05), the top eigenvectors coincide, and the remaining four are randomly oriented relative to each other — so the alignment terms ⟨ui, uj⟩2 among the tail are around 1/4 each by symmetry. Compare what each index reports.
The layer pair genuinely corresponds. CKA says 0.99 and puts it on the diagonal. CCA says 0.60, which is not distinguishable from what a non-corresponding layer pair would give. Now run that across a 50×50 grid of layer pairs and one heatmap has a diagonal while the other is a smear. The mechanism is entirely in the weighting.
Train the same architecture at several widths, two seeds each, and ask how similar the two seeds are to each other. The answer: wider networks trained from different seeds are more similar to each other than narrow ones are. At small widths, two seeds land on genuinely different solutions; as width grows, the two runs converge on the same representation.
This is the first appearance in this lesson of a pattern that becomes the whole point by Chapter 5, so it is worth stating in its general form: as capacity grows, independent training runs stop diverging. Here it is two seeds of a CNN. In Chapter 5 it will be a language model and a vision model that never saw each other’s data.
The mechanism is not mysterious. A narrow layer must choose which features to keep; different runs choose differently, and the choices are near-arbitrary among many roughly-as-good options. A wide layer does not have to choose — it can afford to represent everything that helps, so both runs represent the same everything, plus their own idiosyncratic extras that carry little variance and therefore barely move CKA.
Nguyen, Raghu, and Kornblith (2021) took the same tool to very wide and very deep ResNets and found something that looks broken until you understand it: a large square block in the CKA heatmap where dozens of consecutive layers are all highly similar to each other.
Interpretation: those layers are not doing much. The representation enters the block, gets nudged, and leaves nearly unchanged. The block appears when the model is large relative to the dataset — overparameterisation made visible — and it is associated with a single dominant principal component that propagates through the block, dominating the CKA computation exactly as the spectral identity predicts. Delete layers from inside the block and accuracy barely moves; delete layers from outside it and accuracy falls.
| What CKA showed | The reading | What you would do about it |
|---|---|---|
| Bright diagonal across seeds | Architecture, not initialisation, determines what each depth computes | Trust layer index as a coordinate when transferring or pruning |
| Higher cross-seed similarity at larger width | Capacity removes the need to choose among equally good features | Expect reproducibility to improve with scale, not degrade |
| Large similarity block in deep/wide nets | Redundant depth; one PC carrying the block | Prune inside the block; it is where the free capacity is |
| Early layers similar across datasets and architectures | Early vision is close to universal — edges, colour opponency, blobs | Freeze early layers when transferring; the gains are elsewhere |
| Networks trained on random labels have a distinct signature | Memorisation and generalisation leave different geometric traces | Use representation similarity as a diagnostic, not only accuracy |
The most consequential architectural use of CKA came from Raghu, Unterthiner, Kornblith, Zhang and Dosovitskiy (2021), who used it to ask whether a Vision Transformer computes the same thing as a ResNet. The heatmaps answered clearly, and the answer explained several otherwise-puzzling empirical facts.
| What the CKA heatmap showed | What it implies |
|---|---|
| ViT’s layer-to-layer similarity is far more uniform than a ResNet’s, which shows a clear two-stage structure | ViT is not building a hierarchy in the CNN sense. Its early layers already do something global |
| ViT’s lower half corresponds, in cross-model CKA, to a large fraction of the ResNet’s entire stack | Self-attention reaches global context immediately; convolution has to spend depth growing a receptive field |
| Removing skip connections causes the similarity structure to collapse in ViT much more than in ResNets | The residual stream is doing more of the representational work in transformers — the layers write into a shared channel rather than transforming a pipeline |
| ViT preserves spatial location information much deeper into the network | Explains why ViT features transfer well to dense tasks (detection, segmentation) without architectural surgery |
Notice what kind of result this is. None of those four statements is an accuracy number, and none could have been obtained by comparing benchmarks. They are statements about how the computation is organised, obtained by measuring geometry. That is what a similarity index is for, and it is the strongest case for using one.
Train a network on correctly labelled data, and train an identical one on data with the labels shuffled at random. Both can reach near-zero training loss — that was the disquieting result of Zhang et al. in 2017. Only one of them generalises. Do they build the same representation?
CKA says: early layers, yes; late layers, no. The two networks agree substantially in the first third and diverge sharply thereafter. The reading is that early layers learn generic structure regardless of what the labels say, and the memorisation required by random labels is concentrated in the later layers, where the network must build a lookup table rather than a concept.
The output of a sweep is one square image: layers of A down, layers of B across, similarity as colour. Six patterns cover almost everything you will see, and each one has a diagnosis and an action.
| Pattern | What it means | What to do |
|---|---|---|
| Clean bright diagonal | Layer i of A corresponds to layer i of B. The healthy baseline | Nothing — this is your control. If it is missing between two seeds of the same model, suspect a probe-order bug before you suspect a discovery |
| Diagonal that shifts off-centre | One network does the same computation at a different depth — common when comparing architectures of unequal depth | Read off the offset: it tells you the depth-to-function mapping between the two families |
| Bright square block | Many consecutive layers are near-identical — redundant depth, usually one dominant PC propagating | Prune inside the block. Check the residual CKA first: a block driven purely by PC1 is a different (weaker) finding than a genuinely stalled representation |
| Bright only in the top-left corner | Early layers agree, later layers diverge. The classic cross-task or cross-dataset signature | Freeze the agreeing prefix when transferring; the divergence point is where task-specific learning begins |
| Uniformly bright everywhere | Almost always an artefact: a shared dominant component, a probe set with extreme outliers, or forgetting to centre | Check the norm distribution of your probes, and recompute after removing PC1. Do not report this as a finding |
| Uniformly dark, including the diagonal | Almost always a bug: mismatched probe order, wrong centring axis, or fp16 accumulation | Compute CKA(X, X) as a self-test. It must be exactly 1 |
Now the honest half. Return to the spectral identity and consider two representations that share one enormous direction and disagree about everything else. Concretely, suppose both have Gram eigenvalues (100, 1) — but their second eigenvectors are orthogonal to each other, meaning the two networks encode completely unrelated secondary structure.
Total disagreement about the second direction, and the score is 0.9999. This is not a contrived edge case: real representations have heavy-tailed spectra, and a single component often carries an order of magnitude more variance than the next. When you read “CKA = 0.95” in a paper, the honest question is how much of that is one component?
Davari, Horoi, Natik, Lajoie, Wolf and Belilovsky (ICLR 2023) went looking for ways to break CKA and found a clean one: CKA is highly sensitive to a small number of outlier probe points. Shift a few examples far from the rest and CKA moves dramatically, in either direction, without any change to how the network behaves on anything.
The reason falls straight out of the definition. Kij = x̄i · x̄j is a product of two magnitudes. A probe at distance 10 from the centre contributes entries a hundred times larger than a probe at distance 1, so it dominates every sum in numerator and denominator. Push a handful of probes far out and they, not the other 9,990, decide the score.
Worked example — one probe rewrites the verdict. Take our running pair, which honestly scores CKA(X, Z) = 0.600. Now append a single fourth probe on which both networks produce a large, similar activation — say (20, 20) in both spaces. Nothing about the first three probes has changed and nothing about either network has changed; we merely added an easy, extreme example to the probe set.
The centered Gram entries involving the new probe are on the order of its squared distance from the centre, which is roughly (20−6.5)2 + (20−2)2 ≈ 500 per space, versus entries of size 2–5 among the original three. In the sum ⟨K, M⟩ and in both norms, the new probe’s terms are two orders of magnitude larger than everything else, so the score becomes a statement about that one probe:
“These representations agree 99.8%” is now the reported result, and it is true only of a single input. In a real sweep the outliers are not added deliberately — they are the handful of images on which some layer saturates, or the one document whose embedding has an anomalous norm. They are already in your probe set.
What to do about it. Three cheap habits. Report the distribution of ||x̄i|| across your probe set, and look at it. Recompute CKA with the top 1% of probes by norm removed — if the number moves materially, say so. And consider a rank-based measure such as the mutual-kNN alignment of Chapter 5, which is by construction immune to this because it never multiplies magnitudes together.
Their headline demonstration: you can construct transformations of a representation that leave its functional behaviour unchanged while driving CKA to arbitrary values, and conversely you can find pairs with high CKA whose downstream behaviour differs. The measure and the thing you care about are correlated, not identical.
CKA is defined relative to a chosen set of n inputs. Change the set and change the number. This is obvious once stated and almost never controlled for.
| Probe choice | What it makes CKA measure | Failure mode |
|---|---|---|
| Test set of the training distribution | Agreement on the data both nets were optimised for | Flatters similarity — both nets are on-manifold and confident |
| One class only | Fine-grained within-class structure | Kills the dominant between-class PC, so scores plummet and look alarming |
| Out-of-distribution images | Whether the shared structure generalises | Often the most informative, almost never reported |
| Random noise inputs | The network’s inductive prior, not its learned features | Surprisingly high similarity — architecture alone explains a lot |
The practical rule: a CKA number without a stated probe set is not a measurement, it is a rumour. Report n, the distribution, and whether you centered per-batch or globally.
This is the deepest one, and it sets up the entire next chapter. CKA answers a question about arrangement: are the same probes close in both spaces? It does not answer a question about use: can the downstream half of network B consume network A’s layer?
Those come apart in both directions, and both directions are real:
Ding, Denain and Steinhardt (2021) made this concrete by proposing that similarity measures be validated like classifiers: a good measure must be sensitive (it drops when the representation changes in ways that matter functionally) and specific (it does not drop when the change is functionally irrelevant). Under those tests CKA passes some and fails others, and orthogonal Procrustes distance — which we will build by hand in Chapter 7 — often behaves better on their benchmarks.
Chapter 3 ended with a complaint: CKA scores geometry, and geometry is not use. So stop scoring. Cut network A in half, cut network B in half, and bolt A’s front onto B’s back. If the resulting Frankenstein still classifies images, the two representations were interchangeable in the only sense that pays rent.
Lenc and Vedaldi proposed exactly this in 2015, in a paper mostly remembered for a different contribution, and Bansal, Nakkiran and Barak revived and sharpened it in 2021 as the modern standard for “did they learn the same thing.”
Write a network as a composition split at layer k:
a≤k is everything up to and including layer k — the front. a>k is everything after — the back. The stitched network inserts a small learned map T between A’s front and B’s back:
Three rules make this a test rather than a trick:
| Rule | Why it is non-negotiable |
|---|---|
| Both networks are frozen. Only T is trained. | If either half adapts, you are measuring fine-tuning capacity, not compatibility. |
| T is affine. A 1×1 convolution (or a dense layer) with a bias, nothing more. | Given a deep enough T you could re-derive B’s features from scratch, and every pair of representations would “stitch.” The restriction is the measurement. |
| Same data, same loss. T is trained on the original training set with the original objective. | Otherwise you are testing transfer, not equivalence. |
Then the stitching penalty is the performance you lose:
A concrete size check, because the whole argument depends on T being small. Stitching two ResNet-50s at the output of stage 3 — 1024 channels, spatial map preserved — a 1×1 convolution has
Against roughly 15 million parameters in the ResNet-50 stages after that point, the stitch is about 7% of the machinery it is trying to drive — and, critically, it is linear, so it cannot be that 7% doing the work. At an earlier stitch point, 256 channels, T is 256×256 + 256 = 65,792 parameters, well under 1%.
Everything above becomes concrete on the running example. Let network B’s back be the simplest possible thing: a linear head that reads the first coordinate and takes its sign.
On B’s own representation Ȳ = [(3, 6); (−6, −3); (3, −3)] the logits are the first coordinates: 3, −6, 3, giving predictions +, −, +. That is the behaviour we must reproduce.
Attempt 1 — the identity stitch (T = I). Feed A’s representation X̄ = [(2, −1); (−1, 2); (−1, −1)] straight in:
Attempt 2 — the scalar stitch (T = tI). Fit the single best scale by least squares, t = ⟨X̄, Ȳ⟩F / ||X̄||F2. The numerator, term by term:
Zero. The best scalar stitch is t = 0, which maps every probe to the origin, giving logits 0, 0, 0 — no signal at all. And the reason is beautiful: X̄ and its own 90° rotation are orthogonal as matrices. A scalar can stretch but cannot turn, and turning is the entire difference between these two representations.
Attempt 3 — the diagonal stitch (T = diag(t1, t2)). Fit each output feature to the matching input feature: tj = ⟨X̄:,j, Ȳ:,j⟩ / ||X̄:,j||2. We computed those dot products in Chapter 1: 9 and −9, over ||X̄:,j||2 = 6 each.
Attempt 4 — the full linear stitch. Solve the least-squares problem T = (X̄TX̄)−1X̄TȲ. We already have both pieces. X̄TX̄ = [[6, −3]; [−3, 6]], whose inverse is (1/27)[[6, 3]; [3, 6]]. And X̄TȲ = [[9, 18]; [−18, −9]] (computed in Chapter 7, but you can verify the first entry: 2·3 + (−1)(−6) + (−1)(3) = 6 + 6 − 3 = 9). Multiply:
Check the top-left entry: (6·9 + 3·(−18))/27 = (54 − 54)/27 = 0. Top-right: (6·18 + 3·(−9))/27 = (108 − 27)/27 = 81/27 = 3. And T = 3R, exactly the transformation that generated Y from X. Apply it:
| Stitch family | Free parameters | Fitted T | Correct | Penalty |
|---|---|---|---|---|
| Identity | 0 | I | 2/3 | 33 pts |
| Scalar | 1 | t = 0 (collapse) | chance | — |
| Diagonal | 2 | diag(1.5, −1.5) | 2/3 | 33 pts |
| Full linear | 4 | 3R | 3/3 | 0 |
python — a stitched network, end to endimport torch, torch.nn as nn class Stitched(nn.Module): def __init__(self, net_a, net_b, k, c_a, c_b): super().__init__() self.front = net_a.layers[:k] # A's front — FROZEN self.back = net_b.layers[k:] # B's back — FROZEN self.T = nn.Conv2d(c_a, c_b, kernel_size=1, bias=True) for p in self.front.parameters(): p.requires_grad = False for p in self.back.parameters(): p.requires_grad = False def forward(self, x): with torch.no_grad(): h = self.front(x) # (B, c_a, H, W) h = self.T(h) # (B, c_b, H, W) — the ONLY trained op return self.back(h) # (B, num_classes) net = Stitched(A, B, k=12, c_a=256, c_b=256).cuda() net.train() net.front.eval(); net.back.eval() # <- see gotcha 1 opt = torch.optim.SGD(net.T.parameters(), lr=0.1, momentum=0.9) # train T with the ORIGINAL loss on the ORIGINAL data, then: penalty = evaluate(B) - evaluate(net)
| Gotcha | Symptom | Fix |
|---|---|---|
| 1. BatchNorm keeps updating | “Frozen” halves change behaviour anyway; the penalty drifts down over training and you conclude the models are more compatible than they are | requires_grad=False does not stop running-statistic updates. Call .eval() on both halves, and check running_mean before and after |
| 2. Statistics mismatch at the seam | Loss starts astronomically high and T spends its budget learning a scale factor | Initialise T so that its output matches B’s layer-k statistics: solve the least-squares fit in closed form on one batch and use it as the initialisation. Convergence goes from thousands of steps to hundreds |
| 3. Spatial or channel-count mismatch | Shapes do not line up at all between the two architectures | A 1×1 conv handles the channel mismatch for free. Spatial mismatch needs a resize, which is a real confound — report it, because interpolation is not an affine map on the feature vector |
| 4. Reporting only one direction | You conclude “these are the same” from a symmetric-sounding experiment that was not symmetric | Always measure A→B and B→A. The gap is the most informative number in the experiment — see the next section |
Stitching is asymmetric, and the asymmetry is informative. Suppose network C’s layer collapsed a direction — it produces a rank-1 representation where A produces rank 2. Take our earlier Z̄ but flattened onto one axis: Z̄′ = [(1, 1); (1, 1); (−2, −2)]. Probes 1 and 2 are now literally identical.
Try to stitch C into B. Any linear T maps identical inputs to identical outputs, so probes 1 and 2 receive the same logit, and B’s head must give them the same prediction. But B’s own behaviour on those probes was + and −. The penalty is irreducible: no linear map, and for that matter no function at all, can separate inputs that are equal.
Now stitch the other way, B into C. B’s representation is rank 2 and full of information; C’s back only ever needed one direction of it. A linear map that projects onto that direction exists, and the penalty is zero.
Lenc and Vedaldi’s original finding, on AlexNet-era convolutional networks: early layers of independently trained networks are interchangeable up to a linear map, and remain substantially interchangeable even across different architectures and different training tasks. The interchangeability degrades with depth — the later the layer, the more the two networks have specialised in incompatible ways.
Bansal, Nakkiran and Barak (2021) pushed this into the modern setting and reported results that were, at the time, surprising:
They proposed reading this as a working definition: two models learned the same thing to the extent that their halves are stitchable. It is a definition with no free parameters to argue about — no probe set, no kernel, no centering convention — just a number of accuracy points.
Forty probe points. Network A’s layer-k representation is on the left; network B’s is the same representation rotated by the angle you choose, scaled, and optionally degraded. B’s frozen head is the dashed decision boundary. The stitch T is fitted live by least squares within whichever family you pick, and the bar shows the resulting agreement with B’s own behaviour. Turn the angle up with the scalar family selected and watch the penalty explode; switch to full affine and watch it vanish. Then hit A collapses a direction — now even full affine cannot recover, because the information is gone.
Two readings worth doing deliberately. First, set the family to scalar and the angle to exactly 90°. The fitted stitch is the zero matrix, every one of the forty probes lands on a single point, and agreement falls to 50% — chance — for a 50-point penalty. That is the hand-worked collapse from earlier in this chapter, rendered: a scalar can stretch but it cannot turn.
Second, set the family to full affine with noise at zero. The penalty is 0 and the CKA readout is 0.999 — the two measures agree. Now switch on A collapses a direction. CKA falls only to about 0.76, because the surviving direction still carries most of the variance and CKA weights by variance. Agreement falls to about 72%, a 28-point penalty, because B’s head needed the direction that vanished. A geometric score that barely moved and a behavioural score that fell off a cliff, on the same pair: that gap is Chapter 3’s blind spot 4, live.
Chapters 2 through 4 built two tools and used them on pairs of models that were, by construction, close cousins: same architecture, same data, different seed. In 2024 Huh, Cheung, Wang and Isola pointed the same kind of measurement at models that have nothing in common — different architectures, different objectives, different modalities — and reported that they are converging.
Their claim, which they name the Platonic Representation Hypothesis: neural networks trained with different objectives on different data and different modalities are converging toward a shared statistical model of the reality that generated their data. And the better a model is, the closer it sits to that shared model.
To compare a language model to a vision model you need a metric that survives the fact that one takes tokens and the other takes pixels. The trick is a small paired set: images with captions. Embed the images with the vision model and the captions with the language model. Now you have two representations of the same n items, and everything from Chapter 2 applies.
The paper’s metric of choice is not CKA but mutual k-nearest-neighbour alignment, which is more robust to the blind spots we catalogued:
In words: for each item, list its k nearest neighbours in each space and count the overlap. Perfect agreement gives 1; the expected value under random neighbour sets is k/(n−1).
Worked example. Five items, k = 2. Model f and model g each report two nearest neighbours per item:
| Item | knn under f | knn under g | Overlap | Score |
|---|---|---|---|---|
| 1 | {2, 3} | {2, 4} | {2} | 1/2 = 0.5 |
| 2 | {1, 3} | {1, 3} | {1, 3} | 2/2 = 1.0 |
| 3 | {1, 2} | {2, 5} | {2} | 1/2 = 0.5 |
| 4 | {5, 3} | {5, 1} | {5} | 1/2 = 0.5 |
| 5 | {4, 3} | {4, 3} | {4, 3} | 2/2 = 1.0 |
0.70 against a chance floor of 0.50. Note how important the baseline is: with n = 5 and k = 2 the floor is enormous, and any headline alignment number is meaningless without it. At realistic scale — n in the tens of thousands, k around 10 — the floor is around 0.0005, and the measured alignments, though small in absolute terms, are orders of magnitude above it.
What the pipeline actually looks like, because the abstraction hides two decisions that matter.
python — cross-modal alignment on captioned images# A paired corpus: n items, each an image and a caption of the SAME thing. # Wikipedia-based image-text sets are the standard choice. imgs, caps = load_paired(n=10_000) F = vision_model.encode_image(imgs) # (10000, 1024) G = language_model.encode_text(caps) # (10000, 768) <- different d, fine # mutual k-NN alignment: cosine neighbours inside each space, then overlap def knn(E, k): E = E / np.linalg.norm(E, axis=1, keepdims=True) S = E @ E.T np.fill_diagonal(S, -2) # never your own neighbour return np.argsort(-S, axis=1)[:, :k] # (n, k) A, B = knn(F, 10), knn(G, 10) m = np.mean([len(set(a) & set(b)) / 10 for a, b in zip(A, B)]) chance = 10 / (10_000 - 1) # 0.0010
The chance floor at n = 10,000 and k = 10 is 0.0010. So an observed alignment of, say, 0.15 is a hundred and fifty times chance — a real effect — while also meaning that 85% of each item’s nearest neighbours differ between the two models. Both readings are correct and you should hold both.
| Decision | Why it matters |
|---|---|
| Which pairs | The metric is defined on a paired set, so the paired set defines what “the same thing” means. Image-caption pairs measure agreement about depicted content; they say nothing about agreement on style, sentiment, or anything a caption never mentions |
| Which k | Small k measures fine local structure and is noisy; large k measures coarse cluster structure and inflates toward chance. Report the curve over k, not one value |
| Cosine or Euclidean | Cosine ignores norm, which is usually right for embeddings, and is what makes the metric immune to a global scale difference between the two spaces |
| Which layer | “The” representation of a model is a choice. Final-layer pooled embeddings are conventional; intermediate layers often align better, which is itself informative |
The paper’s central plot is a scatter. Each point is a vision model. The x-axis is how good it is — average transfer performance across a broad downstream benchmark suite. The y-axis is how aligned its representation is with the other models in the pool. The correlation is clear and positive: the models that perform best are also the models that agree with each other most.
The stronger version repeats this across modalities. Take a family of language models of increasing capability, embed a set of captions with each, embed the corresponding images with a fixed vision model, and measure alignment. Alignment increases with language-model capability — a model trained only on text becomes more like a model trained only on pixels, as it gets better at text.
That is the finding that makes the hypothesis interesting rather than a restatement of Chapter 3’s width result. Two seeds of a CNN converging is unsurprising: same data, same loss. A text model and an image model converging requires an explanation.
The paper argues that convergence is what you should expect, from three pressures that all point the same way.
Notice that argument one does the heavy lifting and does not require anything exotic. It is a statement about constraint satisfaction: more constraints, smaller feasible set, more agreement among anything that satisfies them all.
Force one deserves arithmetic, because “the intersection shrinks” is the kind of phrase that sounds convincing without committing to anything.
Model the situation crudely. Let H be the set of representations a model of a given capacity could produce. Each task the model must solve is a constraint that keeps only the fraction f of H that supports it. Assume — and this is the strong assumption — that the constraints are roughly independent. Then after N tasks the surviving fraction is fN:
| Tasks N | Surviving fraction at f = 0.5 | At f = 0.8 |
|---|---|---|
| 1 | 0.5 | 0.8 |
| 5 | 0.031 | 0.33 |
| 10 | 9.8 × 10−4 | 0.107 |
| 20 | 9.5 × 10−7 | 0.012 |
| 50 | 8.9 × 10−16 | 1.4 × 10−5 |
Read row four. Twenty tasks, each individually eliminating only half the candidates, leave a region a millionth of the size of where you started. Two models that both satisfy all twenty must both be inside it, however differently they got there — and the expected distance between two points drawn from a region shrinks with the region.
Now notice what this predicts, and check it against the evidence. It predicts that convergence should track breadth of training, not merely size. A large model trained on one narrow task should not converge with anything; a moderate model trained on a very broad distribution should. That is a falsifiable and specific claim, and it is the reason the paper’s x-axis is a multi-task benchmark aggregate rather than a parameter count.
The paper’s most concrete theoretical claim is a statement about what an idealised contrastive learner converges to, and it is worth working out because it names the shared object precisely.
Set up the idealised world. There are latent events; observations co-occur when they are caused by nearby events. A contrastive learner is trained to give co-occurring observations high dot product and non-co-occurring ones low. Under an idealised analysis, the representation that minimises this objective has inner products proportional to pointwise mutual information:
Worked example. Suppose the world contains three concepts with marginal probabilities P(dog) = 0.4, P(cat) = 0.4, P(rocket) = 0.2, and the joint probabilities of appearing together in one scene are P(dog, cat) = 0.20 and P(dog, rocket) = 0.02.
Those two numbers are properties of the world. They mention no model, no architecture, no modality. If a vision model learns contrastively from images of scenes, and a language model learns contrastively from sentences describing the same scenes, and both worlds have the same event statistics, then both converge on Gram matrices containing +0.223 and −1.386 in the corresponding cells. The shared representation is not a coincidence; it is a statistic of reality that both learners are estimating.
Twelve concepts live in a shared latent world (centre). Two models — a “vision” model (left) and a “language” model (right) — each observe that world through their own distortion. As you raise capability, each model’s distortion shrinks and its layout snaps toward the latent one. Neither model can see the other. The mutual-kNN alignment between them rises anyway, against the chance floor drawn as a dashed line. That is the shape of the paper’s central plot, generated from nothing but two independent estimation problems with a common answer.
Press Add a modality gap and watch what happens: alignment barely moves. That is deliberate, and it is the subject of the next section — a rigid offset between two spaces is invisible to a neighbour-based metric, which is both the metric’s greatest strength and a real caveat on what “converged” means.
The paper names several limits itself, and the literature supplies more. All of them matter for Chapter 8, where we decide what to actually do with this.
| Objection | Substance |
|---|---|
| The modality gap | Liang et al. (2022) showed that in CLIP-style models the image embeddings and text embeddings occupy two separate cones in the shared space, with a persistent offset that training does not close. The relational geometry inside each cone can be beautifully aligned while the absolute positions are nowhere near each other — which is exactly the situation the sim’s gap button reproduces. “Converged” means “same internal geometry,” not “same coordinates.” |
| Not all information is shared | Text contains things images cannot show (counterfactuals, negation, abstraction). Images contain things text almost never records (exact texture, precise spatial layout). Convergence can only happen on the intersection, and the intersection is not everything. |
| Some domains do not converge | Representations for control and robotics are shaped by an embodiment — joint limits, sensor placement, dynamics — that is not shared across agents. There is no reason to expect a quadruped and a manipulator to converge on the same latent state. |
| Alignment is measured, capability is estimated | The headline correlation is between an alignment score and a benchmark aggregate. Both are proxies, and the pool of models is not a random sample — the good models also tend to share training data, architecture families, and even individual data sources. Shared causes are a real alternative explanation to convergence toward truth. |
| Absolute alignment is still low | The measured mNN values are far above chance and far below 1. The claim that is supported is about a trend; the claim that models are already interchangeable is not. |
A hypothesis you cannot imagine losing is not a hypothesis. Three observations would each do real damage, and it is worth knowing what they are before you decide how much to believe.
Notice that all three are measurements, not arguments. That is the useful property of this hypothesis: it is stated in a metric, so it can lose.
Here is a scenario. You have obtained a file. It contains eight million vectors, each 1536 floating-point numbers long. You know nothing else: not the text they came from, not which model produced them, not the tokeniser, not the dimensionality convention. You cannot query the model that made them. You have no examples of a text and its vector together.
Classical security reasoning says you have nothing. An embedding is a lossy, model-specific encoding; without the model or paired examples there is no key to turn.
Jha, Zhang, Shmatikov and Morris showed in 2025 that this reasoning is wrong, and the reason it is wrong is the previous chapter. If every good encoder converges toward the same underlying geometry, then the geometry is the key, and the geometry is sitting right there in the file.
Do the argument in miniature first, because it is exactly Chapter 7’s hand computation and it makes the whole thing feel inevitable rather than magical.
Suppose two encoders produce representations of the same underlying set of documents, and suppose — this is the Platonic assumption — that their Gram matrices are equal up to scale. Then there exists an orthogonal matrix Q with Y = c · XQ. Now: how much information do you need to find Q?
If you know which row of Y corresponds to which row of X — a paired set — the answer is a closed-form solve, which is Chapter 7. But you can go further. The matrix of pairwise distances within X is a complete, basis-free fingerprint of the point cloud. So is the one within Y. If the two clouds are the same cloud, those two distance matrices are equal up to a scale factor and a relabelling of rows. Finding the relabelling that makes them match is finding the correspondence — and you never needed pairs, only the assumption that the shape is shared.
The reason this result reads as surprising is that the naive search space is unimaginable, so it is worth quantifying both the space and the collapse.
Suppose you had 1,000 vectors from the unknown encoder and 1,000 from yours, and you knew they described the same 1,000 documents in some order. The number of possible correspondences is 1000! — a number with 2,568 digits:
Enumeration is not on the table by any margin. But the geometry does not enumerate; it constrains. Each vector has a distance profile: the sorted list of its 999 distances to the others. Under the shared-geometry assumption those profiles are preserved (up to a global scale) by whatever map relates the two spaces, so a vector in space A can only correspond to a vector in space B with a matching profile.
Count the information. A profile is 999 numbers. Two vectors’ profiles matching to a few significant figures across 999 coordinates is an event of vanishing probability unless they really do correspond. The search space of 102568 collapses to roughly one candidate per item, and the whole problem becomes a nearest-neighbour lookup in profile space — which is exactly what the Chapter 7 simulation does, and exactly what fails when noise makes the profiles indistinct.
| What the attacker has | What the attacker does not have |
|---|---|
| A dump of embedding vectors from the target system | The target’s encoder, its weights, or its identity |
| Any good public encoder of their own | Any query access to the target system |
| An unpaired corpus of their own text, in a roughly similar domain | Any text-and-vector pair from the target |
| Compute for a small adapter training run | The tokeniser, the dimension convention, the preprocessing |
That left column is a strikingly low bar, and it maps exactly onto the most common real leak: a vector index exported for debugging, a snapshot in an object store with over-broad permissions, an analytics pipeline that copies embeddings out of the primary system. None of those leaks include the model, which is why they have historically been triaged as low severity.
At scale you do not solve a matching problem over eight million points. You learn the translation with a network, and the design is a careful piece of engineering. For each encoder space i there are two small modules:
Translation from space i to space j is then just composition:
The shared latent Z is the load-bearing design choice. It is the architecture’s bet on the Platonic hypothesis, made explicit: we assume a universal space exists, and we allocate a tensor for it. Every encoder space gets a door into and out of the same room. Adding a new encoder is two more small modules, not a retraining of everything.
With no pairs, the loss cannot say “this vector should map to that vector.” It has to constrain the map by what it must preserve and what it must look like. Four terms do that job.
| Loss | What it says | What it forbids |
|---|---|---|
| Adversarial (output space) | A discriminator on space j cannot tell translated vectors Fi→j(u) from real vectors of space j | Translations that land off-manifold — technically in Rdj but nowhere a real embedding would ever be |
| Adversarial (latent space) | A discriminator on Z cannot tell which encoder a latent came from | The latent silently splitting into per-encoder regions, which would make the “shared” space a fiction |
| Cycle consistency | Fj→i(Fi→j(u)) ≈ u | Information destruction. A map that sends everything to the mean would fool both discriminators and fails here immediately |
| Vector-space preservation | cos(u, u′) ≈ cos(F(u), F(u′)) for pairs within a batch | Any translation that scrambles the relative geometry — even one that produces perfectly realistic-looking outputs |
The last one is the point of contact with everything in this lesson. Vector-space preservation is the Platonic assumption written as a penalty term. It says: whatever else you do, carry the Gram structure across intact. Adversarial losses supply realism, cycle consistency supplies invertibility, and vector-space preservation supplies the actual content of the translation. Drop it and there is nothing pinning the map to the correct correspondence rather than any of the many realistic-looking wrong ones.
Worth seeing as equations, because each one is a sentence from the table above with the ambiguity removed. Write u for a vector sampled from encoder space i, v for one from space j, and D for a discriminator.
Worked micro-example of why Lvsp is the one that pins the answer. Suppose a batch contains three documents whose pairwise cosines in the source space are
Any candidate translation must produce three vectors in the target space with those same three cosines. That is three equations. With a batch of b documents it is b(b−1)/2 equations — for b = 512, that is 130,816 constraints from a single batch, none of which required knowing what any document says. The adversarial losses supply almost no information by comparison (they constrain a distribution, not a configuration). This is where the signal comes from, and it is precisely the “shared geometry” of the previous chapter, cashed in.
The reported results: translated embeddings reach cosine similarity with the true target-space embedding of the same text up to roughly 0.92 for the best encoder pairs, with top-1 matching accuracy against a held-out candidate pool that is high enough to make the translation practically usable. Performance is best between encoders with similar training regimes, degrades across very different architectures, and degrades further out of distribution — a translator fit on one text domain does not transfer cleanly to a very different one.
And a detail that matters for the threat model: the method works between encoders of different output dimensionality. The shared latent absorbs the mismatch. You do not need to guess d.
“Invert the embedding” sounds like a hand-wave until you see the mechanism, which is simple enough to describe in three sentences and is the reason the attack chain terminates in readable text rather than in vague topic labels.
Morris et al. report exact recovery of a large fraction of short (32-token) inputs on standard corpora, with near-complete recovery of named entities and topical content on longer ones. Note what makes step 2 possible: you need query access to an encoder, but only to your own. That is the hinge that Chapter 6’s translation supplies — once the victim’s vectors have been moved into your space, your encoder is the only one the loop needs.
| Proposed defence | Does it work? | Why / what it costs |
|---|---|---|
| Add Gaussian noise to stored vectors | Partly, expensively | Translation degrades with noise — and so does retrieval, along the same curve. Noise large enough to defeat a translator is large enough to hurt your top-k. You are trading the product for the defence. |
| Apply a secret random rotation before storing | No | An orthogonal map preserves every inner product, so it changes nothing about the Gram matrix — which is the only thing the translator uses. This is the single most commonly proposed defence and Chapter 1 already told you it is a no-op. |
| Reduce dimensionality (PCA to 128) | Weakly | Removes tail directions and some fine detail, so full inversion gets harder. The dominant geometry — the part carrying topic, entity and attribute information — survives, and that is usually the sensitive part. |
| Quantise to int8 / binary | Weakly | Same story: coarse geometry survives quantisation by design, since that is why quantisation is usable for retrieval at all. |
| Encrypt at rest, restrict access, audit | Yes | The boring answer, and the correct one. The attack needs the vectors; make getting them as hard as getting the documents. |
| Store vectors for content you would publish anyway | Yes | Architectural rather than cryptographic: if the corpus is not sensitive, neither is its index. |
“Add noise” is the defence everyone proposes, so it deserves an actual calculation rather than a shrug.
Take unit-norm embeddings of dimension d = 1024 and add independent Gaussian noise of per-coordinate standard deviation σ. The expected squared length of the noise is dσ2, and since it is essentially orthogonal to the signal in high dimensions, the cosine between the stored vector and the true one is
| σ | dσ2 at d = 1024 | cosine retained | What it does to your retrieval |
|---|---|---|---|
| 0.005 | 0.026 | 0.987 | Barely measurable — and barely inconveniences a translator either |
| 0.01 | 0.102 | 0.952 | Noticeable reordering in the top-10 for close calls |
| 0.02 | 0.410 | 0.842 | Serious recall damage; nearest-neighbour ties become coin flips |
| 0.05 | 2.56 | 0.530 | The index no longer works |
Now the key observation. The attacker’s translation reads the same pairwise cosines your ranker does, so the noise degrades both by the same factor. The ratio of “attacker signal” to “defender signal” is unchanged at every σ. You are not buying an advantage; you are buying a uniform reduction in the usefulness of your own data, and hoping the attacker’s threshold for “good enough” is higher than yours. It usually is not: a translation good enough to leak topics and entities is far less demanding than a retrieval index good enough to ship.
The pattern in that table is worth naming. Every defence that tries to hide the coordinates fails, because no attack was reading the coordinates. Every defence that degrades the geometry works exactly to the extent that it degrades your own retrieval, because retrieval reads the same geometry. There is no free parameter to tune; utility and exposure are the same quantity measured twice.
Chapter 6 asserted that shared geometry pins down the map between two spaces. This chapter proves it, on numbers small enough to check, and then shows exactly where the proof stops working — which is where the machine learning has to start.
Two centered representations of the same n probes, X̄ ∈ Rn×d and Ȳ ∈ Rn×d. Find the orthogonal matrix R that best maps one onto the other:
This is the orthogonal Procrustes problem, named for the innkeeper who made guests fit the bed. It has a closed-form solution, and deriving it takes four lines.
Expand the objective using ||A||F2 = tr(ATA):
The third term does not involve R. The first term does, but only trivially: because R is orthogonal, tr(RTX̄TX̄R) = tr(X̄TX̄RRT) = tr(X̄TX̄), also constant. So minimising the whole thing is the same as maximising the middle term:
Take the singular value decomposition C = UΣVT and substitute:
W is a product of orthogonal matrices, so it is orthogonal, so every entry has magnitude at most 1. Since Σ is diagonal with non-negative entries, tr(ΣW) = ∑i σiWii ≤ ∑i σi, with equality exactly when W = I. Set W = I and solve:
The whole solution is: form one d×d matrix, take its SVD, throw away the singular values, multiply the two orthogonal factors. No iteration, no learning rate.
Our X̄ and Ȳ from Chapter 1. First C = X̄TȲ, a 2×2. X̄T has rows (2, −1, −1) and (−1, 2, −1); Ȳ has columns (3, −6, 3) and (6, −3, −3).
Now the SVD, by hand, via CCT:
A symmetric matrix of the form [[a, b], [b, a]] always has eigenvectors (1, −1) and (1, 1), with eigenvalues a − b and a + b:
So U has columns u1 = (1, −1)/√2 and u2 = (1, 1)/√2. Recover V from vi = CTui/σi, with CT = [[9, −18], [18, −9]]:
Assemble R = UVT. With VT having rows v1T and v2T:
And the optimal scale falls out of the same SVD, since the best s minimising ||sX̄R − Ȳ|| is the ratio of the achieved alignment to the source energy:
Rotation and scale both recovered exactly, from three probe points, with nothing but arithmetic. Verify on row 1: X̄1R = (2, −1)R = (1, 2), times 3 gives (3, 6) = Ȳ1. ✓
python — Procrustes alignment between two embedding spacesimport numpy as np def procrustes(X, Y, allow_scale=True, allow_reflection=True): """X, Y both (n, d), rows PAIRED. Returns R, s, b with X @ R * s + b ~= Y.""" mx, my = X.mean(0), Y.mean(0) Xc, Yc = X - mx, Y - my U, S, Vt = np.linalg.svd(Xc.T @ Yc) # (d, d) — the only heavy step if not allow_reflection and np.linalg.det(U @ Vt) < 0: U[:, -1] *= -1; S = S.copy(); S[-1] *= -1 # flip the smallest direction R = U @ Vt s = S.sum() / (Xc ** 2).sum() if allow_scale else 1.0 b = my - s * (mx @ R) return R, s, b def fit_quality(X, Y): """How well the two clouds match, independent of the fitted map. In [0, 1].""" Xc, Yc = X - X.mean(0), Y - Y.mean(0) S = np.linalg.svd(Xc.T @ Yc, compute_uv=False) return S.sum() / (np.linalg.norm(Xc) * np.linalg.norm(Yc))
Three things in that snippet are not decoration.
The reflection flag. R = UVT may have determinant −1, meaning the optimal map includes a mirror. For comparing representations that is usually fine — a reflection is an orthogonal map and downstream linear layers do not care. If you are aligning something with a genuine handedness (a pose, a physical configuration), forbid it by flipping the sign of the last column of U and the last singular value, which is the standard Kabsch correction.
fit_quality is the number people forget to report. R always exists; it is the best orthogonal map, not necessarily a good one. The ratio tr(Σ)/(||X̄||F||Ȳ||F) lies in [0, 1] and tells you how much of the two clouds’ energy the alignment actually explains. On our worked example: 36/(√12 · √108) = 36/36 = 1.000, a perfect match. Publish an alignment without this number and you have published a rotation matrix with no evidence that rotation was the right model.
The bias term b. This is the piece that handles Chapter 5’s modality gap. If image embeddings sit in one cone and text embeddings in another, b is the vector that walks between the cones, and R handles the rotation once you are there. Alignment across modalities without a bias term is fitting the wrong model.
| Variant | When you need it | The change |
|---|---|---|
| Different dimensions dX ≠ dY | Aligning a 768-dim text encoder to a 1024-dim vision one | C is dX×dY; R = UVT is a semi-orthogonal dX×dY map. Everything else is identical |
| Weighted pairs | Some correspondences are more trustworthy than others | C = X̄TWȲ with W diagonal. Same SVD |
| Robust to bad pairs | Your correspondence set has errors | RANSAC: fit on random subsets of size d+1, keep the R with the most inliers, refit on inliers |
| Full linear, not orthogonal | You want the best possible map and do not need invariance | Ordinary least squares, T = (X̄TX̄)−1X̄TȲ — the Chapter 4 stitch. Fits better, tells you less |
Now the hard version. You have the same two point clouds but the rows of Ȳ have been shuffled by an unknown permutation. Procrustes needs to know which row goes with which.
The key insight is the one from Chapter 1, used offensively: pairwise distances are a basis-free fingerprint. Compute all pairwise squared distances within X̄:
And within Ȳ:
Every Y distance is exactly 9 = 32 times the corresponding X distance. Divide the whole matrix by its own mean and the two are identical. The scale is recoverable from the ratio; the correspondence is recoverable by finding the row permutation that makes the normalised distance matrices agree.
Three further reasons the exact solve is not what vec2vec does at scale:
| Complication | Effect on the closed form | What replaces it |
|---|---|---|
| Noise: the geometries match only approximately | No permutation makes the distance matrices agree exactly; matching becomes an optimisation over n! options | Learn the map with a soft objective — the vector-space-preservation loss is the relaxed version of “distances agree” |
| The two corpora are different documents entirely | There is no correspondence to find; row i of X has no counterpart in Y | Match distributions instead of points — which is exactly what the adversarial losses do |
| Different dimensionality, di ≠ dj | R is not square; orthogonality is not even definable | Route through a shared latent Z of fixed size, with per-space adapters |
Read that table as a translation key. Every column-three entry is one component of the vec2vec architecture from Chapter 6, and every one of them exists to relax one assumption in the two-page closed form you just computed. The architecture is not arbitrary; it is Procrustes with each hypothesis replaced by a loss.
Space A on the left, space B on the right: the same twelve items, rotated by the true angle, scaled, and perturbed. Choose how many known pairs you have. With zero, the solver has to guess the correspondence from distance fingerprints alone, then run Procrustes on its guess — the unsupervised case. Watch the recovered angle lock onto the true one, and watch the match rate collapse once the noise is large enough that the fingerprints stop being distinctive.
Two behaviours are worth provoking deliberately. First, set known pairs to 2 and noise to zero: two correspondences are enough to pin a 2-D rotation exactly, and the recovered angle snaps to the truth. Second, set known pairs to 0 and push the noise up slowly. The recovered angle stays accurate for a long time and then fails abruptly rather than gradually — because the failure is a matching failure, and once a few items are mismatched the Procrustes solve is fitting the wrong problem. That cliff is the practical limit of unsupervised alignment, and it is why the real method uses distribution matching rather than point matching.
Eight chapters of measurement. This one is about what you change on Monday.
The classic migration problem: you built a retrieval system on encoder v1, indexed a hundred million documents, and now v2 is better. Re-embedding means running the whole corpus through a GPU again.
Cost it out. At a realistic 2,000 documents per second per accelerator:
Now the convergence-flavoured alternative: freeze the index and learn an adapter that maps v2 queries into v1’s space. Fit it on a modest sample — a few hundred thousand documents embedded by both models, which is a fraction of a percent of the corpus — and the adapter is a single matrix trained in minutes.
| Approach | Compute | Downtime | Quality |
|---|---|---|---|
| Full re-embed | ~14 GPU-hours + reindex | Dual-write window or a full swap | Exactly v2 |
| Learned linear adapter v2 → v1 | Minutes, on a sample | None — queries change, index does not | v2’s query understanding through v1’s geometry: better than v1, short of v2 |
| Hybrid: adapter now, re-embed lazily | Amortised over weeks | None | Converges to v2 as documents are touched |
If a vision encoder and a text encoder converge on the same relational geometry, you do not need a CLIP-scale paired dataset to connect them. You need enough paired data to fit the map between two already-aligned spaces — which, as Chapter 7 showed, can be a closed-form Procrustes solve on a few thousand pairs rather than a contrastive training run on four hundred million.
Cost the two routes against each other. Joint contrastive training at CLIP scale is four hundred million paired examples and a multi-week multi-GPU run. Post-hoc alignment on d = 768 needs a single matrix product and one SVD:
Three thousand pairs and a second of arithmetic, against four hundred million pairs and a cluster. That is the leverage convergence gives you — provided the premise holds.
The honest caveat is the modality gap from Chapter 5: the two spaces sit in separate cones, so the map needs a translation term as well as a rotation, and the residual after alignment is not negligible. Measure it with fit_quality from Chapter 7 before you commit — if that number is 0.4, no rotation is going to save you and you need the joint training after all. This works, and it works less well than joint training. Use it when paired data is the binding constraint.
Consequence 1 as a sequence of steps you could actually put in a ticket, with the check that gates each one.
| # | Step | The gate |
|---|---|---|
| 1 | Sample 50k documents; embed each with both v1 and v2. Keep row order identical | Row order. This is Chapter 2’s silent-bug row, and it will produce a plausible wrong answer if you get it wrong |
| 2 | Compute CKA(v1, v2) and Procrustes fit_quality | If either is low, stop — the spaces are not close enough for a linear map and re-embedding is the honest answer |
| 3 | Recompute both after removing the top 3 principal directions | If the numbers collapse, the agreement is one global axis and will not carry fine-grained ranking |
| 4 | Fit the adapter: full least squares v2 → v1, with bias | Hold out 10k of the sample; report residual on the held-out part, not the fit part |
| 5 | Evaluate end-to-end: recall@10 and nDCG on your real query set, adapter vs full re-embed vs v1 baseline | This is the stitching penalty. It is the only number that decides anything |
| 6 | Ship the adapter; re-embed lazily on document update | Track the fraction of the index that is native-v2 and re-measure recall as it climbs |
Step 5 is the whole point of Chapter 4 arriving in an engineering process. Steps 2 and 3 are cheap enough to run first and will save you step 4 when the answer is no.
Restating Chapter 6’s conclusion in the register of a design review, because this is the consequence with a compliance deadline attached.
| Old assumption | Replace with |
|---|---|
| “Embeddings are derived features, not personal data” | Embeddings are a reversible encoding. If the source text is personal data, so is the vector. |
| “An attacker would need our exact model” | An attacker needs any good model plus an unpaired corpus. Convergence supplies the rest. |
| “We can share embeddings with a vendor without sharing text” | You are sharing the text, with extra steps and a false sense of security. |
| “Deletion means deleting the document row” | Deletion means deleting the vector too, and any downstream index built from it. |
If encoders are replaceable components, they need the discipline that replaceable components get. Most vector stores today record the vector and the document id and nothing else, which makes every one of the operations above impossible to do safely later.
| Record with every vector | Why you will need it |
|---|---|
| Encoder identity and exact version | You cannot fit an adapter between two spaces you cannot name. Mixed-version indices are silently broken: the geometry is only shared within a version |
| Preprocessing revision (chunking, truncation, prefix) | “query: ” prefixes and chunk boundaries change the embedding more than most model upgrades do |
| Normalisation state (raw or L2-normalised) | Mixing normalised and raw vectors in one index reintroduces the length bug that Chapter 4’s scalar stitch made vivid |
| Embedding timestamp | Lets you re-embed lazily and measure the fraction of the index that has migrated |
| Dimensionality and dtype | Obvious, and routinely missing. Matryoshka-style truncated embeddings make this non-trivial |
Six claims that do not follow, each with the chapter that refutes it. This section exists because the hypothesis is the kind of idea that gets over-applied within a week of hearing it.
Everything so far has treated encoders as given. If you are training one, three properties of the space you produce are now design decisions rather than accidents, because they determine how it behaves under every tool in this lesson.
Anisotropy and rogue dimensions. Transformer embedding spaces are notoriously anisotropic: a handful of coordinates carry outsized magnitude and dominate every cosine, every Gram entry, and every CKA computation. Those coordinates usually encode something uninteresting — frequency effects, position artefacts — and they act as a permanent version of Chapter 3’s blind spot 1, baked into the space. Measure your spectrum. If λ1/λ2 is 10 or more, most of what your similarity numbers report is one axis.
Whitening is not free. The standard fix — remove the top components, or whiten the space so every direction has unit variance — genuinely improves retrieval on many benchmarks. It also destroys exactly the eigenvalue structure that Chapter 1 showed CCA discarding, and it moves your space toward a canonical form that every whitened encoder shares. In convergence terms: whitening makes your space more like everyone else’s. That is good for interoperability and bad for the argument that your vectors are meaningfully yours.
Dimensionality is a leak surface, not just a cost. Matryoshka-style training, where the first k coordinates of an embedding are themselves a usable embedding for every k, makes truncation a projection onto the dominant geometry. That is precisely the part of the space that carries topic and entity information, and precisely the part a translator recovers first. Truncating for storage does not truncate the sensitivity.
| Design decision | Effect on retrieval | Effect on translatability |
|---|---|---|
| Leave the space anisotropic | Worse — a few dims dominate every comparison | Slightly harder — the idiosyncratic axes are encoder-specific |
| Whiten / remove top components | Better on most benchmarks | Easier — you moved toward the shared canonical form |
| Train with a broader task mixture | Better generalisation | Easier — Chapter 5’s contraction argument, applied to you |
| Shrink d | Cheaper, slightly worse | Marginally harder for full inversion, no harder for attribute inference |
| Anti-pattern | Why it is wrong | Chapter |
|---|---|---|
| “We compared the models by correlating their neurons” | Zero for identical representations at any realistic width — the metric decays as 1/√p | 1 |
| “CKA is 0.95, so we can swap the encoder” | Geometric agreement is dominated by high-variance directions; the ones your ranker uses may be exactly the ones that differ. Measure the penalty | 3, 4 |
| “We report CKA” (without probe set, n, or residual) | The number is defined relative to a probe set and can be set by a handful of outliers or one principal component | 3 |
| “We rotate embeddings with a secret key before storing” | Rotations leave the Gram matrix exactly unchanged. It is a no-op against anything that reads geometry | 2, 6 |
| “Embeddings are derived data, so the retention policy does not apply” | They are a reversible encoding of the source text, and translation removes the “you would need our model” barrier | 6 |
Push the convergence result to its engineering conclusion and you get an idea worth stating even though nobody has built it: if all good encoders share a geometry, then the geometry — not any particular encoder’s coordinates — is the natural interface between systems.
| Today | If the convergence premise holds |
|---|---|
| An index is bound to one encoder’s coordinate system | An index is stored in a reference space, with a small adapter per encoder |
| Changing encoders means re-embedding everything | Changing encoders means fitting one adapter |
| Two teams’ indices cannot be merged | Two indices merge after alignment, with a reported fit quality |
| Cross-modal retrieval needs a jointly trained pair | Any two encoders can be bridged with a few thousand pairs |
The honest reason nobody has built it: the residual matters. Alignment recovers most of the geometry and not all of it, so a reference-space index is measurably worse than a native one, and “measurably worse” is a hard sell against “expensive but exact.” What is worth taking from the idea is the direction — that the durable asset is the geometry, and the encoder is a lens you point at it. Design so that assumption can become true rather than against it.
| Your question | Right tool | What to report alongside it |
|---|---|---|
| Which layer of A corresponds to which layer of B? | Linear CKA sweep | Probe set and n; CKA after removing the top PC |
| Can I replace this encoder in production? | Stitching penalty — fit the adapter, measure the loss | Both directions; recall@k on a held-out slice |
| Are these two models redundant in my ensemble? | Mutual-kNN alignment | The chance baseline k/(n−1); error overlap, not just representation overlap |
| Is my vector store sensitive? | Assume yes. Then attempt an inversion on a sample to size the exposure | What fraction of entities and attributes were recovered |
| Is this architecture change actually doing something? | CKA against the baseline, layer by layer | Where the heatmap stops matching — that is where the change bit |
| Are my deep layers redundant? | CKA block structure | Ablation accuracy inside vs outside the block |
Everything, compressed to the page you would keep open.
If you remember nothing else, remember the chain. Each step follows from the one above it, and each is a chapter.
| Number | What it is |
|---|---|
| 101166 | 512! — the functionally identical weight settings for one layer of width 512. Why weight-space comparison is dead |
| 0.000 vs 1.000 | Same-index correlation vs CKA on two representations that are literally identical up to a 90° rotation |
| 0.600 | CKA between our X and Z: 36 / (√90 · √40) = 36/60, verified three independent ways |
| 0.707 | CKA on the anisotropic caricature where CCA says 1.000. The gap is why CKA exists |
| 0.9999 | CKA when two representations share one dominant component and disagree completely about everything else. Blind spot 1 |
| t = 0 | The optimal scalar stitch between a representation and its own 90° rotation. A scalar cannot turn |
| 1,049,600 | Parameters in a 1×1 stitching conv at 1024 channels — about 7% of the ResNet-50 stages it drives |
| 0.70 vs 0.50 | The worked mutual-kNN example against its chance floor. Always read the floor |
| R = [[0,1],[−1,0]], s = 3 | Procrustes recovering the exact rotation and scale from three probe points, by hand |
| ≈ 0.92 | Best reported cosine similarity of a vec2vec translation to the true target embedding, with no paired data |
| 409.6 GB / 14 GPU-hours | The cost of re-embedding 100M documents at d = 1024 — what an adapter saves you |
| CKA | CCA / SVCCA | Procrustes distance | Stitching penalty | |
|---|---|---|---|---|
| What it reads | Gram-matrix cosine | Shared subspace | Residual after best rotation | End-task accuracy |
| Permutation-invariant | Yes | Yes | Yes | Yes |
| Rotation-invariant | Yes | Yes | Yes | Yes |
| Scale-invariant | Yes | Yes | Yes (if scale is fitted) | Yes |
| Invariant to invertible maps | No — correct | Yes — fatal | No — correct | Yes — by design |
| Needs matched dimensions | No | No | No (semi-orthogonal) | No (1×1 conv) |
| Outlier-sensitive | Very | Moderately | Moderately | No |
| Dominated by the top PC | Yes | No | Partly | No |
| Cost | One matrix product | One SVD + whitening | One SVD | A training run per layer pair |
| Answers “interchangeable?” | No | No | No | Yes |
Read the bottom row against the cost row. The only measure that answers the question people actually mean is also the only expensive one, which is why the cheap ones get over-interpreted. Use CKA and Procrustes to screen — hundreds of layer pairs, seconds each — and stitching to decide, on the two or three pairs that survived screening.
| Term | In one line |
|---|---|
| Probe set | The fixed inputs pushed through both networks. Everything downstream is defined relative to it |
| Representation matrix X | n probes × p features. The thing being compared |
| Gram matrix K | X̄X̄T. How every probe relates to every other. Basis-free |
| HSIC | Covariance between two similarity structures. Zero iff independent, for general kernels |
| CKA | Normalised HSIC. The cosine between two Gram matrices |
| Stitching layer | An affine map inserted between one network’s front and another’s back; the only thing trained |
| Stitching penalty | Accuracy lost by the stitched network relative to the intact one |
| Mutual-kNN alignment | Average overlap of nearest-neighbour sets across two spaces. The PRH metric |
| Orthogonal Procrustes | The closed-form best rotation mapping one point cloud onto another |
| Modality gap | A persistent offset between two modalities’ regions of a shared space, invisible to every centred metric |
| Vector-space preservation | A loss requiring pairwise cosines to survive translation. The Platonic assumption as a penalty term |
| Platonic representation | The shared statistical model of reality that converging models are hypothesised to be approaching |
| Paper | The one thing it added |
|---|---|
| Lenc & Vedaldi 2015 | Stop scoring representations; swap them and see if the network still works |
| Kornblith et al. 2019 | A similarity index invariant to rotation and scale but not to arbitrary invertible maps — which finally revealed layer correspondence |
| Bansal, Nakkiran & Barak 2021 | Stitching at modern scale: different seeds, and even different objectives, produce interchangeable halves |
| Nguyen, Raghu & Kornblith 2021 | Block structure — overparameterisation is visible in the CKA heatmap, and prunable |
| Huh, Cheung, Wang & Isola 2024 | Alignment increases with capability, across modalities. The Platonic Representation Hypothesis |
| Jha, Zhang, Shmatikov & Morris 2025 | Shared geometry is enough to translate between embedding spaces with no paired data — and to read the documents |
| If you want… | Go to |
|---|---|
| The contrastive machinery that produces these spaces | CLAP and contrastive learning |
| The geometry of embedding spaces from the ground up | Vector embeddings and similarity metrics |
| What a vector database actually does with this geometry | Vector databases |
| The SVD machinery behind Procrustes | Singular value decomposition and PCA |
| Kernels and HSIC in their native habitat | Kernel methods |
| Retrieval systems that depend on all of this being stable | RAG |
| Step | What to do | The decision that matters |
|---|---|---|
| 1. Probes | Fix 5,000–10,000 inputs and use the same set for every comparison | State the distribution. A CKA number without a probe set is a rumour |
| 2. Capture | Forward hooks on every layer; spatially average-pool conv maps to (n, C) | Pooling choice changes the answer — pick one and be consistent |
| 3. Center | X - X.mean(axis=0) | Axis 0, not axis 1. This is the most common silent bug |
| 4. CKA | Gram form if n < p, cross-covariance form otherwise | 50,000 probes in Gram form is 10 GB per layer. Choose by shape |
| 5. Minibatch | If you must batch, use the unbiased HSIC estimator and accumulate numerator and denominator separately | The biased estimator makes CKA depend on batch size |
| 6. Diagnose | Recompute after removing the top 1–3 principal directions | The gap between the two numbers is the real result |
| 7. Stitch | Freeze both nets, insert one 1×1 conv, train only it on the original loss | Affine only. A nonlinear stitch makes the test vacuous |
| 8. Both directions | Measure A→B and B→A separately | Asymmetry is information: it says which representation contains which |
| 9. Align | Procrustes on a few hundred pairs: U, S, Vt = svd(X.T @ Y); R = U @ Vt | Report tr(Σ)/(||X||F||Y||F) too — it says how well the clouds actually match |
| 10. Threat-model | Run an inversion on a sample of your own vector store | Size the exposure with a number before you argue about it in a review |
U, S, Vt = svd(X.T @ Y); R = U @ Vt. Then measure how much of encoder B’s behaviour survives the round trip through encoder A’s space. That number is this entire lesson, and you can have it before lunch.Without scrolling up: (1) explain why two networks with identical function can be arbitrarily far apart in weight space, with a number; (2) write linear CKA in both its forms and say when you would use each; (3) compute CKA for two Gram matrices with eigenvalues (9, 3) and (2, 6) on shared eigenvectors; (4) explain why the stitching layer must be affine and not deeper; (5) state the Platonic Representation Hypothesis, its metric, and two honest objections; (6) explain why a secret orthogonal transform does not protect a vector database. If any of the six stalls, its chapter is one tap away.