use crate::ANNResult;
use diskann_utils::views::rowmajor::{self, Matrix, MatrixMut};
use super::{
check_output_rows, distance_scratch,
leaf_metric::LeafMetric,
simd::Simd,
topk::{Candidate, select_top_k_symmetric},
};
#[derive(Debug, Default)]
pub(super) struct LeafKernelWorkspace {
distance_scratch: Vec<f32>,
kth_distances: Vec<f32>,
}
pub(super) fn select_leaf_neighbors<A, M>(
arch: A,
points: rowmajor::Ref<'_, f32>,
output: rowmajor::Mut<'_, Candidate>,
workspace: &mut LeafKernelWorkspace,
) -> ANNResult<()>
where
A: Simd,
M: LeafMetric,
{
let point_count = points.nrows();
check_output_rows(point_count, output.nrows())?;
let mut distances =
distance_scratch(&mut workspace.distance_scratch, point_count, point_count)?;
M::compute_distances(points, distances.as_view_mut())?;
select_top_k_symmetric(
arch,
distances.as_view(),
output,
&mut workspace.kth_distances,
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::pipnn::test_support::{self, ArchCheck, for_each_arch};
use crate::graph::pipnn::{Cosine, CosineNormalized, InnerProduct, L2};
use diskann_vector::distance::Metric;
use diskann_wide::ARCH;
use rstest::rstest;
use std::marker::PhantomData;
#[rstest]
#[case::l2(L2, Metric::L2, [[1,3,2,4], [0,2,3,4], [4,0,1,3], [0,2,1,4], [2,1,0,3]])]
#[case::cosine(Cosine, Metric::Cosine, [[3,1,2,4], [0,3,4,2], [4,3,0,1], [0,2,1,4], [2,1,3,0]])]
#[case::normalized_cosine(CosineNormalized, Metric::CosineNormalized, [[3,1,2,4], [0,3,4,2], [4,3,0,1], [0,2,1,4], [2,1,3,0]])]
#[case::inner_product(InnerProduct, Metric::InnerProduct, [[3,1,2,4], [0,3,2,4], [4,3,0,1], [0,2,1,4], [2,1,0,3]])]
fn neighbors_are_other_points_of_the_leaf_in_metric_order<M: LeafMetric>(
#[case] _metric: M,
#[case] scalar_metric: Metric,
#[case] expected_ids: [[u32; 4]; 5],
) {
let coordinates = [
[2.0, 2.0],
[-1.0, 3.0],
[0.0, -2.0],
[5.0, 0.0],
[-3.0, -4.0],
];
let values =
test_support::packed_points(&coordinates, 2, scalar_metric == Metric::CosineNormalized);
let points = rowmajor::Ref::try_from_data(values.as_slice(), 5, 2).unwrap();
for neighbors in [0, 1, 2, 3, 4, 6] {
let mut output = vec![Candidate::new(0, -100.0); 5 * neighbors];
select_leaf_neighbors::<_, M>(
ARCH,
points,
rowmajor::Mut::try_from_data(output.as_mut_slice(), 5, neighbors).unwrap(),
&mut LeafKernelWorkspace::default(),
)
.unwrap_or_else(|error| panic!("neighbors={neighbors}: {error}"));
let filled = neighbors.min(4);
for point in 0..5 {
let actual = &output[point * neighbors..(point + 1) * neighbors];
assert_eq!(
actual[..filled]
.iter()
.map(|c| c.local_idx)
.collect::<Vec<_>>(),
expected_ids[point][..filled],
"neighbors={neighbors}, point={point}"
);
assert!(
actual[filled..].iter().all(|&c| c == Candidate::EMPTY),
"neighbors={neighbors}, point={point}: {actual:?}"
);
for candidate in &actual[..filled] {
let expected = test_support::distance(
scalar_metric,
points.row(point),
points.row(candidate.local_idx as usize),
);
let tolerance = 8.0 * f64::from(f32::EPSILON) * expected.abs().max(1.0);
assert!(
(f64::from(candidate.distance) - expected).abs() <= tolerance,
"neighbors={neighbors}, point={point}, candidate={candidate:?}, expected={expected}"
);
}
}
}
}
#[rstest]
#[case::l2(L2, Metric::L2)]
#[case::cosine(Cosine, Metric::Cosine)]
#[case::normalized_cosine(CosineNormalized, Metric::CosineNormalized)]
#[case::inner_product(InnerProduct, Metric::InnerProduct)]
fn large_leaves_select_the_nearest_neighbors_on_every_architecture<M: LeafMetric>(
#[case] _metric: M,
#[case] metric: Metric,
) {
struct LargeLeaves<M> {
metric: Metric,
leaf_metric: PhantomData<M>,
}
impl<M: LeafMetric> ArchCheck for LargeLeaves<M> {
fn check<A: Simd>(&self, arch: A) {
let metric = self.metric;
let arch_name = std::any::type_name::<A>();
for (point_count, dimensions) in [(35, 129), (129, 1536)] {
let mut values = test_support::dense_points(point_count, dimensions, 1287);
if metric == Metric::CosineNormalized {
test_support::normalize(&mut values, dimensions);
}
let points =
rowmajor::Ref::try_from_data(values.as_slice(), point_count, dimensions)
.unwrap();
let tolerance = test_support::dense_tolerance(metric, dimensions);
let oracle: Vec<Vec<(u32, f64)>> = (0..point_count)
.map(|point| {
let mut others: Vec<_> = (0..point_count)
.filter(|&other| other != point)
.map(|other| {
let distance = test_support::distance(
metric,
points.row(point),
points.row(other),
);
(other as u32, distance)
})
.collect();
others.sort_by(|left, right| left.1.total_cmp(&right.1));
others
})
.collect();
for neighbors in [3, 11] {
let context = format!(
"{arch_name}, {metric:?}, shape=({point_count}, {dimensions}), neighbors={neighbors}"
);
let mut output = vec![Candidate::EMPTY; point_count * neighbors];
select_leaf_neighbors::<A, M>(
arch,
points,
rowmajor::Mut::try_from_data(
output.as_mut_slice(),
point_count,
neighbors,
)
.unwrap(),
&mut LeafKernelWorkspace::default(),
)
.unwrap_or_else(|error| panic!("{context}: {error}"));
for (point, expected) in oracle.iter().enumerate() {
let actual = &output[point * neighbors..(point + 1) * neighbors];
for (rank, candidate) in actual.iter().enumerate() {
assert!(
!actual[..rank]
.iter()
.any(|previous| previous.local_idx == candidate.local_idx),
"{context}, point={point}: duplicate {candidate:?}"
);
let own_distance = expected
.iter()
.find(|&&(id, _)| id == candidate.local_idx)
.unwrap_or_else(|| {
panic!("{context}, point={point}: invalid or self {candidate:?}")
})
.1;
assert!(
(f64::from(candidate.distance) - own_distance).abs()
<= tolerance,
"{context}, point={point}: {candidate:?} != {own_distance}"
);
assert!(
(own_distance - expected[rank].1).abs() <= tolerance,
"{context}, point={point}, rank={rank}: {own_distance} != {:?}",
expected[rank]
);
}
}
}
}
}
}
for_each_arch(&LargeLeaves::<M> {
metric,
leaf_metric: PhantomData,
});
}
#[test]
fn workspace_reuse_does_not_mix_results_from_different_leaves() {
let values = [0.0, 1.0, 4.0, 10.0, 21.0];
let mut workspace = LeafKernelWorkspace::default();
let mut output = Vec::new();
for (count, neighbors) in [(4, 1), (2, 1), (5, 3), (1, 2)] {
output.resize(count * neighbors, Candidate::new(4, -100.0));
let points = rowmajor::Ref::try_from_data(&values[..count], count, 1).unwrap();
select_leaf_neighbors::<_, L2>(
ARCH,
points,
rowmajor::Mut::try_from_data(output.as_mut_slice(), count, neighbors).unwrap(),
&mut workspace,
)
.unwrap();
for point in 0..count {
let mut expected: Vec<_> = (0..count)
.filter(|&other| other != point)
.map(|other| {
Candidate::new(other as u32, (values[point] - values[other]).powi(2))
})
.collect();
expected.sort_by(|a, b| a.distance.total_cmp(&b.distance));
expected.resize(neighbors, Candidate::EMPTY);
assert_eq!(
&output[point * neighbors..(point + 1) * neighbors],
expected,
"count={count}, neighbors={neighbors}, point={point}"
);
}
}
}
#[test]
fn output_without_one_row_per_point_is_rejected() {
let values = [1.0, 2.0, 4.0];
let mut output = [Candidate::EMPTY; 2];
let error = select_leaf_neighbors::<_, L2>(
ARCH,
rowmajor::Ref::try_from_data(&values[..], 3, 1).unwrap(),
rowmajor::Mut::try_from_data(&mut output[..], 2, 1).unwrap(),
&mut LeafKernelWorkspace::default(),
)
.unwrap_err();
assert!(
error
.to_string()
.contains("invalid kernel output row count 2 for 3 points"),
"{error}"
);
}
#[test]
fn ranking_reads_only_pairs_in_the_strict_lower_triangle() {
let distances = [
-100.0, -100.0, -100.0, -100.0, 7.0, -100.0, -100.0, -100.0, 3.0, 8.0, -100.0, -100.0,
5.0, 2.0, 6.0, -100.0,
];
let mut output = [Candidate::new(0, -200.0); 8];
let mut kth_distances = vec![-200.0; 4];
select_top_k_symmetric(
ARCH,
rowmajor::Ref::try_from_data(&distances[..], 4, 4).unwrap(),
rowmajor::Mut::try_from_data(&mut output[..], 4, 2).unwrap(),
&mut kth_distances,
);
let expected = [
[Candidate::new(2, 3.0), Candidate::new(3, 5.0)],
[Candidate::new(3, 2.0), Candidate::new(0, 7.0)],
[Candidate::new(0, 3.0), Candidate::new(3, 6.0)],
[Candidate::new(1, 2.0), Candidate::new(0, 5.0)],
];
assert_eq!(output, expected.as_flattened());
assert_eq!(kth_distances, [5.0, 7.0, 6.0, 5.0]);
}
}