RRF for RAG Hybrid Search

黎 浩然/ 8 10 月, 2026/ 大语言模型/LARGELANGUAGEMODEL/LLM, 机器学习/MACHINELEARNING, 研究生/POSTGRADUATE, 计算机/COMPUTER/ 0 comments

Keyword and vector retrieval can each find useful material for RAG while returning scores that are not directly comparable. Reciprocal Rank Fusion (RRF) combines their ranks: retain each result list’s order, then sum rank-based contributions for the same document. It avoids forcing BM25 scores and vector similarities onto one numerical scale.

Contents
  1. Why not add the raw scores?
  2. The formula and a small example
  3. Recompute it in Python
  4. The candidate window is easy to overlook
  5. What still needs checking after fusion?
  6. References

Why not add the raw scores?

Keyword retrieval uses term-matching signals; vector retrieval uses similarity in a representation space. A score of 12 from one method and 0.8 from another cannot establish which result is more relevant. Addition implicitly chooses a scale. Normalization and learned fusion are alternative approaches; RRF uses ranks instead.

Literal matches may matter for a specific error code, while semantic matching may help with a paraphrased concept. This motivates hybrid search rather than guarantees complementary results. Validate it on actual queries.

The formula and a small example

For document d, RRF(d) = Σ 1 / (k + rankᵢ(d)). Ranks start at 1, and only lists containing d contribute. A document absent from one list receives no contribution from that list. k is a smoothing constant, not the requested result count. This example uses k = 60.

Original RRF diagram: keyword ranks A B C and vector ranks B D A fuse into B A D C
Hypothetical IDs and rankings, without relevance labels or a retrieval-quality benchmark.
Document Keyword rank Vector rank Fused score
B 2 1 1/62 + 1/61 ≈ 0.03252247
A 1 3 1/61 + 1/63 ≈ 0.03226646
D Absent 2 1/62 ≈ 0.01612903
C 3 Absent 1/63 ≈ 0.01587302

B ranks near the top in both lists and overtakes A, the keyword list’s first result. Cross-list support does not prove B is correct. A fused score is a ranking-rule output, not a probability that an answer is correct.

Recompute it in Python

from collections import defaultdict
from fractions import Fraction

def rrf(lists, k=60):
    if k <= 0:
        raise ValueError('k must be positive')
    scores = defaultdict(Fraction)
    for ranked in lists:
        if len(set(ranked)) != len(ranked):
            raise ValueError('duplicate document ID in one list')
        for rank, doc in enumerate(ranked, 1):
            scores[doc] += Fraction(1, k + rank)
    return sorted(scores.items(), key=lambda x: (-x[1], x[0]))

results = rrf([['A', 'B', 'C'], ['B', 'D', 'A']])
assert [doc for doc, score in results] == ['B', 'A', 'D', 'C']
assert rrf([]) == []
assert rrf([['Z'], ['A']])[0][0] == 'A'
try:
    rrf([['A', 'A']])
except ValueError:
    pass
else:
    raise AssertionError('duplicates must be rejected')
for doc, score in results:
    print(doc, f'{float(score):.8f}')

The code was executed and matches the table. Exact fractions accumulate scores; decimals are used only for display. Ties are resolved by document ID for reproducibility. Duplicate IDs within one list are rejected to avoid double counting; matching IDs across lists are exactly what fusion combines. Empty input was also checked.

Let N be the total input entries and U the unique documents. Ignoring changes in numerical bit width, dictionary accumulation averages O(N), sorting costs O(U log U), and extra storage is O(U). The example runs neither keyword nor vector retrieval and measures no production latency.

The candidate window is easy to overlook

Only retrieved candidates can enter fusion. RRF cannot recover a document missing from every list. If ten final results are needed but each method retrieves very few candidates, examine candidate windows before adjusting the rank constant.

Larger k narrows the relative difference between early and later ranks; it is not an accuracy knob that always improves results. Window size, retriever quality, and correlation between lists also matter. Elastic and OpenSearch parameter names and constraints belong to their respective APIs; this function is not a complete implementation of either product.

What still needs checking after fusion?

Use consistent document or chunk IDs. The same content with different IDs becomes separate candidates; different content sharing an incorrect ID is wrongly merged. Apply access controls so fusion cannot send unauthorized material to the model.

A task-appropriate reranker can follow fusion, but it still works on candidates. Measure retrieval relevance, whether cited material supports answer claims, and end-to-end latency separately. Better search does not guarantee that generation uses evidence faithfully; check citations against source text.

RRF addresses retrieval, not generation-time KV cache. Read KV cache memory estimation to separate these costs. 中文版 / Chinese version.

References

  • Original RRF paper, SIGIR 2009. This explains an established algorithm, not a new paper or breaking news; its experiments are not performance promises for this site.
  • Elastic: Reciprocal rank fusion, first-party formula, rank numbering, and candidate-window documentation.
  • OpenSearch: RRF, documentation of another product implementation. Check the deployed version.

Catch-up note: this edition was published later on October 8, 2026. Its article date retains the planned 09:00 Beijing slot.

Support

If this article helped you, you can support this site.

WeChat support QR code; click to enlarge
WeChat
Alipay support QR code; click to enlarge
Alipay
Buy Me a Coffee; support this site
Buy Me a Coffee

Click a QR code to enlarge. More options: support page。

Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*