mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-07-09 17:28:42 +00:00
chore(gnn-rerank): cargo fmt — fix pre-existing rustfmt CI blocker
This formatting diff has blocked every PR's rustfmt check for weeks. Formatting only (no logic changes). Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
aa17345a9c
commit
c8af857714
5 changed files with 109 additions and 30 deletions
|
|
@ -35,8 +35,7 @@ impl CandidateGraph {
|
|||
// Cosine similarity is symmetric: sim(i,j) == sim(j,i). Compute each
|
||||
// pair once (upper triangle) and push it into both neighbour lists,
|
||||
// halving the dot-product work vs. the naive O(n²) double computation.
|
||||
let mut sims: Vec<Vec<(usize, f32)>> =
|
||||
vec![Vec::with_capacity(n.saturating_sub(1)); n];
|
||||
let mut sims: Vec<Vec<(usize, f32)>> = vec![Vec::with_capacity(n.saturating_sub(1)); n];
|
||||
for i in 0..n {
|
||||
let (vi, ni) = (&candidates[i].vector, norms[i]);
|
||||
for j in (i + 1)..n {
|
||||
|
|
|
|||
|
|
@ -71,10 +71,14 @@ fn validate(candidates: &[Candidate], k: usize) -> Result<(), RerankerError> {
|
|||
});
|
||||
}
|
||||
if !c.noisy_score.is_finite() {
|
||||
return Err(RerankerError::NonFinite { what: "candidate score" });
|
||||
return Err(RerankerError::NonFinite {
|
||||
what: "candidate score",
|
||||
});
|
||||
}
|
||||
if c.vector.iter().any(|x| !x.is_finite()) {
|
||||
return Err(RerankerError::NonFinite { what: "candidate vector" });
|
||||
return Err(RerankerError::NonFinite {
|
||||
what: "candidate vector",
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ fn build_candidate_sets() -> (Vec<Vec<f32>>, Vec<Vec<Candidate>>) {
|
|||
let queries: Vec<Vec<f32>> = (0..N_SETS)
|
||||
.map(|_| {
|
||||
let base = &corpus[rng.gen_range(0..CORPUS)];
|
||||
base.iter().map(|&x| x + rng.gen_range(-0.1_f32..0.1)).collect()
|
||||
base.iter()
|
||||
.map(|&x| x + rng.gen_range(-0.1_f32..0.1))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let noise = Normal::new(0.0_f32, 0.40).unwrap();
|
||||
|
|
@ -51,14 +53,22 @@ fn build_candidate_sets() -> (Vec<Vec<f32>>, Vec<Vec<Candidate>>) {
|
|||
scored
|
||||
.into_iter()
|
||||
.take(RETRIEVAL_K)
|
||||
.map(|(id, s)| Candidate { id: id as u32, vector: corpus[id].clone(), noisy_score: s })
|
||||
.map(|(id, s)| Candidate {
|
||||
id: id as u32,
|
||||
vector: corpus[id].clone(),
|
||||
noisy_score: s,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect();
|
||||
(queries, sets)
|
||||
}
|
||||
|
||||
fn time_reranker<R: CandidateReranker>(r: &R, queries: &[Vec<f32>], sets: &[Vec<Candidate>]) -> f64 {
|
||||
fn time_reranker<R: CandidateReranker>(
|
||||
r: &R,
|
||||
queries: &[Vec<f32>],
|
||||
sets: &[Vec<Candidate>],
|
||||
) -> f64 {
|
||||
// warm up
|
||||
for (q, c) in queries.iter().zip(sets).take(16) {
|
||||
let _ = r.rerank(q, c, K).unwrap();
|
||||
|
|
@ -83,9 +93,18 @@ fn rerank_latency_throughput() {
|
|||
let gnn_us = time_reranker(&GnnDiffusionReranker::default(), &queries, &sets);
|
||||
|
||||
eprintln!("rerank latency (DIM={DIM}, candidates={RETRIEVAL_K}, k={K}, n={N_SETS}):");
|
||||
eprintln!(" NoisyScore {noisy_us:8.2} µs/q {:.2} M QPS", 1.0 / noisy_us);
|
||||
eprintln!(" GnnDiffusion {gnn_us:8.2} µs/q {:.2} M QPS", 1.0 / gnn_us);
|
||||
eprintln!(" diffusion overhead: {:.1}× baseline", gnn_us / noisy_us.max(1e-6));
|
||||
eprintln!(
|
||||
" NoisyScore {noisy_us:8.2} µs/q {:.2} M QPS",
|
||||
1.0 / noisy_us
|
||||
);
|
||||
eprintln!(
|
||||
" GnnDiffusion {gnn_us:8.2} µs/q {:.2} M QPS",
|
||||
1.0 / gnn_us
|
||||
);
|
||||
eprintln!(
|
||||
" diffusion overhead: {:.1}× baseline",
|
||||
gnn_us / noisy_us.max(1e-6)
|
||||
);
|
||||
|
||||
assert!(
|
||||
gnn_us < BUDGET_US,
|
||||
|
|
|
|||
|
|
@ -10,9 +10,7 @@ use std::collections::HashSet;
|
|||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use rand_distr::{Distribution, Normal};
|
||||
use ruvector_gnn_rerank::{
|
||||
Candidate, CandidateReranker, GnnDiffusionReranker, NoisyScoreReranker,
|
||||
};
|
||||
use ruvector_gnn_rerank::{Candidate, CandidateReranker, GnnDiffusionReranker, NoisyScoreReranker};
|
||||
|
||||
const N: usize = 5_000;
|
||||
const DIM: usize = 128;
|
||||
|
|
@ -44,7 +42,9 @@ fn gen_queries(corpus: &[Vec<f32>], n_queries: usize, seed: u64) -> Vec<Vec<f32>
|
|||
(0..n_queries)
|
||||
.map(|_| {
|
||||
let base = &corpus[rng.gen_range(0..corpus.len())];
|
||||
base.iter().map(|&x| x + rng.gen_range(-0.1_f32..0.1)).collect()
|
||||
base.iter()
|
||||
.map(|&x| x + rng.gen_range(-0.1_f32..0.1))
|
||||
.collect()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -79,12 +79,24 @@ fn noisy_retrieve(
|
|||
scored
|
||||
.into_iter()
|
||||
.take(retrieval_k)
|
||||
.map(|(id, noisy_score)| Candidate { id: id as u32, vector: corpus[id].clone(), noisy_score })
|
||||
.map(|(id, noisy_score)| Candidate {
|
||||
id: id as u32,
|
||||
vector: corpus[id].clone(),
|
||||
noisy_score,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn recall_at_k(results: &[ruvector_gnn_rerank::RankedResult], gt: &HashSet<usize>, k: usize) -> f64 {
|
||||
let hits = results.iter().take(k).filter(|r| gt.contains(&(r.id as usize))).count();
|
||||
fn recall_at_k(
|
||||
results: &[ruvector_gnn_rerank::RankedResult],
|
||||
gt: &HashSet<usize>,
|
||||
k: usize,
|
||||
) -> f64 {
|
||||
let hits = results
|
||||
.iter()
|
||||
.take(k)
|
||||
.filter(|r| gt.contains(&(r.id as usize)))
|
||||
.count();
|
||||
hits as f64 / gt.len().min(k) as f64
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ use ruvector_gnn_rerank::{
|
|||
};
|
||||
|
||||
fn cand(id: u32, vector: Vec<f32>, noisy_score: f32) -> Candidate {
|
||||
Candidate { id, vector, noisy_score }
|
||||
Candidate {
|
||||
id,
|
||||
vector,
|
||||
noisy_score,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run an input through every variant; return whether ALL returned Ok.
|
||||
|
|
@ -28,15 +32,24 @@ fn all_variants(query: &[f32], cands: &[Candidate], k: usize) -> Vec<Result<(),
|
|||
|
||||
#[test]
|
||||
fn rejects_nan_score() {
|
||||
let cands = vec![cand(0, vec![1.0, 0.0], f32::NAN), cand(1, vec![0.0, 1.0], 0.5)];
|
||||
let cands = vec![
|
||||
cand(0, vec![1.0, 0.0], f32::NAN),
|
||||
cand(1, vec![0.0, 1.0], 0.5),
|
||||
];
|
||||
for r in all_variants(&[1.0, 0.0], &cands, 1) {
|
||||
assert!(matches!(r, Err(RerankerError::NonFinite { .. })), "NaN score must be rejected, got {r:?}");
|
||||
assert!(
|
||||
matches!(r, Err(RerankerError::NonFinite { .. })),
|
||||
"NaN score must be rejected, got {r:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_inf_score() {
|
||||
let cands = vec![cand(0, vec![1.0, 0.0], f32::INFINITY), cand(1, vec![0.0, 1.0], 0.5)];
|
||||
let cands = vec![
|
||||
cand(0, vec![1.0, 0.0], f32::INFINITY),
|
||||
cand(1, vec![0.0, 1.0], 0.5),
|
||||
];
|
||||
assert!(matches!(
|
||||
GnnDiffusionReranker::default().rerank(&[1.0, 0.0], &cands, 1),
|
||||
Err(RerankerError::NonFinite { .. })
|
||||
|
|
@ -45,7 +58,10 @@ fn rejects_inf_score() {
|
|||
|
||||
#[test]
|
||||
fn rejects_nan_in_vector() {
|
||||
let cands = vec![cand(0, vec![f32::NAN, 0.0], 0.9), cand(1, vec![0.0, 1.0], 0.5)];
|
||||
let cands = vec![
|
||||
cand(0, vec![f32::NAN, 0.0], 0.9),
|
||||
cand(1, vec![0.0, 1.0], 0.5),
|
||||
];
|
||||
assert!(matches!(
|
||||
GnnDiffusionReranker::default().rerank(&[1.0, 0.0], &cands, 1),
|
||||
Err(RerankerError::NonFinite { .. })
|
||||
|
|
@ -54,17 +70,29 @@ fn rejects_nan_in_vector() {
|
|||
|
||||
#[test]
|
||||
fn rejects_candidate_dimension_mismatch() {
|
||||
let cands = vec![cand(0, vec![1.0, 0.0, 0.0], 0.9), cand(1, vec![0.0, 1.0], 0.5)];
|
||||
let cands = vec![
|
||||
cand(0, vec![1.0, 0.0, 0.0], 0.9),
|
||||
cand(1, vec![0.0, 1.0], 0.5),
|
||||
];
|
||||
for r in all_variants(&[1.0, 0.0, 0.0], &cands, 1) {
|
||||
assert!(matches!(r, Err(RerankerError::DimMismatch { .. })), "dim mismatch must be rejected, got {r:?}");
|
||||
assert!(
|
||||
matches!(r, Err(RerankerError::DimMismatch { .. })),
|
||||
"dim mismatch must be rejected, got {r:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_empty_and_k_too_large() {
|
||||
assert!(matches!(GnnDiffusionReranker::default().rerank(&[1.0], &[], 1), Err(RerankerError::Empty)));
|
||||
assert!(matches!(
|
||||
GnnDiffusionReranker::default().rerank(&[1.0], &[], 1),
|
||||
Err(RerankerError::Empty)
|
||||
));
|
||||
let cands = vec![cand(0, vec![1.0], 0.5)];
|
||||
assert!(matches!(GnnDiffusionReranker::default().rerank(&[1.0], &cands, 5), Err(RerankerError::KTooLarge { .. })));
|
||||
assert!(matches!(
|
||||
GnnDiffusionReranker::default().rerank(&[1.0], &cands, 5),
|
||||
Err(RerankerError::KTooLarge { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -72,11 +100,28 @@ fn degenerate_inputs_do_not_panic() {
|
|||
// k=0 → empty result; single candidate; all-identical vectors (zero/degenerate
|
||||
// cosine); k == n. None of these may panic.
|
||||
let one = vec![cand(0, vec![1.0, 2.0], 0.5)];
|
||||
assert_eq!(GnnDiffusionReranker::default().rerank(&[1.0, 2.0], &one, 0).unwrap().len(), 0);
|
||||
assert_eq!(GnnDiffusionReranker::default().rerank(&[1.0, 2.0], &one, 1).unwrap().len(), 1);
|
||||
assert_eq!(
|
||||
GnnDiffusionReranker::default()
|
||||
.rerank(&[1.0, 2.0], &one, 0)
|
||||
.unwrap()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
GnnDiffusionReranker::default()
|
||||
.rerank(&[1.0, 2.0], &one, 1)
|
||||
.unwrap()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
|
||||
let identical: Vec<Candidate> = (0..5).map(|i| cand(i, vec![0.0, 0.0, 0.0], 0.1 * i as f32)).collect();
|
||||
let identical: Vec<Candidate> = (0..5)
|
||||
.map(|i| cand(i, vec![0.0, 0.0, 0.0], 0.1 * i as f32))
|
||||
.collect();
|
||||
for r in all_variants(&[0.0, 0.0, 0.0], &identical, 5) {
|
||||
assert!(r.is_ok(), "all-identical/zero vectors must not error or panic, got {r:?}");
|
||||
assert!(
|
||||
r.is_ok(),
|
||||
"all-identical/zero vectors must not error or panic, got {r:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue