Skip to content

Commit fe414e9

Browse files
author
Jack Moffitt
committed
Inline filtering for diskann-garnet
1 parent 999fa5d commit fe414e9

11 files changed

Lines changed: 291 additions & 355 deletions

File tree

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-garnet/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "diskann-garnet"
3-
version = "2.0.4"
3+
version = "3.0.0"
44
edition = "2024"
55
authors.workspace = true
66
license.workspace = true

diskann-garnet/diskann-garnet.nuspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<package>
33
<metadata>
44
<id>diskann-garnet</id>
5-
<version>2.0.4</version>
5+
<version>3.0.0</version>
66
<readme>docs/README.md</readme>
77
<authors>Microsoft</authors>
88
<projectUrl>https://github.com/microsoft/DiskANN</projectUrl>

diskann-garnet/src/dyn_index.rs

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use crate::{
77
SearchResults,
88
garnet::{Context, GarnetId},
9-
labels::GarnetQueryLabelProvider,
109
provider::{DynamicQuantization, GarnetProvider},
1110
};
1211
use diskann::{
@@ -15,10 +14,7 @@ use diskann::{
1514
provider::DataProvider,
1615
utils::VectorRepr,
1716
};
18-
use diskann_providers::{
19-
index::wrapped_async::DiskANNIndex, model::graph::provider::layers::BetaFilter,
20-
};
21-
use std::sync::Arc;
17+
use diskann_providers::index::wrapped_async::DiskANNIndex;
2218

2319
/// Type-erased version of `DiskANNIndex<GarnetProvider>`.
2420
/// All vector data is passed as untyped byte slices.
@@ -31,17 +27,31 @@ pub(crate) trait DynIndex: Send + Sync {
3127
&self,
3228
context: &Context,
3329
data: &[u8],
34-
params: &search::Knn,
35-
filter: Option<(&GarnetQueryLabelProvider, f32)>,
30+
params: search::Knn,
3631
output: &mut SearchResults<'_>,
3732
) -> ANNResult<SearchStats>;
3833

3934
fn search_element(
4035
&self,
4136
context: &Context,
4237
id: &GarnetId,
43-
params: &search::Knn,
44-
filter: Option<(&GarnetQueryLabelProvider, f32)>,
38+
params: search::Knn,
39+
output: &mut SearchResults<'_>,
40+
) -> ANNResult<SearchStats>;
41+
42+
fn filtered_search_vector(
43+
&self,
44+
context: &Context,
45+
data: &[u8],
46+
params: search::InlineFilterSearch,
47+
output: &mut SearchResults<'_>,
48+
) -> ANNResult<SearchStats>;
49+
50+
fn filtered_search_element(
51+
&self,
52+
context: &Context,
53+
id: &GarnetId,
54+
params: search::InlineFilterSearch,
4555
output: &mut SearchResults<'_>,
4656
) -> ANNResult<SearchStats>;
4757

@@ -84,32 +94,50 @@ impl<T: VectorRepr> DynIndex for DiskANNIndex<GarnetProvider<T>> {
8494
&self,
8595
context: &Context,
8696
data: &[u8],
87-
params: &search::Knn,
88-
filter: Option<(&GarnetQueryLabelProvider, f32)>,
97+
params: search::Knn,
8998
output: &mut SearchResults<'_>,
9099
) -> ANNResult<SearchStats> {
91100
let query = bytemuck::cast_slice::<u8, T>(data);
92-
if let Some((labels, beta)) = filter {
93-
let beta_filter = BetaFilter::new(DynamicQuantization, Arc::new(labels.clone()), beta);
94-
self.search(*params, &beta_filter, context, query, output)
95-
} else {
96-
self.search(*params, &DynamicQuantization, context, query, output)
97-
}
101+
self.search(params, &DynamicQuantization, context, query, output)
98102
}
99103

100104
fn search_element(
101105
&self,
102106
context: &Context,
103107
id: &GarnetId,
104-
params: &search::Knn,
105-
filter: Option<(&GarnetQueryLabelProvider, f32)>,
108+
params: search::Knn,
109+
output: &mut SearchResults<'_>,
110+
) -> ANNResult<SearchStats> {
111+
// Look up internal ID
112+
let iid = self.inner.provider().to_internal_id(context, id)?;
113+
let data = self.inner.provider().get_full_vector(context, iid)?;
114+
let data_bytes = bytemuck::cast_slice::<T, u8>(&data);
115+
self.search_vector(context, data_bytes, params, output)
116+
}
117+
118+
fn filtered_search_vector(
119+
&self,
120+
context: &Context,
121+
data: &[u8],
122+
params: search::InlineFilterSearch,
123+
output: &mut SearchResults<'_>,
124+
) -> ANNResult<SearchStats> {
125+
let query = bytemuck::cast_slice::<u8, T>(data);
126+
self.search(params, &DynamicQuantization, context, query, output)
127+
}
128+
129+
fn filtered_search_element(
130+
&self,
131+
context: &Context,
132+
id: &GarnetId,
133+
params: search::InlineFilterSearch,
106134
output: &mut SearchResults<'_>,
107135
) -> ANNResult<SearchStats> {
108136
// Look up internal ID
109137
let iid = self.inner.provider().to_internal_id(context, id)?;
110138
let data = self.inner.provider().get_full_vector(context, iid)?;
111139
let data_bytes = bytemuck::cast_slice::<T, u8>(&data);
112-
self.search_vector(context, data_bytes, params, filter, output)
140+
self.filtered_search_vector(context, data_bytes, params, output)
113141
}
114142

115143
fn remove(&self, context: &Context, id: &GarnetId) -> ANNResult<()> {

diskann-garnet/src/ffi_recall_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ mod tests {
173173
callbacks.write_callback(),
174174
callbacks.delete_callback(),
175175
callbacks.rmw_callback(),
176+
callbacks.filter_callback(),
176177
)
177178
};
178179
assert!(!index_ptr.is_null());

diskann-garnet/src/ffi_tests.rs

Lines changed: 1 addition & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ mod tests {
6060
callbacks.write_callback(),
6161
callbacks.delete_callback(),
6262
callbacks.rmw_callback(),
63+
callbacks.filter_callback(),
6364
)
6465
};
6566

@@ -759,76 +760,6 @@ mod tests {
759760
}
760761
}
761762

762-
#[test]
763-
fn search_with_bitmap_all_match() {
764-
let store = Store;
765-
let (index_ptr, ctx) = create_test_index(&store, VectorQuantType::NoQuant);
766-
767-
unsafe {
768-
assert_eq!(
769-
insert_f32_vector(&ctx, index_ptr, 10, &[1.0, 0.0]),
770-
InsertResult::Success
771-
);
772-
assert_eq!(
773-
insert_f32_vector(&ctx, index_ptr, 20, &[0.0, 1.0]),
774-
InsertResult::Success
775-
);
776-
assert_eq!(
777-
insert_f32_vector(&ctx, index_ptr, 30, &[1.0, 1.0]),
778-
InsertResult::Success
779-
);
780-
781-
// Bitmap with bits 1,2,3 set (internal IDs for the 3 inserted vectors;
782-
// internal ID 0 is the start point)
783-
let bitmap: [u8; 8] = [0b00001110, 0, 0, 0, 0, 0, 0, 0];
784-
let (ids, _dists) = do_search(&ctx, index_ptr, &[1.0, 0.0], 3, Some(&bitmap));
785-
// Start point (internal ID 0) is filtered out from results,
786-
// so we may get fewer than k results.
787-
assert!(ids.len() >= 2, "should return at least 2 matching vectors");
788-
assert_eq!(ids[0], 10, "closest should still be id=10");
789-
790-
drop_index(ctx.get(), index_ptr);
791-
}
792-
}
793-
794-
#[test]
795-
fn search_with_bitmap_partial_match() {
796-
let store = Store;
797-
let (index_ptr, ctx) = create_test_index(&store, VectorQuantType::NoQuant);
798-
799-
unsafe {
800-
// Internal ID 0 -> EID 10, vector [1,0]
801-
assert_eq!(
802-
insert_f32_vector(&ctx, index_ptr, 10, &[1.0, 0.0]),
803-
InsertResult::Success
804-
);
805-
// Internal ID 1 -> EID 20, vector [0,1]
806-
assert_eq!(
807-
insert_f32_vector(&ctx, index_ptr, 20, &[0.0, 1.0]),
808-
InsertResult::Success
809-
);
810-
// Internal ID 2 -> EID 30, vector [1,1]
811-
assert_eq!(
812-
insert_f32_vector(&ctx, index_ptr, 30, &[1.0, 1.0]),
813-
InsertResult::Success
814-
);
815-
816-
// Bitmap with only bit 2 set (internal ID 2 = EID 20, second inserted vector)
817-
let bitmap: [u8; 8] = [0b00000100, 0, 0, 0, 0, 0, 0, 0];
818-
// Query close to EID 20's vector [0,1] to ensure it appears in results
819-
let (ids, _dists) = do_search(&ctx, index_ptr, &[0.0, 1.0], 3, Some(&bitmap));
820-
// BetaFilter biases toward matching vectors by scaling their distances.
821-
assert!(!ids.is_empty(), "should return at least one result");
822-
// EID 20 should appear since it's the closest to query AND matches the filter
823-
assert!(
824-
ids.contains(&20),
825-
"filtered vector EID 20 should be in results"
826-
);
827-
828-
drop_index(ctx.get(), index_ptr);
829-
}
830-
}
831-
832763
#[test]
833764
fn search_with_null_bitmap_same_as_unfiltered() {
834765
let store = Store;

diskann-garnet/src/garnet.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,15 @@ pub(crate) type ReadModifyWriteCallback =
8585
unsafe extern "C" fn(u64, *const u8, usize, usize, RmwDataCallback, *mut c_void) -> bool;
8686
pub(crate) type ReadDataCallback = unsafe extern "C" fn(u32, *mut c_void, *const u8, usize);
8787
pub(crate) type RmwDataCallback = unsafe extern "C" fn(*mut c_void, *mut u8, usize);
88+
pub(crate) type FilterCallback = unsafe extern "C" fn(u64, u32) -> bool;
8889

8990
#[derive(Copy, Clone)]
9091
pub(crate) struct Callbacks {
9192
read_callback: ReadCallback,
9293
write_callback: WriteCallback,
9394
delete_callback: DeleteCallback,
9495
rmw_callback: ReadModifyWriteCallback,
96+
filter_callback: FilterCallback,
9597
}
9698

9799
impl Callbacks {
@@ -100,12 +102,14 @@ impl Callbacks {
100102
write_callback: WriteCallback,
101103
delete_callback: DeleteCallback,
102104
rmw_callback: ReadModifyWriteCallback,
105+
filter_callback: FilterCallback,
103106
) -> Self {
104107
Self {
105108
read_callback,
106109
write_callback,
107110
delete_callback,
108111
rmw_callback,
112+
filter_callback,
109113
}
110114
}
111115

@@ -129,6 +133,11 @@ impl Callbacks {
129133
self.rmw_callback
130134
}
131135

136+
#[cfg(test)]
137+
pub(crate) fn filter_callback(&self) -> FilterCallback {
138+
self.filter_callback
139+
}
140+
132141
#[cfg(test)]
133142
pub(crate) fn exists_iid(&self, ctx: &Context, id: u32) -> bool {
134143
let key = [4, id];
@@ -477,6 +486,12 @@ impl Callbacks {
477486
)
478487
}
479488
}
489+
490+
/// Evaluate the filter callback on an ID.
491+
#[must_use]
492+
pub(crate) fn matches_filter(&self, ctx: &Context, id: u32) -> bool {
493+
unsafe { (self.filter_callback)(ctx.inner, id) }
494+
}
480495
}
481496

482497
unsafe extern "C" fn read_call<'a, F, T>(index: u32, ptr: *mut c_void, data: *const u8, len: usize)

0 commit comments

Comments
 (0)