M07 · Retrieval-augmented generation (RAG)07-0630 min read
Lesson 46 of 106 · Module 8 of 14 · Week 4
Threads:The measurement threadThe weights threadThe infrastructure threadThe control thread
Hybrid search: combining keyword and vector retrieval in RAG
Hybrid search runs sparse keyword retrieval and dense vector retrieval over the same corpus and fuses their two result lists into one ranking, so a query is answered whether its signal is lexical or semantic. Reciprocal rank fusion is the default fusion method because it combines ranks rather than scores and therefore needs no calibration between two incomparable scoring scales. It is a correction for the complementary blind spots of each retriever — and it does nothing at all for recency, authority, or permissions.
What hybrid search is
Hybrid search is a two-leg retrieval architecture with a fusion step:
user query
│
┌─────────────┴─────────────┐
▼ ▼
┌────────────────────┐ ┌─────────────────────┐
│ sparse leg (BM25) │ │ dense leg (vectors) │
│ inverted index │ │ ANN index │
│ returns top-k_s │ │ returns top-k_d │
└─────────┬──────────┘ └──────────┬──────────┘
│ ranked list A │ ranked list B
└──────────────┬──────────────┘
▼
┌───────────────────┐
│ fusion (RRF) │ ← one ranking
└─────────┬─────────┘
▼
optional reranking (07-07)
▼
context assembly (07-08)
Three design parameters, and it is worth being precise about them because they are commonly conflated:
| Parameter | Meaning | Typical practice |
|---|---|---|
k_s, k_d | how many candidates each leg returns before fusion | usually equal and larger than the final k — you want a deep pool to fuse |
| fusion method | how the two lists become one | RRF by default; weighted score fusion if you have calibrated it |
final k | how many chunks reach the context or the reranker | small — the context has a budget (04-06) |
What hybrid search is not. It is not a single retriever that does both things. It is not an embedding model with keyword awareness. And it is not the same as query expansion — rewriting the query with synonyms before a single-leg search — although the two are often confused because both aim at vocabulary coverage. Two legs and a fusion step is the architecture; anything with one retrieval index is not hybrid search, whatever it is doing to the query.
The other name you will see is "hybrid retrieval", and in some vector databases the feature is exposed as "sparse-dense search" or "sparse vectors alongside dense vectors". These are the same idea. Note that "sparse vectors" in that phrasing sometimes means a learned sparse representation — SPLADE-style models that produce term weights from a neural network rather than from BM25 arithmetic — which is a genuine third option and covered briefly in §9.
How hybrid search works: fusion, step by step
L1 — The intuition you can carry into an exam
Ask two specialists the same question and merge their answers, trusting a document more when both nominate it and when either nominates it highly. Merge by position in each list, not by score, because the two specialists grade on incompatible scales.
Three facts to carry:
- RRF is the default fusion method because ranks are comparable across retrievers and raw scores are not.
- Documents appearing in both lists get promoted, which is the property you actually wanted — agreement is evidence.
- Hybrid search costs two retrievals per query, so latency is roughly the slower leg plus fusion, and both legs can run in parallel.
L2 — The mechanism: RRF and its alternative
Reciprocal rank fusion. For each document d, sum a reciprocal-rank term over every result list it appears in:
RRF(d) = Σ over lists L : 1 / (k_rrf + rank_L(d))
where rank_L(d) is d's 1-based position in list L, and k_rrf is a smoothing constant conventionally set to 60. Documents absent from a list contribute nothing from that list.
Read the shape rather than the formula. With k_rrf = 60:
| Rank in a list | Contribution 1/(60 + rank) |
|---|---|
| 1 | 0.01639 |
| 2 | 0.01613 |
| 3 | 0.01587 |
| 5 | 0.01538 |
| 10 | 0.01429 |
| 20 | 0.01250 |
| 50 | 0.00909 |
| 100 | 0.00625 |
Two behaviours follow, and they are the whole reason k_rrf exists:
The curve is deliberately flat at the top. Rank 1 and rank 3 differ by about 3%. That means RRF does not treat "first" as vastly better than "third" within one list — which is correct, because neither retriever's top-3 ordering is trustworthy at that resolution. A smaller k_rrf sharpens the curve and lets a single leg's rank-1 dominate; a larger one flattens it further and weights agreement across legs more heavily.
Appearing in both lists usually beats topping one. A document at rank 3 in both lists scores 0.01587 + 0.01587 = 0.03174. A document at rank 1 in one list and absent from the other scores 0.01639. The two-list document wins by nearly 2×. That is the fusion doing its job: cross-retriever agreement is stronger evidence than one retriever's confidence.
Weighted score fusion, the alternative, normalises each leg's scores to a common range and takes a weighted sum:
score(d) = α × norm(bm25_score(d)) + (1 − α) × norm(cosine_score(d))
It is more expressive — you can tune α toward whichever leg your eval set says helps more — and it is more fragile, for reasons that matter:
| Problem with score fusion | Why it bites |
|---|---|
| Normalisation needs a range | Min-max over the returned candidates makes the mapping depend on this query's result set, so the same document scores differently depending on what else was retrieved |
| BM25 scores are corpus-dependent | They shift as the corpus grows and IDF values change; a tuned α silently decays |
| Cosine similarities are compressed | Unrelated text scores 0.2–0.5 (07-02), so min-max normalisation stretches noise into apparent signal |
| Missing documents need a value | A document absent from one leg has no score there; imputing zero is a strong and usually wrong claim |
α overfits | Tuned on 20 queries, it will not hold on 200 |
The practical rule: start with RRF, move to weighted fusion only if a real eval set shows a real gain, and re-verify after any corpus change. RRF's parameterlessness is a feature, not a limitation.
L3 — Where fusion sits relative to filtering and reranking, and why the order is fixed
The pipeline order is not arbitrary and each position has a reason:
| Step | Why it is here |
|---|---|
| 1. Permission filter, in both legs | Every leg is a retrieval path, so every leg needs the boundary (07-05). A filter applied to only one leg is not a filter |
2. Retrieve k_s and k_d independently | Legs run in parallel; neither knows about the other |
| 3. Fuse | Ranks are combined; the pool is now one list, deeper than the final k |
4. Rerank (07-07) | A cross-encoder scores the fused pool properly; expensive per candidate, so it must run on a small pool |
| 5. Truncate to the context budget | Then order the survivors deliberately (07-08) |
The step-1 note is the one that gets missed in real systems and it is a security defect, not a quality one. If the dense leg is permission-filtered and the sparse leg is not, the sparse leg leaks. Both legs are retrieval, so both legs carry the predicate. This is one of the underrated arguments for running hybrid search inside a single system that already does both — Elasticsearch/OpenSearch with a vector field, or a vector database with BM25 support — because there is then one filter expression rather than two that must agree.
A second L3 subtlety: fusion depth interacts with reranking. RRF over top-10 from each leg gives a fused pool of at most 20 unique documents, which is a thin pool for a reranker to work with. Retrieving top-50 or top-100 per leg and fusing gives the reranker a genuine choice. The cost is that reranking is per-candidate, so pool depth is a direct latency multiplier. The canonical shape is deep retrieve → fuse → rerank → shallow context, with each stage narrowing.
Third: the two legs need not return the same document granularity. If your sparse index is over whole documents and your dense index is over chunks, fusion has to reconcile them — either by rolling chunk hits up to their parent document or by expanding document hits into their chunks. Mismatched granularity across legs is a real source of silently poor fusion, and the fix is to make both legs index the same units.
Hybrid search vs sparse-only vs dense-only vs query expansion vs reranking
| Sparse only | Dense only | Hybrid (fused) | Query expansion | Reranking | |
|---|---|---|---|---|---|
| Number of indexes | 1 inverted | 1 ANN | 2 | 1 (either) | 0 — operates on candidates |
| Fixes vocabulary mismatch | no | yes | yes | partially | no |
| Fixes exact-identifier miss | yes | no | yes | no | no |
| Fixes unseen-vocabulary miss | yes | no | yes | no | no |
| Fixes negation | barely | no | barely | no | best available |
| Fixes recency | no | no | no | no | no |
| Fixes authority | no | no | no | no | helps |
| Fixes permissions | no | no | no | no | no |
| Improves ordering within candidates | no | no | somewhat — agreement promotes | no | yes, its whole job |
| Query-time cost | one posting-list walk | one encoder pass + ANN | both legs (parallelisable) | extra LLM/thesaurus call, then one leg | per-candidate model pass |
| Needs calibration | no | no | no, with RRF | prompt/thesaurus tuning | no |
| Can hurt | n/a | n/a | yes — a bad leg dilutes a good one | yes — expansion adds noise terms | rarely, if the model is suited |
Four readings that exam items probe.
Hybrid is a recall intervention; reranking is a precision intervention. Hybrid gets the right chunk into the candidate pool. Reranking gets it to the top of the pool. They compose, and neither substitutes for the other — which is exactly why this lesson and 07-07 are taught as one paired session.
The three "no" rows are the exam's favourite trap. Recency, authority, and permissions are unaffected by hybrid search. A scenario describing stale documents or a leak, with "enable hybrid search" among the options, is testing whether you know the difference between a coverage problem and a metadata problem.
Query expansion is a different intervention with a similar goal. Rewriting "why is my thing slow" into "why is my request slow OR high latency OR timeout" widens a single leg's lexical reach. It is cheaper than a second index and weaker than one, because it depends on guessing the corpus's vocabulary. Multi-turn query rewriting — resolving "what about the second one?" into a self-contained question — is a related but distinct technique covered in 12-11.
Hybrid search can make things worse. This is the honest caveat. If one leg is badly broken — a mismatched encoder (07-02), or an analyser mismatch in the sparse index (07-01) — fusing it with a working leg injects noise into a ranking that was fine. Fusion assumes both legs are individually competent. Measure each leg alone before fusing them, which is the discipline 07-01 set up.
Worked example: RRF over two result lists
This is a constructed illustrative example. The ranks, BM25 scores, and cosine similarities are invented so the arithmetic is checkable. They are not measurements.
A user of an internal support assistant asks:
"getting ERR_CERT_AUTHORITY_INVALID when the client connects — why?"
The query mixes both signal types, which is why it is the right example: ERR_CERT_AUTHORITY_INVALID is a rare exact identifier (sparse territory) and "when the client connects — why?" is natural language about a concept (dense territory).
Six candidate chunks exist in the corpus:
| id | Text (abridged) |
|---|---|
d1 | "ERR_CERT_AUTHORITY_INVALID is returned when the presented certificate chain terminates in an untrusted root." |
d2 | "The client aborts the connection if the server certificate cannot be validated against a configured trust anchor." |
d3 | "Common client connection errors and their remediation steps." |
d4 | "Certificate pinning configuration reference." |
d5 | "ERR_CERT_AUTHORITY_INVALID appears in the changelog for release 4.2." |
d6 | "Why connections fail: an overview of TLS handshake failure causes." |
Step 1 — the sparse leg (BM25, top-5)
ERR_CERT_AUTHORITY_INVALID is a maximally rare term, so both chunks containing it dominate. Constructed BM25 scores:
rank 1 d1 9.84 contains the exact error string, and explains it
rank 2 d5 8.91 contains the exact error string, but it is a changelog line
rank 3 d3 2.15 "client connection errors"
rank 4 d6 1.62 "connections fail"
rank 5 d4 0.94 "certificate"
Note d2 is absent. It is arguably the second-best chunk in the corpus — it explains the mechanism correctly — and it shares almost no terms with the query: no ERR_CERT_AUTHORITY_INVALID, no getting, no why. This is the vocabulary-mismatch failure from 07-01, live.
Note also d5 at rank 2. A changelog mention of the error string is nearly useless as an answer, and BM25 ranks it second because IDF cannot distinguish "explains the error" from "mentions the error". This is the precision problem that reranking exists for.
Step 2 — the dense leg (cosine, top-5)
Constructed similarities:
rank 1 d2 0.83 "cannot be validated against a configured trust anchor"
rank 2 d1 0.79 the correct explanatory chunk
rank 3 d6 0.74 "TLS handshake failure causes"
rank 4 d3 0.68 "common client connection errors"
rank 5 d4 0.55 "certificate pinning reference"
The dense leg recovered d2 — the chunk sparse retrieval could not see — and it correctly demoted d5 out of the top-5, because a changelog line about a release is semantically distant from a question about why a connection fails. It also put d2 above d1, which is arguably wrong: d1 names the exact error. Dense retrieval's identifier weakness (07-02) in miniature.
Neither leg's top-1 is unambiguously correct. Sparse says d1 (right, for a fragile reason). Dense says d2 (a good chunk, but not the one that names the error). This is the situation fusion is for.
Step 3 — fuse with RRF, k_rrf = 60
For each document, sum 1/(60 + rank) over the lists it appears in:
d1: sparse rank 1 → 1/61 = 0.016393
dense rank 2 → 1/62 = 0.016129
RRF = 0.032522 ← highest
d2: sparse absent → 0
dense rank 1 → 1/61 = 0.016393
RRF = 0.016393
d3: sparse rank 3 → 1/63 = 0.015873
dense rank 4 → 1/64 = 0.015625
RRF = 0.031498 ← second
d4: sparse rank 5 → 1/65 = 0.015385
dense rank 5 → 1/65 = 0.015385
RRF = 0.030770 ← third
d5: sparse rank 2 → 1/62 = 0.016129
dense absent → 0
RRF = 0.016129
d6: sparse rank 4 → 1/64 = 0.015625
dense rank 3 → 1/63 = 0.015873
RRF = 0.031498 ← tied second
Fused ranking: d1 (0.032522) > d3 ≈ d6 (0.031498) > d4 (0.030770) > d2 (0.016393) > d5 (0.016129).
Step 4 — read the result honestly, including what went wrong
The good news first:
d1is now unambiguously first. It was rank 1 in one list and rank 2 in the other, and dual appearance promoted it above everything. The system got the right answer to the top for the right reason: two independent retrievers agreed.d5collapsed to last. The changelog line was rank 2 in sparse and absent from dense, and fusion correctly discounted a document only one leg liked.d2entered the pool at all. Sparse-only retrieval would never have surfaced it.
Now the bad news, which is the more instructive half:
d2— the second-best chunk in the corpus — fused to rank 4, belowd3,d6, andd4, all of which are generic and less useful. Why? Becaused2appeared in only one list, and RRF's structure means one appearance rarely beats two mediocre appearances.d3at ranks 3 and 4 scored 0.031498;d2at rank 1 in one list scored 0.016393.d4— a pinning configuration reference, largely irrelevant — fused to rank 3, purely because it scraped rank 5 in both lists.
This is RRF's real and inherent bias: it rewards mutual presence over single-leg conviction. Usually that is what you want, because agreement is evidence. Sometimes it buries a genuinely excellent single-leg hit under a pile of mediocre mutual ones.
The correction is not to abandon RRF. It is:
- Fuse a deeper pool. With top-5 per leg the pool is tiny and mediocre documents crowd it. At top-50 per leg,
d3/d4/d6-class documents sit at ranks 20–40 where their contributions are visibly smaller, whiled2still holds rank 1 in the dense list. - Rerank the fused pool. A cross-encoder reading the query jointly with
d2will recognise that it answers the question, and withd4that it does not. Reranking is the stage that fixes exactly the defect this example exposes — which is why this lesson and07-07are a paired session and why the module's design note says the two corrections are close to meaningless apart. - Do not tune
k_rrfto patch it. Loweringk_rrfsharpens the top of the curve and would promoted2, and it would also make the whole ranking hostage to each leg's rank-1 pick. That is a worse trade.
Step 5 — what the comparison table shows
| Retriever | rank 1 | rank 2 | rank 3 | Is the best chunk first? | Is a bad chunk in the top 3? |
|---|---|---|---|---|---|
| Sparse only | d1 | d5 | d3 | yes | yes — d5 |
| Dense only | d2 | d1 | d6 | no | no |
| Hybrid (RRF) | d1 | d3/d6 | d4 | yes | yes — d4 |
| Hybrid + rerank | (what 07-07 fixes) |
Hybrid search fixed the top of the ranking and left the rest of the pool imperfectly ordered. That is precisely the division of labour: hybrid search is responsible for the right chunk being in the pool and near the top; reranking is responsible for the pool being correctly ordered.
Decision table: when to add hybrid search and when not to
| Situation | Add hybrid? | Reasoning |
|---|---|---|
| Queries mix natural language and exact identifiers | Yes | The canonical case; each leg covers the other's blind spot |
| Corpus contains error codes, SKUs, versions, drug or legal citations | Yes | The sparse leg is the only thing that reliably finds these |
| New proper nouns and product codes arrive continuously | Yes | The encoder has never seen them; IDF handles them for free |
| Users are experts typing precise keyword queries | Sparse may be enough | Measure before adding a second index |
| Users are non-experts asking conversational questions | Dense may be enough | Measure; the sparse leg may add little |
| Neither leg has been measured alone yet | No — measure first | Fusion of an unmeasured leg can dilute a good one |
| One leg is known to be broken | No — fix it first | Fusion propagates the noise |
| Complaint is "answers cite outdated documents" | No | Recency is metadata, not coverage (07-03) |
| Complaint is "forum posts beat official docs" | No | Authority is metadata, not coverage (07-03) |
| Complaint is "users see documents they shouldn't" | No — and urgently | This is 07-05; hybrid search would double the leaking paths |
| Complaint is "the right chunk is retrieved but ranked fifth" | Not the fix | That is reranking (07-07) |
| Complaint is "the right chunk is never retrieved at all" | Yes, likely | This is a recall problem, which is what hybrid addresses |
| Hard latency floor with no budget for two retrievals | Weigh carefully | Legs parallelise, so cost is ~max(leg) not sum, but you pay two systems' overhead |
| Corpus under ~10k chunks | Yes, and it is cheap | Both legs are trivial at this scale; no vector database needed for either (07-04) |
The diagnostic that makes this table usable: distinguish "not retrieved" from "retrieved but ranked low." Those are different failures with different fixes, and telling them apart requires looking at the retrieved candidates rather than the final answer. Retrieve top-50 for a failing query and search the list for the correct chunk:
- Absent from the top-50 → a recall problem → hybrid search, chunking (
06-02), or the embedding model (03-03). - Present but at rank 23 → a precision/ordering problem → reranking (
07-07). - Present at rank 1 and the answer is still wrong → a generation problem →
07-10,07-11.
That three-way split is the most valuable half-hour in RAG debugging and 07-10 formalises it.
Why hybrid search is on the NCA-GENL exam
Hybrid search is named explicitly in the course's retrieval coverage, alongside dense-versus-sparse retrieval, ANN indexing, the recall/latency trade-off, metadata filtering, and when a vector database is unnecessary. It serves:
- 1.3 / 4.2 — Build LLM use cases such as RAG, chatbots, and summarizers
[OFFICIAL]. Hybrid retrieval is standard practice in a production RAG build. - 1.4 — Curate and embed content datasets for RAGs. Whether a corpus needs a sparse leg is a corpus-shape judgement made during curation.
- 1.6 / 4.3 — Familiarity with the capabilities of Python natural language packages (spaCy, NumPy, vector databases, etc.)
[OFFICIAL]. Hybrid support is one of the capabilities that distinguishes vector databases from ANN libraries. - 4.4 — Identify system data, hardware, or software components required to meet user needs. Two indexes plus a fusion step is a component decision.
On the NVIDIA stack side, NeMo Retriever is the product framing for retrieval accuracy at scale and the NVIDIA AI Blueprint for RAG is the reference workflow [NVIDIA-DOC]; 07-09 covers both when the full pipeline is assembled. It is also worth remembering the [FIELD] calibration that NVIDIA-branded answers tend to be favoured when two options are technically defensible — which is about the stack map (12-13 covers Triton and NIM; 13-01 the trust principles) rather than about retrieval algorithms, but it is a useful tie-breaker.
Exam depth is general-level [FIELD]. Know that hybrid search means two legs plus fusion, know RRF by name and know why rank-based fusion avoids the score-calibration problem, and know the coverage/metadata distinction. Do not memorise k_rrf derivations.
Question phrasings you should recognise:
| Phrasing | Testing | Answer shape |
|---|---|---|
| "Which retrieval approach combines keyword and semantic search?" | naming it | hybrid search / hybrid retrieval |
| "Why is reciprocal rank fusion preferred over weighted score fusion?" | the calibration problem | ranks are comparable across retrievers; raw scores are not |
| "A RAG system finds conceptual answers but misses exact error codes. What should be added?" | the sparse leg | keyword/BM25 retrieval fused with the existing vector search |
| "A RAG system finds exact strings but misses paraphrased questions. What should be added?" | the dense leg | embedding-based retrieval fused with the existing keyword search |
| "Which problem is not solved by hybrid search?" | the metadata blind spots | recency / source authority / user permissions |
| "In a hybrid system, where must the permission filter be applied?" | both legs | in every retrieval leg (07-05) |
| "What is the relationship between hybrid search and reranking?" | recall vs precision | hybrid widens the candidate pool; reranking orders it |
| "Two retrievers both return a document at rank 3. Under RRF, how does it compare to a document at rank 1 in only one list?" | mutual presence | the dual-list document scores roughly twice as high |
Distractor families:
- "Hybrid search means using two embedding models." Two dense models is ensembling, not hybrid search. Hybrid means sparse plus dense.
- "Hybrid search means query expansion." One index with a rewritten query is not two legs and a fusion step.
- "Normalise BM25 and cosine scores and average them." Offered as the standard method. It is a method, it is the fragile one, and its fragility is the reason RRF exists.
- Hybrid offered as the fix for recency, authority, or permissions. The highest-value distractor family in this lesson.
- Hybrid offered as the fix for a ranking-order problem. That is reranking.
- "Hybrid search always improves retrieval." It can dilute a good leg with a broken one.
- "Hybrid search doubles latency." The legs parallelise; cost is closer to the slower leg plus fusion overhead than to the sum.
- "Hybrid search requires a single unified index." It does not, although one system supporting both is operationally simpler and makes the shared filter easier.
Common mistakes with hybrid search
| # | Symptom | Cause | Fix |
|---|---|---|---|
| 1 | Hybrid enabled, results got worse | One leg is broken — mismatched encoder (07-02) or mismatched analyser (07-01) — and fusion injected its noise | Measure each leg alone against the eval set before fusing; fix the broken leg |
| 2 | An excellent single-leg hit is buried mid-ranking | RRF rewards mutual presence over single-leg conviction | Fuse a deeper pool, then rerank (07-07); do not tune k_rrf to patch it |
| 3 | Tuned α in weighted fusion; performance decayed over months | BM25 scores are corpus-dependent, so the calibration drifted as the corpus grew | Use RRF, or re-verify α on every significant corpus change |
| 4 | Same query gives inconsistent fused rankings | Min-max normalisation makes a document's normalised score depend on which other documents were returned | Use rank-based fusion |
| 5 | Documents a user cannot read appear in results | The permission filter was applied to one leg only | Apply the predicate in every leg (07-05); prefer one system with one filter expression |
| 6 | Fusion produces near-arbitrary orderings | Fused pool is too shallow — top-5 per leg leaves mediocre documents crowding the top | Retrieve deeper (top-50 to top-100 per leg), fuse, then narrow with reranking |
| 7 | Hybrid added to fix stale answers; no change | Wrong intervention for the blind spot; BM25 is equally time-blind | Metadata filter and recency boost (07-03, 06-03) |
| 8 | Fusion drops or duplicates documents | The two legs index different granularities — documents in one, chunks in the other | Index the same units in both legs, or define an explicit roll-up |
| 9 | Latency doubled after enabling hybrid | The legs are running sequentially | Run them in parallel; total should approximate the slower leg plus fusion |
| 10 | Nobody can say whether hybrid helped | It was enabled alongside three other changes | One change at a time against a frozen eval set (01-08, 03-04) |
Mistake 10 is the methodological one and it recurs through this whole module. The four-row table — sparse alone, dense alone, hybrid, hybrid plus rerank, each with recall@5 and recall@10 on the same frozen questions — is the artefact that makes every later decision defensible. Build it once and it pays for the rest of the project.
Why does reciprocal rank fusion use ranks instead of scores?
Because the two retrievers' scores are not on a common scale and cannot be put on one without introducing a calibration that drifts.
Look at what each leg actually emits. BM25 returns an unbounded positive sum of per-term contributions whose magnitude depends on the query's term rarity, the number of query terms, document lengths, and the collection's IDF distribution. A score of 9.84 means nothing on its own — for a one-rare-term query it might be exceptional, and for a five-term query, mediocre. Worse, it shifts as the corpus grows, because IDF is a function of collection statistics.
Cosine similarity returns a value in a compressed positive band, typically 0.2–0.9 for text embeddings, where unrelated text already scores 0.2–0.5 (07-02). The distribution is model-specific and corpus-specific and is not calibrated to anything.
Now try to combine them. You need a mapping from each leg's scale to a shared one, and every available option is flawed:
| Normalisation | Flaw |
|---|---|
| Min-max over the returned candidates | The mapping depends on this query's result set, so a document's normalised score changes based on what else came back |
| Min-max over global observed ranges | Requires maintaining corpus-wide statistics that shift as the corpus changes |
| Z-score | Assumes a distribution shape neither leg has |
| Divide by the top score | Makes every leg's rank-1 exactly 1.0, discarding the information that one leg found a much better match than the other |
Rank sidesteps all of it. "Third in the sparse list" is a statement whose meaning does not depend on scale, corpus size, query length, or model version. It is directly comparable to "third in the dense list". That comparability is bought at a real price — RRF discards magnitude information, so it cannot tell that sparse rank 1 scored 9.84 while sparse rank 2 scored 8.91 rather than 0.94 — and the trade is generally judged worth it, because a robust method with no tuning parameters beats a sharper method whose parameters silently decay.
When is score fusion the better choice? When you have a substantial labelled eval set, a stable corpus, and a measured gain you can re-verify on a schedule. That is a real situation and it is not the default one.
Do I need two separate indexes for hybrid search?
Not necessarily, and the options differ mainly in operational cost.
| Approach | How | Trade-off |
|---|---|---|
| Two systems | e.g. OpenSearch for BM25 plus a vector database for dense; fuse in application code | Most flexibility; two filter expressions that must agree (07-05 risk); two systems to operate |
| One system, both index types | OpenSearch/Elasticsearch with a vector field; or a vector database with BM25 support | One filter, one deployment, often built-in RRF; less freedom to choose each leg's best-of-breed |
| Learned sparse vectors | A SPLADE-style model emits term weights from a neural network; stored as sparse vectors in a vector database that supports sparse-dense search | Genuinely one system with two representations; combines lexical matching with learned term expansion. Requires a model per corpus language and is less well understood than BM25 |
| In-memory, small corpus | BM25 via an in-process library plus exact vector search over a NumPy array; fuse in twenty lines | Simplest thing that works below ~10k chunks (07-04); zero infrastructure |
The last row is worth taking seriously for the same reason 07-04 argues against premature vector databases: at small corpus scale, hybrid search is fifteen lines of code, not an architecture. You compute BM25 scores, you compute cosine similarities, you sort each, you sum reciprocal ranks. There is no reason for a team with 8,000 chunks to defer hybrid search until they have deployed two search systems.
The learned-sparse row deserves a calibration note. SPLADE-style retrieval is real and increasingly used, and it blurs the sparse/dense boundary this module has drawn cleanly: it produces a sparse, interpretable, term-weighted vector from a neural network, so it gets some of dense retrieval's expansion behaviour while remaining searchable by an inverted index. For the exam, the taxonomy that matters is sparse (lexical, BM25) versus dense (embedding, cosine), with hybrid as their combination. Know that learned sparse representations exist; do not expect to be tested on them.
How do I measure whether hybrid search actually helped?
By building the four-row table this module has been pointing at since 07-01, on a frozen labelled eval set, changing one thing at a time.
Setup. Take 20–40 real questions — from users, tickets, or colleagues, never invented, because invented questions are written in the corpus's vocabulary and quietly favour whichever leg you already prefer. For each, label the specific chunk id (or ids) that genuinely answer it. 01-08 and 03-04 cover the discipline.
Measure, in this order:
| Configuration | recall@5 | recall@10 | What it tells you |
|---|---|---|---|
| Sparse only (BM25) | — | — | The baseline. Everything later must beat this |
| Dense only | — | — | Whether your embedding model earns its cost on this corpus |
| Hybrid (RRF) | — | — | Whether fusion added coverage beyond the better single leg |
| Hybrid + rerank | — | — | Whether ordering was the remaining problem (07-07) |
Read it with three specific questions:
- Did hybrid beat the better single leg on recall@10? If not, the legs are not as complementary as assumed on your corpus, or one leg is broken. Investigate rather than shipping.
- Did recall@5 improve more than recall@10? recall@10 measures whether the chunk is in the pool; recall@5 measures whether fusion pushed it up. A gain concentrated in recall@10 means hybrid added coverage; a gain in recall@5 means it also improved ordering.
- Which specific questions changed? The aggregate hides the mechanism. Look at the queries that flipped from fail to pass and confirm they flipped for the reason you expect — an exact identifier now found, or a paraphrase now matched. If they flipped for reasons you cannot explain, you do not yet understand your retriever.
Measure retrieval, not answers, at this stage. End-to-end answer quality moves for many reasons and cannot attribute a change to retrieval. Separating retrieval quality from generation quality is the core discipline of 07-10 and the metric decomposition in 09-07.
Then leave the table in the repository. Six weeks later someone will propose removing the sparse leg to simplify the stack, and this table is the only thing that will answer them.
Glossary recap: the terms this lesson introduced
| Term | Definition |
|---|---|
| Hybrid search | Running sparse and dense retrieval over the same corpus and fusing their result lists into one ranking |
| Leg | One retriever in a hybrid system — the sparse leg or the dense leg |
| Fusion | The step that merges two or more ranked candidate lists into a single ranking |
| Reciprocal rank fusion (RRF) | Fusion by summing 1/(k_rrf + rank) across lists; rank-based, so it needs no score calibration |
k_rrf | RRF's smoothing constant, conventionally 60; larger flattens the curve and weights cross-leg agreement more |
| Weighted score fusion | Fusion by normalising each leg's scores and taking a weighted sum; more expressive, more fragile |
| Score normalisation | Mapping a leg's scores to a common range; the step whose fragility motivates RRF |
| Mutual presence | A document appearing in more than one leg's list — the evidence RRF rewards most |
| Fusion depth | How many candidates each leg contributes before fusion; deeper pools give a reranker a real choice |
| Query expansion | Rewriting a query with additional terms to widen a single leg's lexical reach — related to, but not, hybrid search |
| Learned sparse retrieval | Neural models (SPLADE-style) that emit sparse term weights, blurring the sparse/dense boundary |
| Recall intervention vs precision intervention | Hybrid search gets the right chunk into the pool; reranking gets it to the top |
Key takeaways on hybrid search
- Hybrid search is two retrieval legs plus a fusion step. Sparse covers exact identifiers and unseen vocabulary; dense covers synonyms, paraphrase, and cross-lingual matching.
- RRF is the default because it fuses ranks, not scores. BM25 scores are unbounded and corpus-dependent; cosine similarities are compressed and uncalibrated. Rank is scale-free.
- The worked example's headline result:
d1appeared at rank 1 in one list and rank 2 in the other, fusing to 0.0325 — roughly double the 0.0164 of a document that topped one list and was absent from the other. Mutual presence is the evidence RRF rewards. - The same example's honest failure: the second-best chunk fused to rank 4, buried under mediocre documents that appeared in both lists. RRF's bias toward mutual presence is real, and reranking is what corrects it.
- Hybrid search is a recall intervention; reranking is a precision intervention. They compose and neither substitutes for the other.
- Hybrid search does nothing for recency, authority, or permissions. BM25 is exactly as blind to all three as the embedding model. This is the exam's favourite distractor family.
- Apply the permission filter in every leg. One filtered leg and one unfiltered leg is an unfiltered system (
07-05). - Fuse deep, rerank, then narrow. A top-5-per-leg pool is too thin for either fusion or reranking to work well.
- Hybrid can hurt if one leg is broken. Measure each leg alone before fusing, and keep the four-row table.
Next: reranking with a cross-encoder
Fusion got the right chunk into the pool and usually near the top, and the worked example showed exactly where it stops: the pool's ordering is only as good as two retrievers that each score a passage without ever reading it alongside the query. Next: 07-07 introduces the cross-encoder — a model that encodes query and passage jointly and therefore can tell that a changelog line merely mentions an error code while another passage explains it. It is the most reliable single quality improvement available in a RAG pipeline, it is priced per candidate rather than per query, and it is the reason this lesson and the next are one paired session rather than two.