Kornblith et al. 2019 · Lenc & Vedaldi 2015 · Bansal et al. 2021 · Huh et al. 2024 · Jha et al. 2025

Do Two Networks Learn the Same Thing?

Six years of work on one deceptively simple question — and the answer turned out to have a security bulletin attached to it.

Prerequisites: matrix multiplication and what a dot product measures. Gram matrices, HSIC, CKA, Procrustes alignment, and the whole convergence argument are built from zero.
10
Chapters
5
Interactive Sims
6
Papers
101166
Equivalent Weights

Chapter 0: The Question

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?

Why this is not a philosophy question. It has a budget attached. If two encoders learn the same thing, you can swap one for the other without re-indexing a hundred million documents. If a language model and a vision model learn the same thing, you can retrieve images with text without ever training on a paired dataset. And if an attacker who steals a pile of your embedding vectors can figure out that they learned the same thing as a model they control, they can read your documents. Every one of those follows, and we will get to all three.

Why weight-space comparison is dead on arrival

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.

h1 = ReLU(w1 · x),   h2 = ReLU(w2 · x),   y = v1h1 + v2h2

Network A has w1 = (2, −1), w2 = (−1, 3), and output weights v = (5, 7). Feed it x = (1, 1):

h1 = ReLU(2·1 + (−1)·1) = ReLU(1) = 1
h2 = ReLU((−1)·1 + 3·1) = ReLU(2) = 2
y = 5·1 + 7·2 = 5 + 14 = 19

Network B has exactly the same two hidden units, written down in the other order: w1 = (−1, 3), w2 = (2, −1), and output weights v′ = (7, 5). Same input:

h′1 = ReLU(−1 + 3) = 2,   h′2 = ReLU(2 − 1) = 1
y′ = 7·2 + 5·1 = 14 + 5 = 19

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:

||w1w1||2 = ||(3, −4)||2 = 9 + 16 = 25
||w2w2||2 = ||(−3, 4)||2 = 9 + 16 = 25
||vv′||2 = ||(−2, 2)||2 = 4 + 4 = 8
total = 25 + 25 + 8 = 58  →   distance = √58 ≈ 7.616

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.

And there are a lot of accidents. Any permutation of a hidden layer's units, applied consistently to that layer's outgoing weights, leaves the function untouched. A layer of width p therefore has p! functionally identical weight settings. For a modest layer of width 512 that is 512! ≈ 101166 equivalent points in parameter space. There are roughly 1080 atoms in the observable universe. Two independently trained networks landing in the same one of those 101166 basins by chance is not a thing that happens.

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.

The second symmetry: scaling straight through the ReLU

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.

hj = ReLU((c wj) · x) = c ReLU(wj · x),   (vj/c) · c ReLU(·) = vj ReLU(·)

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):

h1 = ReLU(20 − 10) = 10,   contribution = 0.5 × 10 = 5
y = 5 + 7·2 = 19  — unchanged, while w1 moved by ||(18, −9)|| ≈ 20.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.

The one-line takeaway from both symmetries. The map from weights to functions is enormously many-to-one, and the fibres are large, structured, and unbounded. Any comparison you want to make about what a network learned must be invariant to everything inside a fibre — and the cleanest way to guarantee that is to never look at the weights at all.

Compare what the network computes, not what it stores

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:

X ∈ Rn×p   row i = the layer’s output on probe input i
for ResNet-50’s final block on 10,000 images: X is 10000 × 2048

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.

Why the activation matrix is the right object. It is exactly what the rest of the network sees. Everything downstream of that layer — the next convolution, the classifier head, a linear probe you might train, a retrieval index you might build — consumes X and nothing else. If two X matrices support the same downstream computations, then for every purpose that matters the two layers are the same layer, whatever the weights say.

Capturing X in practice, with the shapes named

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
DecisionOptionsWhat it changes
Where to tapPost-ReLU, post-BatchNorm, pre-residual-addPost-ReLU is the convention. Pre-activation values have a different (unclipped) spectrum and give systematically different numbers
How to reduce spaceAverage-pool to (n, C); flatten to (n, C·H·W); centre-cropPooling discards spatial layout, which is usually what you want for a layer-identity question and exactly wrong for a localisation question
Probe count n512 — 50,000Too few and the estimate is noise; too many and the n×n Gram matrix stops fitting in memory. Chapter 2 has the cost model
Precisionfp16 capture, fp32 or fp64 accumulateGram entries are products of large numbers summed over p; fp16 accumulation loses real precision on wide layers

Three different questions hiding in one

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

QuestionWhat it asksThe toolChapter
InformationIs the same information present, in any form at all?Train a probe and see what it can decodetouched in 3
GeometryIs the same arrangement of examples present — are the same pairs close and the same pairs far?A similarity index: CKA2, 3
InterchangeabilityCan I unplug one and plug in the other and have the system still work?Model stitching4

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.

What is actually invariant, and what we want to be invariant to

Here is the design constraint for any similarity measure, stated once, precisely. We want a number sim(X, Y) that is:

Invariant to relabelling neurons
Permuting the columns of X must not change the answer. Otherwise we are measuring the accident from the top of this chapter.
↓ and, because the next layer is a linear map anyway…
Invariant to rotating the basis
Right-multiplying X by an orthogonal matrix must not change the answer. A rotation of the feature axes is invisible to everything downstream that is linear.
↓ and, because loss scale and normalisation layers wander…
Invariant to isotropic scale
Multiplying all of X by 3 must not change the answer. A BatchNorm downstream will undo it in one step.
↓ but — and this is where most tools die —
Not invariant to arbitrary invertible maps
If squashing the dominant direction by 104 and inflating a noise direction by 104 leaves the score unchanged, the score is worthless: it cannot see the difference between a representation and a badly conditioned caricature of it.

That last line is the whole reason CKA exists rather than the older tools. Chapter 1 makes it a number.

Why matching accuracy is not an answer

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 inConsequence you would care about
Which examples they get wrongAn 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
CalibrationOne is usable for thresholded decisions, the other is not
Robustness to distribution shiftThey diverge the moment they leave the test set
Which features they rely onOne breaks when a spurious cue disappears; the other does not
Transferability of intermediate layersThe 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.

The arc of this lesson

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.

ChaptersWhat happens
1–3Build 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.
4The 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.
5The 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–7The 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–9What to actually do about it, what the hypothesis does not license, and the cheat sheet.
Inline concept check — answer before reading on. Suppose you compare two networks by measuring, for each neuron in A, the maximum correlation with any neuron in B, and averaging. Is that permutation-invariant?  …  Yes — taking a max over B’s neurons does not care which order they are in. But it is not rotation-invariant: rotate B’s basis by 45° and every one of B’s neurons becomes a mixture, so no single neuron of B matches a neuron of A well any more, and the score collapses even though nothing about the representation changed. One down, two to go — and that pattern, a metric that survives one symmetry and dies on the next, is exactly what Chapter 1 catalogues.
Two networks with identical architectures are trained from different random seeds and reach the same accuracy. Why is the Euclidean distance between their weight vectors uninformative about whether they learned the same thing?

Chapter 1: Why Naive Comparison Fails

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:

X =  [ 4  1 ]
      [ 1  4 ]
      [ 1  1 ]

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:

X̄ =  [  2  −1 ]
      [ −1   2 ]
      [ −1  −1 ]

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:

Ȳ = 3 · X̄ R,   R = [ 0   1 ]
              [ −1  0 ]
  →   Ȳ = [  3   6 ]
                             [ −6  −3 ]
                             [  3  −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.

Hold this in mind for the next three subsections. X and Y are the same representation. Not similar — the same. Any linear classifier, probe, retrieval index, or downstream layer that works on X can be made to work on Y by composing it with a fixed 2×2 matrix that does not depend on the data. Anything that calls these two “different” is measuring the wrong thing. That is our unit test.

Naive metric 1: correlate matching neurons

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:

dot = 2·3 + (−1)·(−6) + (−1)·3 = 6 + 6 − 3 = 9
||(2,−1,−1)|| = √(4+1+1) = √6,   ||(3,−6,3)|| = √(9+36+9) = √54
corr = 9 / (√6 · √54) = 9 / √324 = 9/18 = 0.5

Column 2 of X̄ is (−1, 2, −1); column 2 of Ȳ is (6, −3, −3):

dot = (−1)·6 + 2·(−3) + (−1)·(−3) = −6 − 6 + 3 = −9
corr = −9 / (√6 · √54) = −9/18 = −0.5

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 Ȳ:

dot = 2·6 + (−1)·(−3) + (−1)·(−3) = 12 + 3 + 3 = 18
corr = 18 / (√6 · √54) = 18/18 = 1.000

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.

The obvious patch, and why it also breaks. Fine: for each neuron of A, take the best match in B and average those. Now permutation cannot hurt you. But rotate B’s basis by 45° instead of 90°. Every neuron of B becomes an even mixture of two of A’s, so the best single-neuron correlation for any neuron of A caps out around 1/√2 ≈ 0.707 — and with p = 2048 features mixed together it drops toward zero. The patch survives permutation and dies on rotation. There is no fix along this road, because the entire road assumes that individual neurons are the meaningful unit. They are not; the basis is arbitrary.

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

cov(a, (a+b)/√2) = (1/√2)·var(a) = 1/√2,   var((a+b)/√2) = (1 + 1)/2 = 1
corr = (1/√2) / (1 · 1) = 0.707

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

best-match corr ≈ 1/√p  →   p = 2: 0.707,  p = 128: 0.088,  p = 2048: 0.022

At realistic layer widths the metric returns essentially zero for identical representations. The failure is not marginal; it scales away to nothing.

Which invariances you need depends on what comes next

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 layerAbsorbs a permutation?Absorbs a rotation?Absorbs a global scale?
A linear layer / 1×1 convYesYes — it can compose the inverse rotation into its own weights at no costYes
A linear probe you trainYesYes — same argumentYes
Cosine-similarity retrievalYesYes — cosine is rotation-invariant by definitionYes
BatchNormYesNo — it normalises per channel, so the basis mattersYes
ReLU applied directlyYesNo — the positive orthant is basis-dependentYes (positively homogeneous)
A human reading neuron 47NoNoNo

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.

And the last row is why interpretability research is different. If your question is “what does neuron 47 detect?”, then the basis is exactly what you care about, and every invariance in this lesson is destroying the signal you want. That is not a contradiction; it is two different questions. Representation similarity asks whether two networks encode the same structure. Interpretability asks whether a particular coordinate system is meaningful — and the fact that the coordinate system is arbitrary under our symmetries is precisely why finding a privileged one (sparse features, dictionary learning) is hard work rather than a matter of reading off weights.

Naive metric 2: distance between the matrices

Even simpler: ||X̄ − Ȳ||F, the Frobenius distance — square every entry of the difference, sum, take the root.

X̄ − Ȳ = [ −1  −7 ]  
              [  5   5 ]
              [ −4   2 ]

||·||F2 = 1 + 49 + 25 + 25 + 16 + 4 = 120  →   distance ≈ 10.95

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?

Naive metric 3: CCA, and the too-much-invariance trap

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.

Worked example — the caricature that CCA cannot see. Suppose a representation X has two orthogonal directions in probe space, with the Gram-matrix eigenvalues λX = (10000, 0.0001). Read that as: essentially all the variance across your probe set lives in direction 1; direction 2 is numerical dust. Now build Y = XA where A is the invertible map that divides direction 1 by 100 and multiplies direction 2 by 100. Y’s eigenvalues are λY = (1, 1).

Y is a catastrophe. The signal that dominated X has been buried under the dust; a downstream linear layer reading Y sees a 50/50 mixture where it used to see a clean 108:1 ratio. Any classifier with finite precision or finite data will behave completely differently on the two. CCA reports 1.000, perfect similarity, because A is invertible and CCA is blind to invertible maps by construction.

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.

Why CCA has that invariance — and a worked case where it is fatal

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.

whitening → every direction gets variance 1 → λ information destroyed
after this step, a direction carrying 99.99% of the variance and one carrying 0.01% are indistinguishable

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:

Worked example — CCA cannot separate our three matrices. All three of X̄, Ȳ, Z̄ are 3×2 with centered columns, so each one’s column space is a 2-dimensional subspace of R3 orthogonal to (1, 1, 1). But the space orthogonal to (1, 1, 1) in R3 is 2-dimensional, so all three column spaces are the same subspace. QXTQY is therefore an orthogonal matrix, its singular values are both 1, and:

CCA(X, Y) = 1.000   CCA(X, Z) = 1.000

Meanwhile CKA(X, Y) = 1.000 and CKA(X, Z) = 0.600, as we will derive in the next chapter. Y is genuinely X in different clothes; Z genuinely is not. CCA calls them both perfect matches because it looks only at which subspace is occupied, never at how the representation is distributed within it.

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.

Naive metric 4: just train a probe

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.

minT ||X̄T − Ȳ||F2  →   R2 = 1 − ||X̄T* − Ȳ||F2 / ||Ȳ||F2

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.

ProblemWhat goes wrong
It saturatesIf 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 asymmetricR2(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 useFitting Ȳ 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
The fix, previewed. Every one of those three is repaired by the same change: stop scoring the reconstruction and score the end task. Fit T, then plug the result into B’s actual downstream layers and measure accuracy. Saturation stops mattering because a perfect fit that does not classify is visibly a failure. Asymmetry becomes the finding rather than a defect. And “reconstruction versus use” is resolved by definition, because use is what you measured. That change is the whole content of Chapter 4, and it is why stitching is a behavioural test rather than another index.

The invariance ledger

Here is every metric so far against every symmetry. The rightmost column is the one that separates the survivors.

MetricPermute columnsRotate basisIsotropic scaleInvertible map (must be NO)
Same-index correlationNoNoYesNo ✓
Best-match correlationYesNoYesNo ✓
Frobenius distanceNoNoNoNo ✓
CCA / SVCCAYesYesYesYes ✗
Linear CKAYesYesYesNo ✓

Only one row has four green cells. That row is the next chapter. But before we build it, play with the failures.

Watch the naive metrics break

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.

The invariant that survives everything

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:

(XR)i · (XR)j = xiTR RTxj = xiTxj
rotations preserve inner products — that is the definition of orthogonal

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.

This is the entire idea. Stop describing a representation by where each example sits, which is basis-dependent and therefore meaningless. Describe it by how every example relates to every other example, which is basis-free. That n×n table of inner products is the Gram matrix, and comparing two representations means comparing their two Gram matrices. Everything in Chapter 2 is the careful version of this sentence.
CCA is invariant to permutation, rotation, and scaling — all three properties we wanted. Why did the field move away from it as a representation-similarity measure?

Chapter 2: CKA From Scratch

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.

Step 1: the Gram matrix

Given the centered representation X̄ ∈ Rn×p, the Gram matrix is

K = X̄ X̄T ∈ Rn×n,   Kij = i · j
entry (i, j) is “how aligned are probe i and probe j in this representation”

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:

K11 = 2·2 + (−1)(−1) = 4 + 1 = 5
K12 = 2(−1) + (−1)(2) = −2 − 2 = −4
K13 = 2(−1) + (−1)(−1) = −2 + 1 = −1
K22 = (−1)(−1) + 2·2 = 1 + 4 = 5
K23 = (−1)(−1) + 2(−1) = 1 − 2 = −1
K33 = (−1)(−1) + (−1)(−1) = 1 + 1 = 2
K = [  5  −4  −1 ]
      [ −4   5  −1 ]
      [ −1  −1   2 ]

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

Notice something free. Every row of K sums to zero: 5 − 4 − 1 = 0, −4 + 5 − 1 = 0, −1 − 1 + 2 = 0. That is not a coincidence. Because we centered the columns of X̄, the rows of X̄ sum to the zero vector, so K 1 = X̄(X̄T1) = X̄ 0 = 0. Centering the features automatically double-centers the Gram matrix. Every implementation you will read applies an explicit centering matrix H = I − (1/n)11T to K; if you centered the features first, H is a no-op. Knowing that saves you from a class of “why is my CKA slightly off” bugs.

Step 2: HSIC — covariance between two similarity structures

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

HSIC(K, L) = (1/(n−1)2) · tr(K H L H),   H = I − (1/n)11T

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:

tr(A B) = ∑ij Aij Bij = ⟨A, B⟩F   (for symmetric A, B)

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.

Why HSIC and not just correlation of the entries? They are the same thing, once you normalise. HSIC is the un-normalised version, and it earns its name because for a general (non-linear) kernel it equals zero if and only if the two variables are statistically independent. For our purposes — linear kernels K = X̄X̄T — you can safely read HSIC(K, L) as “the dot product between A’s similarity structure and B’s similarity structure,” and everything still works. The kernel-theory pedigree matters because it is what licenses swapping in an RBF kernel later without changing a line of the argument.

Step 3: normalise, and CKA appears

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:

CKA(K, L) = HSIC(K, L) / √( HSIC(K, K) · HSIC(L, L) )

The (n−1)−2 factors cancel top and bottom, leaving the form we will actually compute with:

CKA = ⟨K, L⟩F / ( ||K||F · ||L||F )
the cosine of the angle between two Gram matrices, viewed as vectors of n2 numbers

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.

Proving the three invariances, and the one non-invariance

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

K′ = X̄Q(X̄Q)T = X̄QQTT = X̄X̄T = K

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

CKA(c2K, L) = ⟨c2K, L⟩ / (||c2K|| · ||L||) = c2⟨K, L⟩ / (c2||K|| · ||L||) = CKA(K, L)

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̄AATT, 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 XEffect on KEffect on CKA
Permute columnsK → KNone
Rotate / reflect basisK → KNone
Scale all features by cK → c2KNone
Add a constant to every rowK → K (after centering)None
Scale one direction by cK genuinely changesChanges — correctly
Drop a low-variance directionK changes slightlyChanges slightly — and that is the blind spot of Chapter 3
Permute the rows (probes) of X onlyK → PKPTChanges — correctly. Probe order must match between the two networks
The last row is a real bug, not a footnote. CKA compares row i of X with row i of Y and assumes they are the same input. Shuffle your dataloader between the two capture passes — which is the default in most frameworks — and you will compute the similarity between network A’s view of image 4,231 and network B’s view of image 88. The resulting number is not noisy, it is meaningless, and it will look plausible: somewhere around 0.2–0.4, exactly where a “these models are different” conclusion feels reasonable. Set shuffle=False and fix the seed.

Worked example 1: identical representations, different basis

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:

L11 = 3·3 + 6·6 = 9 + 36 = 45
L12 = 3(−6) + 6(−3) = −18 − 18 = −36
L13 = 3·3 + 6(−3) = 9 − 18 = −9
L22 = 36 + 9 = 45,   L23 = (−6)(3) + (−3)(−3) = −18 + 9 = −9,   L33 = 9 + 9 = 18
L = [  45  −36   −9 ]
      [ −36   45   −9 ]
      [  −9   −9   18 ]
  =  9 · K

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:

⟨K, L⟩F = ⟨K, 9K⟩F = 9·||K||F2
||L||F = 9·||K||F
CKA = 9||K||F2 / (||K||F · 9||K||F) = 1.000

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.

Worked example 2: a genuinely different representation

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:

Z = [ 3  3 ]
      [ 3  1 ]
      [ 0  2 ]
  →   column means (2, 2)  →  Z̄ = [  1   1 ]
                                    [  1  −1 ]
                                    [ −2   0 ]

Its Gram matrix M:

M11 = 1 + 1 = 2,   M12 = 1·1 + 1(−1) = 0,   M13 = 1(−2) + 1·0 = −2
M22 = 1 + 1 = 2,   M23 = 1(−2) + (−1)(0) = −2,   M33 = 4 + 0 = 4
M = [  2   0  −2 ]
      [  0   2  −2 ]
      [ −2  −2   4 ]

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:

⟨K, M⟩F = (5)(2) + (−4)(0) + (−1)(−2)
             + (−4)(0) + (5)(2) + (−1)(−2)
             + (−1)(−2) + (−1)(−2) + (2)(4)
           = 10 + 0 + 2 + 0 + 10 + 2 + 2 + 2 + 8 = 36
||M||F2 = 4 + 0 + 4 + 0 + 4 + 4 + 4 + 4 + 16 = 40  →   ||M||F = √40
CKA = 36 / (√90 · √40) = 36 / √3600 = 36/60 = 0.600

Exactly 0.6. Same structure in some respects, genuinely different in others — which is what a similarity index is for.

Read the three results together. Same-index correlation: X vs Y = 0.000, X vs Z = (work it out) some other number, both meaningless. CKA: X vs Y = 1.000, X vs Z = 0.600. One tool separates “the same thing in different clothes” from “a different thing,” and it does it with arithmetic you just did by hand.

The other form of the same number

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:

tr(X̄X̄TȲȲT) = tr(X̄TȲȲTX̄) = tr( (ȲTX̄)T(ȲTX̄) ) = ||ȲTX̄||F2

And the same move on the norms gives ||K||F = ||X̄TX̄||F. So:

CKA = ||ȲTX̄||F2 / ( ||X̄TX̄||F · ||ȲTȲ||F )
Kornblith et al. 2019, eq. 8 — the cross-covariance form

Verify it on X and Z. First the cross term, Z̄TX̄, a 2×2:

(Z̄TX̄)11 = 1·2 + 1(−1) + (−2)(−1) = 2 − 1 + 2 = 3
(Z̄TX̄)12 = 1(−1) + 1·2 + (−2)(−1) = −1 + 2 + 2 = 3
(Z̄TX̄)21 = 1·2 + (−1)(−1) + 0(−1) = 2 + 1 = 3
(Z̄TX̄)22 = 1(−1) + (−1)(2) + 0 = −3
||Z̄TX̄||F2 = 9 + 9 + 9 + 9 = 36  ✓ matches ⟨K, M⟩F
TX̄ = [  6  −3 ]
                   [ −3   6 ]
 →  ||·||F = √(36+9+9+36) = √90 ✓
TZ̄ = [ 6  0 ]
                 [ 0  2 ]
 →  ||·||F = √(36+4) = √40 ✓

Same 36/60 = 0.6. Two completely different computational paths, one number.

FormIntermediate sizeCostUse when
Gram (n×n)n2O(n2p)Few probes, wide layers. n = 512 probes, p = 8192 channels
Cross-covariance (p×q)pqO(npq)Many probes, narrow layers. n = 100,000 probes, p = 256
The memory trap, with real numbers. A ResNet-50 CKA sweep over 50,000 ImageNet validation images: the Gram form asks for a 50000×50000 float32 matrix per layer — 10 GB, per layer, and you need two at a time. The cross-covariance form for a 2048-channel layer asks for 2048×2048 — 16 MB. Same number, 600× less memory. Pick the form by which of n and p is smaller, always.

What CKA is really weighting: the spectral identity

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

||ȲTX̄||F2 = ∑ij λXi λYjuXi, uYj2
λ = squared singular values = Gram eigenvalues; u = the corresponding directions in probe space

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):

K · (1, −1, 0) = (5+4, −4−5, −1+1) = (9, −9, 0) = 9 · (1, −1, 0) →  λX1 = 9
K · (1, 1, −2) = (5−4+2, −4+5+2, −1−1−4) = (3, 3, −6) = 3 · (1, 1, −2) →  λX2 = 3

And for M — check that the same two vectors are its eigenvectors too:

M · (1, −1, 0) = (2−0, 0−2, −2+2) = (2, −2, 0) = 2 · (1, −1, 0) →  λZ1 = 2
M · (1, 1, −2) = (2+0+4, 0+2+4, −2−2−8) = (6, 6, −12) = 6 · (1, 1, −2) →  λZ2 = 6

The two representations agree perfectly on which directions exist — the inner-product terms ⟨ui, uj2 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:

∑ λXiλZjui,uj2 = 9·2 + 3·6 = 18 + 18 = 36  ✓
||K||F = √(92+32) = √90 ✓,   ||M||F = √(22+62) = √40 ✓

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

numerator = 10000·1 + 0.0001·1 = 10000.0001
||K||F = √(108 + 10−8) ≈ 10000,   ||L||F = √2 ≈ 1.4142
CKA ≈ 10000 / (10000 · 1.4142) = 1/√2 = 0.707

CCA said 1.000. CKA says 0.707. The debt is paid.

The implementation, and the one line people get wrong

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.

The kernel generalisation, and when to bother

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:

Kij = exp( −||xixj||2 / (2σ2) ),   σ = α · median pairwise distance

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 unbiased estimator, for when you must batch

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

HSIC1(K, L) = (1/(n(n−3))) [ tr(K̃L̃) + (1T1)(1T1)/((n−1)(n−2)) − (2/(n−2)) 1TK̃L̃1 ]

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,

CKAminibatch = ∑b HSIC1(Kb, Lb) / √( ∑b HSIC1(Kb, Kb) · ∑b HSIC1(Lb, Lb) )

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.

Minibatch CKA and the bias trap. On a real sweep you cannot hold 50,000 probes in memory, so you accumulate over minibatches. The estimator above is biased: on a batch of size b it systematically overestimates HSIC, and the bias depends on b, which means CKA values computed with different batch sizes are not comparable. Nguyen, Raghu, and Kornblith (2021) use Song et al.’s unbiased HSIC estimator instead, accumulating numerator and denominator separately across batches and dividing only at the end. If you ever see CKA drift as you change batch size, this is why.
Gram-matrix CKA explorer

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.

Geometry match 1.00
PC1 spike 0.0

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.

Inline concept check. Why does CKA equal exactly 1 when L = cK for any c > 0, but not when L = K + cI?  …  Because the cosine between a vector and a positive multiple of itself is 1 by definition — proportionality is the fixed point. Adding cI is a different matrix direction entirely: it inflates only the diagonal, which corresponds to pushing every probe away from the centre by the same amount without changing any pairwise relationship. The cosine drops, correctly, because the two Gram matrices now genuinely differ — one describes a cloud, the other the same cloud with an isotropic halo of extra self-similarity that no rotation or rescaling of the features can produce.
Linear CKA can be written as ∑ij λXiλYj⟨uXi, uYj2 over the Gram eigenvalues and eigenvectors, divided by the two norms. What does that identity tell you about how CKA differs from CCA?

Chapter 3: What CKA Saw — and Its Blind Spots

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.

Finding 1: layers correspond, and only CKA could see it

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.

Why the older methods failed the easy test. Follow the logic from Chapter 2’s spectral identity. Two independently trained networks agree strongly about their high-variance directions — the coarse structure of the data — and agree very little about their low-variance directions, which are dominated by initialisation noise. CCA averages those two regimes with equal weight, so the noise swamps the signal. CKA weights each agreement by λXiλYj, so the noise directions contribute almost nothing. The diagonal was always there. The old ruler was averaging it away.

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, uj2 among the tail are around 1/4 each by symmetry. Compare what each index reports.

CKA numerator ≈ 50·50·1  +  (tail terms, each < 5·5·0.25) ≈ 2500 + 12
||K||F = √(2500 + 25 + 1 + 0.04 + 0.0025) ≈ 50.26
CKA ≈ 2512 / (50.26 × 50.26) ≈ 0.99
CCA: whitening sets all five eigenvalues to 1, so the score is
(1 · strong agreement + 4 · weak agreement)/5 ≈ (1 + 4·0.5)/5 = 0.60

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.

Finding 2: width buys agreement

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.

Finding 3: block structure, and where it comes from

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 showedThe readingWhat you would do about it
Bright diagonal across seedsArchitecture, not initialisation, determines what each depth computesTrust layer index as a coordinate when transferring or pruning
Higher cross-seed similarity at larger widthCapacity removes the need to choose among equally good featuresExpect reproducibility to improve with scale, not degrade
Large similarity block in deep/wide netsRedundant depth; one PC carrying the blockPrune inside the block; it is where the free capacity is
Early layers similar across datasets and architecturesEarly vision is close to universal — edges, colour opponency, blobsFreeze early layers when transferring; the gains are elsewhere
Networks trained on random labels have a distinct signatureMemorisation and generalisation leave different geometric tracesUse representation similarity as a diagnostic, not only accuracy

Finding 4: transformers do not look like convolutional networks

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 showedWhat it implies
ViT’s layer-to-layer similarity is far more uniform than a ResNet’s, which shows a clear two-stage structureViT 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 stackSelf-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 ResNetsThe 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 networkExplains 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.

Finding 5: memorisation has a signature

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.

Why this is a useful diagnostic rather than a curiosity. It gives you a way to ask whether a model is fitting structure or fitting the specific labels, without a held-out set. Compare the layerwise CKA of your model against a deliberately label-shuffled twin. If your model’s late-layer geometry resembles the shuffled twin’s more than it resembles a second honest run, you are looking at memorisation. That is a measurement you can make when a validation split is unavailable or untrustworthy — which happens more often than anyone admits.

How to read a CKA heatmap

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.

PatternWhat it meansWhat to do
Clean bright diagonalLayer i of A corresponds to layer i of B. The healthy baselineNothing — 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-centreOne network does the same computation at a different depth — common when comparing architectures of unequal depthRead off the offset: it tells you the depth-to-function mapping between the two families
Bright square blockMany consecutive layers are near-identical — redundant depth, usually one dominant PC propagatingPrune 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 cornerEarly layers agree, later layers diverge. The classic cross-task or cross-dataset signatureFreeze the agreeing prefix when transferring; the divergence point is where task-specific learning begins
Uniformly bright everywhereAlmost always an artefact: a shared dominant component, a probe set with extreme outliers, or forgetting to centreCheck the norm distribution of your probes, and recompute after removing PC1. Do not report this as a finding
Uniformly dark, including the diagonalAlmost always a bug: mismatched probe order, wrong centring axis, or fp16 accumulationCompute CKA(X, X) as a self-test. It must be exactly 1
The two self-tests that catch most bugs. CKA(X, X) must be exactly 1.0. CKA(X, XQ) for a random orthogonal Q must also be exactly 1.0. Both take four lines and both fail loudly under the common mistakes — wrong centring axis, unequal probe ordering, precision loss. Run them before you run anything else, every time.

Blind spot 1: the top component can carry the whole score

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.

numerator = 100·100·1  +  100·1·0  +  1·100·0  +  1·1·0 = 10000
||K||F = ||L||F = √(1002 + 12) = √10001 ≈ 100.005
CKA = 10000 / 10001 = 0.9999

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?

The diagnostic that costs you three lines. Compute CKA. Then project out the top k principal directions from both representations and compute it again. If CKA(X, Y) = 0.95 and CKA(X−1, Y−1) = 0.2, you have learned that the two networks agree about one global axis and about nothing else. Report both. The sim in Chapter 2 shows the two numbers side by side precisely so this stops being an abstraction.

Blind spot 2: a handful of probes can set the answer

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 = i · 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:

CKA(X, Z) with 3 probes     = 0.600
+ one probe at (20, 20)   = 0.998
+ one probe at (8, 8) only  = 0.975

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

Blind spot 3: the probe set is a hyperparameter nobody reports

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 choiceWhat it makes CKA measureFailure mode
Test set of the training distributionAgreement on the data both nets were optimised forFlatters similarity — both nets are on-manifold and confident
One class onlyFine-grained within-class structureKills the dominant between-class PC, so scores plummet and look alarming
Out-of-distribution imagesWhether the shared structure generalisesOften the most informative, almost never reported
Random noise inputsThe network’s inductive prior, not its learned featuresSurprisingly 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.

Blind spot 4: geometric agreement is not functional agreement

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:

High CKA, not interchangeable
Two representations can be near-proportional in Gram space while differing in a low-variance direction that B’s next layer happens to key on. CKA barely notices — the direction carries little λ — and the stitched network fails.
Low CKA, perfectly interchangeable
A representation can be a badly conditioned but fully invertible warp of another. CKA drops (correctly, by its own definition). A single learned linear layer undoes the warp completely, and the stitched network works perfectly.

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.

Where this leaves CKA. It is the right tool for a specific job: cheap, dimension-agnostic, differentiable, and good at the coarse question “which layers correspond?” across thousands of layer pairs. It is the wrong tool for the claim “these two models learned the same thing,” because it measures the geometry of a chosen probe set, dominated by a few directions and a few points, and geometry is not behaviour. When the claim is about behaviour, you need a behavioural test. That is Chapter 4.
You compute CKA = 0.96 between the penultimate layers of two vision models and conclude they learned nearly the same representation. What single follow-up measurement would most sharply test that conclusion?

Chapter 4: Stitching — The Behavioural Test

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

The construction, precisely

Write a network as a composition split at layer k:

A = a>k ∘ a≤k,    B = b>k ∘ b≤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:

SA→B = b>k ∘ T ∘ a≤k

Three rules make this a test rather than a trick:

RuleWhy 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:

penalty(A → B, k) = perf(B) − perf(SA→B)
near zero means A’s layer-k output is, for B’s purposes, B’s layer-k output in disguise
Why affine and not linear-with-no-bias, or a two-layer MLP? The bias matters because networks differ by a shift as well as a rotation — BatchNorm statistics, in particular, move the mean. Excluding it makes the test needlessly strict. A second layer, on the other hand, introduces a nonlinearity, and a nonlinearity is a computation: it can build features that neither network had. The affine family is exactly the set of transformations that reindex and rescale existing information without creating any. That is what “same representation, different coordinates” means, made operational.

What the stitch costs, in parameters

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

1024 × 1024  weights  +  1024  biases  = 1,049,600 parameters

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

Worked example: stitching by hand in two dimensions

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.

b>k(r) = sign(w · r),   w = (1, 0)

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:

logits = 2, −1, −1  →   predictions +, −, −
probe 3 flips: 2 of 3 correct. Penalty = 33 points.

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:

2·3 + (−1)·6  +  (−1)(−6) + 2(−3)  +  (−1)(3) + (−1)(−3)
= 6 − 6  +  6 − 6  +  −3 + 3  =  0

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.

t1 = 9/6 = 1.5,   t2 = −9/6 = −1.5
X̄·diag(1.5, −1.5) = [(3, 1.5); (−1.5, −3); (−1.5, 1.5)]
logits = 3, −1.5, −1.5 →  +, −, −   still 2 of 3. Penalty = 33 points.

Attempt 4 — the full linear stitch. Solve the least-squares problem T = (X̄TX̄)−1TȲ. 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:

T = (1/27)[ 6  3 ][  9   18 ] = (1/27)[   0   81 ] = [   0   3 ]
             [ 3  6 ][ −18  −9 ]            [ −81   0 ]        [ −3   0 ]

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:

X̄T = [(3, 6); (−6, −3); (3, −3)] = Ȳ  →   logits 3, −6, 3 →  +, −, +
3 of 3. Stitching penalty = 0.
Stitch familyFree parametersFitted TCorrectPenalty
Identity0I2/333 pts
Scalar1t = 0 (collapse)chance
Diagonal2diag(1.5, −1.5)2/333 pts
Full linear43R3/30
What the table proves, in one sentence. A representation can be perfectly interchangeable and still look broken to every restricted comparison, because the difference between the two is a rotation and rotations are exactly what restricted families cannot express. This is the same fact that killed same-index correlation in Chapter 1, arriving now as an engineering failure instead of a metric failure. The scalar stitch is the most instructive row: it does not merely score badly, it collapses to zero, because the two representations are Frobenius-orthogonal.

The implementation, and the four ways it goes wrong

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)
GotchaSymptomFix
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 arerequires_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 seamLoss starts astronomically high and T spends its budget learning a scale factorInitialise 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 mismatchShapes do not line up at all between the two architecturesA 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 directionYou conclude “these are the same” from a symmetric-sounding experiment that was not symmetricAlways measure A→B and B→A. The gap is the most informative number in the experiment — see the next section
Why gotcha 2 is worth the extra code. The closed-form initialisation is exactly the Chapter 2 machinery: capture X (A’s layer-k output) and Y (B’s layer-k output) on one batch of probes, solve T = (X̄TX̄)−1TȲ and set the bias to match the means. That single solve is the “best possible affine stitch in representation space”, and training from there only has to fix up what the end task needs beyond raw reconstruction. If the gradient-trained T ends up far from this initialisation, that difference is itself a finding: it says the end task cares about directions that carry little variance.

One-way stitching, and the rank argument

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.

The general principle. Stitching A → B succeeds when A’s front retains everything B’s back needs, in any linear coordinates. It fails when A discarded something. So a low A→B penalty with a high B→A penalty is a directional statement: A’s representation contains B’s. Bansal et al. use exactly this to compare training regimes — and this is why stitching gives you information a symmetric similarity score like CKA structurally cannot.

What stitching found

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:

Different seeds, same everything else
Near-zero stitching penalty across essentially all depths. Two runs of the same recipe produce genuinely interchangeable halves — despite weight vectors with no relationship whatsoever.
Self-supervised vs supervised
Models trained with completely different objectives often stitch with small penalty. The objective changed; the representation, in the sense that matters, largely did not.
Weaker model into stronger model
Asymmetric. A better-trained front can drive a worse-trained back, more readily than the reverse — the rank argument above, at scale.

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.

Stitching-penalty playground

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.

Rotation A→B 70°
Probe noise 0.06
Stitch family:

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.

In model stitching, why must the stitching layer be restricted to an affine map rather than, say, a two-layer MLP?

Chapter 5: Convergence & the Platonic Hypothesis

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.

Why “Platonic.” Plato’s allegory of the cave: prisoners see only shadows on a wall, cast by objects they never observe directly. The hypothesis is that images and text are two different shadows of one underlying world, and that a sufficiently good model of either shadow is forced to reconstruct the object casting it. Two models trained on two different shadows therefore end up with the same internal thing — not because they copied each other, but because there was only one thing to find.

The measurement: mutual nearest-neighbour alignment

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:

mNN(f, g) = (1/n) ∑i=1n | knnf(i) ∩ knng(i) | / k
knnf(i) = the indices of the k nearest neighbours of item i under representation f

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:

Itemknn under fknn under gOverlapScore
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
mNN = (0.5 + 1.0 + 0.5 + 0.5 + 1.0)/5 = 3.5/5 = 0.700
chance baseline = k/(n−1) = 2/4 = 0.500

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.

Why nearest neighbours rather than CKA here. Three reasons, each a direct answer to a Chapter 3 blind spot. It is rank-based, so a handful of outlier points cannot dominate the sum (blind spot 2). It is local, so a single global principal component cannot carry the whole score (blind spot 1). And it makes no assumption about matched dimensionality or a shared linear structure, which matters when one side is a 768-dim text encoder and the other a 1024-dim vision transformer.

The measurement at realistic scale, with shapes

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.

DecisionWhy it matters
Which pairsThe 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 kSmall 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 EuclideanCosine 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 empirical claim: better models align more

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.

Three forces that would produce convergence

The paper argues that convergence is what you should expect, from three pressures that all point the same way.

1. Task generality contracts the solution set
Each task a model must solve rules out representations that cannot support it. Solving N tasks means living in the intersection of N constraint sets. As N grows the intersection shrinks — and every model trained on enough tasks is forced into the same small region, regardless of how it got there.
2. Capacity lets you reach the optimum
A small model cannot occupy that intersection even if it exists; it must compromise, and different models compromise differently. Scale removes the compromise. This is Chapter 3’s width finding, generalised.
3. Simplicity bias selects the same point
Even inside the feasible set there are many solutions. Deep networks trained with SGD and weight decay prefer simple ones, and “simplest solution in the feasible set” is a unique answer, not a matter of taste.

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.

The contraction argument, with numbers on it

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 NSurviving fraction at f = 0.5At f = 0.8
10.50.8
50.0310.33
109.8 × 10−40.107
209.5 × 10−70.012
508.9 × 10−161.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.

Where the argument is weakest, stated plainly. Independence. Real tasks are heavily correlated — classifying dogs and classifying cats constrain overlapping structure — so the effective N is far below the nominal N, and fN badly overstates the contraction. The qualitative direction survives, because correlated constraints still contract. The dramatic exponent does not. Treat the table as an existence proof for the mechanism, not as a quantitative prediction.

The formal core: contrastive learners recover a PMI kernel

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:

⟨f(xa), f(xb)⟩ ∝ PMI(a, b) = log [ P(a, b) / (P(a) P(b)) ]
the Gram matrix of the learned representation is the PMI matrix of the underlying events

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.

PMI(dog, cat) = ln (0.20 / (0.4 × 0.4)) = ln (0.20/0.16) = ln 1.25 = +0.223
PMI(dog, rocket) = ln (0.02 / (0.4 × 0.2)) = ln (0.02/0.08) = ln 0.25 = −1.386

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.

Connect this back to Chapter 2. The claim is not that two models produce the same activation vectors — Chapter 1 established that is meaningless. It is that they produce the same Gram matrix, up to the rotations and rescalings that CKA is built to ignore. The Platonic Representation Hypothesis is, at its technical core, a claim that CKA between good models trained on the same underlying world tends toward 1 as they improve. The ruler from Chapter 2 is exactly the instrument the hypothesis is stated in.
Convergence explorer — mutual-kNN alignment as capability grows

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.

Capability 0.35
k (neighbours) 3

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 honest counterweights

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.

ObjectionSubstance
The modality gapLiang 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 sharedText 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 convergeRepresentations 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 estimatedThe 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 lowThe 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.
Inline concept check. If two models had perfectly aligned geometry but a large constant offset between their spaces, which of our tools would notice?  …  None of the ones built so far, and that is by design. CKA centers the features first, so a constant shift is removed before anything is computed. Mutual-kNN uses only rank order among neighbours, which an offset does not change if it applies to everything equally. Stitching would not notice either, because the affine stitch has a bias term whose entire job is to absorb offsets. All three tools were built to ignore exactly the thing the modality gap is — which is why the gap can be simultaneously real, visible in a scatter plot, and absent from every alignment number.

What would falsify it

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.

Alignment plateaus or reverses with capability
If, past some scale, better models started agreeing less — specialising into distinct competent solutions — the contraction argument would be wrong. The measurement is easy; the models simply have to exist.
Alignment tracks shared training data rather than capability
Hold capability fixed and vary data overlap. If alignment follows the overlap and not the benchmark score, the finding is about corpora, not about reality. This is the single most important control and it is the hardest one to run, because nobody publishes their exact mixtures.
Stitching penalties stay high while alignment scores rise
Chapter 4’s warning, applied here. If geometric alignment climbs but the models remain non-interchangeable, then what is converging is a summary statistic rather than the representation. Behavioural and geometric measures disagreeing is the most useful negative result available.

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.

The Platonic Representation Hypothesis argues that scaling drives convergence partly because “task generality contracts the solution set.” What is the actual mechanism in that phrase?

Chapter 6: vec2vec — Translation Without Pairs

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.

The claim, stated flatly. Given a dump of embeddings from an unknown encoder and a completely separate, unpaired corpus of embeddings from an encoder you control, you can learn a translation between the two spaces — with no paired data, no access to the unknown encoder, and no knowledge of what it is. Once translated, existing embedding-inversion attacks apply, and the vectors start giving up their text.

Why unpaired translation should be possible at all

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 one-sentence version. Paired data tells you the correspondence, from which you recover the map. Shared geometry lets you recover the correspondence from the geometry itself, and then the map. Pairs were never the only route; they were just the easy one.

How big the search space is, and how the geometry collapses it

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:

log10(1000!) ≈ 2567.6  →   about 102568 candidate matchings

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.

The security inversion, stated once. In cryptography, more ciphertext is more material for an attacker but the key space is fixed. Here, more vectors make the problem itself easier: profiles get longer, ties get rarer, the symmetry group of the point cloud shrinks toward trivial. A database of a thousand embeddings is harder to translate than a database of ten million. Scale is the attacker’s friend, and it is the defender’s product requirement.

The threat model, spelled out

What the attacker hasWhat the attacker does not have
A dump of embedding vectors from the target systemThe target’s encoder, its weights, or its identity
Any good public encoder of their ownAny query access to the target system
An unpaired corpus of their own text, in a roughly similar domainAny text-and-vector pair from the target
Compute for a small adapter training runThe 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.

The architecture: adapters through a shared latent

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:

Ai : Rdi → Z  (input adapter, into a shared latent space)
Bi : Z → Rdi  (output adapter, back out to encoder i’s space)

Translation from space i to space j is then just composition:

Fi→j = Bj ∘ Ai

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.

The four losses, and what each one forbids

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.

LossWhat it saysWhat it forbids
Adversarial (output space)A discriminator on space j cannot tell translated vectors Fi→j(u) from real vectors of space jTranslations 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 fromThe latent silently splitting into per-encoder regions, which would make the “shared” space a fiction
Cycle consistencyFj→i(Fi→j(u)) ≈ uInformation destruction. A map that sends everything to the mean would fool both discriminators and fails here immediately
Vector-space preservationcos(u, u′) ≈ cos(F(u), F(u′)) for pairs within a batchAny 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.

The relationship to CycleGAN, and where it differs. The unpaired image-translation recipe — two generators, two discriminators, a cycle loss — is clearly the ancestor. What is new is that the shared object is not a visual style but a metric structure, and that structure can be constrained directly, which images do not permit. That is why an approach that produces charming-but-unreliable results on photographs produces attack-grade results on embeddings: the constraint is much tighter.

The losses, written out

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.

Ladvout = Ev[ log Dj(v) ] + Eu[ log(1 − Dj(Fi→j(u))) ]
translated vectors must be indistinguishable from native ones — realism
Ladvlat = Eu[ log DZ(Ai(u)) ] + Ev[ log(1 − DZ(Aj(v))) ]
the shared latent must actually be shared — no per-encoder ghettos
Lcyc = Eu || Fj→i(Fi→j(u)) − u ||2
a round trip must return you to where you started — no information destroyed
Lvsp = Eu, u′ ( cos(u, u′) − cos(Fi→j(u), Fi→j(u′)) )2
the geometry must be carried across intact — the Platonic assumption as a penalty

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

cos(u1, u2) = 0.91,   cos(u1, u3) = 0.12,   cos(u2, u3) = 0.08

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.

Notice the shape of the argument. Chapter 1 told you the coordinates are meaningless and the pairwise structure is the invariant. Chapter 2 built a measure out of the pairwise structure. Chapter 5 said the pairwise structure converges across models. Chapter 6 is what happens when you realise the pairwise structure is also sufficient to identify the map — the same fact, read as an attack instead of a metric. Nothing new was discovered in Chapter 6; something already known was pointed in a different direction.

What it achieves, and where it degrades

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.

The attack chain

1. Obtain vectors
A leaked vector-store snapshot, a misconfigured index endpoint, a backup, an over-permissive internal API. No text, no model — the exact thing teams currently classify as low sensitivity.
2. Translate
Train vec2vec-style adapters between the unknown space and a space you control, using your own unpaired corpus. No queries to the victim system.
3. Invert or attribute
In your own space, apply what already exists: attribute-inference classifiers, or full text reconstruction of the kind Morris et al. demonstrated in Text Embeddings Reveal (Almost) As Much As Text, which recovers a large fraction of the original string from its vector.
4. Read the documents
Demonstrated on corpora including medical records and corporate email. The vector store was the document store the whole time.
The security conclusion, in the form your threat model needs. An embedding is not a hash, not a redaction, and not a de-identification step. It is a lossy but largely reversible encoding of the source text, and the “you would need our model” assumption that made it feel safe has been removed by representational convergence. Classify a vector store at the same sensitivity as the corpus it was built from. Same encryption, same access control, same retention policy, same breach-notification obligations.

What step 3 actually recovers

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

1. Hypothesise
A model trained to go from embedding to text proposes a first guess. On its own this is mediocre — roughly the right topic, wrong words.
2. Re-embed the guess
Run the hypothesis through the encoder you control. Now you have two vectors: the target and your guess. Their difference is a direction in embedding space that says which way to edit.
3. Correct, and repeat
A corrector model conditioned on (target, hypothesis, hypothesis embedding) proposes a better hypothesis. Iterate tens of times with beam search. The cosine to the target climbs monotonically, and the text converges on the original.

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.

The property of embeddings that makes this work. Inversion is a search over texts guided by a distance in embedding space, and it works because that distance is smooth and informative: texts that are nearly right have embeddings that are nearly right, so gradient-free hill climbing finds the summit. That smoothness is not incidental — it is the exact property that makes embeddings useful for retrieval. A representation you could not hill-climb would also be a representation you could not rank with.

Defences, honestly evaluated

Proposed defenceDoes it work?Why / what it costs
Add Gaussian noise to stored vectorsPartly, expensivelyTranslation 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 storingNoAn 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)WeaklyRemoves 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 / binaryWeaklySame story: coarse geometry survives quantisation by design, since that is why quantisation is usable for retrieval at all.
Encrypt at rest, restrict access, auditYesThe 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 anywayYesArchitectural rather than cryptographic: if the corpus is not sensitive, neither is its index.

Costing the noise defence properly

“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

cos(x, x + n) ≈ 1 / √(1 + dσ2)
σ2 at d = 1024cosine retainedWhat it does to your retrieval
0.0050.0260.987Barely measurable — and barely inconveniences a translator either
0.010.1020.952Noticeable reordering in the top-10 for close calls
0.020.4100.842Serious recall damage; nearest-neighbour ties become coin flips
0.052.560.530The 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.

A team proposes protecting their vector database by multiplying every stored embedding by a secret 1536×1536 orthogonal matrix, keeping the matrix in an HSM. Why does this provide essentially no protection against a vec2vec-style translation attack?

Chapter 7: Recover the Rotation, By Hand

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.

The problem, stated

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:

minimise  ||X̄R − Ȳ||F2  subject to  RTR = I

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.

The derivation

Expand the objective using ||A||F2 = tr(ATA):

||X̄R − Ȳ||F2 = tr(RTTX̄R) − 2 tr(RTTȲ) + tr(ȲTȲ)

The third term does not involve R. The first term does, but only trivially: because R is orthogonal, tr(RTTX̄R) = tr(X̄TX̄RRT) = tr(X̄TX̄), also constant. So minimising the whole thing is the same as maximising the middle term:

maximise  tr(RTC),   where  C = X̄T

Take the singular value decomposition C = UΣVT and substitute:

tr(RTUΣVT) = tr(Σ VTRTU) = tr(Σ W),   W = VTRTU

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:

VTRTU = I  ⇒   RT = V UT  ⇒   R = U VT

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.

The full worked example

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

C11 = 2(3) + (−1)(−6) + (−1)(3) = 6 + 6 − 3 = 9
C12 = 2(6) + (−1)(−3) + (−1)(−3) = 12 + 3 + 3 = 18
C21 = (−1)(3) + 2(−6) + (−1)(3) = −3 − 12 − 3 = −18
C22 = (−1)(6) + 2(−3) + (−1)(−3) = −6 − 6 + 3 = −9
C = [   9   18 ]
      [ −18  −9 ]

Now the SVD, by hand, via CCT:

CCT = [ 81+324   −162−162 ] = [  405  −324 ]
          [ −162−162   324+81 ]     [ −324   405 ]

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:

eigenvector (1, −1)/√2 → eigenvalue 405 + 324 = 729 → σ1 = √729 = 27
eigenvector (1, 1)/√2    → eigenvalue 405 − 324 = 81  → σ2 = √81 = 9

So U has columns u1 = (1, −1)/√2 and u2 = (1, 1)/√2. Recover V from vi = CTuii, with CT = [[9, −18], [18, −9]]:

CTu1 = (1/√2)(9 + 18, 18 + 9) = (1/√2)(27, 27) →  v1 = (1, 1)/√2
CTu2 = (1/√2)(9 − 18, 18 − 9) = (1/√2)(−9, 9) →  v2 = (−1, 1)/√2

Assemble R = UVT. With VT having rows v1T and v2T:

R11 = (1/√2)(1/√2) + (1/√2)(−1/√2) = ½ − ½ = 0
R12 = (1/√2)(1/√2) + (1/√2)(1/√2) = ½ + ½ = 1
R21 = (−1/√2)(1/√2) + (1/√2)(−1/√2) = −½ − ½ = −1
R22 = (−1/√2)(1/√2) + (1/√2)(1/√2) = −½ + ½ = 0
R = [  0   1 ]
       [ −1   0 ]  — exactly the 90° rotation we applied in Chapter 1.

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:

s = tr(Σ) / ||X̄||F2 = (27 + 9) / (4+1+1+4+1+1) = 36/12 = 3

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

What you just did is a Chapter 6 attack in miniature. Given the correspondence between two spaces’ views of the same items, the map between the spaces is a closed-form solve. If an attacker had even a few hundred known text-and-vector pairs, no learning would be needed at all — Procrustes would hand them the translation directly. The reason vec2vec is a research contribution is that it removes the correspondence assumption. Which is the next section.

The code, and the variants you will actually need

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.

VariantWhen you need itThe change
Different dimensions dX ≠ dYAligning a 768-dim text encoder to a 1024-dim vision oneC is dX×dY; R = UVT is a semi-orthogonal dX×dY map. Everything else is identical
Weighted pairsSome correspondences are more trustworthy than othersC = X̄TWȲ with W diagonal. Same SVD
Robust to bad pairsYour correspondence set has errorsRANSAC: fit on random subsets of size d+1, keep the R with the most inliers, refit on inliers
Full linear, not orthogonalYou want the best possible map and do not need invarianceOrdinary least squares, T = (X̄TX̄)−1TȲ — the Chapter 4 stitch. Fits better, tells you less
Procrustes distance as a similarity measure. Once you have the optimal R, the residual ||X̄R − Ȳ||F (normalised) is itself a representation-similarity index — and Ding, Denain and Steinhardt found it outperforms CKA on their sensitivity and specificity benchmarks. It has exactly the invariances we wanted (orthogonal, scale, translation) and, unlike CCA, it is not invariant to general invertible maps. If you take one practical recommendation from this lesson beyond CKA, take this one: report Procrustes distance alongside CKA. They cost the same and they fail differently.

Doing it without the correspondence

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̄:

d2(1,2) = ||(2,−1) − (−1,2)||2 = ||(3,−3)||2 = 9 + 9 = 18
d2(1,3) = ||(2,−1) − (−1,−1)||2 = ||(3,0)||2 = 9
d2(2,3) = ||(−1,2) − (−1,−1)||2 = ||(0,3)||2 = 9

And within Ȳ:

d2(1,2) = ||(3,6) − (−6,−3)||2 = ||(9,9)||2 = 162
d2(1,3) = ||(3,6) − (3,−3)||2 = ||(0,9)||2 = 81
d2(2,3) = ||(−6,−3) − (3,−3)||2 = ||(−9,0)||2 = 81

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.

Where it breaks, and the break is instructive. In this tiny example the correspondence is not uniquely determined. Point 1’s distance profile is {18, 9}. Point 2’s is also {18, 9}. Only point 3, with profile {9, 9}, is distinguishable. So the geometry alone cannot tell you whether X’s point 1 maps to Y’s point 1 or Y’s point 2 — the cloud has a reflection symmetry, and the map is determined only up to that symmetry group.

This is not a flaw in the method, it is a real property of the object: you can recover the map exactly as far as the point cloud is asymmetric. Three points in a plane have plenty of symmetry. Eight million documents in 1536 dimensions have essentially none — every point has a distinct distance profile, the symmetry group is trivial, and the correspondence is pinned. The attack gets easier as the dataset gets bigger, which is the opposite of how security usually works.

Three further reasons the exact solve is not what vec2vec does at scale:

ComplicationEffect on the closed formWhat replaces it
Noise: the geometries match only approximatelyNo permutation makes the distance matrices agree exactly; matching becomes an optimisation over n! optionsLearn the map with a soft objective — the vector-space-preservation loss is the relaxed version of “distances agree”
The two corpora are different documents entirelyThere is no correspondence to find; row i of X has no counterpart in YMatch distributions instead of points — which is exactly what the adversarial losses do
Different dimensionality, di ≠ djR is not square; orthogonality is not even definableRoute 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.

Two-space alignment — recover the map

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.

True angle 55°
Noise 0.08
Known pairs 0

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.

In the orthogonal Procrustes solution R = UVT, where C = XTY = UΣVT, why are the singular values Σ discarded?

Chapter 8: What Convergence Means for Embedding Design

Eight chapters of measurement. This one is about what you change on Monday.

Consequence 1: encoders become swappable, at adapter prices

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:

100,000,000 ÷ 2,000 = 50,000 seconds ≈ 13.9 GPU-hours — and that is the fast case
storage, fp32 at d = 1024: 108 × 1024 × 4 bytes = 409.6 GB (204.8 GB at fp16)

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.

ApproachComputeDowntimeQuality
Full re-embed~14 GPU-hours + reindexDual-write window or a full swapExactly v2
Learned linear adapter v2 → v1Minutes, on a sampleNone — queries change, index does notv2’s query understanding through v1’s geometry: better than v1, short of v2
Hybrid: adapter now, re-embed lazilyAmortised over weeksNoneConverges to v2 as documents are touched
The engineering rule this implies. Treat the encoder as a replaceable component and the index as the durable asset, rather than the other way round. That inverts the instinct most teams have — and it is only safe because of the convergence result: two good encoders are close enough that a linear map recovers most of the difference. Measure it before you rely on it: fit the adapter, evaluate recall@k against a full re-embed on a held-out slice, and only then decide. Chapter 4’s stitching penalty is exactly the right framing — you are stitching a new front onto an old back.

Consequence 2: cross-modal retrieval without paired training

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:

C = X̄TȲ  costs  n · d2 = 3000 × 7682 ≈ 1.8 × 109 multiply-adds
SVD of a 768×768 matrix  costs  O(d3) ≈ 4.5 × 108 operations
both complete in well under a second on a laptop CPU

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.

The migration runbook

Consequence 1 as a sequence of steps you could actually put in a ticket, with the check that gates each one.

#StepThe gate
1Sample 50k documents; embed each with both v1 and v2. Keep row order identicalRow order. This is Chapter 2’s silent-bug row, and it will produce a plausible wrong answer if you get it wrong
2Compute CKA(v1, v2) and Procrustes fit_qualityIf either is low, stop — the spaces are not close enough for a linear map and re-embedding is the honest answer
3Recompute both after removing the top 3 principal directionsIf the numbers collapse, the agreement is one global axis and will not carry fine-grained ranking
4Fit the adapter: full least squares v2 → v1, with biasHold out 10k of the sample; report residual on the held-out part, not the fit part
5Evaluate end-to-end: recall@10 and nDCG on your real query set, adapter vs full re-embed vs v1 baselineThis is the stitching penalty. It is the only number that decides anything
6Ship the adapter; re-embed lazily on document updateTrack 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.

Consequence 3: a vector store is a document store

Restating Chapter 6’s conclusion in the register of a design review, because this is the consequence with a compliance deadline attached.

Old assumptionReplace 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.

Consequence 4: version your embeddings like a schema

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 vectorWhy you will need it
Encoder identity and exact versionYou 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 timestampLets you re-embed lazily and measure the fraction of the index that has migrated
Dimensionality and dtypeObvious, and routinely missing. Matryoshka-style truncated embeddings make this non-trivial
The failure this prevents. A team upgrades their encoder and, over a few weeks, half the index is v2 and half is still v1. Queries are embedded with v2. Retrieval quietly degrades for the v1 half — not catastrophically, just enough that relevance metrics slip a few points and nobody can find the cause, because the two halves are indistinguishable in the store. Convergence is what makes the degradation gentle rather than obvious, which is precisely what makes it hard to catch. Record the version.

What convergence does not mean

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.

“High similarity means interchangeable”
No. Chapter 3’s blind spot 4 and Chapter 4’s worked example: two representations can score 0.9999 while differing exactly in the low-variance direction the downstream head depends on. If the claim is about swapping components, run the stitch and measure the penalty. Do not infer it from a similarity score.
“Models are converging on the same coordinates”
No. Every tool in this lesson centers, normalises, or uses ranks precisely so that coordinates are irrelevant. The modality gap is direct evidence that absolute positions can be far apart while relational geometry agrees. Convergence is a statement about shape.
“Alignment is high, so the models know the same things”
No. Measured alignments are far above chance and far below 1. The supported claim is about the direction of a trend across model scales, not about the current absolute level. Read the chance baseline before reading the headline.
“It holds for every domain”
No. The evidence is concentrated in vision and language, where the underlying world is shared and heavily sampled. Embodied control, proprioception, molecular property prediction — domains where the representation is shaped by an agent-specific or instrument-specific interface — have no comparable evidence and a clear reason to differ.
“The metric is settled”
No. Chapter 3: CKA is outlier-sensitive, top-component-dominated, and probe-set dependent; competing measures disagree on benchmark tests of sensitivity and specificity. Report at least two measures, one geometric and one behavioural, and report where they disagree — the disagreement is usually the finding.
“Convergence proves models are finding truth”
Not established. Models trained on overlapping web-scale corpora, similar architectures, and similar objectives have abundant shared causes for agreeing. Convergence toward a common representation is consistent with convergence toward reality — and also with convergence toward the internet’s particular way of describing reality, biases included.

If you are the one building the encoder

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 λ12 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 decisionEffect on retrievalEffect on translatability
Leave the space anisotropicWorse — a few dims dominate every comparisonSlightly harder — the idiosyncratic axes are encoder-specific
Whiten / remove top componentsBetter on most benchmarksEasier — you moved toward the shared canonical form
Train with a broader task mixtureBetter generalisationEasier — Chapter 5’s contraction argument, applied to you
Shrink dCheaper, slightly worseMarginally harder for full inversion, no harder for attribute inference
Read the middle column against the right one. Every choice that makes your embeddings better also makes them easier to translate, because both properties are downstream of the same thing: how well the space captures the actual structure of the data. There is no configuration where you get a great encoder whose vectors are meaningfully opaque. Plan for that rather than hoping to engineer around it.

Five anti-patterns, each traceable to a chapter

Anti-patternWhy it is wrongChapter
“We compared the models by correlating their neurons”Zero for identical representations at any realistic width — the metric decays as 1/√p1
“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 penalty3, 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 component3
“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 geometry2, 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” barrier6

What a universal-geometry interface would look like

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.

TodayIf the convergence premise holds
An index is bound to one encoder’s coordinate systemAn index is stored in a reference space, with a small adapter per encoder
Changing encoders means re-embedding everythingChanging encoders means fitting one adapter
Two teams’ indices cannot be mergedTwo indices merge after alignment, with a reported fit quality
Cross-modal retrieval needs a jointly trained pairAny 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.

A decision table for practitioners

Your questionRight toolWhat to report alongside it
Which layer of A corresponds to which layer of B?Linear CKA sweepProbe set and n; CKA after removing the top PC
Can I replace this encoder in production?Stitching penalty — fit the adapter, measure the lossBoth directions; recall@k on a held-out slice
Are these two models redundant in my ensemble?Mutual-kNN alignmentThe 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 exposureWhat fraction of entities and attributes were recovered
Is this architecture change actually doing something?CKA against the baseline, layer by layerWhere the heatmap stops matching — that is where the change bit
Are my deep layers redundant?CKA block structureAblation accuracy inside vs outside the block
The synthesis, in one paragraph. Two networks trained on the same world converge on the same relational geometry — the pattern of which inputs are near which — while agreeing on nothing at all about coordinates, bases, or weights. Every tool in this lesson is a way of looking at that geometry while ignoring the coordinates: CKA compares Gram matrices, stitching asks whether a linear change of coordinates suffices, mutual-kNN compares neighbour sets, Procrustes finds the change of coordinates explicitly. The Platonic Representation Hypothesis says the geometry is converging as models improve. vec2vec is the demonstration that the geometry, on its own, is enough to reconstruct the map — which is a gift if you are migrating an index and a breach if you are storing one.
Your team wants to swap encoder v1 for v2 in a production retrieval system without re-embedding 100M documents. What is the correct way to decide whether a learned linear adapter is good enough?

Chapter 9: Connections & Cheat Sheet

Everything, compressed to the page you would keep open.

The whole argument, in ten steps

If you remember nothing else, remember the chain. Each step follows from the one above it, and each is a chapter.

1
The map from weights to functions is many-to-one, with fibres of size p! and unbounded scaling manifolds. Weight-space comparison measures a coordinate on the fibre, not the function.
2
So compare activations: X ∈ Rn×p over a shared probe set. That is what everything downstream reads.
3
Feature-wise comparisons die, because the basis is arbitrary. Subspace comparisons die, because they are invariant to everything invertible.
4
What survives every basis change is the pairwise structure: the Gram matrix. Compare Gram matrices by cosine and you have CKA.
5
CKA weights each direction-pair agreement by the variance it carries. That is its power (layer correspondence became visible) and its blind spot (one component can carry the score).
6
Geometry is not use. Freeze both networks, insert one affine map, and measure the accuracy you lose. That is a test with nothing left to argue about.
7
By these measures, models converge — across seeds, across objectives, and across modalities — and they converge more as they get better.
8
Convergence is convergence of geometry, not coordinates. Every metric in the chain was built to ignore coordinates, which is why the modality gap can be real and invisible at once.
9
But shared geometry determines the map between two spaces. With correspondences it is a closed-form SVD; without them it is a learned adapter constrained by pairwise cosines.
10
Therefore: encoders are swappable at adapter prices, and a pile of embedding vectors is a pile of documents. The same fact, twice.

The equations

(1)  K = X̄X̄T,  L = ȲȲT  —  Gram matrices over a shared probe set
center the FEATURES (axis 0); this double-centers the Gram matrix for free
(2)  HSIC(K, L) = tr(K H L H) / (n−1)2,  H = I − (1/n)11T
the covariance between two similarity structures
(3)  CKA = ⟨K, L⟩F / (||K||F||L||F) = ||ȲTX̄||F2 / (||X̄TX̄||F||ȲTȲ||F)
two forms, one number; pick by whichever of n and p is smaller
(4)  ||ȲTX̄||F2 = ∑ij λXiλYjuXi, uYj2
CKA weights principal-direction agreement by the variance each direction carries — its strength and its blind spot
(5)  SA→B = b>k ∘ T ∘ a≤k,   penalty = perf(B) − perf(S)
both nets frozen, T affine, same data and loss — the behavioural test
(6)  mNN(f, g) = (1/n)∑i |knnf(i) ∩ knng(i)| / k,  chance = k/(n−1)
rank-based, outlier-robust, dimension-agnostic — the PRH metric
(7)  R = UVT where X̄TȲ = UΣVT,   s = tr(Σ)/||X̄||F2
orthogonal Procrustes — the closed-form alignment between two spaces

The numbers worth remembering

NumberWhat it is
101166512! — the functionally identical weight settings for one layer of width 512. Why weight-space comparison is dead
0.000 vs 1.000Same-index correlation vs CKA on two representations that are literally identical up to a 90° rotation
0.600CKA between our X and Z: 36 / (√90 · √40) = 36/60, verified three independent ways
0.707CKA on the anisotropic caricature where CCA says 1.000. The gap is why CKA exists
0.9999CKA when two representations share one dominant component and disagree completely about everything else. Blind spot 1
t = 0The optimal scalar stitch between a representation and its own 90° rotation. A scalar cannot turn
1,049,600Parameters in a 1×1 stitching conv at 1024 channels — about 7% of the ResNet-50 stages it drives
0.70 vs 0.50The worked mutual-kNN example against its chance floor. Always read the floor
R = [[0,1],[−1,0]], s = 3Procrustes recovering the exact rotation and scale from three probe points, by hand
≈ 0.92Best reported cosine similarity of a vec2vec translation to the true target embedding, with no paired data
409.6 GB / 14 GPU-hoursThe cost of re-embedding 100M documents at d = 1024 — what an adapter saves you

The four measures, side by side

CKACCA / SVCCAProcrustes distanceStitching penalty
What it readsGram-matrix cosineShared subspaceResidual after best rotationEnd-task accuracy
Permutation-invariantYesYesYesYes
Rotation-invariantYesYesYesYes
Scale-invariantYesYesYes (if scale is fitted)Yes
Invariant to invertible mapsNo — correctYes — fatalNo — correctYes — by design
Needs matched dimensionsNoNoNo (semi-orthogonal)No (1×1 conv)
Outlier-sensitiveVeryModeratelyModeratelyNo
Dominated by the top PCYesNoPartlyNo
CostOne matrix productOne SVD + whiteningOne SVDA training run per layer pair
Answers “interchangeable?”NoNoNoYes

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.

Glossary

TermIn one line
Probe setThe fixed inputs pushed through both networks. Everything downstream is defined relative to it
Representation matrix Xn probes × p features. The thing being compared
Gram matrix KX̄X̄T. How every probe relates to every other. Basis-free
HSICCovariance between two similarity structures. Zero iff independent, for general kernels
CKANormalised HSIC. The cosine between two Gram matrices
Stitching layerAn affine map inserted between one network’s front and another’s back; the only thing trained
Stitching penaltyAccuracy lost by the stitched network relative to the intact one
Mutual-kNN alignmentAverage overlap of nearest-neighbour sets across two spaces. The PRH metric
Orthogonal ProcrustesThe closed-form best rotation mapping one point cloud onto another
Modality gapA persistent offset between two modalities’ regions of a shared space, invisible to every centred metric
Vector-space preservationA loss requiring pairwise cosines to survive translation. The Platonic assumption as a penalty term
Platonic representationThe shared statistical model of reality that converging models are hypothesised to be approaching

The six papers, in one line each

PaperThe one thing it added
Lenc & Vedaldi 2015Stop scoring representations; swap them and see if the network still works
Kornblith et al. 2019A similarity index invariant to rotation and scale but not to arbitrary invertible maps — which finally revealed layer correspondence
Bansal, Nakkiran & Barak 2021Stitching at modern scale: different seeds, and even different objectives, produce interchangeable halves
Nguyen, Raghu & Kornblith 2021Block structure — overparameterisation is visible in the CKA heatmap, and prunable
Huh, Cheung, Wang & Isola 2024Alignment increases with capability, across modalities. The Platonic Representation Hypothesis
Jha, Zhang, Shmatikov & Morris 2025Shared geometry is enough to translate between embedding spaces with no paired data — and to read the documents

Where to go from here

If you want…Go to
The contrastive machinery that produces these spacesCLAP and contrastive learning
The geometry of embedding spaces from the ground upVector embeddings and similarity metrics
What a vector database actually does with this geometryVector databases
The SVD machinery behind ProcrustesSingular value decomposition and PCA
Kernels and HSIC in their native habitatKernel methods
Retrieval systems that depend on all of this being stableRAG

Build it yourself — the afternoon recipe

StepWhat to doThe decision that matters
1. ProbesFix 5,000–10,000 inputs and use the same set for every comparisonState the distribution. A CKA number without a probe set is a rumour
2. CaptureForward hooks on every layer; spatially average-pool conv maps to (n, C)Pooling choice changes the answer — pick one and be consistent
3. CenterX - X.mean(axis=0)Axis 0, not axis 1. This is the most common silent bug
4. CKAGram form if n < p, cross-covariance form otherwise50,000 probes in Gram form is 10 GB per layer. Choose by shape
5. MinibatchIf you must batch, use the unbiased HSIC estimator and accumulate numerator and denominator separatelyThe biased estimator makes CKA depend on batch size
6. DiagnoseRecompute after removing the top 1–3 principal directionsThe gap between the two numbers is the real result
7. StitchFreeze both nets, insert one 1×1 conv, train only it on the original lossAffine only. A nonlinear stitch makes the test vacuous
8. Both directionsMeasure A→B and B→A separatelyAsymmetry is information: it says which representation contains which
9. AlignProcrustes on a few hundred pairs: U, S, Vt = svd(X.T @ Y); R = U @ VtReport tr(Σ)/(||X||F||Y||F) too — it says how well the clouds actually match
10. Threat-modelRun an inversion on a sample of your own vector storeSize the exposure with a number before you argue about it in a review

References

  1. Kornblith, S., Norouzi, M., Lee, H., Hinton, G. “Similarity of Neural Network Representations Revisited,” ICML 2019 — arXiv:1905.00414. CKA, the spectral identity, and the layer-correspondence experiments.
  2. Lenc, K., Vedaldi, A. “Understanding Image Representations by Measuring Their Equivariance and Equivalence,” CVPR 2015 — arXiv:1411.5908. The original stitching layers.
  3. Bansal, Y., Nakkiran, P., Barak, B. “Revisiting Model Stitching to Compare Neural Representations,” NeurIPS 2021 — arXiv:2106.07682. Stitching penalty as the modern standard.
  4. Huh, M., Cheung, B., Wang, T., Isola, P. “The Platonic Representation Hypothesis,” ICML 2024 — arXiv:2405.07987. Convergence across models and modalities; the PMI-kernel argument.
  5. Jha, R., Zhang, C., Shmatikov, V., Morris, J. X. “Harnessing the Universal Geometry of Embeddings,” 2025 — arXiv:2505.12540. vec2vec: unsupervised translation between embedding spaces, and the security consequence.
  6. Nguyen, T., Raghu, M., Kornblith, S. “Do Wide and Deep Networks Learn the Same Things?” ICLR 2021 — arXiv:2010.15327. Block structure and minibatch CKA.
  7. Davari, M., Horoi, S., Natik, A., Lajoie, G., Wolf, G., Belilovsky, E. “Reliability of CKA as a Similarity Measure in Deep Learning,” ICLR 2023 — arXiv:2210.16156. The outlier-sensitivity critique.
  8. Ding, F., Denain, J.-S., Steinhardt, J. “Grounding Representation Similarity with Statistical Testing,” NeurIPS 2021 — arXiv:2108.01661. Sensitivity and specificity tests for similarity measures.
  9. Morris, J. X., Kuleshov, V., Shmatikov, V., Rush, A. M. “Text Embeddings Reveal (Almost) As Much As Text,” EMNLP 2023 — arXiv:2310.06816. The inversion step that makes translation dangerous.
  10. Liang, W., Zhang, Y., Kwon, Y., Yeung, S., Zou, J. “Mind the Gap: Understanding the Modality Gap in Multi-modal Contrastive Representation Learning,” NeurIPS 2022 — arXiv:2203.02053. Why aligned geometry does not mean shared coordinates.
  11. Ainsworth, S., Hayase, J., Srinivasa, S. “Git Re-Basin: Merging Models modulo Permutation Symmetries,” ICLR 2023 — arXiv:2209.04836. Undoing Chapter 0’s permutation symmetry directly in weight space.
  12. Gretton, A., Bousquet, O., Smola, A., Schölkopf, B. “Measuring Statistical Dependence with Hilbert-Schmidt Norms,” ALT 2005. The origin of HSIC.
Cross-domain bridge
This is classical multidimensional scaling, and it is also how cryptanalysts break substitution ciphers
Two ideas you already know are the same idea. Multidimensional scaling takes a matrix of pairwise distances and reconstructs a point cloud from it — the claim being that distances determine the configuration up to rigid motion. That is Chapter 7’s premise exactly. And frequency analysis breaks a substitution cipher without any known plaintext: the letters are relabelled, but the statistical relationships between them survive the relabelling, and those relationships identify each letter uniquely. Chapter 6’s attack is frequency analysis on an embedding space — the coordinates are an arbitrary relabelling, the geometry is the invariant statistic, and enough data makes the assignment unique. Whenever a structure is preserved and only the labels change, the labels are recoverable. See vector embeddings and SVD for the same geometry under different names.
“What I cannot create, I do not understand.”
Two encoders, a thousand shared documents, ten lines of numpy: 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.
Exit gate — teach it back before you leave.

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.

Which single sentence best captures the through-line from CKA to vec2vec?