Accepted. Implemented on branch research/nightly/2026-05-21-gnn-rerank as
crates/ruvector-gnn-rerank. All 14 unit tests pass; build is green with
cargo build --release -p ruvector-gnn-rerank.
Benchmark (x86-64, Linux 6.18, cargo run --release, N=5K, D=128, K=10,
retrieval_k=80, noise_σ=0.40):
| Variant | recall@10 | mean µs | p50 µs | p95 µs |
|---|---|---|---|---|
| NoisyScore (baseline) | 28.0% | 0.2 | 0.2 | 0.2 |
| GnnDiffusion (1-hop, α=0.60) | 38.4% | 1006 | 997 | 1053 |
| GnnMincut (coh≥0.50, α=0.60) | 38.4% | 999 | 992 | 1025 |
| ExactL2 (oracle) | 74.9% | 13.8 | 12.5 | 16.5 |
GNN score diffusion delivers +10.4 pp recall@10 over the noisy baseline.
Every approximate ANN index — whether HNSW, DiskANN, IVF, or RaBitQ — returns
a ranked candidate set whose distance estimates contain noise. For quantised
indexes (1-bit RaBitQ, low-bit PQ, coarse IVF with small nprobe) this noise
can cause significant recall loss: items near the K-boundary swap order, pushing
true positives out of the top-K window.
Existing ruvector crates address the first stage (better indexing: RaBitQ in ADR-177, RAIRS IVF in ADR-193, filtered ACORN in ADR-187) but not the second stage (post-retrieval reranking).
The 2025–2026 literature identifies graph-based reranking as a promising direction:
- GNRR (arXiv:2406.11720): +5.8% Average Precision on TREC-DL19
- Maniscope (arXiv:2602.15860): +7% NDCG, 3.2× faster than cross-encoders
- AQR-HNSW (arXiv:2602.21600): 2.5–3.3× QPS with 98%+ recall using multi-stage reranking
No production vector database (Milvus, Qdrant, Weaviate, LanceDB, FAISS, pgvector) applies GNN message passing over the ANN candidate subgraph topology.
We introduce crates/ruvector-gnn-rerank implementing the CandidateReranker
trait with four variants:
| Variant | Algorithm | Use case |
|---|---|---|
NoisyScoreReranker |
passthrough | baseline measurement |
GnnDiffusionReranker |
1-hop score averaging | general reranking after quantised retrieval |
GnnMincutReranker |
structurally-gated diffusion | reranking with cross-cluster isolation |
ExactL2Reranker |
exact Euclidean sort | oracle and production fallback |
- Accept
ncandidates (id, full-precision vector, noisy_score). - Build a cosine k-NN graph over the candidate set: O(n² × dim).
- Run
hopsrounds of score averaging:s_i^{t+1} = α · s_i^t + (1-α) · mean_{j ∈ N(i)} s_j^t - Sort by final score; return top-k.
True top-K items occupy the same vector cluster; in the candidate k-NN graph they are mutually connected. Averaging their noisy scores cancels i.i.d. noise by the law of large numbers. False positives with artificially high noisy scores are isolated from the true cluster, so diffusion reduces rather than amplifies their apparent relevance. This is discrete graph spectral low-pass filtering.
GnnMincutReranker gates propagation on structural edge weight (cosine
similarity between candidates) rather than on score ratios. This prevents
cross-cluster score bleeding while allowing intra-cluster diffusion. Score-ratio
gating was explicitly rejected because it blocks the most important edges — those
connecting a true positive that received a low noisy score to its correctly-scored
true-positive neighbours.
- +10.4 pp recall@10 over noisy baseline (measured).
- Composable with any first-stage retriever via the
CandidateRerankertrait. - No external service dependency.
- WASM-compatible (no unsafe code, no OS dependencies).
- Production candidate for use after
ruvector-rairs/ruvector-rabitqretrieval.
- Graph construction is O(n² × dim): adds ~1ms latency for n=80, dim=128.
ExactL2Rerankeris 73× faster (14µs vs 1000µs) and provides the recall ceiling (74.9% at this noise level); GNN reranking only makes sense when exact vector comparison for all candidates is too expensive (e.g., remote fetch).- The +10.4 pp gain is relative to a noisy baseline; the oracle gap is 36.5 pp.
crates/ruvector-gnn-rerank— core library (this ADR). ✓ Done.- Integration into
ruvector-serversearch pipeline — future work. - Candidate graph construction from compressed (RaBitQ) vectors — future work.
- Adaptive alpha / hop selection via query feedback — future work.
Hardware: Intel Celeron N4020, x86-64, Linux 6.18.5, rustc 1.87.0 --release.
Data: multi-Gaussian, 20 clusters, σ=0.5, N=5K, D=128, 100 queries, K=10.
Noisy retrieval simulates quantised index: score = −L2 + N(0, 0.40²).
Full output: cargo run --release -p ruvector-gnn-rerank --bin benchmark.
- Candidate graph too sparse: if candidates are from disjoint clusters, k-NN
graph is disconnected; diffusion has no effect. Mitigation: use
ExactL2Rerankerfallback. - Noise too high / coverage too low: if true top-K items aren't in the
candidate set, no reranker can recover them. Mitigation: increase
retrieval_k. - Alpha too low: aggressive diffusion (small alpha) washes out signal; score homogenisation hurts recall. Recommendation: α ≥ 0.5.
- k_graph too small: insufficient graph density → poor noise cancellation. Recommendation: k_graph ≥ 5, typically 8.
- No file I/O, no network calls, no unsafe code.
- Candidate vectors are caller-provided; no validation of vector dimensionality across candidates (caller responsibility).
- No secrets or credentials involved.
CandidateReranker is a new trait; no existing code is modified. Integration
into ruvector-server would add a post-search pipeline stage behind a feature
flag gnn-rerank.
- Can candidate graph be built from compressed (4-bit) vectors with < 5% recall degradation, removing the need to fetch full-precision vectors?
- What is the optimal α for production workloads with real embedding models?
- Is 2-hop diffusion worth the 2× graph cost at larger n (200, 500)?
- Can the O(n²) graph construction be approximated in O(n log n)?
