Repository navigation
Expand file tree
/
Copy pathprovider.rs
More file actions
3080 lines (2747 loc) · 99.5 KB
/
Copy pathprovider.rs
File metadata and controls
3080 lines (2747 loc) · 99.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*/
use std::{
fmt::Debug,
future::Future,
io::{Read, Write},
num::NonZeroUsize,
str::FromStr,
};
use diskann_quantization::{
alloc::{GlobalAllocator, Poly},
spherical::iface::{self as spherical_iface, try_deserialize, Opaque, Quantizer},
};
use serde::{Deserialize, Serialize};
use bf_tree::{BfTree, Config};
use diskann::{
default_post_processor,
error::{ErrorExt, Infallible, RankedError},
graph::{
glue::{
self, Batch, CopyIds, DefaultPostProcessor, InplaceDeleteStrategy, InsertStrategy,
MultiInsertStrategy, PruneStrategy, SearchStrategy,
},
strategy::{FullPrecision, Quantized},
workingset::map,
AdjacencyList, SearchOutputBuffer,
},
neighbor::Neighbor,
provider::{DataProvider, DefaultContext, Delete, ElementStatus, HasId, NoopGuard, SetElement},
utils::{IntoUsize, VectorRepr},
ANNError, ANNResult,
};
use diskann_utils::{
future::{AsyncFriendly, SendFuture},
views::MatrixView,
};
use diskann_vector::{distance::Metric, DistanceFunction, PreprocessedDistanceFunction};
use super::{
neighbors::{NeighborAccessor, NeighborProvider},
quant::QuantVectorProvider,
vectors::VectorProvider,
AccessError, NoStore,
};
use crate::locks::StripedLocks;
use diskann_providers::model::graph::provider::async_::distances::UnwrapErr;
use diskann_providers::storage::{LoadWith, SaveWith, StorageReadProvider, StorageWriteProvider};
/////////////////////
// BfTreeProvider //
/////////////////////
/// An Bf-tree based implementation of a [`DataProvider`] built around the idea of having up to
/// two layers of vector stores: a full-precision store and an optional quantized store.
/// This provider uses the identity mapping between external and internal vector IDs.
///
/// # Type Parameters:
///
/// * `T`: The primitive element type of the full-precision vector. This is typically some
/// type like `f32` or `half::f16`.
///
/// * `Q`: The full type of the quant vector store. This is not constrained by a trait and
/// rather relies on implementation for several concrete types, including:
///
/// - [`QuantVectorProvider`]: A Bf-Tree based spherical quantized vector store.
/// - [`NoStore`]: Disable quantization altogether. Note that this disables all
/// methods reached through quantization based [`Accessor`]s at compile-time.
///
/// # Indexing Strategies
///
/// * [`FullPrecision`]: Only retrieves data from the full-precision portion of the index.
/// No quantized vectors are used. During search, start points are filtered from the
/// final results.
///
/// * [`Quantized`]: Performs all operations (search, pruning, insert) entirely in the
/// quantized space using spherical distance functions. Post-processing copies candidate
/// IDs forward without reranking. Fastest option — full-precision vectors are not
/// touched at query time.
///
/// # Examples
///
/// The following code demonstrates how to instantiate and use the `BfTreeProvider` in
/// a number of different scenarios.
///
/// ## Full-Precision Only - No Deletes
///
/// This example demonstrates how to create a `BfTreeProvider` that only supports
/// full-precision vectors.
/// ```
/// use diskann_bftree::provider::{
/// BfTreeProvider, BfTreeProviderParameters
/// };
/// use diskann_bftree::NoStore;
/// use diskann_vector::distance::Metric;
/// use diskann_utils::views::{Init, Matrix};
/// use bf_tree::Config;
/// use std::num::NonZeroUsize;
///
/// let parameters = BfTreeProviderParameters {
/// max_points: 5,
/// num_start_points: NonZeroUsize::new(1).unwrap(),
/// dim: 4,
/// metric: Metric::L2,
/// max_degree: 32,
/// vector_provider_config: Config::default(),
/// quant_vector_provider_config: Config::default(),
/// neighbor_list_provider_config: Config::default(),
/// graph_params: None,
/// use_snapshot: false,
/// };
///
/// // Create a table that supports 5 points and 1 start point.
/// let start_points = Matrix::new(Init(|| 0.0f32), 1, 4);
/// let provider = BfTreeProvider::<f32, _>::new(
/// parameters,
/// start_points.as_view(),
/// NoStore,
/// );
/// ```
///
/// ## Full-Precision and Spherical Quantization
///
/// To create a two-level provider with a spherical quantization-based quant vector store,
/// a `Poly<dyn Quantizer>` can be supplied for the `quant_precursor` argument, as this
/// implements the [`CreateQuantProvider`] trait.
/// ```
/// use diskann_quantization::{
/// alloc::{GlobalAllocator, Poly, poly},
/// algorithms::TransformKind,
/// spherical::{iface, SphericalQuantizer, SupportedMetric, PreScale},
/// };
/// use diskann_utils::views::{Init, Matrix};
/// use diskann_bftree::provider::{
/// BfTreeProvider, BfTreeProviderParameters
/// };
/// use diskann_vector::distance::Metric;
/// use bf_tree::Config;
/// use std::num::NonZeroUsize;
/// use rand::rngs::StdRng;
/// use rand::SeedableRng;
///
/// let dim = 4;
/// let data = Matrix::new(Init(|| 1.0f32), 4, dim);
/// let mut rng = StdRng::seed_from_u64(42);
/// let sq = SphericalQuantizer::train(
/// data.as_view(), TransformKind::Null,
/// SupportedMetric::SquaredL2, PreScale::None,
/// &mut rng, GlobalAllocator,
/// ).unwrap();
/// let imp = iface::Impl::<1>::new(sq).unwrap();
/// let poly = Poly::new(imp, GlobalAllocator).unwrap();
/// let quantizer: Poly<dyn iface::Quantizer> = poly!(iface::Quantizer, poly);
///
/// let parameters = BfTreeProviderParameters {
/// max_points: 5,
/// num_start_points: NonZeroUsize::new(1).unwrap(),
/// dim: 4,
/// metric: Metric::L2,
/// max_degree: 32,
/// vector_provider_config: Config::default(),
/// quant_vector_provider_config: Config::default(),
/// neighbor_list_provider_config: Config::default(),
/// graph_params: None,
/// use_snapshot: false,
/// };
///
/// // Create a table that supports 5 points and 1 start point.
/// let start_points = Matrix::new(Init(|| 0.0f32), 1, 4);
/// let provider = BfTreeProvider::<f32, _>::new(
/// parameters,
/// start_points.as_view(),
/// quantizer,
/// );
/// ```
pub struct BfTreeProvider<T, Q = QuantVectorProvider>
where
T: VectorRepr,
{
// The quant vector store. If `Q == NoStore`, the quantized operations are disabled.
//
pub(super) quant_vectors: Q,
// The full vector store.
//
pub(super) full_vectors: VectorProvider<T>,
// Provider that holds the graph structure as neighbors of vectors.
//
pub(crate) neighbor_provider: NeighborProvider<u32>,
// The metric to use for distances
//
pub(super) metric: Metric,
// Graph configuration parameters for persistence
//
pub(crate) graph_params: Option<GraphParams>,
// Whether CPR snapshot support is enabled for this provider's trees.
pub(crate) use_snapshot: bool,
// Striped locks for per-vertex synchronization across all stores.
//
// # Locking protocol
//
// Each mutating operation (`set_element`, `delete`, `write_neighbors`,
// `set_neighbors`, `append_vector`) acquires exactly **one** stripe lock
// for the target vertex ID, performs its work, and drops the guard at
// scope exit. No operation acquires a second lock while holding the first,
// so **deadlock is structurally impossible** — there is no ordering
// concern.
//
// Reads (`get_neighbors`, `get_vector`) do **not** acquire the stripe
// lock. They rely on bf-tree's internal thread safety for reads. A reader
// may therefore observe a partially-updated neighbor list (e.g.,
// mid-append), but bf-tree guarantees the read will not crash or return
// corrupt memory. The DiskANN search algorithm tolerates stale neighbor
// reads — it is approximate by design, and momentary inconsistency
// affects quality briefly rather than correctness.
pub(crate) locks: StripedLocks,
}
#[derive(Debug, Clone)]
pub struct BfTreeProviderParameters {
// The maximum number of valid points that provider can hold.
pub max_points: usize,
// The number of start points (frozen points) for graph search entry.
pub num_start_points: NonZeroUsize,
// The dimension of the full-precision vectors.
pub dim: usize,
// The metric to use for distance computations
pub metric: Metric,
// The physical maximum degree (maximum neighbor list capacity per vertex).
// Callers are responsible for applying any slack factor externally before
// passing this value.
pub max_degree: u32,
// bf-tree config for vector provider.
// bf-tree requires a minimum circular buffer size relative to leaf_page_size:
// - Cache-only mode: cb_size_byte >= 4 * leaf_page_size
// - Non cache-only mode: cb_size_byte >= 2 * leaf_page_size
// The default leaf_page_size is 4096 and default cb_size_byte is 32MB.
pub vector_provider_config: Config,
// bf-tree config for quant vector provider.
// Same minimum circular buffer constraint as above.
pub quant_vector_provider_config: Config,
// bf-tree config for neighbor list provider.
// Same minimum circular buffer constraint as above.
pub neighbor_list_provider_config: Config,
// Optional graph configuration parameters for persistence
pub graph_params: Option<GraphParams>,
// Whether to enable CPR snapshot support on the underlying bf-trees.
pub use_snapshot: bool,
}
impl<T, Q> BfTreeProvider<T, Q>
where
T: VectorRepr,
{
/// Construct a new data provider from empty. Callers of this are required to manually set start
/// points before performing search tasks.
///
/// This constructor for `BfTreeProvider` should be used when building and constructing from
/// scratch.
///
/// # Arguments
/// * `params`: An instance of [`BfTreeProviderParameters`] collecting shared
/// configuration information.
/// * `quant_precursor`: A precursor type for the quantizer layer.
///
/// # Type Constraints
/// * `Self: StartPoint<T>` - The provider must implement the `StartPoint` trait.
fn new_empty<TQ>(mut params: BfTreeProviderParameters, quant_precursor: TQ) -> ANNResult<Self>
where
Self: StartPoint<T>,
TQ: CreateQuantProvider<Target = Q>,
{
// Force all configs to match the provider-level use_snapshot setting so the
// underlying bf-trees are always consistent with our snapshot guard.
params
.vector_provider_config
.use_snapshot(params.use_snapshot);
params
.neighbor_list_provider_config
.use_snapshot(params.use_snapshot);
params
.quant_vector_provider_config
.use_snapshot(params.use_snapshot);
Ok(Self {
quant_vectors: quant_precursor.create(params.quant_vector_provider_config)?,
full_vectors: VectorProvider::new_with_config(
params.max_points,
params.dim,
params.num_start_points.get(),
params.vector_provider_config,
)?,
neighbor_provider: NeighborProvider::new_with_config(
params.max_degree,
params.neighbor_list_provider_config,
)?,
metric: params.metric,
graph_params: params.graph_params,
use_snapshot: params.use_snapshot,
locks: StripedLocks::new(),
})
}
/// Construct a new data provider with start points initialized.
///
/// This is the primary constructor for `BfTreeProvider` where start points are known from the
/// beginning. It creates the provider and sets the start points in one operation.
///
/// # Arguments
/// * `params`: An instance of [`BfTreeProviderParameters`] collecting shared
/// configuration information.
/// * `start_points`: A matrix view containing the start point vectors. The number
/// of rows must match `params.num_start_points.get()`.
/// * `quant_precursor`: A precursor type for the quantizer layer.
///
/// # Type Constraints
/// * `Self: StartPoint<T>` - The provider must implement the `StartPoint` trait.
pub fn new<TQ>(
params: BfTreeProviderParameters,
start_points: MatrixView<'_, T>,
quant_precursor: TQ,
) -> ANNResult<Self>
where
Self: StartPoint<T>,
TQ: CreateQuantProvider<Target = Q>,
{
// Early validation before allocating resources
if start_points.nrows() != params.num_start_points.get() {
return Err(ANNError::log_async_index_error(format!(
"start_points matrix has {} rows, but params.num_start_points is {}",
start_points.nrows(),
params.num_start_points.get(),
)));
}
let provider = Self::new_empty(params.clone(), quant_precursor)?;
provider.set_start_points(Hidden(()), start_points)?;
// Neighbor lists are created lazily on first write. `get_neighbors`
// treats a bf-tree `NotFound` as an empty adjacency list, so there is no
// need to eagerly initialize an empty list for every id up front. This
// keeps construction O(num_start_points) instead of O(max_points),
// which matters for billion-scale, larger-than-memory datasets.
Ok(provider)
}
// /// Return a predicate that can be applied to `Iter::filter` to remove start points
// /// from an iterator of neighbors.
// ///
// /// This is used during post-processing
// ///
// pub(crate) fn is_not_start_point(&self) -> impl Fn(&Neighbor<u32>) -> bool {
// let range = self.full_vectors.start_point_range();
// move |neighbor| !range.contains(&neighbor.id.into_usize())
// }
/// Return a vector of starting points.
pub fn starting_points(&self) -> ANNResult<Vec<u32>> {
self.full_vectors.starting_points()
}
/// An iterator over all ids including start points (even if they are deleted).
pub fn iter(&self) -> std::ops::Range<u32> {
0..(self.full_vectors.total() as u32)
}
pub fn num_start_points(&self) -> usize {
self.full_vectors.num_start_points
}
/// Return the maximum number of points (excluding frozen/start points)
pub fn max_points(&self) -> usize {
self.full_vectors.max_vectors
}
/// Return the vector dimension
pub fn dim(&self) -> usize {
self.full_vectors.dim()
}
/// Return the distance metric
pub fn metric(&self) -> Metric {
self.metric
}
/// Return the maximum degree from the neighbor provider
pub fn max_degree(&self) -> u32 {
self.neighbor_provider.max_degree()
}
}
impl<T> BfTreeProvider<T, QuantVectorProvider>
where
T: VectorRepr,
{
/// Return the number of vector reads for full-precision and quant-vectors respectively
///
pub fn counts_for_get_vector(&self) -> (usize, usize) {
(
self.full_vectors.num_get_calls.get(),
self.quant_vectors.num_get_calls.get(),
)
}
}
impl<T> BfTreeProvider<T, NoStore>
where
T: VectorRepr,
{
/// Return the number of vector reads for full-precision and quant-vectors respectively
///
pub fn counts_for_get_vector(&self) -> (usize, usize) {
(self.full_vectors.num_get_calls.get(), 0)
}
}
/// Trait for types that may need cleanup during hard-delete.
///
/// `QuantVectorProvider` implements this to delete its quantized embedding.
/// `NoStore` implements this as a no-op.
pub(crate) trait DeleteQuant {
fn delete_vector(&self, id: usize);
}
impl DeleteQuant for QuantVectorProvider {
fn delete_vector(&self, id: usize) {
QuantVectorProvider::delete_vector(self, id);
}
}
impl DeleteQuant for NoStore {
fn delete_vector(&self, _id: usize) {}
}
/// Hard-delete implementation for `BfTreeProvider`.
///
/// This provider performs **hard deletes**: vector data is immediately and irrecoverably erased
/// from the underlying bf-tree storage when [`Delete::delete`] is called. When a quantized
/// store is present, both the full-precision and quantized entries are removed.
///
/// This has an important consequence for inplace delete: the
/// [`InplaceDeleteMethod::VisitedAndTopK`] strategy is **incompatible** with this provider
/// because it requires reading the deleted vector's data (via
/// [`InplaceDeleteStrategy::get_delete_element`]) *after* the delete has already been committed.
/// Use [`InplaceDeleteMethod::OneHop`] or [`InplaceDeleteMethod::TwoHopAndOneHop`] instead,
/// as these strategies only require neighbor topology (which remains accessible).
impl<T, Q> Delete for BfTreeProvider<T, Q>
where
T: VectorRepr,
Q: AsyncFriendly + DeleteQuant,
{
fn release(
&self,
_context: &Self::Context,
_id: Self::InternalId,
) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send {
std::future::ready(Ok(()))
}
fn delete(
&self,
_context: &Self::Context,
gid: &Self::ExternalId,
) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send {
let id = *gid;
let _guard = self.locks.lock(id as usize);
// Only delete vector data here. Neighbor adjacency cleanup (zeroing the
// deleted vertex's edge list and patching neighbors-of-neighbors) is
// handled by `DiskANNIndex::inplace_delete` → `drop_adj_list`.
self.full_vectors.delete_vector(id as usize);
self.quant_vectors.delete_vector(id as usize);
std::future::ready(Ok(()))
}
fn status_by_external_id(
&self,
context: &Self::Context,
gid: &Self::ExternalId,
) -> impl std::future::Future<Output = Result<diskann::provider::ElementStatus, Self::Error>> + Send
{
self.status_by_internal_id(context, *gid)
}
fn status_by_internal_id(
&self,
_context: &Self::Context,
id: Self::InternalId,
) -> impl std::future::Future<Output = Result<diskann::provider::ElementStatus, Self::Error>> + Send
{
let status = match self.full_vectors.get_vector_sync(id.into_usize()) {
Ok(_) => Ok(ElementStatus::Valid),
Err(RankedError::Transient(_)) => Ok(ElementStatus::Deleted),
Err(RankedError::Error(e)) => Err(e),
};
std::future::ready(status)
}
}
/// Allow `&BfTreeProvider` to implement `IntoIter`
///
impl<T, Q> IntoIterator for &BfTreeProvider<T, Q>
where
T: VectorRepr,
{
type Item = u32;
type IntoIter = std::ops::Range<u32>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
/// A helper trait to select the quant vector store.
///
/// This is also implemented for [`NoStore`], which explicitly disables deletion
/// related functionality
///
pub trait CreateQuantProvider {
// The type of the created quant provider.
//
type Target;
// Create a quant provider capable of tracking `max_points` with and additional
// `frozen_points` at the end.
//
fn create(self, bf_tree_config: Config) -> ANNResult<Self::Target>;
}
impl CreateQuantProvider for NoStore {
type Target = NoStore;
fn create(self, _bf_tree_config: Config) -> ANNResult<Self::Target> {
Ok(self)
}
}
/// Allow a `FixedChunkPQTable` to be promoted to full quant vector store.
///
impl CreateQuantProvider for Poly<dyn Quantizer> {
type Target = QuantVectorProvider;
fn create(self, bf_tree_config: Config) -> ANNResult<Self::Target> {
QuantVectorProvider::new_with_config(self, bf_tree_config)
}
}
impl<T, Q> BfTreeProvider<T, Q>
where
T: VectorRepr,
Q: AsyncFriendly,
{
pub fn neighbors(&self) -> &NeighborProvider<u32> {
&self.neighbor_provider
}
}
///////////////////
// Data Provider //
///////////////////
impl<T, Q> DataProvider for BfTreeProvider<T, Q>
where
T: VectorRepr,
Q: AsyncFriendly,
{
type Context = DefaultContext;
// The `BfTreeProvider` uses the identity map for IDs.
//
type InternalId = u32;
// The `BfTreeProvider` uses the identity map for IDs.
//
type ExternalId = u32;
// Use a general error type for now.
//
type Error = ANNError;
// No insert-ID recovery.
type Guard = NoopGuard<u32>;
// Translate an external id to its corresponding internal id.
//
fn to_internal_id(
&self,
_context: &DefaultContext,
gid: &Self::ExternalId,
) -> Result<Self::InternalId, Self::Error> {
Ok(*gid)
}
// Translate an internal id its corresponding external id.
//
fn to_external_id(
&self,
_context: &DefaultContext,
id: Self::InternalId,
) -> Result<Self::ExternalId, Self::Error> {
Ok(id)
}
}
impl<T, Q> HasId for BfTreeProvider<T, Q>
where
T: VectorRepr,
Q: AsyncFriendly,
{
type Id = u32;
}
////////////////
// SetElement //
////////////////
/// Assign to both the full-precision and quant vector stores
///
impl<T> SetElement<&[T]> for BfTreeProvider<T, QuantVectorProvider>
where
T: VectorRepr,
{
type SetError = ANNError;
/// Store the provided element in both the full-precision and quant vector stores.
///
/// Full-precision is written first as the authoritative store. This ensures that
/// if the quant write fails, the worst case is a vector reachable in full-precision
/// but not yet in quantized search (benign), rather than a quantized ghost with no
/// full-precision backing data.
fn set_element(
&self,
_context: &Self::Context,
id: &u32,
element: &[T],
) -> impl Future<Output = Result<Self::Guard, Self::SetError>> + Send {
let _guard = self.locks.lock(id.into_usize());
// First, write to the authoritative full-precision store.
if let Err(err) = self.full_vectors.set_vector_sync(id.into_usize(), element) {
return std::future::ready(Err(err));
}
// Then, write the compressed representation to the quant store.
if let Err(err) = self.quant_vectors.set_vector_sync(id.into_usize(), element) {
debug_assert!(
false,
"quant write failed after full-precision success: {err}"
);
return std::future::ready(Err(err));
}
std::future::ready(Ok(NoopGuard::new(*id)))
}
}
/// Assign to just the full-precision store
///
impl<T> SetElement<&[T]> for BfTreeProvider<T, NoStore>
where
T: VectorRepr,
{
type SetError = ANNError;
/// Store the provided element in just the full-precision vector stores
///
fn set_element(
&self,
_context: &Self::Context,
id: &u32,
element: &[T],
) -> impl Future<Output = Result<Self::Guard, Self::SetError>> + Send {
let _guard = self.locks.lock(id.into_usize());
if let Err(err) = self.full_vectors.set_vector_sync(id.into_usize(), element) {
return std::future::ready(Err(err));
}
std::future::ready(Ok(NoopGuard::new(*id)))
}
}
//////////////////////
// StartPoint Trait //
//////////////////////
/// A struct with a private member that cannot be constructed outside of this module.
///
/// This is used to prevent users from calling internal methods directly.
pub struct Hidden(());
/// A trait for setting the start points of a BfTreeProvider.
///
/// This trait is implemented by `BfTreeProvider` variants that support setting start points.
/// The `Hidden` parameter ensures that users cannot call `set_start_points` directly;
/// they must go through the `BfTreeProvider::new` constructor which handles this internally.
pub trait StartPoint<T> {
/// Set the start points of the provider.
///
/// # Safety
/// This method is internal and should not be called directly by users.
/// Use `BfTreeProvider::new` instead.
#[doc(hidden)]
fn set_start_points(&self, hidden: Hidden, start_points: MatrixView<'_, T>) -> ANNResult<()>;
}
////////////////////
// SetStartPoints //
////////////////////
/// Set start points for the BfTreeProvider with quantization.
///
/// This implementation sets both the full-precision and quantized vectors for each
/// start point, as well as initializing empty neighbor lists.
impl<T> StartPoint<T> for BfTreeProvider<T, QuantVectorProvider>
where
T: VectorRepr,
{
fn set_start_points(&self, _hidden: Hidden, start_points: MatrixView<'_, T>) -> ANNResult<()> {
let start_point_ids = self.full_vectors.starting_points()?;
if start_points.nrows() != start_point_ids.len() {
return Err(ANNError::log_async_index_error(format!(
"expected start_points to contain `{}` rows, instead it has {}",
start_point_ids.len(),
start_points.nrows(),
)));
}
let mut scratch = self.neighbor_provider.scratch(&self.locks);
for (id, v) in std::iter::zip(start_point_ids, start_points.row_iter()) {
// Set the full-precision vector
self.full_vectors.set_vector_sync(id.into_usize(), v)?;
self.quant_vectors.set_vector_sync(id.into_usize(), v)?;
// Initialize empty neighbor list
scratch.write_neighbors(id, &[])?;
}
Ok(())
}
}
/// Set start points for the BfTreeProvider without quantization.
///
/// This implementation sets the full-precision vectors for each start point
/// and initializes empty neighbor lists.
impl<T> StartPoint<T> for BfTreeProvider<T, NoStore>
where
T: VectorRepr,
{
fn set_start_points(&self, _hidden: Hidden, start_points: MatrixView<'_, T>) -> ANNResult<()> {
let start_point_ids = self.full_vectors.starting_points()?;
if start_points.nrows() != start_point_ids.len() {
return Err(ANNError::log_async_index_error(format!(
"expected start_points to contain `{}` rows, instead it has {}",
start_point_ids.len(),
start_points.nrows(),
)));
}
let mut scratch = self.neighbor_provider.scratch(&self.locks);
for (id, v) in std::iter::zip(start_point_ids, start_points.row_iter()) {
// Set the full-precision vector
self.full_vectors.set_vector_sync(id.into_usize(), v)?;
// Initialize empty neighbor list
scratch.write_neighbors(id, &[])?;
}
Ok(())
}
}
//////////////////
// FullAccessor //
//////////////////
/// An accessor for retrieving full-precision vectors from the `BfTreeProvider`.
///
/// This type implements the following traits:
///
/// * [`Accessor`] for the [`BfTreeProvider`].
/// * [`BuildQueryComputer`].
///
pub struct FullAccessor<'a, T, Q>
where
T: VectorRepr,
Q: AsyncFriendly,
{
/// The host provider.
provider: &'a BfTreeProvider<T, Q>,
/// The fused query-distance computer.
computer: T::QueryDistance,
/// A buffer to store retrieved elements.
element: Box<[T]>,
}
impl<'a, T, Q> FullAccessor<'a, T, Q>
where
T: VectorRepr,
Q: AsyncFriendly,
{
pub(crate) fn new(provider: &'a BfTreeProvider<T, Q>, query: &[T]) -> Self {
Self {
provider,
computer: T::query_distance(query, provider.metric),
element: (0..provider.full_vectors.dim())
.map(|_| T::default())
.collect(),
}
}
fn get_distance(&mut self, id: u32) -> Result<f32, AccessError> {
self.provider
.full_vectors
.get_vector_into(id.into_usize(), &mut self.element)
.map(|_: ()| self.computer.evaluate_similarity(&self.element))
}
}
impl<T, Q> HasId for FullAccessor<'_, T, Q>
where
T: VectorRepr,
Q: AsyncFriendly,
{
type Id = u32;
}
impl<T, Q> glue::SearchAccessor for FullAccessor<'_, T, Q>
where
T: VectorRepr,
Q: AsyncFriendly,
{
fn starting_points(&self) -> impl Future<Output = ANNResult<Vec<u32>>> {
std::future::ready(self.provider.starting_points())
}
async fn start_point_distances<F>(&mut self, mut f: F) -> ANNResult<()>
where
F: FnMut(Self::Id, f32) + Send,
{
for i in self.provider.starting_points()? {
f(
i,
self.get_distance(i)
.escalate("starting point retrieval must succeed")?,
)
}
Ok(())
}
async fn expand_beam<Itr, P, F>(
&mut self,
ids: Itr,
mut pred: P,
mut on_neighbors: F,
) -> ANNResult<()>
where
Itr: Iterator<Item = Self::Id> + Send,
P: glue::HybridPredicate<Self::Id> + Send + Sync,
F: FnMut(Self::Id, f32) + Send,
{
let mut neighbors = AdjacencyList::new();
for n in ids {
self.provider.neighbors().get_neighbors(n, &mut neighbors)?;
for &id in neighbors.iter().filter(|i| pred.eval_mut(i)) {
if let Some(distance) = self
.get_distance(id)
.allow_transient("skipping deleted vectors")?
{
on_neighbors(id, distance)
}
}
}
Ok(())
}
}
///////////////////
// QuantAccessor //
///////////////////
/// An accessor that retrieves the quantized portion of the [`BfTreeProvider`].
///
/// This type implements the following traits:
///
/// * [`Accessor`] for the `BfTreeProvider`.
///
pub struct QuantAccessor<'a, T>
where
T: VectorRepr,
{
provider: &'a BfTreeProvider<T, QuantVectorProvider>,
/// The fused query-distance computer.
computer: super::quant::QuantQueryComputer,
/// A buffer to store retrieved elements.
element: Box<[u8]>,
}
impl<'a, T> QuantAccessor<'a, T>
where
T: VectorRepr,
{
pub(crate) fn new(
provider: &'a BfTreeProvider<T, QuantVectorProvider>,
query: &[T],
) -> ANNResult<Self> {
let computer = provider.quant_vectors.query_computer(query)?;
Ok(Self {
provider,
computer,
element: (0..provider.quant_vectors.quantizer.bytes())
.map(|_| u8::default())
.collect(),
})
}
fn get_distance(&mut self, id: u32) -> Result<f32, AccessError> {
match self
.provider
.quant_vectors
.get_vector_into(id.into_usize(), &mut self.element)
{
Ok(()) => self
.computer
.evaluate(&self.element)
.map_err(RankedError::Error),
Err(err) => Err(err),
}
}
}
impl<T> HasId for QuantAccessor<'_, T>
where
T: VectorRepr,
{
type Id = u32;
}
impl<T> glue::SearchAccessor for QuantAccessor<'_, T>
where
T: VectorRepr,
{
fn starting_points(&self) -> impl Future<Output = ANNResult<Vec<u32>>> {
std::future::ready(self.provider.starting_points())
}
async fn start_point_distances<F>(&mut self, mut f: F) -> ANNResult<()>
where
F: FnMut(Self::Id, f32) + Send,
{
for i in self.provider.starting_points()? {
f(
i,
self.get_distance(i)
.escalate("starting point retrieval must succeed")?,
)
}
Ok(())
}
async fn expand_beam<Itr, P, F>(
&mut self,
ids: Itr,
mut pred: P,
mut on_neighbors: F,
) -> ANNResult<()>
where
Itr: Iterator<Item = Self::Id> + Send,
P: glue::HybridPredicate<Self::Id> + Send + Sync,
F: FnMut(Self::Id, f32) + Send,
{
let mut neighbors = AdjacencyList::new();
for n in ids {
self.provider.neighbors().get_neighbors(n, &mut neighbors)?;
for &id in neighbors.iter().filter(|i| pred.eval_mut(i)) {
if let Some(distance) = self
.get_distance(id)
.allow_transient("skipping deleted vectors")?
{
on_neighbors(id, distance)
}
}
}
Ok(())
}
}
///////////////////////