Skip to content

Commit e4a904d

Browse files
author
Magdalen Manohar
committed
Merge branch 'main' of github.com:microsoft/DiskANN into users/magdalen/better_dataset_in_test_data
2 parents d00f3b1 + 0449d4d commit e4a904d

69 files changed

Lines changed: 4131 additions & 1746 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ jobs:
257257
needs: basics
258258
name: sde-baseline-tests
259259
runs-on: ubuntu-latest
260+
if:
261+
false # Disabled until we can resolve SDE binary downloads.
260262
env:
261263
# Compile for the x86-64 baseline — no AVX, no AVX2.
262264
RUSTFLAGS: "-Dwarnings -Ctarget-cpu=x86-64"
@@ -318,6 +320,8 @@ jobs:
318320
needs: basics
319321
name: sde-avx512-tests
320322
runs-on: ubuntu-latest
323+
if:
324+
false # Disabled until we can resolve SDE binary downloads.
321325
env:
322326
# Use SDE as the test runner so cargo test automatically runs binaries under emulation.
323327
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: "${{ github.workspace }}/intel-sde/sde64 -spr --"

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

diskann-benchmark-core/src/search/graph/inline.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::sync::Arc;
77

88
use diskann::{
99
ANNResult,
10-
graph::{self, glue, search::AdaptiveL},
10+
graph::{self, ext::labeled, glue, search::AdaptiveL},
1111
provider,
1212
};
1313
use diskann_utils::{future::AsyncFriendly, views::Matrix};
@@ -32,7 +32,7 @@ where
3232
index: Arc<graph::DiskANNIndex<DP>>,
3333
queries: Arc<Matrix<T>>,
3434
strategy: Strategy<S>,
35-
labels: Arc<[Arc<dyn graph::index::QueryLabelProvider<DP::InternalId>>]>,
35+
labels: Arc<[Arc<dyn labeled::QueryLabelProvider<DP::InternalId>>]>,
3636
adaptive_l: Option<AdaptiveL>,
3737
}
3838

@@ -63,7 +63,7 @@ where
6363
index: Arc<graph::DiskANNIndex<DP>>,
6464
queries: Arc<Matrix<T>>,
6565
strategy: Strategy<S>,
66-
labels: Arc<[Arc<dyn graph::index::QueryLabelProvider<DP::InternalId>>]>,
66+
labels: Arc<[Arc<dyn labeled::QueryLabelProvider<DP::InternalId>>]>,
6767
adaptive_l: Option<AdaptiveL>,
6868
) -> anyhow::Result<Arc<Self>> {
6969
strategy.length_compatible(queries.nrows())?;
@@ -89,7 +89,14 @@ where
8989
impl<DP, T, S> Search for InlineFilterSearch<DP, T, S>
9090
where
9191
DP: provider::DataProvider<Context: Default, ExternalId: search::Id>,
92-
S: for<'a> glue::DefaultSearchStrategy<'a, DP, &'a [T], DP::ExternalId> + Clone + AsyncFriendly,
92+
S: for<'a> glue::DefaultSearchStrategy<
93+
'a,
94+
DP,
95+
&'a [T],
96+
DP::ExternalId,
97+
SearchAccessor: glue::SearchAccessor,
98+
> + Clone
99+
+ AsyncFriendly,
93100
T: AsyncFriendly + Clone,
94101
{
95102
type Id = DP::ExternalId;
@@ -114,16 +121,16 @@ where
114121
O: graph::SearchOutputBuffer<DP::ExternalId> + Send,
115122
{
116123
let context = DP::Context::default();
117-
let inline_search = graph::search::InlineFilterSearch::new(
118-
*parameters,
119-
&*self.labels[index],
120-
self.adaptive_l.clone(),
121-
);
124+
let inline_search =
125+
graph::search::InlineFilterSearch::new(*parameters, self.adaptive_l.clone());
126+
let strategy =
127+
labeled::Filtered::new(self.strategy.get(index)?.clone(), &*self.labels[index]);
128+
122129
let stats = self
123130
.index
124131
.search(
125132
inline_search,
126-
self.strategy.get(index)?,
133+
&strategy,
127134
&context,
128135
self.queries.row(index),
129136
buffer,
@@ -148,13 +155,13 @@ mod tests {
148155
use super::*;
149156

150157
use crate::recall::GroundTruthMode;
151-
use diskann::graph::{index::QueryLabelProvider, test::provider};
158+
use diskann::graph::{ext::labeled::QueryLabelProvider, test::provider};
152159

153160
// A simple [`QueryLabelProvider`] that rejects odd indices.
154161
#[derive(Debug)]
155162
struct NoOdds;
156163

157-
impl graph::index::QueryLabelProvider<u32> for NoOdds {
164+
impl labeled::QueryLabelProvider<u32> for NoOdds {
158165
fn is_match(&self, id: u32) -> bool {
159166
id.is_multiple_of(2)
160167
}

diskann-benchmark-core/src/search/graph/knn.rs

Lines changed: 113 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::{
2323
};
2424

2525
/// A built-in helper for benchmarking the K-nearest neighbors method
26-
/// [`graph::DiskANNIndex::search`].
26+
/// [`graph::DiskANNIndex::search`] with optional post-processing support.
2727
///
2828
/// This is intended to be used in conjunction with [`search::search`] or
2929
/// [`search::search_all`] and provides some basic additional metrics for
@@ -32,21 +32,31 @@ use crate::{
3232
///
3333
/// The provided implementation of [`Search`] accepts [`graph::search::Knn`]
3434
/// and returns [`Metrics`] as additional output.
35+
///
36+
/// # Type Parameters
37+
///
38+
/// - `DP`: The data provider type
39+
/// - `T`: The query element type
40+
/// - `S`: The search strategy type
41+
/// - `PP`: Post-processor selector. Defaults to [`Defaulted`], which uses the
42+
/// strategy's default post-processor. Use [`KNN::with_postprocessor`] to
43+
/// supply an explicit post-processor.
3544
#[derive(Debug)]
36-
pub struct KNN<DP, T, S>
45+
pub struct KNN<DP, T, S, PP = Defaulted>
3746
where
3847
DP: provider::DataProvider,
3948
{
4049
index: Arc<graph::DiskANNIndex<DP>>,
4150
queries: Arc<Matrix<T>>,
4251
strategy: Strategy<S>,
52+
post_processor: PP,
4353
}
4454

45-
impl<DP, T, S> KNN<DP, T, S>
55+
impl<DP, T, S> KNN<DP, T, S, Defaulted>
4656
where
4757
DP: provider::DataProvider,
4858
{
49-
/// Construct a new [`KNN`] searcher.
59+
/// Construct a new [`KNN`] searcher using the strategy's default post-processor.
5060
///
5161
/// If `strategy` is one of the container variants of [`Strategy`], its length
5262
/// must match the number of rows in `queries`. If this is the case, then the
@@ -68,10 +78,98 @@ where
6878
index,
6979
queries,
7080
strategy,
81+
post_processor: Defaulted,
82+
}))
83+
}
84+
}
85+
86+
impl<DP, T, S, PP> KNN<DP, T, S, Forwarded<PP>>
87+
where
88+
DP: provider::DataProvider,
89+
{
90+
/// Construct a new [`KNN`] searcher with an explicit post-processor.
91+
///
92+
/// # Errors
93+
///
94+
/// Returns an error if the number of elements in `strategy` is not compatible with
95+
/// the number of rows in `queries`.
96+
pub fn with_postprocessor(
97+
index: Arc<graph::DiskANNIndex<DP>>,
98+
queries: Arc<Matrix<T>>,
99+
strategy: Strategy<S>,
100+
post_processor: PP,
101+
) -> anyhow::Result<Arc<Self>> {
102+
strategy.length_compatible(queries.nrows())?;
103+
104+
Ok(Arc::new(Self {
105+
index,
106+
queries,
107+
strategy,
108+
post_processor: Forwarded(post_processor),
71109
}))
72110
}
73111
}
74112

113+
impl<DP, T, S, PP> KNN<DP, T, S, PP>
114+
where
115+
DP: provider::DataProvider,
116+
{
117+
/// Access the index.
118+
pub fn index(&self) -> &Arc<graph::DiskANNIndex<DP>> {
119+
&self.index
120+
}
121+
}
122+
123+
/// Resolves a post-processor for [`KNN`] given a search strategy.
124+
///
125+
/// This trait lets [`KNN`] support both "use the strategy's default post-processor"
126+
/// ([`Defaulted`]) and "use this explicit post-processor" ([`Forwarded`]) without
127+
/// duplicating the search loop.
128+
pub trait AsPostProcessor<'a, S, DP, T>
129+
where
130+
DP: provider::DataProvider,
131+
S: glue::SearchStrategy<'a, DP, T>,
132+
{
133+
/// The concrete post-processor used for a single search.
134+
type Processor: glue::SearchPostProcess<S::SearchAccessor, T, DP::ExternalId> + Send + Sync;
135+
136+
/// Construct the post-processor to use for a single search.
137+
fn as_post_processor(&'a self, strategy: &'a S) -> Self::Processor;
138+
}
139+
140+
/// Marker indicating that [`KNN`] should use the strategy's default post-processor.
141+
#[derive(Debug, Clone, Copy)]
142+
pub struct Defaulted;
143+
144+
impl<'a, S, DP, T> AsPostProcessor<'a, S, DP, T> for Defaulted
145+
where
146+
DP: provider::DataProvider,
147+
S: glue::DefaultPostProcessor<'a, DP, T, DP::ExternalId>,
148+
{
149+
type Processor = S::Processor;
150+
151+
fn as_post_processor(&'a self, strategy: &'a S) -> Self::Processor {
152+
strategy.default_post_processor()
153+
}
154+
}
155+
156+
/// Wraps an explicit post-processor for use with [`KNN::with_postprocessor`].
157+
#[derive(Debug, Clone, Copy)]
158+
pub struct Forwarded<PP>(PP);
159+
160+
impl<'a, S, DP, T, PP> AsPostProcessor<'a, S, DP, T> for Forwarded<PP>
161+
where
162+
DP: provider::DataProvider,
163+
S: glue::SearchStrategy<'a, DP, T>,
164+
PP: glue::SearchPostProcess<S::SearchAccessor, T, DP::ExternalId> + Clone + AsyncFriendly,
165+
{
166+
type Processor = PP;
167+
168+
fn as_post_processor(&'a self, _strategy: &'a S) -> Self::Processor {
169+
self.0.clone()
170+
}
171+
}
172+
75173
/// Additional metrics collected during [`KNN`] search.
76174
///
77175
/// # Note
@@ -86,10 +184,13 @@ pub struct Metrics {
86184
pub hops: u32,
87185
}
88186

89-
impl<DP, T, S> Search for KNN<DP, T, S>
187+
impl<DP, T, S, PP> Search for KNN<DP, T, S, PP>
90188
where
91189
DP: provider::DataProvider<Context: Default, ExternalId: search::Id>,
92-
S: for<'a> glue::DefaultSearchStrategy<'a, DP, &'a [T], DP::ExternalId> + Clone + AsyncFriendly,
190+
S: for<'a> glue::SearchStrategy<'a, DP, &'a [T]> + Clone + AsyncFriendly,
191+
PP: for<'a> AsPostProcessor<'a, S, DP, &'a [T]> + AsyncFriendly,
192+
graph::search::Knn:
193+
for<'a> graph::Search<'a, DP, S, &'a [T], Output = graph::index::SearchStats>,
93194
T: AsyncFriendly + Clone,
94195
{
95196
type Id = DP::ExternalId;
@@ -115,11 +216,15 @@ where
115216
{
116217
let context = DP::Context::default();
117218
let knn_search = *parameters;
219+
let strategy = self.strategy.get(index)?;
220+
let processor = self.post_processor.as_post_processor(strategy);
221+
118222
let stats = self
119223
.index
120-
.search(
224+
.search_with(
121225
knn_search,
122-
self.strategy.get(index)?,
226+
strategy,
227+
processor,
123228
&context,
124229
self.queries.row(index),
125230
buffer,

diskann-benchmark-core/src/search/graph/multihop.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::sync::Arc;
77

88
use diskann::{
99
ANNResult,
10-
graph::{self, glue},
10+
graph::{self, ext::labeled, glue},
1111
provider,
1212
};
1313
use diskann_utils::{future::AsyncFriendly, views::Matrix};
@@ -32,7 +32,7 @@ where
3232
index: Arc<graph::DiskANNIndex<DP>>,
3333
queries: Arc<Matrix<T>>,
3434
strategy: Strategy<S>,
35-
labels: Arc<[Arc<dyn graph::index::QueryLabelProvider<DP::InternalId>>]>,
35+
labels: Arc<[Arc<dyn labeled::QueryLabelProvider<DP::InternalId>>]>,
3636
}
3737

3838
impl<DP, T, S> MultiHop<DP, T, S>
@@ -62,7 +62,7 @@ where
6262
index: Arc<graph::DiskANNIndex<DP>>,
6363
queries: Arc<Matrix<T>>,
6464
strategy: Strategy<S>,
65-
labels: Arc<[Arc<dyn graph::index::QueryLabelProvider<DP::InternalId>>]>,
65+
labels: Arc<[Arc<dyn labeled::QueryLabelProvider<DP::InternalId>>]>,
6666
) -> anyhow::Result<Arc<Self>> {
6767
strategy.length_compatible(queries.nrows())?;
6868

@@ -86,7 +86,14 @@ where
8686
impl<DP, T, S> Search for MultiHop<DP, T, S>
8787
where
8888
DP: provider::DataProvider<Context: Default, ExternalId: search::Id>,
89-
S: for<'a> glue::DefaultSearchStrategy<'a, DP, &'a [T], DP::ExternalId> + Clone + AsyncFriendly,
89+
S: for<'a> glue::DefaultSearchStrategy<
90+
'a,
91+
DP,
92+
&'a [T],
93+
DP::ExternalId,
94+
SearchAccessor: glue::SearchAccessor,
95+
> + Clone
96+
+ AsyncFriendly,
9097
T: AsyncFriendly + Clone,
9198
{
9299
type Id = DP::ExternalId;
@@ -111,13 +118,14 @@ where
111118
O: graph::SearchOutputBuffer<DP::ExternalId> + Send,
112119
{
113120
let context = DP::Context::default();
114-
let multihop_search =
115-
graph::search::MultihopFilterSearch::new(*parameters, &*self.labels[index]);
121+
let multihop_search = graph::search::MultihopFilterSearch::new(*parameters);
122+
let strategy =
123+
labeled::Filtered::new(self.strategy.get(index)?.clone(), &*self.labels[index]);
116124
let stats = self
117125
.index
118126
.search(
119127
multihop_search,
120-
self.strategy.get(index)?,
128+
&strategy,
121129
&context,
122130
self.queries.row(index),
123131
buffer,
@@ -142,13 +150,13 @@ mod tests {
142150
use super::*;
143151

144152
use crate::recall::GroundTruthMode;
145-
use diskann::graph::{index::QueryLabelProvider, test::provider};
153+
use diskann::graph::{ext::labeled::QueryLabelProvider, test::provider};
146154

147155
// A simple [`QueryLabelProvider`] that rejects odd indices.
148156
#[derive(Debug)]
149157
struct NoOdds;
150158

151-
impl graph::index::QueryLabelProvider<u32> for NoOdds {
159+
impl labeled::QueryLabelProvider<u32> for NoOdds {
152160
fn is_match(&self, id: u32) -> bool {
153161
id.is_multiple_of(2)
154162
}

diskann-benchmark-core/src/search/graph/range.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ impl<DP, T, S> Search for Range<DP, T, S>
8080
where
8181
DP: provider::DataProvider<Context: Default, ExternalId: search::Id>,
8282
S: for<'a> glue::DefaultSearchStrategy<'a, DP, &'a [T], DP::ExternalId> + Clone + AsyncFriendly,
83+
graph::search::Range: for<'a> graph::Search<'a, DP, S, &'a [T]>,
8384
T: AsyncFriendly + Clone,
8485
{
8586
type Id = DP::ExternalId;

0 commit comments

Comments
 (0)