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 07 · Quick learn Fixed at training · No error, no signature · Headroom buys context

A ceilingyou cannot raise

A ceiling you cannot raise, a failure that raises no error, and the reason a model with room to spare is worth paying for even though your chunks are small.

“Stored, indexed, and unfindable — with nothing in the vector to find it by.”
512 / 8192
a position table, or a formula
200–500 tokens
useful chunk size, whatever the limit
2.0 chars/token
JSON and logs — half of prose
80–230 tokens
what the headroom is actually for
A ceiling you cannot raiseand the number it gets confused with

Every embedding model refuses to read past a certain amount of text. That ceiling is measured in tokens and it is fixed at training time: you cannot raise it, configure it, or pay for more of it.

The input limit

How much text the model will read.

512 on classic encoders, 8192 on modern ones. A structural property of the encoder
The output dimension

How many numbers come out.

384, 768, 1536, 3072. Unrelated to the input limit — a 512-token model can emit 1024 dimensions
Why this matters more than it looks

This is the same failure family as the prefix bug and the normalisation asymmetry. Silent, structural, and invisible in every log you have.

Real limits on real modelsre-check the model card before any interview
BERT-based encoders

512 tokens · 768 dims · learned position table

all-MiniLM-L6-v2

256 configured (512 architectural) · 384 dims

The trap row — the configured value wins
E5 / multilingual-E5

512 · 384–1024 dims · learned table

Cohere embed v3

512 · 1024 dims · learned table

OpenAI text-embedding-3

8191 · 1536 / 3072 dims · modern

BGE-M3

8192 · 1024 dims · modern

Nomic / Jina long-context

8192 · 768 dims · rotary / ALiBi

Embedding models are small

A few hundred million parameters, not billions.

They have to be — you run one over every chunk in the corpus, and then over every query, forever
Why some are 512 and others 8192

It is how they handle position. Classic encoders add a learned position signal from a lookup table with one row per position, and the table’s height was fixed during pre-training — so there is no row 513, and the forward pass cannot accept token 513.

Newer models compute the position signal from a formula, so position 9000 is as computable as position 9. They advertise 8192 because that is what they were trained and validated at — and past that range they degrade gradually rather than failing, which is quieter and arguably worse
The MiniLM trap

The underlying architecture supports 512, but the shipped configuration truncates at 256. Teams read the architecture number, size their chunks to 512, and lose half of every chunk.

The configured value wins — read it from the config, not the paper
What truncation actually doestraced to a user

A 700-token chunk, a 512-token limit.

The chunk

700 tokens, stored in full in the payload.

The cut

The tokeniser slices at 512. The model never sees the last 188 tokens.

And returns a normal-looking vector built from the head only
A query about the head

Matches, the chunk is retrieved, the model receives the whole chunk — tail included.

So it answers correctly
A query about the tail

The vector knows nothing about it, so the chunk scores low and is missed.

Same chunk, inconsistent behaviour, no log line anywhere
You cannot detect truncation from a stored vector

It looks completely normal — right dimension, right norm, right distribution. There is no forensic signature.

So detection has to happen before embedding, at ingest time
Tokens are not characterswhere the limit bites in practice

You size chunks in one unit and the model counts in another. English prose runs at roughly four characters per token — a heuristic that is safe for prose and badly wrong for anything structured.

EN
English prose~4.0 chars/token
low risk
DOC
Technical documentation~3.5
moderate
PY
Code~2.5
high
TBL
Tables rendered as text~2.5
high
{}
JSON or YAML config~2.0
high
LOG
Logs with IDs and stack traces~2.0 or worse
very high
The signature of this bug: “retrieval is bad for one product only”

That symptom — fine overall, bad for one slice — is character-based chunking meeting a token-dense format. If that product’s docs are full of configuration examples, code blocks or log excerpts, they run at ~2 characters per token while the prose corpus runs at 4, so the same chunker produces chunks twice as long in token terms — and they truncate.

Check token counts by document type first: it costs ten minutes and it explains the shape of the complaint, which most retrieval hypotheses do not
The opposite mistakea long limit is not permission to use long chunks

Over the limit

The text is cut and the model never sees the tail.

  • No error — a perfectly normal vector comes back
  • Stored, indexed and unfindable for any question about the tail
  • Fix: assert before embedding
Both silent.
Opposite causes.

Legally within the limit, and far too long

Nothing truncates. The vector is simply vague.

  • The output has fixed capacity whatever the input length
  • Fifteen ideas compressed into 1024 numbers lands in the middle of all of them
  • Fix: 200–500 tokens, whatever the model permits
So what is the headroom actually for?

The headroom buys context, not length. A section path and a short summary prepended to a normal-sized chunk — worth 80 to 230 tokens — which improves recall materially on large corpora.

Budget: path 20–50 · summary 50–150 · metadata 10–30. At 400 tokens of content plus 230 of context you are at 630 — over a 512-token model
Chunk size first, model second

Start from the natural semantic unit of the corpus and what a sweep against the gold set says. Add the context you intend to prepend. Count it with the model’s own tokeniser, including special tokens, on the densest content type. Then choose a model whose limit accommodates that.

The wrong direction is “the model does 512, so we chunk at 512” — which lets the number of rows in somebody’s position table choose your retrieval strategy
Detection and prooffour layers, and only the last looks backwards
1 · An assertion at ingest

Count with the real tokeniser, include special tokens, and raise rather than truncate.

Most libraries default to silent truncation
2 · A p99 token-count metric

So a new document type shifts the distribution before it breaks anything.

3 · A CI test

Over fixed texts, including a deliberately token-dense one.

Catches a tokeniser change or a config change
4 · The audit nobody runs

Over what is already stored: how many chunks are over the limit, by document type and ingest date.

The shape of that report usually names the cause
Special tokens really do matter

Encoders add markers at the start and end of the sequence, and those consume positions. A chunk that is exactly 512 tokens before special tokens is over the limit after them — so a chunker targeting the limit exactly will truncate every single chunk by a token or two.

Always count with add_special_tokens=True, and target slightly below the limit
The embedding client contractthree documents, one conclusion

There should be exactly one function in your system that turns text into a vector, and it should own every convention. One function, two callers, one guarantee.

The role prefix

Query and documents land in different regions of the space; recall falls by a third.

— doc 05
The model and version pin

A vendor version bump silently changes the space under a live index.

— doc 05
Normalisation, and the assertion after it

Asymmetric norms, broken thresholds, uneven quantisation damage.

— doc 06
Truncation and renormalisation, together

Documents whose magnitude sits early get an unearned ranking bonus.

— doc 06
The token count, raising rather than truncating

Content that is stored, indexed and unfindable.

— this document
Batching by token budget, not item count

A batch of token-dense chunks blowing a per-request limit that prose would not.

— this document
Batch by tokens, not by items

“Send 64 chunks per request” is a rule that works until 64 config files arrive together and the batch is three times the size the same count of prose would be.

Accumulate up to a token budget, with an item cap as a secondary guard and a short flush timeout so a trickle of edits does not wait indefinitely behind a half-full batch
Answer it out loudthe ninety-second version, then six questions
“Every embedding model has a hard input limit fixed at training time, and exceeding it is not an error — the tokeniser cuts, the model embeds the head, and you get a perfectly normal vector back. So the chunk is stored, indexed and unfindable for any question about its tail, and there is no signature in the stored vector to find afterwards. Detection has to be an assertion before embedding. The counter-intuitive half is that a long limit is not permission to use long chunks: the output vector has fixed capacity, so a legal 6,000-token chunk is not truncated, it is just vague. Useful chunks are 200 to 500 tokens whatever the model permits — and what the headroom actually buys is context.”

Six questions, and the spine of each answer

Architect

What happens if a chunk is longer than the limit?

  • Nothing visible, which is the problem
  • The tokeniser cuts, the model embeds the head, and returns a normal vector — right dimension, right norm
  • Every downstream system accepts it because there is nothing to reject
  • Damage shows up later and asymmetrically: head questions work, tail questions never find it
So the defence has to be an assertion before embedding, because there is no forensic signature in the stored vector afterwards.
Architect

Why do some models have 512 and others 8192?

  • It is how they handle position
  • Classic encoders add a learned signal from a lookup table fixed at pre-training — there is no row 513
  • Newer models compute the signal from a formula, so position 9000 is as computable as position 9
They advertise 8192 because that is what they were trained and validated at — not because anything breaks above it. Past that range they degrade gradually rather than failing, which is quieter and arguably worse.
Architect

Our model accepts 8192. Should we use 8000-token chunks?

  • No — and this is the more interesting half of the topic
  • Nothing truncates, so there is no error. The vector is simply vague
  • The output has fixed capacity whatever the input length, so fifteen ideas compressed into 1024 numbers matches everything weakly
Useful chunks land at 200–500 tokens regardless. The limit is a ceiling, not a target. What the headroom buys is context — a section path and short summary, 80 to 230 tokens.
Architect

How do you size chunks against a model limit?

  • Chunk size first, model second
  • Start from the natural semantic unit and what a gold-set sweep says
  • Add the context you intend to prepend
  • Count with the model’s own tokeniser, including special tokens, on the densest content type — not the average
The wrong direction lets the number of rows in somebody’s position table choose your retrieval strategy. And if no acceptable model has the headroom, that becomes an explicit costed choice rather than a silent truncation nobody decided on.
Architect

How would you detect that truncation has been happening?

  • Four layers, and only the last looks backwards
  • An assertion at ingest that counts with the real tokeniser and raises rather than truncating
  • A p99 token-count metric, so a new document type shifts the distribution before it breaks anything
  • A CI test over fixed texts including a deliberately token-dense one
Then an audit over what is already stored — how many chunks are over the limit, by document type and ingest date. The shape of that report usually names the cause.
Eng manager

What goes in the definition of done for the embedding path?

  • One shared client owning every convention: role prefix, pinned version, normalisation, truncation-with-renormalisation, token assertion
  • Deliberately no function that takes raw text without declaring its role
  • Three CI tests: gold-set recall above a threshold, norms equal to one on both paths, token counts on fixed texts
The framing for the team: retrieval fails quietly and almost everything else fails loudly. Other subsystems can rely on exceptions; this one cannot, so it needs assertions instead. That explains why the tests are not optional — which is usually the actual argument.
Fast answersthe eight that come up most

QCan I just let the library truncate and accept the loss?

You can — and the reason not to is that you will not know how much you lost or where. Truncation is not uniform: it hits your token-dense documents and leaves the prose alone, so it damages one part of the corpus and looks like a general quality problem. If you accept it, at least count it.

QDo special tokens really matter for a 512-token limit?

Yes — a real edge case rather than a pedantic one. A chunk that is exactly 512 tokens before special tokens is over the limit after them, so a chunker targeting the limit exactly truncates every single chunk. Count with add_special_tokens=True.

QIs a longer limit always better if I can afford it?

Never worse, and often not the differentiator people assume. The headroom is genuinely valuable — it is what makes contextual prefixes affordable — but a long-context model that is weaker at retrieval is a bad trade. Treat the limit as one filter among several.

QShould the chunker count tokens or characters?

Tokens, using the model’s own tokeniser — and you usually can afford it, because tokenising is cheap compared with embedding. Character-based chunking is the root cause of the one-document-type failure, and switching removes an entire class of bug.

QWhat is HyDE, and why is it in a token-limit document?

Hypothetical document embedding: a model writes a plausible answer and you embed that instead of the question. It appears here because it turns a fifteen-token query into a 200–600 token one — putting the query path near a limit nobody was watching.

QOur chunks are 300 tokens and we still have recall problems.

Almost certainly not the limit, and worth establishing quickly so you stop looking here. At 300 tokens you are neither truncating nor diluting. The usual suspects are the prefix convention, normalisation asymmetry, a chunk that lost its context, or identifier-shaped queries.

QDoes the limit apply to metadata, or only to the text I embed?

Only to what you send to the model. Metadata stored for filtering and citation costs storage, not tokens. The confusion is worth resolving carefully though, because the section path is often both: stored as a field, and prepended before embedding. When prepended, it counts.

QHow much context should I prepend?

Start with the section path alone, because it is free and deterministic, and measure. Add a generated summary only if the gold set shows it helps — it is 50–150 tokens on every chunk plus a generation call at ingest, and on some corpora the section path already carried the missing words.

Exceeding the limit is not an error, and neither is staying far below it. Over the limit, the tokeniser cuts and you get a perfectly normal vector built from the head — the chunk is stored, indexed and unfindable for any question about its tail, with no signature left behind. Under it by too much, nothing truncates and the vector is simply vague. Both are silent, both have opposite causes, and the only defence for either is an assertion before embedding, in the one shared function that owns every convention. Retrieval fails quietly; everything else fails loudly.

Where this goes next

Questions to ask them

What is the model’s configured limit — not the architectural one?

Does the chunker count tokens or characters?

Is there an assertion that raises rather than truncating?

Has anyone audited how many already-stored chunks are over the limit?

Are token counts tracked by document type?

Does the query path have a token limit check, or only ingest?

← 06 · Vectors Read the full chapter → 08 · Model ops →