A retrieval-augmented generation (RAG) system can retrieve highly relevant chunks and still build a poor context.

The problem is redundancy. Imagine a support assistant answering a question about an API timeout. Vector search returns five chunks, but four are slightly different copies of the same timeout definition. The fifth useful chunk about retry behavior never reaches the model. Each result looked relevant in isolation, yet the set wastes most of its context budget repeating one idea.

Maximum marginal relevance (MMR) addresses this set-level problem. Instead of selecting every result only by similarity to the query, it repeatedly asks two questions: how relevant is this candidate, and how much new information does it add compared with what has already been selected?

This article develops that mental model, walks through a small numerical example, and explains how to use MMR without confusing diversity with correctness.

Retrieval is a set-selection problem

A common semantic retrieval pipeline embeds a query and document chunks into vectors, scores the chunks against the query, and returns the top k scores.

Conceptually:

query -> embed -> rank all chunks by similarity -> take top k

This works well when the highest-scoring chunks are both relevant and sufficiently different. It becomes inefficient when the corpus contains overlapping chunks, repeated templates, mirrored documentation, near-duplicate records, or several passages describing the same fact.

Suppose a query asks:

How should a client handle a 429 response?

The top four candidates might be:

A: 429 means the request rate limit was exceeded.
B: HTTP 429 indicates that the caller exceeded a rate limit.
C: On 429, use the Retry-After header when the service provides it.
D: Retrying immediately can produce another 429; use backoff when appropriate.

A and B may have the highest query similarity because they closely restate the query’s central concept. But selecting both contributes less useful coverage than selecting A together with C or D.

Top-k similarity ranks items. MMR tries to construct a useful set.

The MMR mental model

MMR starts with a candidate pool produced by ordinary retrieval. It then selects results one at a time.

At each step, an unselected candidate receives a score that rewards query relevance and penalizes similarity to the items already selected:

MMR(d) = lambda * relevance(d, query)
         - (1 - lambda) * max_similarity(d, selected)

Here:

  • relevance(d, query) measures how well candidate d matches the query;
  • max_similarity(d, selected) measures how similar d is to its most similar already-selected item;
  • lambda controls the relevance-versus-diversity trade-off.

When lambda is close to 1, selection behaves more like ordinary relevance ranking. As lambda decreases, redundancy receives a larger penalty.

The formula is best understood as a selection rule, not as a new embedding model. MMR does not retrain the encoder and does not create information that retrieval failed to find. It reranks an existing candidate pool.

Work through the smallest useful example

Assume ordinary vector search produces three candidates with these query-relevance scores:

A: 0.92
B: 0.90
C: 0.84

The first MMR selection has no existing selected item to compare against, so choose the most relevant candidate, A.

Now suppose the candidate-to-A similarities are:

similarity(B, A) = 0.95
similarity(C, A) = 0.30

B is almost a duplicate of A. C is somewhat less relevant to the query, but substantially different from A.

With lambda = 0.6, B receives:

0.6 * 0.90 - 0.4 * 0.95
= 0.54 - 0.38
= 0.16

C receives:

0.6 * 0.84 - 0.4 * 0.30
= 0.504 - 0.12
= 0.384

MMR therefore selects C next, even though B had the higher original query score.

That is the entire mechanism. C wins because its modest loss in relevance is outweighed by the new coverage it adds.

The numerical values are a teaching example. Production scores depend on the embedding model, similarity function, normalization, corpus, and any transformations applied by the retrieval system.

Why the maximum similarity matters

The redundancy term usually compares a candidate with the most similar item already in the selected set:

max_similarity(candidate, selected)

This makes the penalty sensitive to the candidate’s closest duplicate.

Suppose the selected set already contains chunks about rate-limit definitions and retry headers. A new candidate is very different from the definition chunk but nearly identical to the retry-header chunk. Averaging its similarity to both selected chunks could make it look acceptably diverse. Taking the maximum exposes the duplication with the retry chunk.

As selection continues, each newly chosen item changes the scores of the remaining candidates. MMR is therefore greedy: it builds the result set incrementally rather than assigning one fixed reranking score to every candidate at the start.

Candidate retrieval and MMR have different jobs

A useful implementation separates two sizes:

fetch_k = number of candidates retrieved initially
k       = number of chunks finally sent downstream

For example:

vector search -> 30 candidates -> MMR -> 6 selected chunks

The initial retrieval stage protects relevance by finding a plausible neighborhood around the query. MMR then has room to choose a less redundant subset.

If you retrieve exactly six candidates and ask MMR to return six, reranking cannot remove redundancy because every candidate must still be selected. A diversity algorithm needs alternatives to choose among.

On the other hand, making the candidate pool arbitrarily large is not free. More candidates require more candidate-to-selected similarity calculations, and a very broad pool can contain weakly related material that becomes attractive only because it is different.

Treat fetch_k as a quality, latency, and compute parameter rather than a constant that should simply be maximized.

Keep the similarity scales compatible

The MMR formula combines two quantities:

query relevance
candidate-to-selected similarity

Their relative scales matter.

If query relevance ranges from 0.75 to 0.95 while the redundancy score ranges from 0 to 100, the diversity term will dominate regardless of a seemingly reasonable lambda. Likewise, mixing a distance where smaller means better with a similarity where larger means better can invert the intended behavior.

A clean design uses compatible score semantics for both terms. With cosine similarity, for example, both comparisons can be computed from vectors in the same embedding space. If your retrieval engine transforms or rescales query scores, inspect those transformations before reusing them directly in the MMR equation.

Do not assume a library parameter named lambda, diversity, or score has the same direction or range as another implementation. The mathematical idea is stable; API conventions are implementation details.

A simple implementation pattern

The core algorithm can be expressed without a framework-specific API:

candidates = retrieve(query, fetch_k)
selected = []

while candidates and len(selected) < k:
    if selected is empty:
        pick candidate with highest query relevance
    else:
        for each candidate:
            redundancy = maximum similarity(candidate, selected)
            score = lambda * relevance(candidate, query)
                    - (1 - lambda) * redundancy
        pick candidate with highest score

    move picked candidate from candidates to selected

For a small candidate pool, this direct approach is often sufficient. Larger systems can reuse precomputed embeddings, vectorize similarity calculations, or cache pairwise similarities for the current query.

The important architectural point is that MMR belongs after candidate generation and before final context assembly.

Tune for the context you actually need

There is no universally correct lambda.

A high value is sensible when missing the closest evidence is costly and duplicates are uncommon. A lower value can help when the corpus is highly repetitive and the answer benefits from several aspects of a topic.

Tune the parameter against end-to-end retrieval goals rather than inspecting a few attractive result lists.

For a RAG application, useful evaluation questions include:

  • Does the selected context still contain the evidence required to answer the query?
  • How many selected chunks are near-duplicates?
  • Does broader coverage improve answer correctness or completeness?
  • Does the reranking step change latency enough to matter at production load?
  • Does the behavior remain useful for both narrow factual queries and broad multi-part questions?

If you have labeled relevance judgments, evaluate retrieval metrics as well. If the final product generates answers, also evaluate answer-level outcomes because a more diverse retrieval set is only useful when it helps the downstream task.

Diversity can reduce quality

MMR deliberately trades some relevance for novelty. That trade can fail.

Consider a query asking for the exact documented default value of one configuration option. Several near-duplicate chunks may all contain the correct value. A more diverse chunk discussing a related option adds no benefit and may distract the generator.

For narrow lookup questions, ordinary top-k retrieval or even k = 1 may be simpler and better.

MMR is more compelling when useful evidence is naturally distributed across aspects: troubleshooting causes, policy requirements, product features, multiple constraints in a question, or long documents where overlapping chunks otherwise crowd one another out.

The goal is not maximum diversity. The goal is useful diversity while preserving relevance.

MMR does not replace deduplication

If a corpus contains exact duplicate documents, remove or collapse them when practical.

MMR can reduce the chance that duplicates occupy the final context, but repeatedly paying to embed, index, retrieve, and compare known duplicates is unnecessary work. Corpus-level deduplication solves a data-quality problem; MMR solves a query-time selection problem.

The two techniques are complementary.

Likewise, if overlapping chunk boundaries create many nearly identical passages, revisit the chunking strategy. MMR can make retrieval more robust to overlap, but it should not be used to hide a clearly broken ingestion pipeline.

MMR does not fix missing evidence

Suppose the initial candidate pool contains ten chunks about rate-limit definitions but none about retry behavior. MMR cannot invent the missing retry guidance.

This failure is important because a diverse-looking result set can create false confidence. If candidate generation has poor recall, improve that stage first. Depending on the system, that might mean better chunking, a different embedding model, hybrid lexical and semantic retrieval, query rewriting, metadata filtering, or a larger candidate pool.

MMR only chooses among candidates it receives.

Watch metadata filters and access controls

Diversity reranking must operate inside the set of documents the user is allowed to retrieve.

Apply authorization, tenant, locale, freshness, and other mandatory filters before MMR considers candidates. A reranker should never broaden the candidate set by bypassing access constraints in the name of diversity.

This also affects evaluation. If production queries use filters but offline tests do not, measured retrieval behavior may not represent the candidate pools seen in the real system.

Separate retrieval diversity from generation randomness

MMR changes which evidence enters the context. It is unrelated to sampling parameters such as generation temperature or top-p.

A deterministic generator can benefit from diverse retrieval. A stochastic generator can still receive five redundant chunks. Treat these as separate controls:

retrieval diversity -> evidence coverage
generation sampling -> token-selection behavior

Keeping those responsibilities separate makes failures easier to diagnose. If the answer omits a key fact because the relevant chunk never entered the context, changing generation randomness does not repair retrieval.

Measure the cost of reranking

For each selection step, MMR compares remaining candidates with already-selected items. With modest fetch_k and k, this is usually manageable, especially when embeddings are already available. At larger scales or strict latency budgets, the extra work deserves measurement.

You can often control cost by:

  • keeping the candidate pool only as large as evaluation shows useful;
  • reusing stored document embeddings rather than recomputing them;
  • batching or vectorizing similarity calculations;
  • avoiding MMR for query classes where redundancy is not a problem.

The relevant comparison is not whether MMR adds zero overhead. It is whether the additional retrieval work produces enough context quality to justify its latency and compute cost.

When to use MMR

MMR is a good candidate when ordinary semantic search has adequate recall but final contexts repeatedly contain near-duplicate evidence. It is especially useful when a response needs coverage across several related aspects and the context window is too valuable to spend on repetition.

Prefer a simpler relevance ranking when the task is a precise lookup, the corpus has little redundancy, or evaluation shows that diversity frequently replaces necessary evidence with merely different evidence.

If duplicates are caused by ingestion mistakes, fix ingestion. If evidence never enters the candidate pool, fix candidate retrieval. If the candidate pool is good but the final set is repetitive, MMR targets the right layer of the problem.

Conclusion

Maximum marginal relevance changes retrieval from “take the highest individual scores” to “build a relevant set whose members add something new.”

The practical pattern is straightforward: retrieve a candidate pool, select the strongest initial result, then repeatedly balance query relevance against similarity to what is already selected. Tune that balance on real queries, keep score semantics consistent, and measure both retrieval quality and downstream answer quality.

MMR is not a cure for weak embeddings, missing evidence, bad chunking, or duplicate source data. Used at the right layer, however, it can turn a redundant top-k list into a context that covers more of what the model actually needs.