Runbooks/ RAG Runbook Track B · Embeddings and index Deep readQuick learn
  1. 00 Start
  2. /
  3. 01 Chunking
  4. 02 Parsing
  5. 03 Identity
  6. 04 Access
  7. /
  8. 05 Models
  9. 06 Vectors
  10. 07 Limits
  11. 08 Model ops
  12. 09 Index I
  13. 10 Index II
  14. 11 Tuning
  15. 12 Capacity
  16. /
  17. 13 Sharding
  18. 14 Filtering
  19. 15 Hybrid
  20. /
  21. 16 Proof
Document 08 · Quick learn Not facts, geometry · Reranker first · One table, one model

What similarmeans here

Teaching a model what “similar” means in your domain, deciding whether it should be the embedder or the reranker, and changing either one without an outage.

“Both dashboards green, the arithmetic perfect, and the answer has nothing to do with the question.”
recall@100
the one diagnostic that picks the stage
2e-5
learning rate · 1–3 epochs
8–9 of 10
top-k overlap that means low risk
18 months
the question to ask before starting
What fine-tuning actually meansand what it does not teach

You take a model already trained on a very large amount of general text and continue training it — on your own examples, for a short while, with a small learning rate. The examples are pairs: a real question, and the chunk that correctly answers it.

It does not teach facts

It will not know your products, prices or policies — those live in the chunks, which is the entire premise of RAG.

It teaches what similar means in your domain

That “bricked” and “fails to initialise” are the same event, and that ERR_5521 and ERR_5522 are not.

The model is a similarity function, and fine-tuning reshapes that function — which is why every vector you have already computed came from the old geometry and is now wrong
This is not LoRA territory

LoRA and its relatives were built for large language models — seven billion parameters and up — which are too large to fully fine-tune on ordinary hardware. Embedding models are a few hundred million parameters, so you fine-tune them fully. Often under an hour on one GPU.

Worth clearing early, because it changes what the work looks like
The training loopone batch at a time
1 · Step input

An anchor (a real question), its positive, and several negatives — all embedded by the current model.

2 · Score

Similarity of the anchor against each. A negative outranking the positive means a large adjustment.

3 · Adjust

Weights move to widen the gap: positive pulled in, negatives pushed out.

4 · Repeat

Hundreds of batches accumulate into a shifted geometry.

The loss asks a ranking question, not a correctness question

Which is exactly why it suits retrieval — and why batch size is not a speed knob. In an in-batch-negatives loss, the batch is the negative count.

learning rate ~2e-5 — too high and the model forgets its general ability · epochs 1 to 3, more and it memorises · batch size as large as memory allows

Which loss

In-batch negatives

MultipleNegativesRanking, InfoNCE family.

The default for retrieval. Needs only question-and-correct-chunk pairs
… with explicit hard negatives

A known-wrong-but-plausible chunk per pair.

Usually the single biggest quality gain
Triplet loss

Anchor, positive and negative supplied explicitly.

Older style, still fine
Cosine / CoSENT

When labels are graded scores rather than right-or-wrong.

Rare in RAG
A Matryoshka wrapper

Combine with any of the above.

Preserves truncatable dimensions — doc 06
Where the pairs come fromand the part that decides quality

You need thousands. Realistically, a few thousand good pairs beats tens of thousands of poor ones — and the difference is almost entirely about whether the questions are real.

Production feedback

Thumbs, click-throughs, corrections on real queries.

The only genuinely representative source
Existing artefacts

Resolved tickets, FAQs, click logs.

Most organisations already have these
Generated pairs

A model writes questions for each chunk.

Acceptable for training. Unacceptable for evaluation
The trap in the third source

Generated questions are phrased the way a language model phrases things — “What is the default timeout value for the Payments API?” — and real users type “payment timeout too short”.

Train on synthetic and evaluate on synthetic and both numbers look excellent while production does not move

Hard negatives: training against the current model’s mistakes

Easy vs hard

A chunk about the parking policy teaches nothing. One about rotating the database password is plausible, adjacent and wrong.

How they are mined

Retrieve the top 50 with the current model, drop the known correct chunk, keep what remains.

The false-negative danger

Mining assumes exactly one chunk is correct. When several are, you train the model to push a correct answer away.

The guards

Cap mined-negative similarity at ~0.95 against the positive, or sample-check with a stronger model or a human.

Embedder or reranker?the middle option most candidates never mention

The two stages have the same training data and comparable effort, and completely different consequences. This is usually the right answer.

recall@100 low → the embedder   ·   recall@100 fine, NDCG@10 poor → the reranker
a single diagnostic that answers it — is the right chunk absent, or present and badly ordered?

Fine-tune the embedder

The right chunk is not reaching the candidate pool at all.

  • Only the embedder can fix that
  • But: a full re-embed immediately, 2.8 billion tokens on 8M chunks
  • A rebuild, a parallel table, a cutover
  • Blast radius: the whole index
One diagnostic.
Two completely
different projects.

Fine-tune the reranker

The right chunk is in the pool and badly ordered.

  • No re-embed, no rebuild
  • Rollback by swapping a file
  • Blast radius: one ranking stage
  • So where both would work, do the reranker
Why the cross-encoder is more accurate, stated precisely

Because it sees both texts at once, its attention can relate a word in the question directly to a word in the chunk. A bi-encoder cannot: each side was compressed into a vector in isolation, before the other side existed.

Which is also exactly why it cannot be used for search. Scoring eight million chunks per query with a model is not a system, it is an outage
What it costs after the training runthree of the four are permanent

The training is the cheap part — often under an hour on one GPU.

A full re-embed, immediately

New weights mean new geometry, so every stored vector is now meaningless.

2.8 billion tokens on 8M chunks — and a parallel table
Retraining, forever

Content changes, vocabulary changes, new products arrive with new jargon.

A model fine-tuned on last year’s language decays
An upgrade tax

Every future base-model improvement now has a retraining step attached before you can take it.

You have opted out of free progress
Ownership

Weights, training data, evaluation, reproducibility, and the person who understands all of it.

A standing commitment, not a project
The question to ask before starting

“Who owns retraining this in eighteen months?” If the honest answer is nobody, the fine-tune is a liability with a quality benefit attached rather than the other way round.

And the reranker option gets most of the benefit with none of that obligation
Why there is no partial migrationnoise, not degradation

A vector from model A and a vector from model B cannot be compared. Position 500 in the list encodes something decided during that model’s training, by that model, for itself.

Halfway

Four million rows from model A, four million from model B, in one table.

A query arrives

Embedded with B. Against the B half the scores mean something. Against the A half they are noise.

And noise sometimes scores high
The top ten

Interleaves real results with arbitrary chunks.

Every dashboard is green
The complaint

Arrives as a language-model quality problem, three layers downstream.

Noise, not degradation — and the distinction matters

Degradation means the right answer is present and ranked lower: recoverable, detectable. Noise means the right answer may be absent, and what replaced it has nothing to do with the query.

The rule

One table, one model, one version. Always. There is no valid partial state and no “mostly migrated”.

Which is precisely why the migration has to happen somewhere other than the live table
The parallel tablefour phases, and the ordering rule
1 · Dual-write on

Live edits go to both tables. This comes first.

2 · Backfill

A one-off batch job over everything that already existed.

3 · Shadow read

Real production queries to both; serve only v1; compare offline.

4 · Flip the alias

Seconds, atomic, and deliberately not a code deploy.

The ordering rule — costs nothing to get right, a great deal to get wrong

If the backfill starts first, every document edited after the backfill has passed it is permanently stale in the new table — with nothing to reveal it.

Dual-write handles the present; backfill handles the past. Neither alone covers the corpus, and confusing the two is how a migration ends up with a hole in it
The overlap is deliberate

Some chunks get written twice. The writes are idempotent upserts, and you would rather write something twice than miss it once.

The one hazard it introduces is resurrection
Resurrection — a subtle race, and knowing it is a strong signal

A document is deleted while the backfill is running. The backfill, working from a snapshot taken before the delete, writes it into the new table anyway — and the deleted document is now live in the collection you are about to cut over to.

Three guards, and the third catches everything: the backfill reads a tombstone list and skips deleted ids at write time; it takes a watermark timestamp and never overwrites a row written more recently; and a reconciliation run against the source before cutover

Validation, cutover and rollback

Never switch on offline metrics alone

Offline metrics have been wrong before.

Shadow reads give you thousands of paired result sets from genuine production queries, rather than a gold set of a few hundred
What to compare

Gold-set recall and NDCG per document type, top-10 overlap, rank movement of known-good chunks, latency percentiles, score distribution.

Read the overlap signal

Agreement on eight or nine of the top ten means low risk.

Low overlap means something more than a model swap has happened
Keep v1 warm — and still dual-written to

For at least one full incident cycle.

Stop writing to it at cutover and it goes stale immediately, closing your rollback window without telling you
The model stampwithout it there is no detection mechanism at all

Everything above assumes you can tell which model produced a given vector. You cannot, unless you recorded it — and if you did not record it at write time, the information is gone. The vectors are identical in every inspectable property: same dimension, same norm, no signature.

model_name

Which model.

model_version

Which version — fine-tunes especially, because the name does not change.

dimension

Catches a truncation or configuration mismatch.

embedded_at

Lets you scope a repair to an ingest window.

normalised

Makes the invariant from document 06 auditable rather than assumed.

Workeda scheduled GROUP BY model_name, model_version — one row is healthy; two or more and you know immediatelythat single query is the whole detection mechanism

The decommission checklist

☐ One full incident cycle

Cutover complete for one to four weeks, depending on traffic.

☐ No regressions

None reported or detected.

☐ Gold-set scores stable

On the new table.

☐ Dual-write off

To the old table.

☐ A documented rebuild path

If it is ever needed.

☐ And only then

Drop the table.

Answer it out loudthe ninety-second version, then six questions
“Fine-tuning teaches the model what similar means in your domain — not facts, which live in the chunks. You do it when users say ‘bricked’ where the docs say ‘fails to initialise’ and no off-the-shelf model has seen that usage. But I would walk the cheap ladder first, out loud, and I would probably fine-tune the reranker rather than the embedder: one diagnostic decides it — recall@100 low means the embedder; recall fine and NDCG@10 poor means the reranker, and the reranker is far cheaper to be wrong about. Either way the migration is the same shape, because a vector from one model and a vector from another cannot be compared: dual-write on first, then backfill, then shadow read, then flip an alias — and keep the old table warm and still written to.”

Six questions, and the spine of each answer

Architect

When would you fine-tune an embedding model?

  • When the domain uses language differently from the general internet, and no off-the-shelf model has seen that usage
  • That gap is real and prompt engineering cannot close it
  • But walk the ladder first, out loud: chunking, section path, hybrid if the losses are identifiers, an off-the-shelf reranker, a different base model
Only then fine-tune — and even then, probably the reranker rather than the embedder.
Architect

Embedder or reranker — which do you fine-tune?

  • One diagnostic answers it: recall@100 against NDCG@10
  • recall@100 low → the right chunk is not reaching the pool. Only the embedder fixes that
  • recall fine, NDCG@10 poor → present and badly ordered. That is the reranker’s job
And the reranker is far cheaper to be wrong about: no re-embed, no rebuild, rollback by swapping a file, blast radius one ranking stage. Where both would work, do the reranker.
Architect

Where do the training pairs come from, and what is the trap?

  • Three sources, cheapest first: production feedback, existing artefacts, generated pairs
  • The trap is the third. Generated questions are phrased the way a model phrases things
  • Real users type “payment timeout too short”, not “What is the default timeout value for the Payments API?”
Train on synthetic and evaluate on synthetic, and both numbers look excellent while production does not move. Generated pairs are acceptable for training and unacceptable for evaluation.
Architect

Why can you not migrate incrementally?

  • Because a vector from one model and a vector from another cannot be compared
  • The arithmetic runs perfectly — both are 1024 numbers, the dot product returns a valid score
  • And it carries no information about meaning
Be precise that this is noise, not degradation. Degradation means the right answer is present and ranked lower — recoverable and detectable. Noise means it may be absent. So: one table, one model, one version.
Architect

Walk me through the migration.

  • Dual-write on, then backfill, then shadow read, then flip an alias
  • The ordering of the first two is the part that matters
  • Backfill first → every document edited after the backfill passed it is permanently stale, with nothing to reveal it
  • Dual-write handles the present; backfill handles the past. The overlap is deliberate and the writes are idempotent
Then shadow-read real queries against both and compare on gold-set recall per document type, top-ten overlap and latency. And keep the old table warm and still dual-written to.
Eng manager

The team wants to fine-tune. What do you ask before approving?

  • What does the gold set say we are losing on — and is it a loss a fine-tune fixes, or an identifier problem hybrid would fix?
  • What have we tried from the cheap end — specifically, has anyone put an off-the-shelf reranker in front of it?
  • Would fine-tuning the reranker get most of the gain without the re-embed?
  • Who owns retraining this in eighteen months?
If the honest answer to the last one is nobody, it is a liability with a quality benefit attached rather than the other way round.
Fast answersthe seven that come up most

QHow would you know a table had two models in it?

Only if you stamped it, because the vectors are identical in every inspectable property — same dimension, same norm, no signature. Every row carries model name and version, the table declares its expected version, and a scheduled group-by compares them. Without the stamp there is no detection mechanism at all.

QHow many pairs do I need?

Thousands — but a few thousand good pairs beats tens of thousands of poor ones, and the difference is almost entirely whether the questions are real. Hard negatives are usually the single biggest quality gain.

QWhat learning rate and how many epochs?

Around 2e-5 to start; too high and the model forgets its general ability. 1 to 3 epochs; more than that and it memorises. And batch size as large as memory allows — in an in-batch-negatives loss the batch is the negative count, not a speed knob.

QWhat is the false-negative danger in mining hard negatives?

Mining assumes exactly one chunk is correct. When several are, a correct answer becomes a “hard negative” and you train the model to push it away. Guards: cap mined similarity at ~0.95 against the positive, or sample-check.

QCan I fine-tune and keep Matryoshka truncation?

Yes — wrap the loss so it is computed at several prefix lengths, exactly as the base model was trained. Skip that and your fine-tune silently destroys the truncation property you were relying on.

QWhy not just cut over on offline metrics?

Because offline metrics have been wrong before. Shadow reads give you thousands of paired result sets from genuine production queries, rather than a gold set of a few hundred — and top-ten overlap is a signal a gold set cannot give you.

QWhen can I drop the old table?

After a full incident cycle — one to four weeks depending on traffic — with no regressions reported or detected, gold-set scores stable, dual-write turned off, and a documented path to rebuild it. And only then.

One table, one model, one version — always. A vector from model A and a vector from model B produce a perfectly valid dot product that carries no information about meaning, so a half-migrated table is not degraded, it is noise: the right answer may be absent and what replaced it has nothing to do with the query, while every dashboard stays green. Which is why the migration happens in a parallel table, why dual-write comes before backfill, and why the old table stays warm and still written to. And before any of that: fine-tune the reranker, not the embedder, unless recall@100 says otherwise.

Where this goes next

Questions to ask them

Is every row stamped with model name and version?

What does the gold set say you are losing on — absence or ordering?

Has an off-the-shelf reranker been tried first?

If a migration ran, did dual-write start before the backfill?

Who owns retraining a fine-tuned model in eighteen months?

Is the old table still dual-written to after a cutover?

← 07 · Limits Read the full chapter → 09 · Index I →