Skip to content
RAG Explained Better

MMR: Diversifying Retrieved Results

Maximal marginal relevance trades similarity for diversity so the top-k isn't five near-duplicate chunks.

Maximal Marginal Relevance (MMR) iteratively selects the next chunk that is relevant to the query and dissimilar to chunks already chosen — Carbonell and Goldstein (SIGIR 1998) — so the packed top-k covers more aspects instead of near-duplicate paraphrases. Hash-deletion of exact copies is the sibling packing step on deduplicating retrieved context; this page is the retrieval mechanism under retrieval.

Why does similarity search return near-duplicate chunks?

Pure similarity top-k ranks by query match alone, so near-paraphrases of the same fact all score high and crowd out a lower-scoring but new aspect. Learnixo’s warfarin-dosing illustration is the pattern: cosine scores about 0.940.90 on near-identical elderly-dosing lines before a 0.85 renal-function line that actually adds information. Nick Berens (2025) calls the same failure the echo chamber — three Vue.js chunks before React or Angular appear. Elastic’s search-labs pants catalog shows the e-commerce twin: ten black-capris variants instead of style coverage. The cost is double: wasted context tokens and an answer skewed toward whatever was repeated. Chunk overlap as a design cause lives on chunk size and overlap; deleting exact copies lives on dedup; choosing how large k is lives on top-k. This page owns diversity selection among the candidates that remain.

How does the MMR formula work?

MMR is a greedy selector. It picks the most query-relevant candidate first, then repeatedly scores every remaining candidate with the Carbonell-form objective used by Ailog, Elastic, Learnixo and Ankit Kumar (2025):

MMR (Carbonell & Goldstein, SIGIR 1998 — common RAG form)
MMR(Di) = λ · Sim(Di, Q) − (1 − λ) · max_j Sim(Di, Dj_selected)

  Sim(Di, Q)              = relevance to the query
  max_j Sim(Di, Dj_sel)   = similarity to the closest already-selected chunk
  λ ∈ [0, 1]              = relevance weight (λ=1 → pure top-k; λ=0 → pure diversity)

On a Learnixo-style pool, the first pick is the 0.94 dosing line. The next pick subtracts similarity to that line, so another 0.90 paraphrase is penalised and the 0.85 renal-aspect line can win when λ is not stuck at 1.0.

OpenSearch inverts the λ placement

OpenSearch’s MMR docs (as of the live 2026-07-28 teardown) score MMR = (1 − λ) · relevance − λ · max_sim and name the knob diversity (default 0.5; closer to 1 means more diversity). That is the opposite weight placement from the Carbonell λ above. Never copy a λ value across products without checking which formula the product implements.

How do you tune the MMR lambda parameter?

Tune λ by starting relevance-leaning and moving only with measured redundancy — there is no universal published optimum. Practitioner ranges to cite, not invent:

  • Ailog: λ ≈ 0.7 general (70% relevance / 30% diversity); high-redundancy domains λ = 0.50.6; precision-critical λ = 0.80.9.
  • Elastic search-labs (2025): product discovery λ = 0.30.5; precision search λ = 0.70.9; research coverage λ = 0.50.7; start at λ = 0.7 then adjust.
  • Nick Berens (2025): default λ = 0.6; over-diversification symptom at λ = 0.20.3; under-diversification at λ = 0.80.9.
  • OpenSearch: default diversity = 0.5 under their inverted convention.

Measure pairwise similarity inside the packed set and answer coverage on multi-aspect queries before locking a value.

What is fetch_k in MMR retrieval?

fetch_k is the candidate pool size retrieved before MMR selects k results. LangChain’s max_marginal_relevance_search takes k, fetch_k, and lambda_mult (Ailog’s common demo uses k=5, fetch_k=20, lambda_mult=0.7). OpenSearch’s candidates default is 3 × the requested size.

The hard gate: if fetch_k ≈ k, or every candidate is a near-clone of one aspect, λ cannot invent missing coverage — the renal-function line must be inside the pool for MMR to promote it. Nick Berens flags fetch pools under 10 as too small and starts many setups at 2030 for k around 46 — starting points to measure, not laws. Structural cost is real: MMR adds pairwise comparisons against the growing selected set (Elastic: limit reranking depth; practitioner guides often write the extra work as order O(k × fetch_k)). No universal latency percentage ships here — measure on your store.

Does MMR replace a reranker?

MMR does not replace a reranker. MMR diversifies a first-stage shortlist by penalising similarity to already-selected chunks; a cross-encoder reranker rescores query–document pairs for precision. The production pattern Ailog and svgoudar both describe is retrieve wide → optional MMR diversify → optional cross-encoder shortlist. MMR alone does not repair weak first-stage recall. Stage-2 depth lives on reranking and cross-encoders.

When should you use MMR in RAG?

Use MMR when multi-aspect or exploratory queries return near-paraphrase stacks and the context budget is tight (Ailog’s multi-aspect case; Nick’s “tell me about…”; Elastic and Learnixo redundancy demos). Skip MMR when the corpus is already diverse, when the query is a single precise fact and every top hit is independently necessary (Ankit Kumar’s when-not; Nick’s exact lookups), or when the latency budget forbids the extra pairwise pass (Ailog / Ankit). Exact string clones still want dedup first — hash deletion frees tokens with no information loss; MMR keeps k items that are not embedding-space clones.

How do you implement MMR in RAG?

You implement MMR by retrieving a fetch_k candidate pool, running the greedy λ-scored selection down to k, then packing. Frameworks expose the knobs directly (LangChain search_type="mmr" / max_marginal_relevance_search; OpenSearch’s mmr extension). Popular stores that ship diversity-aware retrieval include Weaviate, Qdrant, OpenSearch and Elastic — name the mechanism, not a winner. Pin λ and fetch_k, measure redundancy before and after, and put the runnable pinned walkthrough on building the pipeline. Packing after selection returns to dedup and the parent retrieval hub.

What is maximal marginal relevance in RAG?

Maximal Marginal Relevance (MMR) iteratively picks the next chunk that is relevant to the query and dissimilar to chunks already selected — Carbonell and Goldstein (SIGIR 1998) — so the top-k covers more aspects instead of near-duplicate paraphrases. It diversifies; it does not hash-delete exact copies.

How does the MMR formula work?

MMR greedily maximises λ · Sim(chunk, query) − (1 − λ) · max similarity to already-selected chunks. λ near 1 behaves like pure similarity top-k; λ near 0 favours diversity. OpenSearch’s diversity parameter uses an inverted weight placement — check the product formula before copying a λ value.

What lambda should I start with for MMR?

There is no universal published optimum. Practitioner starting points include Ailog’s λ ≈ 0.7 general, Elastic’s start-at-0.7 then adjust by use case, and Nick Berens’s λ = 0.6 default. Move λ only after measuring packed-set redundancy and multi-aspect coverage on your corpus.

What is fetch_k in MMR retrieval?

fetch_k is how many candidates you retrieve before MMR selects k results. If fetch_k is roughly equal to k, or the pool is all near-clones of one aspect, λ cannot invent missing coverage. LangChain demos often use fetch_k=20 for k=5; OpenSearch defaults candidates to 3× size — measure rather than treat either as law.

Does MMR replace a reranker?

No. MMR diversifies the shortlist; a cross-encoder reranker raises precision on query–document pairs. Common pattern: retrieve wide, optionally diversify with MMR, then optionally rerank. Depth on that stage is at /reranking/.

Is MMR the same as deduplicating retrieved context?

No. Exact and near-duplicate deletion removes copies to free tokens. MMR keeps k items while penalising embedding-space clones so multi-aspect queries get coverage. Use dedup at /context/dedup/ for identical text; use MMR when distinct chunks are still too similar.