Chapter 3 · Foundations

Embeddings and Semantic Meaning

Chapter 2 assumed that meaning can live in a list of numbers, and never justified it. This chapter earns that assumption, then cashes it in: embeddings are what make a search for "waterproof jacket" find a page that says "rainproof coat".

19 sections 1954 to today Retail search 8 interview drills Reading time ~2 hours

3.1 The debt from Chapter 2

Chapter 2 described attention comparing a query vector against a key vector with a dot product, and treating a large result as "these two words are relevant to each other". That was smuggled past you.

Why should arithmetic on a list of numbers have anything to do with meaning? Nothing in multiplication knows that "shoes" and "footwear" are related. If the numbers were assigned arbitrarily, the dot product would be meaningless and attention would be noise.

So the numbers cannot be arbitrary. They have to be arranged so that geometric closeness corresponds to similarity of meaning. That arrangement is what an embedding is, and this chapter is about how it is produced.

[+] Why this chapter pays off immediately

Embeddings are not only internal machinery. They are the most directly useful thing in this course: semantic search, recommendations, deduplication, clustering and classification all fall out of them. For most retail teams, embedding-powered search is the first AI feature that ships and the first that pays for itself. They are also the foundation of retrieval, which is Chapter 4.

3.2 What a vector is

No jargon without explanation, so: a vector is an ordered list of numbers. That is the entire definition.

Vectors are just liststext
[2, 5]              a 2-dimensional vector
[2, 5, 9]           a 3-dimensional vector
[0.21, -0.88, ...]  a 1536-dimensional vector (typical for embeddings)

The useful part is that a list of numbers can be read as coordinates. The vector [2, 5] is a point 2 across and 5 up. And once things are points in a space, "how similar are they" becomes "how close together are they" — a question arithmetic can answer.

[retail] A two-dimensional product space

Suppose we describe each product with two numbers: price in pounds, and warmth on a scale of 0 to 10.

winter parka     [180, 9]
fleece jacket    [45, 6]
windbreaker      [40, 2]
cotton t-shirt   [12, 1]

The fleece and the windbreaker are close together, because they cost about the same and are moderately warm. The parka sits far away on both counts. Nobody wrote a rule saying "fleece resembles windbreaker" — it emerges from the numbers. That is the whole trick, and everything else is scaling it up.

[def] Embedding

An embedding is a vector representing a piece of content — a word, sentence, document or image — positioned so that similar content sits nearby. The word comes from mathematics: you are embedding items into a space, giving each a location. The dimensions are learned rather than hand-chosen, and typically number in the hundreds or thousands rather than two.

In plain English: give every product a position on a map, arranged so similar products end up near each other. An embedding is that position, for text instead of products, with hundreds of directions instead of two.

3.3 First attempt: one-hot encoding

Before the clever solution, the obvious one — because understanding why it fails makes everything after it feel inevitable.

You need to turn words into numbers. The simplest scheme: number every word in your vocabulary, then represent each word as a list that is all zeros except a single 1 in that word's slot.

One-hot encoding, vocabulary of 5 wordstext
          shoes  boots  jacket  coat  laptop
shoes  =  [  1  ,  0  ,   0   ,  0  ,   0   ]
boots  =  [  0  ,  1  ,   0   ,  0  ,   0   ]
jacket =  [  0  ,  0  ,   1   ,  0  ,   0   ]
coat   =  [  0  ,  0  ,   0   ,  1  ,   0   ]
laptop =  [  0  ,  0  ,   0   ,  0  ,   1   ]

[def] One-hot encoding

One-hot encoding represents each item as a vector the size of the whole vocabulary, with a single 1 marking which item it is. "Hot" means the switched-on position. It is unambiguous, trivially reversible, and was standard practice for decades.

Why it fails, and the failure is total

Take the dot product of "shoes" and "boots": multiply position by position and sum. (1×0) + (0×1) + 0 + 0 + 0 = 0.

Now "shoes" and "laptop": also 0.

[!] Every pair of distinct words is equally unrelated

Under one-hot encoding, "shoes" is exactly as similar to "boots" as it is to "laptop", which is to say not at all. The representation contains zero information about meaning. It is a numbered filing system, not a map. Feed these to the attention mechanism from Chapter 2 and every score is zero.

There is a second problem, which is practical rather than conceptual.

One-hot vectors are enormous and almost entirely empty
Vocabulary Numbers per word Non-zero Memory for one word
1,000 words1,00014 KB
50,000 words50,0001200 KB
500,000 products500,00012 MB

Two megabytes to say "this is product number 40,127". The information content is one number; the storage is half a million.

[retail] What this meant for search, in practice

Search built on exact word matching — which is one-hot thinking — has a characteristic failure mode any retailer will recognise:

  • Customer searches waterproof jacket. Your product is listed as rainproof coat. Zero results.
  • Customer searches trainers. Catalogue says sneakers. Zero results.
  • Customer searches laptop bag. Product is a notebook case. Zero results.

The standard workaround was a hand-maintained synonym list. Someone had to think of every pair in advance, in every language, forever. It never covered the long tail, and every new product category started the work again.

In plain English: numbering words tells you which word it is, but nothing about what it means. We need positions on a map, not entries in a filing cabinet.

3.4 The distributional hypothesis

One-hot fails because we assigned the numbers by hand, arbitrarily. To do better, the numbers have to be learned from something. But learned from what? Meaning is not written down anywhere.

The answer is one of the most productive ideas in the history of language technology, and it predates all of this by decades.

[hist] 1954 and 1957: the distributional hypothesis

The linguist Zellig Harris proposed in 1954 that words appearing in similar contexts tend to have similar meanings. J.R. Firth put it memorably in 1957:

"You shall know a word by the company it keeps."

This is worth pausing on, because it converts an impossible problem into a countable one. You cannot look up what "trainers" means. You can count what words appear near it across millions of documents.

[retail] Watching it work on your own catalogue

Consider the words that surround two products in customer reviews and descriptions:

...comfortable TRAINERS for running, laces, size 9, cushioned...
...these SNEAKERS are comfortable, good laces, true to size 9...
...wore my TRAINERS running daily, cushioned sole, great fit...
...SNEAKERS with cushioned soles, perfect for running, true fit...

"Trainers" and "sneakers" never appear in the same sentence — people use one word or the other. But they are surrounded by an almost identical cloud: comfortable, laces, running, cushioned, size, fit. A system that positions words by their company will place these two on top of each other, and it will do so without anyone writing a synonym rule.

[+] Why this solved the synonym problem permanently

The hand-maintained synonym list from section 3.3 required a person to anticipate every pair. The distributional approach discovers them from data you already have. Add a new product category, feed in the text, and the relationships appear on their own — including the ones nobody thought of, in whatever language the text is in.

[!] The hypothesis has a well-known blind spot

Antonyms keep very similar company. "Hot" and "cold" both appear near weather, water, drink and temperature. "Cheap" and "expensive" both sit near price and value. So distributional methods reliably place opposites close together, because they capture "relates to the same topic" more faithfully than "means the same thing". This is still a live limitation in modern embeddings, and section 3.16 returns to it with a retail example where it causes real damage.

In plain English: you can work out what a word means by looking at what tends to sit around it. Words with similar neighbours get similar positions. That is the entire basis of learned embeddings.

3.5 Counting words: TF-IDF and its limits

The distributional hypothesis says to look at context. The first serious attempt to operationalise that was simply to count, and the resulting technique is still in production use today — which is why it deserves proper treatment rather than a dismissal.

[def] TF-IDF

TF-IDF stands for term frequency, inverse document frequency. It scores how important a word is to one document, relative to a whole collection. Two parts:

  • Term frequency: how often the word appears in this document. Appearing often suggests it matters here.
  • Inverse document frequency: how rare the word is across all documents. A word appearing everywhere carries little information.

Multiply the two and you get a useful signal. In a product catalogue, "the" appears in every listing and scores near zero. "Gore-Tex" appears in a handful and scores highly for those — it is genuinely distinguishing.

[hist] 1972, and still running your search bar

Karen Sparck Jones formalised inverse document frequency in 1972. Its modern refinement, BM25 (1994), remains the default ranking function in Elasticsearch, OpenSearch, Lucene and Solr. If your site has a search box that you did not deliberately build with AI, it is almost certainly running BM25 right now. This is not a historical curiosity — it is the incumbent.

Why it is not enough

TF-IDF still represents documents by which words they contain. It weights those words more intelligently than one-hot, but the underlying comparison is still exact word overlap.

What TF-IDF fixes, and what it does not
Problem One-hot TF-IDF
Distinguishes important from filler words No Yes
Compares documents meaningfully No Yes, by shared vocabulary
Matches "trainers" to "sneakers" No No
Compact representation No No, still vocabulary-sized
Handles a word never seen before No No

[+] When keyword search is genuinely the better choice

A point most AI material gets wrong: BM25 beats embeddings for several real retail queries, and you should know which.

  • Exact identifiers. A customer searching SKU-99432 or ISBN 9780141036144 wants that exact item. Semantic similarity is actively harmful here — it will helpfully return similar product codes.
  • Rare proper nouns. A niche brand name the embedding model never saw in training has no meaningful position in the space. Literal matching handles it correctly.
  • Precise model numbers. "iPhone 15 Pro Max" and "iPhone 15 Pro" are semantically almost identical and commercially quite different.

[retail] What production systems actually do

Mature retail search runs both and combines them, an approach called hybrid search. BM25 catches exact codes and rare terms; embeddings catch synonyms and intent. The two result lists are merged with a fusion algorithm. Choosing one exclusively is usually a mistake, and saying so in an interview marks you out as someone who has shipped search rather than read about it. Chapter 4 covers the fusion mechanics.

3.6 2013: Word2Vec

Counting contexts gives you enormous, sparse vectors. The breakthrough was realising you could learn small dense ones instead — and that a neural network would do it for you if you gave it the right task.

[hist] 2013: Word2Vec at Google

Tomas Mikolov and colleagues published Word2Vec, which trained a deliberately simple network on a deliberately pointless task: given a word, predict the words around it. Nobody wanted those predictions. The point was that to predict context well, the network had to build an internal representation capturing what each word means. That internal representation — discarded from the original task, kept as the actual product — is the embedding.

This is a pattern worth recognising, because it recurs throughout AI: train on a task you do not care about, to obtain a representation you do care about. It is the same logic as next-token prediction in Chapter 1 producing a model that can write emails.

The two training arrangements

CBOW

Continuous bag of words

Show the network the surrounding words and ask it to predict the missing middle one. Given "comfortable ___ for running", predict "trainers". Faster to train, and better on words that appear frequently.

Skip-gram

The usual default

The reverse: show one word and predict its neighbours. Given "trainers", predict "comfortable", "running", "laces". Slower, but noticeably better on rare words — which for a retail catalogue is most of your inventory.

[+] What changed, concretely

A one-hot vector over a 50,000-word vocabulary needs 50,000 numbers to say nothing about meaning. A Word2Vec embedding used 300 numbers and captured a great deal of it. That is roughly 170 times smaller and immeasurably more useful. Dense beats sparse: every number contributes, rather than 49,999 zeros padding out a single 1.

Sparse one-hot vectors compared with dense learned embeddings The upper half shows one-hot vectors as long rows of zeros with a single 1, where the dot product between any two different words is always zero. The lower half shows dense embeddings as short rows of varied decimal numbers, where trainers and sneakers have high similarity of 0.94 while trainers and laptop have low similarity of 0.11. BEFORE: one-hot, 50,000 numbers per word trainers [ 0 0 0 ... 0 1 0 ... 0 0 0 ] 49,999 zeros and one 1 sneakers [ 0 1 0 ... 0 0 0 ... 0 0 0 ] laptop [ 0 0 0 ... 0 0 0 ... 1 0 0 ] similarity(trainers, sneakers) = 0.00 — identical to similarity(trainers, laptop) = 0.00 AFTER: learned embedding, 300 numbers per word trainers [ 0.82 -0.31 0.55 0.09 ... ] sneakers [ 0.79 -0.28 0.61 0.04 ... ] laptop [-0.44 0.67 -0.12 0.88 ... ] similarity(trainers, sneakers) = 0.94 — while similarity(trainers, laptop) = 0.11
Figure 3.1 — The same three words under both schemes. Note the top rows of trainers and sneakers: nearly the same numbers in nearly the same places, which is what produces the high similarity score. Nobody specified those numbers; they were learned from context.

3.7 Arithmetic on meaning

Word2Vec produced a result that startled people, including its authors. The embedding space turned out to have directions that correspond to concepts, which means you can do arithmetic with meaning.

The famous exampletext
vector("king") - vector("man") + vector("woman")  ~=  vector("queen")

Read that as a set of directions. Subtracting "man" and adding "woman" moves you along a consistent gender direction in the space. Starting at "king" and taking that step lands you very near "queen". The same direction connects "actor" to "actress" and "uncle" to "aunt".

Other directions encode other relationships: a plural direction, a past-tense direction, a country-to-capital direction. None of these were designed. They emerged from predicting neighbouring words.

[retail] Concept directions in a catalogue

Train embeddings on your own product text and the same structure appears, in commercially useful forms:

"trainers"  - "casual"  + "formal"    ->  "dress shoes"
"t-shirt"   - "summer"  + "winter"    ->  "jumper"
"iPhone"    - "Apple"   + "Samsung"   ->  "Galaxy"

That third one is a genuine product feature. It is competitor mapping — "customers looking at this Apple product would consider this Samsung equivalent" — derived from text rather than from a manually maintained equivalence table.

[!] The part that usually gets left out

The king-queen example is repeated everywhere and is somewhat oversold. Three caveats worth knowing, because a good interviewer will probe them:

  • The original result excluded the input words from the candidate answers. Without that exclusion, the nearest vector to the computed point is frequently "king" itself. The demonstration is real but was tidied.
  • It works well for a handful of relationships — gender, capitals, tense — and much less reliably beyond them. It is not a general reasoning engine.
  • These directions carry the training data's biases. The same geometry that maps king to queen also maps "doctor" toward male and "nurse" toward female, because that is the pattern in the text. This is exactly the mechanism behind the bias discussion in section 1.16, now visible as arithmetic.

[+] Why it matters anyway

Even discounted, the finding proved something important: the space has structure, not just clustering. Related concepts are not merely near each other, they are arranged in consistent geometric relationships. That is what justifies the assumption Chapter 2 made — comparing vectors with arithmetic is meaningful because the space was built so that geometry encodes semantics.

In plain English: directions in the space mean things. "Add femaleness" is a step you can take, and it works from many different starting points. That structure is learned, not designed, and it is why vector maths on words is not nonsense.

3.8 Similarity metrics

We have been saying "similar" and "near" for six sections without defining either. Two vectors are lists of numbers; turning that into a single similarity score requires choosing a formula, and the three common choices disagree with each other in ways that matter.

Dot product
Multiply the vectors element by element and add the results. Grows with both the alignment of the vectors and their length, so a long vector scores highly against everything.
Cosine similarity
The dot product divided by both lengths, which cancels length out entirely. What remains is purely the angle between the two vectors. Runs from −1 to 1.
Euclidean distance
The straight-line distance between the two points, as measured with a ruler. Unlike the other two this is a distance: smaller means more similar.

Why length is the whole story

The difference between these metrics comes down to one question: should the magnitude of a vector affect similarity? In text embeddings, magnitude tends to encode things you usually do not care about — how long the document was, how frequently its words occurred, how emphatic the phrasing was.

Picture a short product title and a long product description for the same jacket. They point in nearly the same direction, because they are about the same thing, but the longer text often produces a longer vector. Cosine similarity says they are nearly identical, which is what you want. Euclidean distance says they are far apart, because one point sits much further from the origin than the other.

[+] Why cosine is the default for text

Because you almost always want aboutness rather than intensity. A one-line title and a five-paragraph description of the same product should match. Cosine ignores the length difference and compares only direction, which is exactly the question "are these about the same thing?" This is why nearly every embedding model you will use is documented as expecting cosine similarity.

[def] Normalised vectors, and a useful shortcut

A vector is normalised when its length has been scaled to exactly 1. Once every vector in your index is normalised, all three metrics agree on the ordering — and the dot product becomes mathematically identical to cosine similarity, while being cheaper to compute because it skips the division. Most modern embedding APIs return normalised vectors for precisely this reason, so production systems typically store normalised vectors and use plain dot product. If you have ever wondered why a vector database offers you both and the documentation says they are the same, this is why.

Making the three metrics disagreepython
"""Where cosine and Euclidean disagree, demonstrated rather than asserted.

The claim in the prose is that magnitude encodes document length, and that
Euclidean distance is fooled by it while cosine is not. That is easy to say
and easy to get backwards, so the numbers here are computed from vectors
chosen to make the disagreement visible.

No numpy: the arithmetic is short enough to write out, and seeing the
formulas spelled out is the point of the exercise.
"""
from __future__ import annotations

from math import sqrt


def dot(a: list[float], b: list[float]) -> float:
    return sum(x * y for x, y in zip(a, b))


def norm(a: list[float]) -> float:
    return sqrt(dot(a, a))


def cosine(a: list[float], b: list[float]) -> float:
    """Angle only: divides out both magnitudes."""
    return dot(a, b) / (norm(a) * norm(b))


def euclidean(a: list[float], b: list[float]) -> float:
    """Straight-line distance. Smaller means closer."""
    return sqrt(sum((x - y) * (x - y) for x, y in zip(a, b)))


def normalise(a: list[float]) -> list[float]:
    length = norm(a)
    return [x / length for x in a]


# A short title and a long description of the SAME jacket point in nearly the
# same direction, but the longer text produces a longer vector.
TITLE = [0.6, 0.8, 0.1]
DESCRIPTION = [3.0, 4.0, 0.5]        # ~5x longer, same direction

# A different product that happens to sit at a similar distance from the origin
# as the title does.
UMBRELLA = [0.1, 0.2, 0.9]

print("same jacket, different text lengths")
print(f"  cosine     {cosine(TITLE, DESCRIPTION):.3f}   (1.0 = identical direction)")
print(f"  euclidean  {euclidean(TITLE, DESCRIPTION):.3f}   (0.0 = identical point)")

print("\ndifferent products, similar vector lengths")
print(f"  cosine     {cosine(TITLE, UMBRELLA):.3f}")
print(f"  euclidean  {euclidean(TITLE, UMBRELLA):.3f}")

print("\nwhich does each metric call the better match for the title?")
cos_pick = "description" if cosine(TITLE, DESCRIPTION) > cosine(TITLE, UMBRELLA) else "umbrella"
euc_pick = "description" if euclidean(TITLE, DESCRIPTION) < euclidean(TITLE, UMBRELLA) else "umbrella"
print(f"  cosine says     -> {cos_pick}")
print(f"  euclidean says  -> {euc_pick}")

# After normalising, the disagreement disappears entirely.
t, d = normalise(TITLE), normalise(DESCRIPTION)
print("\nafter normalising both to unit length")
print(f"  cosine     {cosine(t, d):.3f}")
print(f"  euclidean  {euclidean(t, d):.3f}")
print(f"  dot product {dot(t, d):.3f}  <- now identical to cosine")
Outputtext
same jacket, different text lengths
  cosine     1.000   (1.0 = identical direction)
  euclidean  4.020   (0.0 = identical point)

different products, similar vector lengths
  cosine     0.333
  euclidean  1.118

which does each metric call the better match for the title?
  cosine says     -> description
  euclidean says  -> umbrella

after normalising both to unit length
  cosine     1.000
  euclidean  0.000
  dot product 1.000  <- now identical to cosine

The two metrics choose opposite answers. Cosine sees that the title and the description point in exactly the same direction and scores a perfect 1.0. Euclidean sees a gap of 4.2 between them — larger than the 1.118 separating the jacket from an umbrella — and concludes the umbrella is the better match. It is not confused about meaning; it is faithfully reporting that one point sits far from the other, which here is entirely an artefact of description length.

The final block is the practical resolution. Once both vectors are scaled to unit length, cosine reads 1.000, Euclidean reads 0.000, and the dot product equals cosine exactly. All three now agree, because the only thing they could have disagreed about has been removed.

[!] Do not mix metrics between indexing and querying

A real and quietly common production bug: a model that returns unnormalised vectors, stored in an index configured for dot product. Long documents then rank highly for every query, because their vectors are simply bigger, and the symptom is a search engine with an inexplicable preference for verbose pages. Check two things when you set up an index — whether your model normalises its output, and which metric the index is configured to use. They must agree.

In plain English: cosine asks "are these pointing the same way?", Euclidean asks "are these in the same place?". For text you nearly always want the first, because writing more words moves a vector further out without changing what it is about. Scale everything to the same length and the question disappears.

3.9 Why averaging word vectors fails

Word2Vec gives you a vector per word. Search needs a vector per sentence or per product description. The obvious bridge is to average the word vectors together, and for a while that is exactly what everyone did.

It works better than it has any right to. Average the vectors for "waterproof", "hiking" and "jacket" and you land somewhere genuinely sensible — near other outdoor clothing. As a cheap baseline it is respectable, and it was the standard approach for several years.

It also fails in three specific ways, and each failure points directly at what had to be invented next.

  1. Word order disappears entirely Averaging is commutative, so dog bites man and man bites dog produce the identical vector. For retail, jacket for dog and dog jacket are the same query, which is fine, but case for phone and phone for case are not. Any meaning carried by structure is lost before you start.
  2. Common words drown out rare ones In a case for the new iPhone, the words a, for and the contribute three vectors of generic filler against one meaningful iPhone. The average drifts toward the centre of the space, where all bland text lives. Weighting by TF-IDF from 3.5 helps considerably, which is why that combination became the standard trick, but it is a patch rather than a fix.
  3. One vector per word, regardless of meaning This is the fundamental one. Word2Vec assigns bank exactly one vector, which must simultaneously serve the riverbank and the financial institution. The result is a compromise vector sitting between two unrelated meanings, representing neither. Every word with more than one sense is permanently blurred.

[def] Polysemy

A word having multiple distinct meanings. Bank, spring, light, charge. Static embeddings such as Word2Vec cannot represent polysemy at all: one word gets one vector, so the senses are averaged into a single muddle. This limitation is not a tuning problem or a data problem. It is built into the design, and no amount of extra training text will fix it.

[retail] Where this bites in a catalogue

Charge means a payment on your billing page, a battery action in your electronics listings, and a rushing motion in your sports equipment copy. A static embedding gives all three the same vector, so a customer searching for phone charging cable gets results pulled slightly toward payment-related pages. Nothing is broken enough to notice in testing, and the ranking is quietly degraded for every ambiguous word in your inventory — which, in English, is most of the common ones.

In plain English: a word does not have one meaning, it has a meaning in context. Averaging fixed word vectors throws away the context and then wonders why the result is vague. The next section is about what happened when models started reading the whole sentence before deciding what each word meant.

3.10 Contextual embeddings

The fix for polysemy is to stop storing a vector for each word and start computing one each time the word appears, using the sentence around it.

[hist] 2018: ELMo, then BERT

ELMo (Peters et al., 2018) was the first widely used contextual embedding model, reading each sentence with a recurrent network in both directions to produce word representations that varied by context. Months later BERT (Devlin et al., 2018) did the same with the transformer architecture from Chapter 2 and comprehensively outperformed it. The name is worth unpacking: Bidirectional Encoder Representations from Transformers. Every word in that phrase now means something to you.

The change is best seen in a single word appearing twice:

The same word, two different vectorstext
"I sat on the river bank"        ->  bank = [ 0.21  -0.88   0.43  ... ]
"I deposited it at the bank"     ->  bank = [-0.72   0.15   0.66  ... ]

cosine(these two) = 0.18       # correctly recognised as different senses

"I withdrew cash from the bank"  ->  bank = [-0.69   0.19   0.71  ... ]

cosine(financial pair) = 0.96  # correctly recognised as the same sense

Nothing about the token bank changed. What changed is that the model read the surrounding words first — and the attention mechanism from Chapter 2 is precisely the machinery that lets river or deposited reach across the sentence and reshape what bank means here.

Static embeddings

  • One vector per word, stored in a lookup table
  • Computed once, at training time
  • Instant to retrieve, trivially cheap
  • Cannot represent multiple senses
  • Word2Vec, GloVe, fastText

Contextual embeddings

  • A vector computed per occurrence
  • Requires a model pass over the sentence
  • Milliseconds and real compute per call
  • Senses separate naturally
  • ELMo, BERT, and everything since

[!] BERT out of the box is a poor sentence embedder

This surprises people, and it is the single most useful thing in this section. BERT produces excellent per-token vectors, so the obvious move is to average them, or to take the special [CLS] token's vector, and call that your sentence embedding. Both perform badly at similarity search — on some benchmarks worse than averaged Word2Vec vectors. The reason is that BERT was trained to predict masked words, not to place similar sentences near each other. Nothing in its training ever asked for that property, so it does not have it. Fixing this is what 3.11 is about.

In plain English: instead of looking a word up in a dictionary, read the sentence and work out what the word means here. That solves polysemy. It does not automatically give you a good vector for the whole sentence, which turns out to be a separate problem.

3.11 Sentence-BERT and bi-encoders

BERT understood language but could not compare sentences. The fix, in 2019, was not a new architecture — it was a new training objective, and it is the reason semantic search became practical.

The problem in numbers

Before Sentence-BERT, the accurate way to compare two sentences with BERT was to feed them in together and let attention run across both, producing a similarity score. This works well and is the cross-encoder design. It also does not scale, and the arithmetic is worth doing.

[!] Why cross-encoders cannot power search

To find the best match for one query among 10,000 products, a cross-encoder must run 10,000 model passes — the query paired with each product in turn. At roughly 10 ms each that is 100 seconds per search. The Sentence-BERT paper made the point more starkly: finding the most similar pair among 10,000 sentences took around 65 hours with BERT. Nothing about the quality is wrong; it simply cannot be deployed.

[def] Bi-encoder

Encode each text independently into a fixed vector, then compare the vectors. Because documents never need to see the query, every product can be embedded once, in advance, and stored. A search becomes one embedding call for the query plus a vector comparison — which is the operation chapter 4 makes fast at billion scale. The 65 hours becomes about 5 seconds.

[hist] 2019: Sentence-BERT

Nils Reimers and Iryna Gurevych, at the Ubiquitous Knowledge Processing Lab in Darmstadt, published the modification that made BERT usable for search. Their sentence-transformers library became the default way to work with embeddings, and most open embedding models you will meet still follow its interface.

How Sentence-BERT actually trains

Reimers and Gurevych's insight was that if you want vectors whose distances are meaningful, you must train on distances. So they fine-tuned BERT on pairs of sentences with a known relationship, using a structure that forces the geometry into shape.

  1. Run two texts through the same model A siamese arrangement: one set of weights, applied separately to each text. Identical inputs must give identical outputs, so the two are directly comparable.
  2. Pool the token vectors into one Mean pooling over the token outputs, which turns out to beat using the [CLS] token. This is the same averaging that failed in 3.9, but it now works — because the next step trains the model so that it does.
  3. Train on a distance objective Show the model a sentence, something that means the same, and something that does not. Penalise it when the matching pair is not closer than the mismatched pair. This is contrastive learning, and it directly optimises the property you intend to use at query time.

[+] The lesson worth carrying forward

Sentence-BERT used the same architecture and mostly the same weights as BERT. The only real change was training on the task that would actually be performed. That is the general principle: a model is good at what it was trained to do, not at what seems related to it. BERT was trained to fill in blanks and was therefore bad at similarity, despite obviously "understanding" language. If you remember one thing from this chapter when choosing a model, make it this.

[retail] Both encoders, in one system

You do not have to choose. The standard production design uses both: the bi-encoder searches the entire catalogue in milliseconds and returns 100 candidates, then the cross-encoder — too slow for 800 million products, perfectly affordable for 100 — reranks those into a final order. Fast and approximate first, slow and accurate second. Chapter 5 builds this pattern out properly in section 5.15.

In plain English: comparing two sentences by running them through a model together is accurate and far too slow. Encoding each one separately and comparing the numbers is fast, but only gives sensible answers if the model was trained so that distances mean something. Sentence-BERT is that training.

3.12 Choosing a model, and what MTEB does not tell you

There are hundreds of embedding models and a public leaderboard ranking them. Picking the top row is the obvious move and usually the wrong one.

[def] MTEB

The Massive Text Embedding Benchmark (2022), a public leaderboard scoring embedding models across dozens of datasets and eight task types — retrieval, classification, clustering, reranking and others. It is genuinely useful and a considerable improvement on the previous situation, which was vendors quoting whichever benchmark flattered them.

The dimensions that actually decide it

What to weigh when choosing
Dimension Why it matters
Dimensions 384, 768 and 1536 are common. This sets your storage bill and your search speed directly — chapter 4 sizes this out. Higher is not reliably better.
Maximum sequence length Many models truncate silently at 512 tokens. If your product descriptions run longer, the tail is discarded without warning and you will never see an error.
Hosted or self-hosted An API is faster to adopt and costs per call forever. Self-hosting costs engineering time and GPU capacity, and keeps your catalogue text inside your own network.
Matryoshka support Models trained so their vectors can be truncated to fewer dimensions with graceful quality loss. Enormously useful for cost control, as chapter 4 covers.
Licence Some strong open models carry restrictions that a commercial deployment cannot accept. Check before building on one.
Asymmetric support Whether the model expects distinct prefixes for queries and documents. See the warning below — this one silently halves your quality if handled wrongly.

[!] Four reasons the leaderboard misleads

  • The average hides everything. MTEB reports a mean across eight task types. You care about one of them — almost always retrieval. A model can lead overall while ranking mid-table on the only column relevant to you.
  • Benchmark data is not your data. Scores come from Wikipedia, news and academic text. None of that resembles Ryobi ONE+ 18V cordless drill, bare tool, which is mostly product codes and specifications.
  • Leaderboards attract overfitting. When a benchmark becomes the target, models get tuned toward it. Differences of half a point are noise, not signal.
  • Cost and latency are absent. A model twice as good on paper that costs ten times as much and adds 200 ms may be a poor trade for a search box.

[!] Symmetric versus asymmetric search

This distinction catches people constantly. Symmetric search compares two things of similar kind — a sentence against a sentence, a product against a product. Asymmetric search compares a short query against a long document, which is what a search box does. Many models are trained specifically for the asymmetric case and require you to prefix inputs, for example query: and passage:. Omitting those prefixes, or applying the same one to both sides, degrades results substantially while producing no error at all. Read the model card, not just the leaderboard row.

[+] The only reliable method

Build a small evaluation set from your data: 100 to 200 real user queries with the products that should be returned. Run three or four candidate models against it and compare recall. It takes about a day and beats every leaderboard, because it measures the exact task you are deploying on the exact text you own. Section 3.17 covers how to build that set. The teams that skip this step are the ones who discover six months later that their model truncates at 512 tokens.

In plain English: the leaderboard tells you which models are broadly competent. It cannot tell you which one is best for your catalogue, because it has never seen your catalogue. Shortlist from the leaderboard, then decide with your own test set.

3.13 Chunking strategies

An embedding model turns a piece of text into one vector. That forces a question with no universally right answer: how big should the piece be?

The constraint is dilution. One vector holds a fixed amount of information, so the more text you push into it, the more each individual idea gets averaged away. This is the same failure as 3.9, operating at document scale rather than word scale.

Chunks too large

  • A ten-page manual becomes one vector
  • Represents the average of ten topics, so matches none of them strongly
  • Retrieval returns a wall of text with one useful line in it
  • May exceed the model's limit and be silently truncated

Chunks too small

  • A single sentence per vector
  • Precise, but stripped of the context that made it meaningful
  • "This does not apply to clearance items" — what does not?
  • More vectors means more storage and slower search
Strategies, in increasing order of effort
Strategy How it works When to use it
Fixed size Every N tokens, with an overlap of perhaps 10–20% so a sentence split across a boundary still appears whole somewhere. The default baseline. Crude, predictable, and works acceptably on uniform prose.
Recursive Split on paragraph breaks first; if a piece is still too big, split on sentences, then on words. Respects natural boundaries where it can. The sensible default for most text. Better than fixed size for almost no extra effort.
Document structure Split on the document's own headings, sections or list items. Best when your content has real structure — policies, manuals, specification sheets.
Semantic Embed each sentence, then start a new chunk wherever consecutive sentences become dissimilar — letting topic shifts define the boundaries. Elegant, and costs an embedding call per sentence at ingestion. Try it when structural splitting is not available.

[retail] Natural chunks in a catalogue

Retail data usually chunks itself, and fighting that is a mistake. A product is one chunk. A review is one chunk. A specification table is one chunk, or one per row for very long ones. You rarely need a sliding window, because the record boundaries already mark the topic boundaries. The harder case is long-form content — buying guides, care instructions, warranty documents — where structural splitting on headings is the right first choice.

[+] One vector, one idea

That is the whole rule. A chunk should contain enough context to stand on its own and little enough that it is about a single thing. When you are unsure, ask whether a person handed just that chunk could answer a question with it. If they would need to ask "what is this referring to?", the chunk is too small or has lost its heading; if they would have to hunt through it, it is too large.

In plain English: one vector can only really be about one thing. Cut your text so that each piece is about one thing, prefer the document's own boundaries where they exist, and overlap a little so nothing important lands exactly on a boundary.

3.14 Multilingual embeddings

If your catalogue serves several markets, you have a choice: one index per language, or one space holding all of them. The second option relies on a genuinely surprising property.

[def] Cross-lingual alignment

A multilingual model places text with the same meaning near each other regardless of the language it is written in. waterproof jacket, chaqueta impermeable and imperméable land in nearly the same region of the space. Language becomes almost irrelevant to position; meaning is what determines it.

The practical consequence is worth stating plainly, because it sounds too good to be true. A customer typing a Spanish query can match an English product description that was never translated. You search across every market's content with one query, in one index, with no translation step anywhere in the pipeline.

How the alignment is trained

The usual technique is knowledge distillation, and Reimers and Gurevych described a clean version of it in 2020. Take a strong English-only model as the teacher. Then train a multilingual student so that, given a translated pair, it produces the same vector the teacher produced for the English side. The student learns to map every language into the teacher's existing English space, which is what makes the languages line up.

[+] Why one index usually beats several

Separate per-language indexes seem tidier and are usually worse. Products that exist in several markets get stored repeatedly, cross-market queries become impossible, and each smaller index gives poorer recall than one large one — a point chapter 4 makes in detail. A single multilingual index also handles the case where the customer's language and the content's language simply differ, which on any real storefront is common.

[!] The costs, which are real

  • Weaker in English than an English-only model of the same size. Capacity is shared across a hundred languages, so each gets less of it. If you serve one market, use a monolingual model.
  • Quality varies enormously by language. Well-resourced languages do well; languages with little training text do noticeably worse. Test the languages you actually serve rather than trusting an average.
  • Code-switching is unreliable. Text mixing two languages in one sentence — extremely common in real customer queries — is handled inconsistently.
In plain English: some models put "waterproof jacket" and "chaqueta impermeable" in the same place, so one search covers every language you sell in. You pay for it with slightly weaker performance in any single language.

3.15 Images and joint spaces

Everything so far has been text. The same idea works for images, and the interesting part is what happens when both live in one space.

[hist] 2021: CLIP

OpenAI trained CLIP on roughly 400 million image and caption pairs scraped from the web, using a contrastive objective: pull each image toward its own caption and push it away from all the others in the batch. The result is a joint embedding space where a photograph of a red jacket and the words red jacket land in the same neighbourhood. No labelled categories were needed — captions people had already written were the supervision.

[def] Joint embedding space

One shared vector space holding more than one type of data, arranged so that items meaning the same thing sit near each other regardless of their format. Because everything is comparable, you can search images with text, search text with images, or search with both at once. It is the same trick as cross-lingual alignment in 3.14, with pictures playing the role of another language.

What this unlocks in a catalogue

Use 1

Text-to-image search

A customer types floral summer dress and matches photographs directly, including products whose written description never used the word "floral". The picture carries information the text omitted.

Use 2

Image-to-image search

"More like this", and visual duplicate detection. Two listings from different sellers showing the same product land close together even when their titles share no words.

Use 3

Search by photograph

A customer photographs a broken part and finds the replacement, without needing to know what it is called. This is the case where text search cannot compete, because the customer has no vocabulary to search with.

Use 4

Catalogue quality checks

Compare each image against its own description. A low similarity score flags listings where the photograph does not match the text — a wrong image, or a misleading one.

[!] CLIP is weaker than it appears on detail

It captures the gist of an image reliably and struggles with specifics. It is poor at reading text inside images, at counting objects, and at spatial relationships — a mug to the left of a laptop is not reliably distinguished from the reverse. For retail this matters: jacket with four pockets is not a query CLIP answers well. Treat visual similarity as a strong signal to combine with text and structured filters, rather than as a complete search system. Models such as SigLIP have since improved on the training objective, but the fine-detail weakness is a general property of this family.

In plain English: train a model on pictures paired with their captions and it learns to put the picture and the words in the same place. Then a text query can find an image, and a photograph can find a product — useful precisely when the customer cannot describe what they want.

3.16 Fine-tuning on your catalogue

A general-purpose embedding model learned the internet's idea of similarity. Your business has its own, and the gap between the two is where fine-tuning pays.

A public model knows that laptop and notebook computer are similar. It does not know that in your catalogue SKU-88421 and Trailmaster II are the same product, that customers searching school shoes want black leather rather than trainers, or that your ONE+ range is a battery platform rather than a model name. None of that exists on the internet in a form a model could have learned it from.

[def] Contrastive fine-tuning

Continue training a pre-trained embedding model on your pairs: things that should be close, and things that should not. The model adjusts its geometry so your definition of similarity holds, while keeping the general language understanding it arrived with. You are not training from scratch; you are bending an existing space to fit your domain.

Where the training pairs come from

This is the part people assume will block them, and usually does not. Most retailers are already sitting on the labels.

  1. Click and purchase logs A query, and the product the customer then clicked or bought, is a positive pair. This is the best source by a wide margin: real user intent, arriving continuously, with nobody labelling anything. It is also the most honest signal you have about what your customers think similar means.
  2. Products the customer skipped Items shown high in the results and not clicked are hard negatives — superficially relevant but wrong. They teach far more than random negatives, because the model already knows a drill is not a dress. What it needs is the distinction between two similar drills.
  3. Your own catalogue structure Products sharing a category, brand or specification are weakly positive pairs. Cheap to generate in bulk from data you already maintain.
  4. Synonym and correction lists Whatever your search team has accumulated: spelling corrections, regional terms, the fact that jumper and sweater are the same garment. Small, high quality, and usually already written down.

[+] Hard negatives are the whole game

If you take one practical detail from this section, take this. Training against randomly chosen negatives teaches the model almost nothing, because distinguishing a cordless drill from a summer dress is a problem it solved long ago. The useful signal comes from near misses: the drill with the wrong voltage, the jacket in the wrong size range, the accessory for the previous model. Mine those from results your customers saw and rejected, and the model learns the distinctions your ranking actually gets wrong.

[!] Fine-tuning obliges you to re-embed everything

A fine-tuned model produces vectors in a different space from the one it started in. Old and new vectors are not comparable, so every item in your index must be re-embedded before the new model can be used — and on a large catalogue that is a substantial batch job, not a configuration change. Plan for a dual-write period where both indexes exist, so you can compare them and roll back. Chapter 4 covers the mechanics of re-indexing at scale.

[alt] Cheaper things to try first

Fine-tuning is not the first move. Try hybrid search with keyword matching, which fixes exact-code failures without touching the model. Try a reranker, which is often a bigger win for less work. Try improving the text you embed — a product whose embedded text is only its title will improve dramatically from including its category, brand and key attributes. Fine-tune when those are exhausted and you have measured a gap that is specifically about your domain's meaning of similar.

In plain English: teach the model your catalogue's idea of what counts as similar, using the clicks your customers have already given you. Focus on the near misses, and remember you will have to re-embed everything afterwards.

3.17 Evaluating embedding quality

Every recommendation in this chapter has ended with "measure it on your own data". This section is how.

[!] The trap: eyeballing similarity scores

The tempting evaluation is to run a few queries, look at the cosine scores, and judge whether they seem high. This tells you nothing. Scores are not comparable across models, they have no absolute meaning, and 0.82 is not "good" — it is only better than 0.79 from the same model. A model returning wrong products at 0.91 looks healthier than one returning correct products at 0.72. Only judgements about relevance measure relevance.

Building the evaluation set

  1. Take real queries, not invented ones Pull the top few hundred from your search logs, and deliberately include the long tail as well as the head. Queries written by your team are systematically too clean: they spell things correctly and use internal vocabulary. Real ones contain typos, partial product names and regional terms, which is exactly where models differ.
  2. Label what should come back For each query, list the products a knowledgeable person would expect. You do not need every relevant item — a handful of clear positives per query is enough to compute recall meaningfully.
  3. Include the awkward cases on purpose Exact product codes, misspellings, questions phrased as sentences, queries with no good answer, and near-duplicate products that must be told apart. Averages hide these, and they are where real systems fail.
  4. Score per category, never as one average Report recall separately for each of those groups. A model can improve overall while collapsing on product codes — a small share of queries carrying a large share of purchase intent.
What to measure, and what each answers
Measure The question it answers
Recall@k Did the right products appear at all? The primary number for an embedding model, since nothing downstream can recover what retrieval missed.
MRR How near the top was the first correct hit? Matters most when there is one right answer.
NDCG@k Is the whole ordering sensible, given that some matches are better than others?
Latency and size What does this model cost to run and to store? A quality gain that triples your index is a trade, not a win.

[+] Two properties worth testing directly

Beyond retrieval metrics, two quick checks catch problems that averages miss. Robustness: embed a query and a misspelled version of it, and compare. A good model keeps them close; a brittle one does not, and your customers do misspell things. Discrimination: take two genuinely different products from the same category and check they are not near-identical. If everything in the catalogue scores 0.9 against everything else, the model is not discriminating and your ranking is effectively random within a category.

[retail] The evaluation that changes a decision

A retailer compares two models. Model A wins on average recall by four points and looks like the obvious choice. Scored per category, model A is worse on queries containing product codes — 6% of traffic, and a much larger share of completed purchases, because a shopper typing an exact code is ready to buy. Model B ships. That decision is invisible without per-category scoring, and it is the single most common reason a model that benchmarked well disappoints in production.

In plain English: collect real queries, write down which products should come back, and measure how often they do. Break the results down by query type, because one average number will hide the failure that matters most.

3.18 Key takeaways

The ten things worth remembering

  1. An embedding is a position, not an identifier. One-hot vectors say which word it is; embeddings say what it is like. Everything in this chapter follows from that difference.
  2. Meaning comes from company. The distributional hypothesis — words appearing in similar contexts mean similar things — is the only assumption the whole field rests on.
  3. Dense beats sparse. 300 useful numbers beat 50,000 mostly-zero ones, because every dimension contributes something.
  4. The space has structure, not just clusters. Directions encode concepts, which is what makes geometric comparison meaningful rather than coincidental.
  5. Cosine asks about direction, Euclidean asks about position. For text you almost always want direction, because writing more words lengthens a vector without changing its subject. Normalise and the distinction disappears.
  6. Static embeddings cannot handle polysemy. One word, one vector, senses averaged into mush. This is a design limit, not a data problem.
  7. Contextual embeddings compute a vector per occurrence. The sentence decides what the word means here, which is what the attention mechanism from Chapter 2 was for.
  8. A model is good at what it was trained to do. BERT understood language and was bad at similarity, because nothing in its training asked for it. Sentence-BERT changed the objective, not the architecture.
  9. Bi-encoders make search possible; cross-encoders make it accurate. Encode independently to search a catalogue in milliseconds, then rerank the survivors. Use both.
  10. The leaderboard cannot see your catalogue. Shortlist from MTEB, decide with 200 of your own queries scored per category.

[def] The one-sentence version

Turn text into positions in a space arranged so that nearby means similar, compare those positions by angle rather than distance, compute them from context rather than from a lookup table, and verify the arrangement matches your own idea of similar before trusting it.

3.19 Interview drills

Embeddings are where interviewers check whether you understand the machinery or have only used the API. The strong answers below all do the same thing: explain a mechanism, then name its limit.

1. Why cosine similarity rather than Euclidean distance for text?

Because vector magnitude in text embeddings mostly encodes length and emphasis, which are usually not what you are asking about. A one-line product title and a five-paragraph description of the same jacket point in nearly the same direction, but the longer text produces a longer vector. Cosine divides magnitude out and compares only the angle, so it calls them near-identical. Euclidean sees a large gap and can rank an unrelated product as the closer match.

The practical footnote is that once vectors are normalised to unit length, all three metrics agree on ordering, and dot product becomes identical to cosine while skipping a division. That is why most production systems store normalised vectors and use dot product.

Follow-up to expect: "When would you want magnitude?" When length or intensity genuinely is the signal — some recommendation setups where vector norm encodes popularity or confidence.

2. Why can't you just average Word2Vec vectors to embed a sentence?

You can, and as a baseline it is not terrible. It fails in three specific ways. Averaging is commutative, so word order vanishes entirely and dog bites man equals man bites dog. Common filler words outnumber meaningful ones and drag the average toward the bland centre of the space, which TF-IDF weighting partly patches.

The fundamental problem is polysemy. Word2Vec gives each word exactly one vector, so bank gets a single compromise vector serving both the riverbank and the financial institution, representing neither. No amount of extra training data fixes that, because it is built into the design. It needs contextual embeddings, where the vector is computed per occurrence from the surrounding sentence.

3. BERT understands language. Why is it bad at semantic similarity?

Because it was never trained for it. BERT's objective was predicting masked words, which requires deep language understanding but says nothing about where sentences should sit relative to each other. Taking the [CLS] token or mean-pooling BERT's outputs gives sentence vectors that perform badly at similarity search — on some benchmarks worse than averaged Word2Vec.

Sentence-BERT fixed it by changing the training objective rather than the architecture: a siamese setup where the same model encodes two texts, with a contrastive loss penalising the model when matching pairs are not closer than mismatched ones. The general lesson is that a model is good at what it was trained to do, not at what seems adjacent to it.

What is being tested: whether you understand that capability follows the training objective. This is the single most transferable idea in the chapter.

4. Bi-encoder or cross-encoder? Talk me through the trade-off.

A cross-encoder feeds query and document through the model together, so attention runs across both and it can judge how each query term relates to each part of the passage. It is markedly more accurate and nothing can be precomputed — searching 10,000 products means 10,000 model passes.

A bi-encoder encodes each text independently, so every product is embedded once in advance and a search is one query embedding plus a vector comparison. That is what makes catalogue-scale search possible at all.

In production I would use both: the bi-encoder retrieves 100 candidates from the whole catalogue in milliseconds, then the cross-encoder reranks just those into a final order. Cross-encoder quality at a cost that scales with the candidate list rather than the corpus.

5. How would you pick an embedding model for a retail catalogue?

I would shortlist three or four from MTEB, filtering on the retrieval column rather than the overall average, then check the practical constraints: dimensions, because that sets storage and search cost; maximum sequence length, because many models truncate at 512 tokens silently; licence; and whether it expects query and passage prefixes.

Then I would decide with my own data. Two hundred real queries from search logs with labelled expected products, scored per category — exact codes, misspellings, natural-language questions, near-duplicates. That takes about a day and is worth more than the leaderboard, because MTEB is scored on Wikipedia and news text and my catalogue is product codes and specifications.

Trap to avoid: saying "I'd use the top-ranked model". The interviewer is checking whether you know the benchmark has never seen your data.

6. Your search misses exact product codes like SKU-88421. Why, and what do you do?

This is a structural limitation of dense retrieval rather than a quality gap. Embeddings map text to meaning, and an arbitrary code carries no distributed meaning to place — it lands in a neighbourhood of other product codes that look similar. A better embedding model tidies that neighbourhood; it does not make the code matchable.

The fix is hybrid search: run BM25 keyword matching alongside vector search and fuse the two ranked lists, ideally with reciprocal rank fusion so you never have to normalise incomparable score scales. Keyword search matches the literal token every time. I would not reach for fine-tuning here — it is a much larger effort aimed at the wrong problem.

7. When is fine-tuning an embedding model worth it, and what does it cost you?

When your domain's notion of similarity genuinely differs from the internet's. A public model does not know that two of your SKUs are the same product under different names, or that school shoes means black leather rather than trainers. The training pairs usually already exist in click and purchase logs, and the valuable part is hard negatives — products shown and not clicked, which teach the near-miss distinctions that matter.

The cost people forget is that a fine-tuned model produces vectors in a different space, so old and new are not comparable and the entire index must be re-embedded. On a large catalogue that is a serious batch job needing a dual-write period and a rollback plan. I would exhaust hybrid search, reranking and improving the text I embed first, since those are cheaper and often larger wins.

8. What does a multilingual embedding model buy you, and what does it cost?

Cross-lingual alignment: text with the same meaning lands in the same region regardless of language, so a Spanish query can match an English product description with no translation step. One index serves every market, which also avoids storing products repeatedly and keeps cross-market queries possible.

The cost is capacity shared across many languages, so a multilingual model is weaker in English than an English-only model of the same size. Quality also varies a lot by language, and code-switched text is handled inconsistently. If I served one market I would use a monolingual model; with several, the single shared index is usually the better trade.

Bonus if asked how it works: knowledge distillation — train a multilingual student to reproduce a strong English teacher's vectors on translated pairs, which maps every language into the teacher's space.

Where this leaves you

Chapter 2 asked you to accept that meaning can live in a list of numbers. That debt is now paid. You know where those numbers come from, why the space has usable structure, how to compare two positions in it, and why the model that produced them is good at exactly the task it was trained on and no other.

You can also make the practical decisions: which model to use and why the leaderboard cannot answer that for you, how to cut text so each vector is about one thing, when a multilingual or joint image space earns its cost, and how to prove any of it on your own catalogue rather than on someone else's benchmark.

What you cannot yet do is store 800 million of these vectors and search them in milliseconds. Comparing a query against every vector works fine for a thousand products and is hopeless at catalogue scale. Chapter 4 is about the data structures and engineering that make it fast: approximate nearest neighbour indexes, quantization, sharding, and the arithmetic for sizing a cluster you can actually afford.