Guide

An embedding is a list of numbers, so comparing two texts means comparing two lists. Three formulas cover most product work: cosine similarity, dot product and Euclidean distance. They look different, but with the right setup they rank results the same way.

Concept

A dimension is one coordinate of a vector. A 768-dimensional embedding is a list of 768 numbers. Pictures of 2D or 3D space are only for intuition.

The picture

Think of coordinates on a map. The distance between two points stands in for how far apart two places are. An embedding space works the same way, with hundreds of dimensions instead of two. Billing tickets form one cluster and password tickets form another. A new ticket lands near the cluster that matches its meaning, even when the wording is different.

Three ways to compare

  • Cosine similarity compares direction and ignores length. It runs from −1 to 1, and higher means more similar.
  • Dot product multiplies matching coordinates and adds the results. It is sensitive to length.
  • Euclidean distance is the straight-line gap between two points. Smaller means more similar, which is easy to reverse by mistake.
Python
import math

def cosine(a: list[float], b: list[float]) -> float:
    dot = sum(x * y for x, y in zip(a, b))
    na = math.sqrt(sum(x * x for x in a))
    nb = math.sqrt(sum(y * y for y in b))
    return dot / (na * nb)

print(round(cosine([1.0, 2.0], [2.0, 4.0]), 3))  # same direction, different length
print(round(cosine([1.0, 0.0], [0.0, 1.0]), 3))  # perpendicular

Output:

1.0
0.0

The first pair scores 1.0 even though one vector is twice as long. Cosine only sees direction.

Why normalizing makes them agree

Scale every vector to length 1. This is called L2 normalization. After that:

  • The dot product equals the cosine.
  • Euclidean distance satisfies ‖a − b‖² = 2 − 2·cos(a, b), so the nearest point is also the one with the highest cosine.

So normalize once, when you embed, and store unit vectors. Then use the inner product, which is the cheapest operation and the one most search indexes optimize.

Walking through a ranking check

Three documents, A, B and C, have unit vectors. Their dot products with the query are 0.91, 0.77 and 0.40. The ranking is A, B, C.

Now suppose the same directions are stored with lengths 2.0, 0.5 and 1.0. The raw dot products become:

A: 0.91 × 2.0 = 1.82
B: 0.77 × 0.5 = 0.385
C: 0.40 × 1.0 = 0.40

The new ranking is A, C, B. Document C moved above B only because B was stored shorter. Retrieval quality looks random after an upgrade that changes normalization.

Scores belong to a model

A cosine of 0.82 means little on its own. Scores from one model do not match scores from another. Use scores to rank results. If you need a "is this a match?" cutoff, measure it on pairs you have labeled yourself.

MetricBest whenWatch out for
CosineThe default for text embeddingsCosts a little more because of the norm
Dot productVectors are already unit lengthLength changes the ranking if they are not
EuclideanUnit vectors, where it matches cosine"Smaller is better" is easy to misread

Common mistakes

  • Storing unnormalized vectors in an index that assumes inner product. Rankings drift without any error message.
  • Copying a threshold from a blog post written for a different model.
  • Comparing scores across models. Each model has its own range.

Check yourself

  1. Two vectors point the same way, but one is three times longer. What is their cosine? 1.0. Cosine ignores length.
  2. Why can the ranking change when vectors are not normalized? Dot product includes length, so a longer document can beat a better-aligned one.

Key takeaways

  • Cosine compares direction. Dot product and Euclidean distance also depend on length unless the vectors are unit length.
  • For unit vectors, all three rank results the same way.
  • Normalize at embed time and store unit vectors.
  • Calibrate any cutoff on your own labeled pairs.

Further learning

Next, see how the two kinds of embedding models split the work in bi-encoders and cross-encoders.