C.W.K.
Stream
Lesson 02 of 05 · published

Reciprocal Rank Fusion

~18 min · hybrid, fusion

Level 0Scout
0 XP0/41 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

Why score fusion is hard

Different retrievers score on different scales. Adding raw scores means whichever ranker has the larger numerical range wins by accident. The fix that production systems converge on is simple: fuse on ranks, not scores.

Reciprocal Rank Fusion (RRF)

For each candidate, compute RRF = sum(1 / (k + rank_i)) across all rankers, where rank_i is its rank in the i-th ranker (1 = best) and k is a smoothing constant (60 is the canonical default from the 2009 Cormack paper).

The result: a fusion score that is robust to outliers, requires zero parameter tuning per retriever, and works across any number of rankers (3, 5, 10 retrievers all combine the same way).

Code

RRF in 10 lines·python
def reciprocal_rank_fusion(rankings: list[list[str]], k: int = 60) -> list[tuple[str, float]]:
    scores: dict[str, float] = {}
    for ranking in rankings:                       # each ranking is a list of ids, best first
        for rank, doc_id in enumerate(ranking):
            scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank + 1)
    return sorted(scores.items(), key=lambda kv: -kv[1])

vector_ids = [r[0] for r in vector_search(query, k=20)]
bm25_ids   = [r[0] for r in bm25_search(query,   k=20)]

for doc_id, score in reciprocal_rank_fusion([vector_ids, bm25_ids])[:10]:
    print(f'{score:.4f}  {doc_id}')

External links

Exercise

Take the vector and BM25 rankings from the previous exercise. Fuse them with RRF. Compare top-5 hit rate against (a) vector only, (b) BM25 only, (c) the simple union. Note where RRF wins and where it loses.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.