All posts
AI & ML

Do You Even Need an Embedding Model? A Fine-Tuning Decision Guide (With a RAG App That Shipped Without One)

The internet tells you how to pick and fine-tune an embedding model but skips the prior question: whether you need one. Here is the decision framework, the fine-tuning code, and the story of a document-Q&A app I built that ships real RAG with zero embeddings — a hand-rolled BM25 — plus the production gotchas that bite after you choose.

Dhileep Kumar6 min read
Do You Even Need an Embedding Model? A Fine-Tuning Decision Guide (With a RAG App That Shipped Without One)

There is a ritual every RAG team performs. You stand up a vector database, reach for the default embedding model, watch retrieval return garbage on your domain, and then spend three weeks tuning prompts and re-rankers to paper over it. The advice you find online is all correct and all downstream: evaluate on your data, match query and document sides, re-embed on every swap. True. But it skips the question that decides whether you should be in this ritual at all.

The embedding model is genuinely the highest-leverage knob in a semantic search stack — an embedder that maps your question and its answer to distant points can never be rescued by anything above it. I agree with that framing. What I want to add is the part nobody says out loud: for a large class of real apps, the correct embedding model is no embedding model. I will show you a shipped app that made exactly that call, and then give you an honest decision framework for the cases where embeddings really do earn their keep — and where fine-tuning them does.

The mental model: embeddings buy you fuzzy, and fuzzy has a price

An embedding turns text into a vector so that things with similar meaning land near each other. The superpower is fuzziness: a query for "how do I cancel" can retrieve a passage titled "terminating your subscription" with no shared words. That is real and valuable. But fuzzy is not free. You inherit a model whose notion of "similar" was learned on someone else's corpus, an index you must rebuild every time you change that model, and a failure mode where two things your users consider opposites sit right next to each other because the model never learned the distinction.

A worked example: the RAG app that shipped with zero embeddings

I built a small document-Q&A app called DocQA — drop in a PDF, ask questions, get answers grounded in the text with clickable citations that jump to the source passage. It is a textbook RAG use case. It has no embedding model, no vector database, and no fine-tuning. The retriever is a hand-written Okapi BM25 — classic lexical search, keyword overlap weighted by rarity — in a few dozen lines with its own tokenizer and stopword list.

That was not laziness; it was a fit judgment. When you upload a document and ask about it, your question tends to reuse the document's own vocabulary — you ask about "the indemnification clause" because that phrase is on the page in front of you. Lexical overlap is high by construction, so the marginal value of fuzzy semantic matching is low, and BM25 with sane defaults is strong and interpretable. Here is the entire scoring core:

No model to choose, no dimensions to right-size, no re-index on upgrade, nothing to fine-tune, and no GPU. And a second design decision made the embedding question even less pressing: DocQA only reaches for retrieval when the document is large. Below a size cutoff it skips retrieval entirely and puts the whole document into a prompt-cached context block, letting the model read everything. Retrieval only kicks in past the line where the full document stops being cheap to send on every question.

Before you agonize over which embedding model to fine-tune, check whether your users are already handing you the keywords. If the query reuses the document's vocabulary, a 40-line BM25 may beat a fine-tuned embedder you have to maintain forever.

To be clear, this is a design claim, not a benchmark — I did not race BM25 against an embedder, and lexical search has real blind spots the moment vocabulary diverges. The point is that for single-document Q&A, the cheapest defensible retriever was the right default, and embeddings would have bought fuzziness the use case barely needed at the cost of a model dependency and a re-index story.

When embeddings — and then fine-tuning — actually earn it

Flip every property that made DocQA a lexical case and you get the embedding case. You need semantic recall when your users and your documents speak different dialects: a support user types "it keeps kicking me out" and the answer lives in a doc that says "session token expiry. " No keyword overlap, so BM25 misses and embeddings shine. You need it across many documents where you cannot stuff the whole corpus into context. And you need it when synonymy and paraphrase are the norm, not the exception.

Fine-tuning is the next escalation, and it is a narrower door than the escalation to embeddings. Reach for it only when a strong off-the-shelf embedder confuses distinctions your domain treats as load-bearing — legal, medical, or your own product jargon where two terms a general model thinks are unrelated are near-identical to an expert, or two it thinks are similar are opposites. You teach it with pairs of real queries and the passages that actually answered them; contrastive learning pulls those together and pushes everything else apart. A few thousand honest pairs, harvested from logs, will move retrieval more than any prompt you could rewrite.

The decision, as a table

  • Query reuses the document's own words, single or few docs, small corpus: use lexical (BM25). No model, no re-index, interpretable. This is DocQA.
  • Query and answer use different vocabulary, or you search across a large corpus: use an off-the-shelf embedder (a strong open or hosted model). Start here; do not fine-tune yet.
  • A strong embedder still confuses domain-specific distinctions your users care about, and you have logged query-passage pairs: fine-tune. Prove it beats the base model on a held-out set before you ship it.
  • You are not sure which case you are in: run BM25 and an off-the-shelf embedder side by side on 30 real queries with known answers. The gap tells you whether fuzzy is worth its price.

Fine-tuning, honestly, in a few lines

The sentence-transformers library makes the mechanics almost anticlimactic. Give it query-and-correct-passage pairs and in-batch negatives do the rest — every other passage in the batch acts as a wrong answer, so you never have to hand-label negatives:

One epoch is often enough to feel the difference. The hard part is not the code; it is the pairs. Harvest them from real usage — logged questions joined to the chunk a user (or a click, or a thumbs-up) confirmed as the answer. Synthetic pairs generated by asking an LLM to invent questions are fine for a cold start but will happily teach your model a domain that does not exist. Weight toward real logs.

Gotchas that bite in production

First, a fine-tuned embedder is a versioned liability, not a file you save once. The moment you change it, every vector in your index was produced by a different function and is no longer comparable — a model swap is a full re-index, and now you own that re-index forever. BM25 has no such coupling; you can change the tokenizer on a Tuesday. Factor the re-index cost into whether fine-tuning is worth it, because it recurs every time the model does.

Second, mismatched sides silently destroy relevance. You must embed queries and documents with the same model and any required prompt prefixes — several strong open embedders expect a literal "query:" or "passage:" prefix, and forgetting it does not error, it just quietly returns worse neighbors. This is the kind of bug that survives to production because nothing crashes.

Third — and this is the one the leaderboards actively hide — retrieval is upstream of a garbage-in problem that has nothing to do with your model. In DocQA the ugliest bug was never the retriever; it was getting clean text out of a PDF at all. The extractor cannot trust pdf. js to hand back sentences, so it reconstructs text from glyph geometry, and for scripts like Telugu, Arabic, or CJK it widens the word-break threshold because those scripts pack glyph clusters with no space between them:

If your chunker feeds shattered words to an embedder, no amount of fine-tuning will save the retrieval. The best embedding model in the world cannot find a passage that got mangled into gibberish before it was ever vectorized. Fix ingestion before you touch the model.

The bottom line

The embedding model is the part of a RAG stack most worth getting right — which is exactly why the first decision is whether you need one. If your users hand you the document's own vocabulary, a small BM25 may beat an embedder you would otherwise maintain forever, and DocQA is a working existence proof. When query and answer speak different languages, reach for a strong off-the-shelf embedder and evaluate it on your real queries. Fine-tune only when your domain confuses that model on distinctions your users treat as load-bearing, and only when you can prove the tuned model wins on a held-out set.

Prompts and re-rankers get the attention because they are the easiest layer to change. Retrieval quality is decided further down — sometimes at the vectors, and sometimes, if you are honest about your use case, before you ever reach for vectors at all.

Share

Enjoyed this?

Get the next deep dive in your inbox. No spam — just the stories worth reading.

Subscribe to the newsletter

Comments