wifi-densepose/v2/crates/wifi-densepose-ruvector/benches/ann_bench.rs

99 lines
3.5 KiB
Rust

//! Criterion bench for the ADR-261 graph-ANN index: linear scan vs float HNSW
//! vs quantized HNSW, on the shared `ann_measure` fixture.
//!
//! The authoritative recall/QPS numbers in ADR-261 come from the
//! `--no-default-features --release` test report
//! (`ann_bench_report` in `src/ann_measure.rs`), which is deterministic and
//! gate-runnable. This criterion bench times the same operations through the
//! criterion harness for stable per-op medians:
//!
//! ```text
//! cargo bench -p wifi-densepose-ruvector --bench ann_bench
//! ```
//!
//! Build is excluded from the timed region (done once in setup); only the query
//! path is measured. The fixture and both indices are identical to the report's,
//! so the bench and the report can never measure different graphs.
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use wifi_densepose_ruvector::ann_measure::{
build_indices, build_quant_bits, queries, AnnBenchParams,
};
fn bench_ann(c: &mut Criterion) {
// Modest N so the bench builds quickly; the report covers the larger N.
let p = AnnBenchParams::default_fixture(10_000);
let (float_idx, quant_idx, vectors) = build_indices(p);
// Multi-bit quant variants over the SAME graph/fixture (ADR-261 §11).
let quant_2bit = build_quant_bits(p, &vectors, 2);
let quant_4bit = build_quant_bits(p, &vectors, 4);
let qs = queries(p);
let k = p.k;
let mut group = c.benchmark_group("ann_query");
group.sample_size(20);
// Linear scan (brute force) — the no-index baseline.
group.bench_function("linear_scan", |b| {
b.iter(|| {
let mut sink = 0u64;
for q in &qs {
sink = sink.wrapping_add(float_idx.brute_force(black_box(q), k).len() as u64);
}
black_box(sink)
})
});
// Float HNSW at a mid beam width.
for &ef in &[64usize, 128] {
group.bench_function(format!("float_hnsw_ef{ef}"), |b| {
b.iter(|| {
let mut sink = 0u64;
for q in &qs {
sink = sink.wrapping_add(float_idx.search(black_box(q), k, ef).len() as u64);
}
black_box(sink)
})
});
}
// Quantized HNSW (1-bit) at matched beam widths + rerank.
for &ef in &[64usize, 128] {
let rr = k * 5;
group.bench_function(format!("quant_hnsw_1bit_ef{ef}_rr{rr}"), |b| {
b.iter(|| {
let mut sink = 0u64;
for q in &qs {
sink = sink
.wrapping_add(quant_idx.search_quantized(black_box(q), k, ef, rr).len() as u64);
}
black_box(sink)
})
});
}
// Multi-bit quant HNSW (ADR-261 §11): 2-bit and 4-bit traversal codes at a
// mid beam width, so the criterion medians show the per-bit QPS cost the
// scaling study reports against recall.
for (label, idx) in [("2bit", &quant_2bit), ("4bit", &quant_4bit)] {
for &ef in &[64usize, 128] {
let rr = k * 5;
group.bench_function(format!("quant_hnsw_{label}_ef{ef}_rr{rr}"), |b| {
b.iter(|| {
let mut sink = 0u64;
for q in &qs {
sink = sink
.wrapping_add(idx.search_quantized(black_box(q), k, ef, rr).len() as u64);
}
black_box(sink)
})
});
}
}
group.finish();
}
criterion_group!(benches, bench_ann);
criterion_main!(benches);