Skip to content

feat: Phase 10 two-stage retrieval and re-rank (ADR-010) - #12

Merged
sathariels merged 1 commit into
mainfrom
cursor/phase-10-two-stage-rerank-e264
Oct 23, 2026
Merged

sathariels merged 1 commit into
mainfrom
cursor/phase-10-two-stage-rerank-e264

Conversation

@sathariels

Copy link
Copy Markdown
Owner

Summary

Phase 10 adds an optional second ranker on top of the ADR-009 embedding API.

  • ADR-010 records the design. Stage 1 is the existing NumPy dot-product retrieval (dot + biases), excluding train-seen items, keeping the top N (default 200, and N must be at least K). Ties stay score descending, then smaller item id.
  • Stage 2 is a small NumPy MLP (one ReLU hidden layer plus a linear skip), trained with listwise softmax. Features, in order: retrieval score, train-only log1p popularity, train-only genre affinity from u.item, and train-only item recency. It is not LightGBM and not PyTorch, so the HTTP process stays free of PyTorch, PyG, and graph_sampler.
  • Training uses the ADR-003 leave-one-out split. The published stage-1 model is implicit MF (movielens_100k_config, seed 7). The MLP is fit on validation items that stage 1 retrieved. Test pools drop train and validation positives. Both systems rank the same stage-1 pool.
  • GET /recommend gains rerank (default false, so the ADR-009 order is unchanged) and n. rerank=true requires SAGEREC_RERANK_ARTIFACT and returns score plus retrieval_score. Unknown users stay 404. A missing artifact or n < k is 422.
  • The load test protocol is serving-latency-v2 and records p50/p95/p99 and throughput for both modes.

MovieLens 100K quality (results/rerank_eval.json)

Single seed 7, 943 eligible users, stage-1 N = 200. Copied from the measured file (six-decimal rendering).

System Recall@10 NDCG@10 MAP@10
retrieval only 0.038176 0.020303 0.014950
retrieval + re-rank 0.053022 0.026510 0.018500
delta (re-rank − retrieval) 0.014846 0.006207 0.003550

Stage-1 hit rate at 200 is 0.357370. The MLP trained on 442 validation hits. Listwise loss moved from 5.181280 to 5.172139 (uniform over 200 items is about 5.30).

Retrieval-only Recall@10 (0.03817603393425239) and NDCG@10 (0.020303175177886473) match results/mf_movielens_100k.json, because N is at least 10 and the tie-break matches.

Latency (results/serving_latency.json)

Synthetic catalog (943 users, 1682 items, dim 16, seed 7) and a seeded synthetic MLP. 1000 measured requests per mode, 100 warm-up, concurrency 16, k=10, N=200. Local uvicorn. Top-level latency_ms duplicates retrieval-only.

Mode p50 ms p95 ms p99 ms requests/s
retrieval_only 23.443567 66.870426 93.590268 534.012774
retrieval_plus_rerank 21.012950 66.638233 94.781428 568.753940

The re-rank p50 is not higher than retrieval-only. That gap is HTTP variance on one machine, not evidence that the MLP is faster. The latency budget is a second in-process NumPy pass over N candidates. There is no numeric SLO.

Caveats

  • Quality numbers are MovieLens 100K, implicit MF stage 1, one seed. They are not a GraphSAGE result and they do not replace the Phase 5 comparison or the ADR-007 five-seed leaderboard.
  • MAP is MAP@10 (one relevant item: 1/rank if rank ≤ 10, else 0).
  • Re-ranking cannot recover a test item outside the top 200. Hit rate at 200 is 0.357370, so most test items are never scored by stage 2.
  • Features use train interactions only. Genres come from u.item. Serving only masks train positives; the offline test pools also drop the validation item.
  • The committed latency catalog and the timed re-ranker are synthetic. The MovieLens-trained .npz stays under gitignored results/serving/.
  • git_dirty was true when both result files were generated, so git_commit is the HEAD the process saw and does not contain those files.
  • Default CI runs the synthetic feature, MLP, and API tests. It does not download MovieLens, run the 100K re-rank job, or regenerate the latency files.

Tests

Local ctest --test-dir build : 18/18 passed, including unittest discovery (test_rerank.py) and pytest python_serving_api.

Open in Web Open in Cursor 

Stage 1 keeps the embedding top-N. Stage 2 is a NumPy listwise MLP on train-only popularity, genre affinity, and recency. GET /recommend can re-rank when an artifact is loaded. The MovieLens comparison and both-mode latency files are measured runs.
@sathariels
sathariels merged commit 6017eb8 into main Oct 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant