Skip to content

Commit 0e5a59e

Browse files
author
Jack Moffitt
committed
[diskann-garnet] Change filter callback to take slices
1 parent 6df1258 commit 0e5a59e

4 files changed

Lines changed: 131 additions & 58 deletions

File tree

diskann-garnet/src/garnet.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ 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;
88+
pub(crate) type FilterCallback = unsafe extern "C" fn(u64, *const u8, usize) -> bool;
8989
pub(crate) type LogCallback = unsafe extern "C" fn(u64, *const u8, usize);
9090

9191
#[derive(Copy, Clone)]
@@ -507,8 +507,8 @@ impl Callbacks {
507507

508508
/// Evaluate the filter callback on an ID.
509509
#[must_use]
510-
pub(crate) fn matches_filter(&self, ctx: &Context, id: u32) -> bool {
511-
unsafe { (self.filter_callback)(ctx.inner, id) }
510+
pub(crate) fn matches_filter(&self, ctx: &Context, data: &[u8]) -> bool {
511+
unsafe { (self.filter_callback)(ctx.inner, data.as_ptr(), data.len()) }
512512
}
513513

514514
/// Log a message to Garnet.

diskann-garnet/src/lib.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -471,20 +471,21 @@ pub unsafe extern "C" fn insert(
471471
return InsertResult::Fail.into();
472472
};
473473

474-
// Write attributes to garnet
475-
let attr_data = if attribute_len > 0 && !attribute_data.is_null() {
476-
unsafe { slice::from_raw_parts(attribute_data, attribute_len) }
477-
} else {
478-
&[]
479-
};
480-
if index.inner.set_attributes(&ctx, &id, attr_data).is_err() {
481-
return InsertResult::Fail.into();
482-
}
483-
484474
let old_ready = ctx.quantizer_ready();
485475

486476
// Insert the vector
487477
if index.inner.insert(&ctx, &id, &v).is_ok() {
478+
// Write attributes to garnet. These are written after insert since
479+
// they are keyed on internal id.
480+
let attr_data = if attribute_len > 0 && !attribute_data.is_null() {
481+
unsafe { slice::from_raw_parts(attribute_data, attribute_len) }
482+
} else {
483+
&[]
484+
};
485+
if index.inner.set_attributes(&ctx, &id, attr_data).is_err() {
486+
return InsertResult::Fail.into();
487+
}
488+
488489
let ready = ctx.quantizer_ready();
489490
if !old_ready && ready {
490491
InsertResult::SuccessStartTraining.into()
@@ -1109,8 +1110,6 @@ mod tests {
11091110
let ctx = Context::new(0);
11101111
let v = [0.0f32, 0.0f32];
11111112

1112-
assert!(store.get(ctx.term(Term::Attributes).get(), &eid).is_none());
1113-
11141113
assert_eq!(
11151114
unsafe {
11161115
super::insert(
@@ -1126,8 +1125,9 @@ mod tests {
11261125
},
11271126
1
11281127
);
1128+
let iid = store.get(ctx.term(Term::IntMap).get(), &eid).unwrap();
11291129
assert_eq!(
1130-
store.get(ctx.term(Term::Attributes).get(), &eid),
1130+
store.get(ctx.term(Term::Attributes).get(), &iid),
11311131
Some(metadata.as_slice().to_owned())
11321132
);
11331133

@@ -1141,7 +1141,7 @@ mod tests {
11411141
0,
11421142
)
11431143
});
1144-
assert!(store.get(ctx.term(Term::Attributes).get(), &eid).is_none());
1144+
assert!(store.get(ctx.term(Term::Attributes).get(), &iid).is_none());
11451145

11461146
unsafe {
11471147
drop_index(0, index_ptr);

diskann-garnet/src/provider.rs

Lines changed: 113 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ const QUANT_STATE_KEY: u32 = u32::from_be_bytes(*b"_qnt");
6666
/// Starting capacity of the pre-allocated rerank buffers.
6767
const RERANK_BUFFER_LENGTH: usize = 1024;
6868

69+
/// Size hint passed to Garnet when batch reading attributes. Attributes are variable
70+
/// length, so this is only an estimate used to size Garnet's read buffer.
71+
const ATTRIBUTE_LENGTH_HINT: usize = 1024;
72+
6973
#[derive(Clone)]
7074
struct AdjList(AdjacencyList<u32>);
7175

@@ -424,9 +428,18 @@ impl<T: VectorRepr> GarnetProvider<T> {
424428
id: &GarnetId,
425429
data: &[u8],
426430
) -> Result<(), GarnetProviderError> {
431+
let mut iid = u32::MAX;
432+
if !self.callbacks.read_single_eid(
433+
&context.term(Term::IntMap),
434+
id,
435+
bytemuck::bytes_of_mut(&mut iid),
436+
) {
437+
return Err(GarnetError::Read.into());
438+
}
439+
427440
if self
428441
.callbacks
429-
.write_eid(&context.term(Term::Attributes), id, data)
442+
.write_iid(&context.term(Term::Attributes), iid, data)
430443
{
431444
Ok(())
432445
} else {
@@ -439,9 +452,18 @@ impl<T: VectorRepr> GarnetProvider<T> {
439452
context: &Context,
440453
id: &GarnetId,
441454
) -> Result<(), GarnetProviderError> {
455+
let mut iid = u32::MAX;
456+
if !self.callbacks.read_single_eid(
457+
&context.term(Term::IntMap),
458+
id,
459+
bytemuck::bytes_of_mut(&mut iid),
460+
) {
461+
return Err(GarnetError::Read.into());
462+
}
463+
442464
if self
443465
.callbacks
444-
.delete_eid(&context.term(Term::Attributes), id)
466+
.delete_iid(&context.term(Term::Attributes), iid)
445467
{
446468
Ok(())
447469
} else {
@@ -1057,7 +1079,7 @@ impl<T: VectorRepr> Delete for GarnetProvider<T> {
10571079
// It is not an error to fail deleting attributes; they may not exist.
10581080
let _: bool = self
10591081
.callbacks
1060-
.delete_eid(&context.term(Term::Attributes), gid);
1082+
.delete_iid(&context.term(Term::Attributes), id);
10611083

10621084
// TODO: inplace_delete needs access to neighbors. Delete these once that bug is fixed.
10631085
// See https://github.com/microsoft/DiskANN/issues/1153.
@@ -1194,6 +1216,32 @@ impl<'a, T: VectorRepr> DynamicAccessor<'a, T> {
11941216
}
11951217
}
11961218
}
1219+
1220+
/// Batch read the attributes for `filtered_ids` and record the filter result for each
1221+
/// into `filtered_decisions`.
1222+
///
1223+
/// Garnet skips ids with no stored attributes, so those keep `default_decision`.
1224+
fn compute_filter_decisions(&mut self, default_decision: bool) {
1225+
let Self {
1226+
provider,
1227+
context,
1228+
filtered_ids,
1229+
filtered_decisions,
1230+
..
1231+
} = self;
1232+
1233+
filtered_decisions.clear();
1234+
filtered_decisions.resize(filtered_ids.len() / 2, default_decision);
1235+
1236+
provider.callbacks.read_multi_lpiid::<_, u8>(
1237+
&context.term(Term::Attributes),
1238+
filtered_ids,
1239+
ATTRIBUTE_LENGTH_HINT,
1240+
|i, attrs| {
1241+
filtered_decisions[i as usize] = provider.callbacks.matches_filter(context, attrs);
1242+
},
1243+
);
1244+
}
11971245
}
11981246

11991247
impl<T: VectorRepr> HasId for DynamicAccessor<'_, T> {
@@ -1507,12 +1555,13 @@ impl<T: VectorRepr> FilteredAccessor for DynamicAccessor<'_, T> {
15071555
// borrow. We put it back at the end to save the allocation.
15081556
let mut id_buffer = mem::take(&mut **self.id_buffer);
15091557

1558+
let default_decision = self.provider.callbacks.matches_filter(self.context, &[]);
1559+
15101560
for nl_id in ids {
15111561
self.provider
15121562
.get_neighbors(self.context, nl_id, &mut id_buffer);
15131563

15141564
self.filtered_ids.clear();
1515-
self.filtered_decisions.clear();
15161565

15171566
for id in id_buffer.iter().copied().filter(|id| pred.eval_mut(id)) {
15181567
if id == Self::START_ID {
@@ -1522,15 +1571,15 @@ impl<T: VectorRepr> FilteredAccessor for DynamicAccessor<'_, T> {
15221571
};
15231572
on_neighbors(Decision::reject(id), dist);
15241573
} else {
1525-
let matches = self.provider.callbacks.matches_filter(self.context, id);
1526-
15271574
self.filtered_ids.push(4);
15281575
self.filtered_ids.push(id);
1529-
1530-
self.filtered_decisions.push(matches);
15311576
}
15321577
}
15331578

1579+
if self.filtered_ids.is_empty() {
1580+
continue;
1581+
}
1582+
15341583
let (ctx, length_hint) = if self.quantized {
15351584
(
15361585
self.context.term(Term::Quantized),
@@ -1543,22 +1592,23 @@ impl<T: VectorRepr> FilteredAccessor for DynamicAccessor<'_, T> {
15431592
)
15441593
};
15451594

1546-
if !self.filtered_ids.is_empty() {
1547-
self.provider.callbacks.read_multi_lpiid(
1548-
&ctx,
1549-
&self.filtered_ids,
1550-
length_hint,
1551-
|i, v| {
1552-
let dist = self.computer.evaluate_similarity(v);
1553-
let decision = if self.filtered_decisions[i as usize] {
1554-
Decision::accept(self.filtered_ids[i as usize * 2 + 1])
1555-
} else {
1556-
Decision::reject(self.filtered_ids[i as usize * 2 + 1])
1557-
};
1558-
on_neighbors(decision, dist);
1559-
},
1560-
);
1561-
}
1595+
self.compute_filter_decisions(default_decision);
1596+
1597+
// Read vectors and calculate distances
1598+
self.provider.callbacks.read_multi_lpiid(
1599+
&ctx,
1600+
&self.filtered_ids,
1601+
length_hint,
1602+
|i, v| {
1603+
let dist = self.computer.evaluate_similarity(v);
1604+
let decision = if self.filtered_decisions[i as usize] {
1605+
Decision::accept(self.filtered_ids[i as usize * 2 + 1])
1606+
} else {
1607+
Decision::reject(self.filtered_ids[i as usize * 2 + 1])
1608+
};
1609+
on_neighbors(decision, dist);
1610+
},
1611+
);
15621612
}
15631613

15641614
**self.id_buffer = id_buffer;
@@ -1580,20 +1630,45 @@ impl<T: VectorRepr> FilteredAccessor for DynamicAccessor<'_, T> {
15801630
// borrow. We put it back at the end to save the allocation.
15811631
let mut id_buffer = mem::take(&mut **self.id_buffer);
15821632

1633+
let default_decision = self.provider.callbacks.matches_filter(self.context, &[]);
1634+
15831635
for nl_id in ids {
15841636
self.provider
15851637
.get_neighbors(self.context, nl_id, &mut id_buffer);
15861638
self.filtered_ids.clear();
15871639

15881640
for id in id_buffer.iter().copied() {
15891641
if id != Self::START_ID && pred.eval(&id) {
1590-
let matches = self.provider.callbacks.matches_filter(self.context, id);
1642+
self.filtered_ids.push(4);
1643+
self.filtered_ids.push(id);
1644+
}
1645+
}
15911646

1592-
if matches && pred.eval_mut(&Accept::new(id)) {
1593-
self.filtered_ids.push(4);
1594-
self.filtered_ids.push(id);
1595-
}
1647+
if self.filtered_ids.is_empty() {
1648+
continue;
1649+
}
1650+
1651+
self.compute_filter_decisions(default_decision);
1652+
1653+
// Remove non-matching ids
1654+
let mut index = 0;
1655+
for (i, &matches) in self.filtered_decisions.iter().enumerate() {
1656+
if !matches {
1657+
continue;
15961658
}
1659+
1660+
let id = self.filtered_ids[i * 2 + 1];
1661+
1662+
if pred.eval_mut(&Accept::new(id)) {
1663+
self.filtered_ids[index * 2] = 4;
1664+
self.filtered_ids[index * 2 + 1] = id;
1665+
index += 1;
1666+
}
1667+
}
1668+
self.filtered_ids.truncate(index * 2);
1669+
1670+
if self.filtered_ids.is_empty() {
1671+
continue;
15971672
}
15981673

15991674
let (ctx, length_hint) = if self.quantized {
@@ -1608,17 +1683,15 @@ impl<T: VectorRepr> FilteredAccessor for DynamicAccessor<'_, T> {
16081683
)
16091684
};
16101685

1611-
if !self.filtered_ids.is_empty() {
1612-
self.provider.callbacks.read_multi_lpiid(
1613-
&ctx,
1614-
&self.filtered_ids,
1615-
length_hint,
1616-
|i, v| {
1617-
let dist = self.computer.evaluate_similarity(v);
1618-
on_neighbors(Accept::new(self.filtered_ids[i as usize * 2 + 1]), dist);
1619-
},
1620-
);
1621-
}
1686+
self.provider.callbacks.read_multi_lpiid(
1687+
&ctx,
1688+
&self.filtered_ids,
1689+
length_hint,
1690+
|i, v| {
1691+
let dist = self.computer.evaluate_similarity(v);
1692+
on_neighbors(Accept::new(self.filtered_ids[i as usize * 2 + 1]), dist);
1693+
},
1694+
);
16221695
}
16231696

16241697
**self.id_buffer = id_buffer;

diskann-garnet/src/test_utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ unsafe extern "C" fn test_rmw(
199199
true
200200
}
201201

202-
unsafe extern "C" fn test_filter(_context: u64, _internal_id: u32) -> bool {
202+
unsafe extern "C" fn test_filter(_context: u64, _data: *const u8, _len: usize) -> bool {
203203
true
204204
}
205205

0 commit comments

Comments
 (0)