Student view — safe to present
Teaching Deck · Notebook 04

Embeddings: turning meaning into points on a map

Notebooks 02 and 03 got us chunks. This one turns those chunks into vectors so "close in meaning" becomes a number you can actually compute. Every stat on these slides came out of a real run last night, nothing here is made up.
~40–45 min session OpenAI + local models 4 model-choice axes 2026 production update included
01 / 12
Where this fits

We've got chunks. They still can't be searched.

You can't compare two strings for "closeness," there's no math operation for that. So we turn each chunk into a vector, a list of numbers, and closeness turns into something we can compute: cosine similarity.
For the trainer

Open with this line, it always lands: "yesterday we decided WHAT to feed the model, today we decide HOW it gets turned into numbers." Keep it short, move on fast, the good stuff is next slide.

02 / 12
Core concept

Paraphrases sit close together. Random text doesn't.

Two sentences, zero words in common, run through cosine similarity:
1.00
0.60
0.09
0.60
1.00
0.09
0.09
0.09
1.00
"cancel
subscription"
"terminate
plan"
"warehouse
migration"
cos(paraphrases) = 0.598, high cos(unrelated) = 0.088, basically nothing
paraphrase pair, sit right on top of each other
"warehouse migration", off on its own
For the trainer

Say the keyword count out loud before you show the number: zero shared words. That's the whole pitch for semantic search over keyword search, right there, don't over-explain it after.

03 / 12
The one non-negotiable rule

Index and query have to use the same model. Full stop.

index
model A
query
model A
index
model A
✕
query
model B
Broken #1: different dims

3-large query (3072d) vs 3-small index (1536d)

→ ValueError, the vectors aren't even the same length

Broken #2: same dims, wrong model

Same 384-dim, the exact same sentence, two different models

→ cos = 0.230, reads as "barely related" and it's identical text

For the trainer

Broken #1 is the boring one, it crashes and you fix it in five minutes. Broken #2 is the one that's bitten actual teams in prod: no error, no stack trace, retrieval just quietly returns garbage. Say it plainly: same dimensions doesn't mean same space. Only the same model gives you that. Tell them to version-lock the embedding model the same way they'd lock a database schema, and don't let anyone swap it "real quick" without re-embedding everything.

04 / 12
Choosing a model

There's no "best" embedding model. Four things to weigh.

Language

Got non-English text in the corpus? Multilingual model, no debate.

Context window

Its max tokens has to beat your chunk size or it just truncates.

Dimensionality

More dims, more nuance. Also more storage and slower search.

Domain fit

Heavy jargon? A general model will blur terms together. Fine-tune.

05 / 12
Axis 1 · Language

Same sentence, three languages, wildly different scores

One legal clause about a 60-day notice period. Embedded in English, French, and Hindi.
English-only: EN↔FR
−0.028
English-only: EN↔HI
−0.052
Multilingual: EN↔FR
+0.859
Multilingual: EN↔HI
+0.945
For the trainer

Point at the negative numbers and just let that sit for a second. The English-only model doesn't merely miss the connection between EN and FR, it scores them as if they're pointing the opposite direction. Then flip to the multilingual row and watch the same pair jump to 0.86-0.94. Same sentence both times.

06 / 12
Axis 2 · Context window

We buried a passphrase at the end of a long document

974 tokens of filler, then "ZEBRA-42" tucked in right at the end. Query: "what is the secret passphrase?"
all-MiniLM (256 tok max)
−0.131
OpenAI 3-small (8191 tok)
+0.333
For the trainer

MiniLM caps out at 256 tokens so it truncated the doc way before it ever reached "ZEBRA-42." It never saw it, so of course the score is negative. And nothing tells you this happened, no error, no warning, the call just returns and looks fine. Loop back to notebook 02 here: chunk size has to respect the embedding model's max tokens or you get exactly this, quietly.

07 / 12
Axis 3 · Dimensionality

Matryoshka truncation: cut it to 256d, keep almost all the signal

3-large, full (3072d)
0.560
3-large, truncated (256d)
0.591
3072d
256d
Production pattern: retrieve a wide candidate set with the cheap truncated vectors, then re-score just the top-K with the full-dimension ones. You keep most of the storage and speed win and give up almost nothing on quality.
For the trainer

Funny thing, truncated actually scored a hair higher here, 0.591 vs 0.560. Don't oversell that, it's not "truncated is better," it's noise on one pair. The real point: at 12x smaller you're still carrying almost all of the useful signal, that's the headline, not this one coin flip.

08 / 12
Axis 4 · Domain fit

A general model doesn't know your jargon

Two logistics KPIs that mean different things to anyone who works in supply chain:
"on-time delivery rate" ↔ "perfect order rate" similarity = 0.545
For the trainer

Ask the room: "to a supply-chain analyst, are these the same metric?" They'll say no pretty fast once you push. On-time delivery is just timing. Perfect order rate also folds in accuracy and completeness, it's a stricter, broader metric. The general model can't tell, it just sees two similar-sounding phrases about deliveries and scores them mid-high. If your domain is full of terms like this, that's your cue to fine-tune on labeled query-passage pairs, or lean on metadata filters instead of trusting the embedding alone.

09 / 12
2026 update, say this part out loud

Don't ship the model we just used in class

What's in this notebookWhat you'd actually deploy
all-MiniLM-L6-v2 (2021, 22M params), fast and tiny, good for a live demoBGE-M3 (multilingual, hybrid retrieval by default) or Qwen3-Embedding (currently on top of the open-weight leaderboard)
text-embedding-3-small/largeStill the current OpenAI option, nothing's replaced it as of this session
For the trainer

Say it plainly so it sticks: the iron rule and the four axes don't care which model name you plug in, they hold either way. We picked MiniLM because it downloads in ten seconds mid-class, not because it's what belongs in production. Don't let anyone walk out thinking MiniLM is the answer.

10 / 12
Suggested session flow

Roughly 40 minutes, here's how I'd run it

TimeSegment
0-5 minQuick recap: we have chunks, now they need to become vectors we can compare
5-12 minParaphrase vs. unrelated demo, heatmap and scatter, let it sink in
12-20 minThe iron rule, run both broken scenarios live, don't skip the crash
20-34 minFour axes back to back: language, context window, dimensions, domain fit
34-40 min2026 model note, then straight into Notebook 05, vector databases
11 / 12
Anticipate these questions

Questions I get every time I teach this

"Can I mix embedding providers to save money?"
No. Back to the iron rule, pick one model per index and stick with it. Switch and you're re-embedding everything, there's no shortcut.
"So more dimensions is always better?"
More nuance, sure, but also more storage and slower search. Matryoshka truncation is the compromise most teams actually use, retrieve wide and cheap, then rerank narrow with the full vectors.
"How do I know if I actually need to fine-tune?"
When two concepts that mean clearly different things to your users still score suspiciously similar, like the KPI pair. That's a real signal, not a hunch.
12 / 12