Reranking benchmark

What does a second stage buy a production pipeline, and does blending several of them buy more? This compares the Switch pipelines of composing recommenders on the users a service really has: ones it knows, and ones arriving with no history.

  • BM25 is Switch(KnownUser(), BM25Recommender(), MostPopularRecommender()): BM25 for known users, the most popular items for everyone else.
  • BM25+CatBoost is the same switch with a CatBoostRanker behind each branch. Known users get BM25's top 100, reordered on who the user is (age, gender, occupation), what the movie is (genres), the BM25 score, how many interactions the user and the movie have (InteractionCounts), EASE's score for the pair (RecommenderScores) and how popular the movie is among users of the same gender, age band and occupation (SegmentPopularity). Cold users get the 100 most popular movies, reordered on popularity and segment popularity, which is all there is to know about someone with no history. That ranker is fitted on users that ColdStartSplit holds out whole inside Cascade.fit, so it learns from candidates exactly as a cold user is served them.
  • BM25+XGBoost and BM25+LightGBM are the same pipeline with XGBRanker or LGBMRanker in place of CatBoost, on exactly the same features. Both are regularized, with a lower learning rate and larger minimum leaves. At their defaults (300 trees at a learning rate of 0.1), both overfit the few hundred users a ranker learns from. That reached 0.166 and 0.178 NDCG@10 on warm users, and fell below the popularity baseline on cold users (0.469 and 0.451).
  • BM25+Blend puts a BlendRanker over all three boosters behind each branch, at its defaults. Each booster is fitted three times on two thirds of the users and scores the third it did not see. A logistic regression then learns how to weigh their rank-normalized scores, and the three are refitted on every user for serving.
  • EASE is the baseline switch with EASE in place of BM25, the second member of the fusions below, shown on its own so a fusion can be compared with each of its members.
  • BM25+EASE RRF serves known users with ReciprocalRankFusion of BM25 and EASE: each retrieves its top 100, and an item scores 1/(60 + rank) summed over the two lists. There is no ranker and nothing is learned. Cold users get the most popular movies, as in the baseline.
  • BM25+RRF is the BM25+CatBoost switch with a training-free ReciprocalRankRanker in place of CatBoost. Known users get BM25's top 100, reordered by the fused ranks of their BM25 and EASE scores. Cold users get the 100 most popular movies, reordered by the fused ranks of popularity and segment popularity.
  • BM25+RRF boosters is the BM25+Blend switch with the three boosters fused by ReciprocalRankRanker instead of stacked by BlendRanker. Each booster is fitted once on every user. There are no out-of-fold refits and no blender to learn: three booster fits per branch, against the blend's twelve.

The split is ColdStartSplit, so the held-out set asks both questions at once, and every quality row is reported three times: for all held-out users, for the warm ones and for the cold ones. Time is reported one way only, as what one user's request costs, since that is what a reranker adds.

uv sync --extra catboost --extra xgboost --extra lightgbm
uv run python benchmarks/run.py run reranking   # measure it, re-render the tables below

MovieLens 100K

MovieLens 100K, ColdStartSplit: 10% of users held out whole, the latest 20% of every other user's ratings held out, k=10. Regenerate with python benchmarks/run.py run reranking --dataset movielens-100k-cold.

Pipeline users n users NDCG@10 P@10 R@10 hit rate MAP MRR cat cov mean pop novelty
BM25 all 943 0.2057 0.1832 0.1004 0.6914 0.1154 0.3642 0.1039 270.9265 8.18
BM25+CatBoost all 943 0.2395 0.2121 0.1183 0.7656 0.1365 0.4085 0.2611 227.8753 8.53
BM25+XGBoost all 943 0.2326 0.2059 0.1152 0.7593 0.1289 0.4107 0.2780 214.6402 8.65
BM25+LightGBM all 943 0.2315 0.2030 0.1138 0.7476 0.1296 0.4090 0.2837 214.7716 8.65
BM25+Blend all 943 0.2352 0.2047 0.1153 0.7497 0.1330 0.4132 0.2780 219.2950 8.61
EASE all 943 0.2210 0.1910 0.1070 0.7349 0.1234 0.4038 0.3062 221.6990 8.58
BM25+EASE RRF all 943 0.2200 0.1945 0.1080 0.7158 0.1245 0.3859 0.1778 252.5215 8.32
BM25+RRF all 943 0.2205 0.1943 0.1062 0.7126 0.1277 0.3874 0.1653 252.2036 8.31
BM25+RRF boosters all 943 0.2387 0.2092 0.1175 0.7667 0.1341 0.4193 0.2743 219.9035 8.60
BM25 warm 849 0.1701 0.1468 0.1010 0.6596 0.0872 0.3242 0.1039 256.4918 8.26
BM25+CatBoost warm 849 0.2052 0.1775 0.1217 0.7420 0.1068 0.3709 0.2611 209.6465 8.64
BM25+XGBoost warm 849 0.1994 0.1723 0.1185 0.7350 0.1008 0.3719 0.2780 196.1783 8.76
BM25+LightGBM warm 849 0.1979 0.1688 0.1168 0.7232 0.1013 0.3708 0.2837 195.8178 8.77
BM25+Blend warm 849 0.2013 0.1710 0.1187 0.7267 0.1038 0.3739 0.2780 200.4595 8.72
EASE warm 849 0.1871 0.1554 0.1084 0.7079 0.0961 0.3682 0.3062 201.8139 8.70
BM25+EASE RRF warm 849 0.1860 0.1592 0.1095 0.6867 0.0973 0.3483 0.1778 236.0490 8.41
BM25+RRF warm 849 0.1847 0.1577 0.1087 0.6867 0.0965 0.3478 0.1653 238.6967 8.39
BM25+RRF boosters warm 849 0.2051 0.1755 0.1209 0.7444 0.1053 0.3816 0.2743 201.1066 8.72
BM25 cold 94 0.5273 0.5128 0.0945 0.9787 0.3695 0.7252 0.0063 401.3000 7.54
BM25+CatBoost cold 94 0.5486 0.5245 0.0872 0.9787 0.4048 0.7480 0.0125 392.5160 7.58
BM25+XGBoost cold 94 0.5323 0.5096 0.0854 0.9787 0.3826 0.7612 0.0194 381.3862 7.62
BM25+LightGBM cold 94 0.5352 0.5117 0.0861 0.9681 0.3854 0.7538 0.0182 385.9606 7.61
BM25+Blend cold 94 0.5411 0.5085 0.0848 0.9574 0.3962 0.7681 0.0144 389.4160 7.59
EASE cold 94 0.5273 0.5128 0.0945 0.9787 0.3695 0.7252 0.0063 401.3000 7.54
BM25+EASE RRF cold 94 0.5273 0.5128 0.0945 0.9787 0.3695 0.7252 0.0063 401.3000 7.54
BM25+RRF cold 94 0.5440 0.5245 0.0837 0.9468 0.4096 0.7451 0.0244 374.1968 7.66
BM25+RRF boosters cold 94 0.5428 0.5138 0.0868 0.9681 0.3947 0.7602 0.0175 389.6755 7.59

Measured on Apple M4 Pro (12 usable cores), macOS-27.0-arm64-arm-64bit-Mach-O, CPython 3.14.3, numpy 2.5.3, skrecsys 0.5.0, _core built in release mode. The quality table above is deterministic and portable; the timings below are not comparable across machines or builds.

Each row below is one call of recommend for a single user, the way a serving process sees a request: each request asks for the next user of its segment, so the samples cover every warm or cold user rather than one user's cached history. Sampled until 10s or 1,000 requests per row, after 10 untimed ones; q95 and q99 are nearest-rank quantiles, so each is a request that really happened.

Pipeline users requests median q95 q99
BM25 warm 1,000 21 us 27 us 34 us
BM25+CatBoost warm 1,000 666 us 858 us 1.00 ms
BM25+XGBoost warm 1,000 800 us 1.07 ms 1.21 ms
BM25+LightGBM warm 1,000 682 us 919 us 1.07 ms
BM25+Blend warm 1,000 1.55 ms 1.77 ms 1.93 ms
EASE warm 1,000 32 us 83 us 117 us
BM25+EASE RRF warm 1,000 240 us 328 us 387 us
BM25+RRF warm 1,000 358 us 573 us 682 us
BM25+RRF boosters warm 1,000 1.49 ms 1.74 ms 1.97 ms
BM25 cold 1,000 24 us 26 us 35 us
BM25+CatBoost cold 1,000 392 us 446 us 486 us
BM25+XGBoost cold 1,000 565 us 751 us 845 us
BM25+LightGBM cold 1,000 443 us 506 us 567 us
BM25+Blend cold 1,000 1.28 ms 1.41 ms 1.49 ms
EASE cold 1,000 24 us 26 us 34 us
BM25+EASE RRF cold 1,000 26 us 33 us 48 us
BM25+RRF cold 1,000 314 us 418 us 588 us
BM25+RRF boosters cold 1,000 1.20 ms 1.33 ms 1.42 ms

The first 3 cold users by id, and the top 5 of what each pipeline serves them. Bold titles are ones the user rated in the held-out set; held out is how many they rated in all.

cold user age, gender, occupation held out BM25 BM25+CatBoost BM25+XGBoost BM25+LightGBM BM25+Blend EASE BM25+EASE RRF BM25+RRF BM25+RRF boosters
9 29, M, student 22 Star Wars (1977)
Fargo (1996)
Return of the Jedi (1983)
Contact (1997)
Liar Liar (1997)
Star Wars (1977)
Contact (1997)
Return of the Jedi (1983)
Fargo (1996)
Independence Day (ID4) (1996)
Star Wars (1977)
English Patient, The (1996)
Contact (1997)
Independence Day (ID4) (1996)
Godfather, The (1972)
Star Wars (1977)
Contact (1997)
Independence Day (ID4) (1996)
Godfather, The (1972)
Scream (1996)
Star Wars (1977)
Contact (1997)
Independence Day (ID4) (1996)
Liar Liar (1997)
Fargo (1996)
Star Wars (1977)
Fargo (1996)
Return of the Jedi (1983)
Contact (1997)
Liar Liar (1997)
Star Wars (1977)
Fargo (1996)
Return of the Jedi (1983)
Contact (1997)
Liar Liar (1997)
Twelve Monkeys (1995)
Return of the Jedi (1983)
Star Wars (1977)
Toy Story (1995)
Scream (1996)
Star Wars (1977)
Contact (1997)
Independence Day (ID4) (1996)
Godfather, The (1972)
Twelve Monkeys (1995)
15 49, F, educator 104 Star Wars (1977)
Fargo (1996)
Return of the Jedi (1983)
Contact (1997)
Liar Liar (1997)
English Patient, The (1996)
Star Wars (1977)
Fargo (1996)
Air Force One (1997)
Contact (1997)
English Patient, The (1996)
Air Force One (1997)
Fargo (1996)
Contact (1997)
Star Wars (1977)
English Patient, The (1996)
Air Force One (1997)
Contact (1997)
Star Wars (1977)
Fargo (1996)
English Patient, The (1996)
Fargo (1996)
Air Force One (1997)
Star Wars (1977)
Contact (1997)
Star Wars (1977)
Fargo (1996)
Return of the Jedi (1983)
Contact (1997)
Liar Liar (1997)
Star Wars (1977)
Fargo (1996)
Return of the Jedi (1983)
Contact (1997)
Liar Liar (1997)
English Patient, The (1996)
Full Monty, The (1997)
Fargo (1996)
Air Force One (1997)
Star Wars (1977)
English Patient, The (1996)
Air Force One (1997)
Star Wars (1977)
Fargo (1996)
Contact (1997)
32 28, F, student 41 Star Wars (1977)
Fargo (1996)
Return of the Jedi (1983)
Contact (1997)
Liar Liar (1997)
Contact (1997)
Liar Liar (1997)
Star Wars (1977)
Scream (1996)
Toy Story (1995)
English Patient, The (1996)
Contact (1997)
Liar Liar (1997)
Scream (1996)
Fargo (1996)
English Patient, The (1996)
Contact (1997)
Scream (1996)
Star Wars (1977)
Liar Liar (1997)
Contact (1997)
Liar Liar (1997)
English Patient, The (1996)
Scream (1996)
Star Wars (1977)
Star Wars (1977)
Fargo (1996)
Return of the Jedi (1983)
Contact (1997)
Liar Liar (1997)
Star Wars (1977)
Fargo (1996)
Return of the Jedi (1983)
Contact (1997)
Liar Liar (1997)
Scream (1996)
Star Wars (1977)
Toy Story (1995)
Liar Liar (1997)
Return of the Jedi (1983)
Contact (1997)
English Patient, The (1996)
Liar Liar (1997)
Scream (1996)
Star Wars (1977)

MovieLens 1M

The same pipelines and settings on MovieLens 1M, split the same way: 5,436 warm users and 604 cold ones, six times as many as MovieLens 100K has, so differences that are noise there can be told apart here.

MovieLens 1M, ColdStartSplit: 10% of users held out whole, the latest 20% of every other user's ratings held out, k=10. Regenerate with python benchmarks/run.py run reranking --dataset movielens-1m-cold.

Pipeline users n users NDCG@10 P@10 R@10 hit rate MAP MRR cat cov mean pop novelty
BM25 all 6,040 0.1725 0.1583 0.0613 0.6101 0.0979 0.3090 0.1092 1596.1259 8.94
BM25+CatBoost all 6,040 0.1916 0.1744 0.0734 0.6662 0.1087 0.3431 0.2165 1492.6599 9.09
BM25+XGBoost all 6,040 0.1871 0.1686 0.0726 0.6781 0.1034 0.3469 0.2334 1411.7101 9.21
BM25+LightGBM all 6,040 0.1899 0.1725 0.0740 0.6879 0.1049 0.3476 0.2373 1386.8866 9.24
BM25+Blend all 6,040 0.1927 0.1753 0.0744 0.6815 0.1082 0.3483 0.2239 1465.7283 9.13
EASE all 6,040 0.1670 0.1515 0.0649 0.6333 0.0919 0.3097 0.3519 1245.7657 9.44
BM25+EASE RRF all 6,040 0.1763 0.1610 0.0661 0.6411 0.0984 0.3213 0.1814 1444.2125 9.13
BM25+RRF all 6,040 0.1768 0.1628 0.0656 0.6379 0.0993 0.3159 0.1609 1459.5701 9.10
BM25+RRF boosters all 6,040 0.1924 0.1741 0.0741 0.6839 0.1073 0.3517 0.2313 1439.3425 9.17
BM25 warm 5,436 0.1379 0.1249 0.0634 0.5747 0.0685 0.2636 0.1092 1518.0176 9.01
BM25+CatBoost warm 5,436 0.1572 0.1401 0.0766 0.6365 0.0787 0.3027 0.2165 1412.7076 9.17
BM25+XGBoost warm 5,436 0.1529 0.1346 0.0758 0.6501 0.0737 0.3058 0.2334 1325.7490 9.30
BM25+LightGBM warm 5,436 0.1561 0.1388 0.0773 0.6613 0.0754 0.3066 0.2373 1300.2331 9.33
BM25+Blend warm 5,436 0.1585 0.1411 0.0777 0.6538 0.0783 0.3086 0.2239 1383.5051 9.21
EASE warm 5,436 0.1318 0.1173 0.0674 0.6004 0.0618 0.2644 0.3519 1128.7286 9.57
BM25+EASE RRF warm 5,436 0.1422 0.1279 0.0687 0.6091 0.0690 0.2773 0.1814 1349.2249 9.22
BM25+RRF warm 5,436 0.1424 0.1281 0.0680 0.6054 0.0696 0.2777 0.1609 1379.4854 9.18
BM25+RRF boosters warm 5,436 0.1585 0.1403 0.0774 0.6560 0.0778 0.3117 0.2313 1355.6555 9.25
BM25 cold 604 0.4833 0.4589 0.0421 0.9288 0.3626 0.7173 0.0027 2299.1000 8.31
BM25+CatBoost cold 604 0.5010 0.4829 0.0452 0.9338 0.3779 0.7065 0.0096 2212.2300 8.37
BM25+XGBoost cold 604 0.4953 0.4750 0.0441 0.9305 0.3708 0.7171 0.0112 2185.3604 8.39
BM25+LightGBM cold 604 0.4947 0.4755 0.0443 0.9272 0.3698 0.7165 0.0120 2166.7677 8.40
BM25+Blend cold 604 0.5006 0.4828 0.0450 0.9305 0.3776 0.7050 0.0096 2205.7369 8.37
EASE cold 604 0.4833 0.4589 0.0421 0.9288 0.3626 0.7173 0.0027 2299.1000 8.31
BM25+EASE RRF cold 604 0.4833 0.4589 0.0421 0.9288 0.3626 0.7173 0.0027 2299.1000 8.31
BM25+RRF cold 604 0.4858 0.4753 0.0438 0.9305 0.3672 0.6595 0.0107 2180.3326 8.39
BM25+RRF boosters cold 604 0.4976 0.4781 0.0444 0.9354 0.3734 0.7116 0.0099 2192.5253 8.38

Measured on Apple M4 Pro (12 usable cores), macOS-27.0-arm64-arm-64bit-Mach-O, CPython 3.14.3, numpy 2.5.3, skrecsys 0.5.0, _core built in release mode. The quality table above is deterministic and portable; the timings below are not comparable across machines or builds.

Each row below is one call of recommend for a single user, the way a serving process sees a request: each request asks for the next user of its segment, so the samples cover every warm or cold user rather than one user's cached history. Sampled until 10s or 1,000 requests per row, after 10 untimed ones; q95 and q99 are nearest-rank quantiles, so each is a request that really happened.

Pipeline users requests median q95 q99
BM25 warm 1,000 25 us 44 us 57 us
BM25+CatBoost warm 1,000 785 us 1.38 ms 1.85 ms
BM25+XGBoost warm 1,000 901 us 1.48 ms 2.07 ms
BM25+LightGBM warm 1,000 779 us 1.33 ms 1.82 ms
BM25+Blend warm 1,000 1.63 ms 2.26 ms 2.80 ms
EASE warm 1,000 71 us 334 us 542 us
BM25+EASE RRF warm 1,000 288 us 617 us 869 us
BM25+RRF warm 1,000 388 us 895 us 1.41 ms
BM25+RRF boosters warm 1,000 1.55 ms 2.15 ms 2.76 ms
BM25 cold 1,000 28 us 33 us 72 us
BM25+CatBoost cold 1,000 406 us 465 us 503 us
BM25+XGBoost cold 1,000 592 us 714 us 843 us
BM25+LightGBM cold 1,000 430 us 504 us 562 us
BM25+Blend cold 1,000 1.24 ms 1.34 ms 1.48 ms
EASE cold 1,000 28 us 29 us 36 us
BM25+EASE RRF cold 1,000 28 us 29 us 36 us
BM25+RRF cold 1,000 317 us 332 us 379 us
BM25+RRF boosters cold 1,000 1.16 ms 1.27 ms 1.34 ms

The first 3 cold users by id, and the top 5 of what each pipeline serves them. Bold titles are ones the user rated in the held-out set; held out is how many they rated in all.

cold user age, gender, occupation held out BM25 BM25+CatBoost BM25+XGBoost BM25+LightGBM BM25+Blend EASE BM25+EASE RRF BM25+RRF BM25+RRF boosters
2 56, M, 16 129 American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Star Wars: Episode VI - Return of the Jedi (1983)
Jurassic Park (1993)
American Beauty (1999)
Star Wars: Episode IV - A New Hope (1977)
Saving Private Ryan (1998)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode VI - Return of the Jedi (1983)
American Beauty (1999)
Fargo (1996)
Jurassic Park (1993)
Saving Private Ryan (1998)
Star Wars: Episode V - The Empire Strikes Back (1980)
American Beauty (1999)
L.A. Confidential (1997)
Fargo (1996)
Silence of the Lambs, The (1991)
Star Wars: Episode V - The Empire Strikes Back (1980)
American Beauty (1999)
Star Wars: Episode IV - A New Hope (1977)
Saving Private Ryan (1998)
Star Wars: Episode V - The Empire Strikes Back (1980)
Fargo (1996)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Star Wars: Episode VI - Return of the Jedi (1983)
Jurassic Park (1993)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Star Wars: Episode VI - Return of the Jedi (1983)
Jurassic Park (1993)
American Beauty (1999)
Star Wars: Episode IV - A New Hope (1977)
Saving Private Ryan (1998)
Jurassic Park (1993)
Fargo (1996)
American Beauty (1999)
Fargo (1996)
Star Wars: Episode V - The Empire Strikes Back (1980)
Jurassic Park (1993)
Saving Private Ryan (1998)
13 45, M, 1 108 American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Star Wars: Episode VI - Return of the Jedi (1983)
Jurassic Park (1993)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Jurassic Park (1993)
Star Wars: Episode VI - Return of the Jedi (1983)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Jurassic Park (1993)
Star Wars: Episode IV - A New Hope (1977)
Fargo (1996)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Jurassic Park (1993)
Fargo (1996)
Terminator 2: Judgment Day (1991)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Jurassic Park (1993)
Star Wars: Episode VI - Return of the Jedi (1983)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Star Wars: Episode VI - Return of the Jedi (1983)
Jurassic Park (1993)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Star Wars: Episode VI - Return of the Jedi (1983)
Jurassic Park (1993)
Star Wars: Episode IV - A New Hope (1977)
Star Wars: Episode V - The Empire Strikes Back (1980)
American Beauty (1999)
Jurassic Park (1993)
Shakespeare in Love (1998)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Jurassic Park (1993)
Star Wars: Episode IV - A New Hope (1977)
Fargo (1996)
15 25, M, 7 201 American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Star Wars: Episode VI - Return of the Jedi (1983)
Jurassic Park (1993)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode VI - Return of the Jedi (1983)
Star Wars: Episode IV - A New Hope (1977)
Terminator 2: Judgment Day (1991)
American Beauty (1999)
Star Wars: Episode VI - Return of the Jedi (1983)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Matrix, The (1999)
American Beauty (1999)
Star Wars: Episode VI - Return of the Jedi (1983)
Star Wars: Episode IV - A New Hope (1977)
Star Wars: Episode V - The Empire Strikes Back (1980)
Matrix, The (1999)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode VI - Return of the Jedi (1983)
Star Wars: Episode IV - A New Hope (1977)
Terminator 2: Judgment Day (1991)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Star Wars: Episode VI - Return of the Jedi (1983)
Jurassic Park (1993)
American Beauty (1999)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Star Wars: Episode VI - Return of the Jedi (1983)
Jurassic Park (1993)
Terminator 2: Judgment Day (1991)
Star Wars: Episode IV - A New Hope (1977)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode VI - Return of the Jedi (1983)
Raiders of the Lost Ark (1981)
American Beauty (1999)
Star Wars: Episode VI - Return of the Jedi (1983)
Star Wars: Episode V - The Empire Strikes Back (1980)
Star Wars: Episode IV - A New Hope (1977)
Matrix, The (1999)

What the rankers buy

On MovieLens 100K, every trained reranker beats the baseline on every segment, and MovieLens 1M agrees. The paragraphs below follow CatBoost on MovieLens 100K, the strongest of them. For the 849 warm users, NDCG@10 rises from 0.170 to 0.205, a fifth better, and every other ranking metric moves with it: hit rate from 0.66 to 0.74, MAP from 0.087 to 0.107. It also recommends less of the head. Catalog coverage goes from a tenth to a quarter and mean pop drops from 256 to 210, because BM25's top 100 holds plenty of less popular films that the ranker's other signals can lift into the top 10. The largest single gain in tuning came from RecommenderScores(EASE()). A second model with a different inductive bias gives the ranker something to weigh BM25's score against. The demographic columns on their own added little.

The 94 cold users are a harder case. Their baseline is already strong, because MovieLens users rate the popular films, and a cold user's whole history is held out. NDCG@10 still rises from 0.527 to 0.549, MAP from 0.370 to 0.405 and MRR from 0.725 to 0.748. Recall@10 falls slightly, from 0.094 to 0.087, so the reranker puts the hits it finds higher rather than finding more of them. The examples show the mechanism: the popularity baseline serves every cold user the same five films, while the reranker gives the 49-year-old educator The English Patient and Air Force One and the 28-year-old student Scream. The segment features only work because of how the ranker is trained. Trained on warm users, whose candidates have their own history removed, the same features scored below the popularity baseline during development: the ranker learned from candidate lists that no cold user ever gets.

The blend does not beat CatBoost here. It comes second on NDCG@10 in every segment: 0.201 on warm users against CatBoost's 0.205, and 0.541 on cold users against 0.549. It is ahead of XGBoost (0.199, 0.532) and LightGBM (0.198, 0.535). The spread between the three boosters is small, and they learn from identical features, so they mostly agree and there is little for a blender to combine. Other variants did no better during tuning. Z-score normalization, five folds and a plain average (blender=None) came within 0.003 of CatBoost on warm users and 0.003–0.011 under it on cold users. Passing the original features to the logistic blender, or using a gradient-boosted blender, did worse. None of them beat CatBoost alone. What the blend buys here is not having to know in advance which booster wins, and it charges for that. Its request runs all three boosters, so it costs 1.6 ms on a warm user against CatBoost's 0.67 ms. Blending pays when its members disagree, for example when they are trained on different features or different candidate sources, and this setup does not give them that.

The price of any reranker is latency. With CatBoost, a warm user's request goes from about 20 µs to about 0.67 ms at the median and 1 ms at q99, and a cold user's to about 0.4 ms. BM25 alone answers in 21 µs, so at least 95% of the reranked request is the second stage: featurizing 100 candidates and running the tree ensemble over them. That fits comfortably in a request budget, but a service ranking per request should know where its time goes.

Reciprocal rank fusion

Fusing two recommenders gets the better of them without having to know which one it is. Which of BM25 and EASE is stronger depends on the dataset: EASE on MovieLens 100K (0.187 NDCG@10 on warm users against 0.170), BM25 on MovieLens 1M (0.138 against 0.132). BM25+EASE RRF, which learns nothing, is never measurably worse than the stronger one, and on MovieLens 1M it beats both, at 0.142. A paired bootstrap over users (2,000 resamples) puts the gains at:

Warm users RRF − BM25 (95% CI) RRF − EASE (95% CI)
MovieLens 100K, 849 users +0.016 [+0.009, +0.022] −0.001 [−0.007, +0.005]
MovieLens 1M, 5,436 users +0.004 [+0.002, +0.007] +0.010 [+0.008, +0.013]

The fusion also recalls more and covers more of the catalog than BM25 (0.18 of it against 0.11 on MovieLens 1M). A request costs about 0.25–0.3 ms, because both members retrieve 100 items. It is still a third of what a booster costs, and there is no training run to schedule. The trained rankers gain more on warm users, 0.157 against 0.142 on MovieLens 1M, because they also learn from the demographics, genres and counts. Fusion is the step before a ranker, or the choice when there is nothing to train one on.

Fusing the three boosters by rank matches stacking them, at a quarter of the fits. On MovieLens 1M, BM25+RRF boosters and BM25+Blend both reach 0.192 NDCG@10 over all users. The fusion is slightly ahead on warm users (0.158, the best warm row) and behind on cold ones (0.497 against 0.501). MovieLens 100K agrees, within 0.001 in each segment. The blend fits every booster once per fold and once more for serving, twelve fits per branch, while the fusion fits each booster once. Neither beats CatBoost alone by a margin that matters, for the reason given above. A request costs the same either way, because both run all three boosters.

As a cold-start ranker, fusion helps but a trained ranker helps more. Fusing popularity with segment popularity (BM25+RRF) lifts cold users' NDCG@10 over the popularity baseline on both datasets, from 0.527 to 0.544 and from 0.483 to 0.486. On MovieLens 100K it even had the best cold MAP of any row, but that rested on 94 users. On MovieLens 1M's 604, CatBoost's 0.501 is clearly ahead.