1use std::{future::Future, sync::Mutex};
7
8use crate::storage::{StorageReadProvider, StorageWriteProvider};
9use diskann::{
10 ANNError, ANNResult, default_post_processor,
11 error::IntoANNResult,
12 graph::{
13 AdjacencyList,
14 glue::{
15 self, DefaultPostProcessor, FilterStartPoints, InsertStrategy, Pipeline, PruneStrategy,
16 SearchStrategy,
17 },
18 workingset,
19 },
20 provider::{ExecutionContext, HasId},
21 utils::{IntoUsize, VectorRepr},
22};
23use diskann_quantization::{
24 AsFunctor, CompressInto,
25 bits::{Representation, Unsigned},
26 meta::NotCanonical,
27 scalar::{
28 CompensatedCosineNormalized, CompensatedIP, CompensatedSquaredL2, CompensatedVector,
29 CompensatedVectorRef, InputContainsNaN, MeanNormMissing, MutCompensatedVectorRef,
30 ScalarQuantizer,
31 },
32};
33use diskann_utils::{Reborrow, ReborrowMut, future::AsyncFriendly};
34use diskann_vector::{DistanceFunction, PreprocessedDistanceFunction, distance::Metric};
35use thiserror::Error;
36
37use super::{DefaultProvider, GetFullPrecision, Rerank};
38use crate::{
39 common::IgnoreLockPoison,
40 model::graph::provider::async_::{
41 FastMemoryVectorProviderAsync, SimpleNeighborProviderAsync,
42 common::{
43 AlignedMemoryVectorStore, CreateVectorStore, NoStore, Quantized, SetElementHelper,
44 TestCallCount, VectorStore,
45 },
46 inmem::{FullPrecisionProvider, FullPrecisionStore},
47 postprocess::{AsDeletionCheck, DeletionCheck, RemoveDeletedIdsAndCopy},
48 },
49 storage::{self, AsyncIndexMetadata, AsyncQuantLoadContext, LoadWith, SaveWith},
50};
51
52type CVRef<'a, const NBITS: usize> = CompensatedVectorRef<'a, NBITS>;
53
54#[derive(Clone)]
60pub struct WithBits<const NBITS: usize> {
61 quantizer: ScalarQuantizer,
62}
63
64impl<const NBITS: usize> WithBits<NBITS> {
65 pub fn new(quantizer: ScalarQuantizer) -> Self {
66 Self { quantizer }
67 }
68}
69
70const WRITE_LOCK_GRANULARITY: usize = 16;
79
80const PREFETCH_DEFAULT: usize = 8;
82
83pub struct SQStore<const NBITS: usize> {
84 data: AlignedMemoryVectorStore<u8>,
85 quantizer: ScalarQuantizer,
86 metric: Metric,
87
88 write_locks: Vec<Mutex<()>>,
92
93 prefetch_lookahead: usize,
95
96 num_get_calls: TestCallCount,
97}
98
99impl<const NBITS: usize> SQStore<NBITS>
100where
101 Unsigned: Representation<NBITS>,
102{
103 pub(super) fn new(
104 quantizer: ScalarQuantizer,
105 num_vectors: usize,
106 metric: Metric,
107 prefetch_lookahead: Option<usize>,
108 ) -> Self {
109 let write_locks = (0..num_vectors.div_ceil(WRITE_LOCK_GRANULARITY))
110 .map(|_| Mutex::new(()))
111 .collect::<Vec<_>>();
112 let bytes = CVRef::<NBITS>::canonical_bytes(quantizer.dim());
115 Self {
116 data: AlignedMemoryVectorStore::with_capacity(num_vectors, bytes),
117 quantizer,
118 metric,
119 write_locks,
120 num_get_calls: TestCallCount::default(),
121 prefetch_lookahead: prefetch_lookahead.unwrap_or(PREFETCH_DEFAULT),
122 }
123 }
124
125 pub(crate) fn prefetch_hint(&self, i: usize) {
131 let data = unsafe { self.data.get_slice(i) };
135 diskann_vector::prefetch_hint_max::<4, _>(data);
136 }
137
138 pub(super) fn dim(&self) -> usize {
139 self.quantizer.dim()
140 }
141
142 pub(super) fn get_vector(&self, i: usize) -> Result<CVRef<'_, NBITS>, SQError> {
143 self.num_get_calls.increment();
144 Ok(CVRef::from_canonical_front(
145 unsafe { self.data.get_slice(i) },
146 self.dim(),
147 )?)
148 }
149
150 pub(super) fn set_vector<T>(&self, i: usize, v: &[T]) -> Result<(), SQError>
151 where
152 T: VectorRepr,
153 {
154 let vf32: &[f32] =
155 &T::as_f32(v).map_err(|e| SQError::FullPrecisionConversionErr(format!("{:?}", e)))?;
156
157 debug_assert!(
158 vf32.len() == self.dim(),
159 "vector f32 dimension {} does not match dimension {}",
160 vf32.len(),
161 self.dim()
162 );
163
164 let lock_id = i / WRITE_LOCK_GRANULARITY;
165 let _guard = self.write_locks[lock_id].lock_or_panic();
166
167 self.quantizer.compress_into(
168 vf32,
169 MutCompensatedVectorRef::<NBITS>::from_canonical_front_mut(
170 unsafe { self.data.get_mut_slice(i) },
171 self.dim(),
172 )?,
173 )?;
174 Ok(())
175 }
176
177 pub(crate) unsafe fn set_quant_vector(&self, i: usize, v: &[u8]) -> ANNResult<()> {
196 let expected_quant_len = CVRef::<NBITS>::canonical_bytes(self.dim());
197 debug_assert!(
198 v.len() == expected_quant_len,
199 "vector length {} does not match dimension {}",
200 v.len(),
201 expected_quant_len
202 );
203
204 let lock_id = i / WRITE_LOCK_GRANULARITY;
205 let _guard = self.write_locks[lock_id].lock_or_panic();
206 unsafe { self.data.get_mut_slice(i) }.copy_from_slice(v);
210 Ok(())
211 }
212
213 pub(super) fn distance_computer(&self) -> Result<DistanceComputer, SQError> {
215 Ok(match self.metric {
216 Metric::L2 => DistanceComputer::SquaredL2(self.quantizer.as_functor()),
217 Metric::InnerProduct => DistanceComputer::InnerProduct(self.quantizer.as_functor()),
218 Metric::CosineNormalized => {
219 DistanceComputer::CosineNormalized(self.quantizer.as_functor())
220 }
221 unsupported_metric => {
222 return Err(SQError::UnsupportedDistanceMetric(unsupported_metric));
223 }
224 })
225 }
226
227 pub(super) fn query_computer<T>(
228 &self,
229 query: &[T],
230 allow_rescale: bool,
231 ) -> Result<QueryComputer<NBITS>, SQError>
232 where
233 T: VectorRepr,
234 {
235 let mut boxed = CompensatedVector::new_boxed(self.dim());
236 let q = T::as_f32(query)
237 .map_err(|e| SQError::FullPrecisionConversionErr(format!("{:?}", e)))?;
238
239 if allow_rescale && !matches!(self.metric, Metric::L2 | Metric::CosineNormalized) {
240 let mut query: Box<[f32]> = q.as_ref().into();
241 self.quantizer.rescale(&mut query)?;
242 self.quantizer
243 .compress_into(&*query, boxed.reborrow_mut())?;
244 } else {
245 self.quantizer
246 .compress_into(q.as_ref(), boxed.reborrow_mut())?;
247 }
248
249 Ok(QueryComputer {
250 inner: self.distance_computer()?,
251 query: boxed,
252 })
253 }
254
255 pub fn prefetch_lookahead(&self) -> usize {
256 self.prefetch_lookahead
257 }
258}
259
260#[derive(Debug)]
261pub enum DistanceComputer {
262 SquaredL2(CompensatedSquaredL2),
263 InnerProduct(CompensatedIP),
264 CosineNormalized(CompensatedCosineNormalized),
265}
266
267impl<const NBITS: usize> DistanceFunction<CVRef<'_, NBITS>, CVRef<'_, NBITS>, f32>
268 for DistanceComputer
269where
270 Unsigned: Representation<NBITS>,
271 CompensatedSquaredL2: for<'a, 'b> DistanceFunction<
272 CVRef<'a, NBITS>,
273 CVRef<'b, NBITS>,
274 diskann_quantization::distances::Result<f32>,
275 >,
276 CompensatedIP: for<'a, 'b> DistanceFunction<
277 CVRef<'a, NBITS>,
278 CVRef<'b, NBITS>,
279 diskann_quantization::distances::Result<f32>,
280 >,
281 CompensatedCosineNormalized: for<'a, 'b> DistanceFunction<
282 CVRef<'a, NBITS>,
283 CVRef<'b, NBITS>,
284 diskann_quantization::distances::Result<f32>,
285 >,
286{
287 #[inline(always)]
288 fn evaluate_similarity(&self, left: CVRef<'_, NBITS>, right: CVRef<'_, NBITS>) -> f32 {
289 let r = match self {
290 DistanceComputer::SquaredL2(f) => f.evaluate_similarity(left, right),
291 DistanceComputer::InnerProduct(f) => f.evaluate_similarity(left, right),
292 DistanceComputer::CosineNormalized(f) => f.evaluate_similarity(left, right),
293 };
294
295 r.map_err(|err| err.panic(left.len(), right.len())).unwrap()
296 }
297}
298
299pub struct QueryComputer<const NBITS: usize>
300where
301 Unsigned: Representation<NBITS>,
302{
303 inner: DistanceComputer,
304 query: CompensatedVector<NBITS>,
305}
306
307impl<const NBITS: usize> PreprocessedDistanceFunction<CVRef<'_, NBITS>, f32>
308 for QueryComputer<NBITS>
309where
310 Unsigned: Representation<NBITS>,
311 DistanceComputer: for<'a, 'b> DistanceFunction<CVRef<'a, NBITS>, CVRef<'b, NBITS>, f32>,
312{
313 fn evaluate_similarity(&self, changing: CVRef<'_, NBITS>) -> f32 {
314 self.inner
315 .evaluate_similarity(self.query.reborrow(), changing)
316 }
317}
318
319impl<const NBITS: usize> CreateVectorStore for WithBits<NBITS>
324where
325 Unsigned: Representation<NBITS>,
326{
327 type Target = SQStore<NBITS>;
328
329 fn create(
331 self,
332 max_points: usize,
333 metric: Metric,
334 prefetch_lookahead: Option<usize>,
335 ) -> Self::Target {
336 SQStore::new(self.quantizer, max_points, metric, prefetch_lookahead)
337 }
338}
339
340impl<const NBITS: usize> VectorStore for SQStore<NBITS> {
341 fn total(&self) -> usize {
342 self.data.max_vectors()
343 }
344
345 fn count_for_get_vector(&self) -> usize {
346 self.num_get_calls.get()
347 }
348}
349
350impl<T, const NBITS: usize> SetElementHelper<T> for SQStore<NBITS>
356where
357 T: VectorRepr,
358 Unsigned: Representation<NBITS>,
359{
360 fn set_element(&self, id: &u32, element: &[T]) -> ANNResult<()> {
361 self.set_vector(id.into_usize(), element)?;
362 Ok(())
363 }
364}
365
366pub struct PruneAccessor<'a, const NBITS: usize> {
371 store: &'a SQStore<NBITS>,
372 neighbors: &'a SimpleNeighborProviderAsync,
373 distance: DistanceComputer,
374}
375
376impl<'a, const NBITS: usize> PruneAccessor<'a, NBITS>
377where
378 Unsigned: Representation<NBITS>,
379 DistanceComputer: for<'x, 'y> DistanceFunction<CVRef<'x, NBITS>, CVRef<'y, NBITS>, f32>,
380{
381 fn new(
382 store: &'a SQStore<NBITS>,
383 neighbors: &'a SimpleNeighborProviderAsync,
384 ) -> ANNResult<Self> {
385 let distance = store.distance_computer()?;
386 Ok(Self {
387 store,
388 neighbors,
389 distance,
390 })
391 }
392}
393
394impl<const NBITS: usize> HasId for PruneAccessor<'_, NBITS> {
395 type Id = u32;
396}
397
398impl<const NBITS: usize> glue::PruneAccessor for PruneAccessor<'_, NBITS>
399where
400 Unsigned: Representation<NBITS>,
401 DistanceComputer: for<'a, 'b> DistanceFunction<CVRef<'a, NBITS>, CVRef<'b, NBITS>, f32>,
402{
403 type ElementRef<'a> = CVRef<'a, NBITS>;
404 type View<'a>
405 = &'a Self
406 where
407 Self: 'a;
408 type Distance<'a>
409 = &'a DistanceComputer
410 where
411 Self: 'a;
412 type Neighbors<'a>
413 = &'a SimpleNeighborProviderAsync
414 where
415 Self: 'a;
416
417 async fn fill<Itr>(&mut self, _itr: Itr) -> ANNResult<(Self::View<'_>, Self::Distance<'_>)>
418 where
419 Itr: ExactSizeIterator<Item = Self::Id> + Clone + Send + Sync,
420 {
421 Ok((self, &self.distance))
422 }
423
424 fn neighbors(&mut self) -> Self::Neighbors<'_> {
425 self.neighbors
426 }
427}
428
429impl<const NBITS: usize> workingset::View<u32> for &PruneAccessor<'_, NBITS>
431where
432 Unsigned: Representation<NBITS>,
433{
434 type ElementRef<'a> = CVRef<'a, NBITS>;
435 type Element<'a>
436 = CVRef<'a, NBITS>
437 where
438 Self: 'a;
439
440 fn get(&self, id: u32) -> Option<Self::Element<'_>> {
441 self.store.get_vector(id.into_usize()).ok()
442 }
443}
444
445pub struct QuantAccessor<'a, const NBITS: usize, V, D, Ctx>
451where
452 Unsigned: Representation<NBITS>,
453{
454 provider: &'a DefaultProvider<V, SQStore<NBITS>, D, Ctx>,
455 computer: QueryComputer<NBITS>,
456 id_buffer: AdjacencyList<u32>,
457}
458
459impl<'a, const NBITS: usize, V, D, Ctx> QuantAccessor<'a, NBITS, V, D, Ctx>
460where
461 V: AsyncFriendly,
462 D: AsyncFriendly,
463 Ctx: ExecutionContext,
464 Unsigned: Representation<NBITS>,
465{
466 pub(crate) fn new(
467 provider: &'a DefaultProvider<V, SQStore<NBITS>, D, Ctx>,
468 query: &[f32],
469 is_search: bool,
470 ) -> ANNResult<Self> {
471 let computer = provider.aux_vectors.query_computer(query, is_search)?;
472 Ok(Self {
473 provider,
474 computer,
475 id_buffer: AdjacencyList::with_capacity(32),
476 })
477 }
478}
479
480impl<T, const NBITS: usize, D, Ctx> GetFullPrecision
481 for QuantAccessor<'_, NBITS, FullPrecisionStore<T>, D, Ctx>
482where
483 T: VectorRepr,
484 Unsigned: Representation<NBITS>,
485{
486 type Repr = T;
487 fn as_full_precision(&self) -> &FastMemoryVectorProviderAsync<T> {
488 &self.provider.base_vectors
489 }
490}
491
492impl<const NBITS: usize, V, D, Ctx> HasId for QuantAccessor<'_, NBITS, V, D, Ctx>
493where
494 Unsigned: Representation<NBITS>,
495{
496 type Id = u32;
497}
498
499impl<const NBITS: usize, V, D, Ctx> glue::SearchAccessor for QuantAccessor<'_, NBITS, V, D, Ctx>
500where
501 V: AsyncFriendly,
502 D: AsyncFriendly,
503 Ctx: ExecutionContext,
504 Unsigned: Representation<NBITS>,
505 QueryComputer<NBITS>: for<'a> PreprocessedDistanceFunction<CVRef<'a, NBITS>, f32>,
506{
507 fn starting_points(&self) -> impl Future<Output = ANNResult<Vec<u32>>> {
508 std::future::ready(self.provider.starting_points())
509 }
510
511 fn num_starting_points(&self) -> impl Future<Output = ANNResult<usize>> {
512 std::future::ready(Ok(self.provider.num_start_points()))
513 }
514
515 fn start_point_distances<F>(
516 &mut self,
517 mut f: F,
518 ) -> impl std::future::Future<Output = ANNResult<()>> + Send
519 where
520 F: FnMut(Self::Id, f32) + Send,
521 {
522 let mut f = move || -> ANNResult<()> {
523 for i in self.provider.starting_points()? {
524 let vector = self.provider.aux_vectors.get_vector(i.into_usize())?;
525 f(i, self.computer.evaluate_similarity(vector));
526 }
527 Ok(())
528 };
529
530 std::future::ready(f())
531 }
532
533 fn expand_beam<Itr, P, F>(
534 &mut self,
535 ids: Itr,
536 mut pred: P,
537 mut on_neighbors: F,
538 ) -> impl std::future::Future<Output = ANNResult<()>> + Send
539 where
540 Itr: Iterator<Item = Self::Id> + Send,
541 P: glue::HybridPredicate<Self::Id> + Send + Sync,
542 F: FnMut(Self::Id, f32) + Send,
543 {
544 let f = move || -> ANNResult<()> {
545 let id_buffer = &mut self.id_buffer;
546 for n in ids {
547 self.provider
548 .neighbor_provider
549 .get_neighbors_sync(n.into_usize(), id_buffer)?;
550
551 id_buffer.retain(|i| pred.eval_mut(i));
552
553 let len = id_buffer.len();
554 let lookahead = self.provider.aux_vectors.prefetch_lookahead();
555
556 for id in id_buffer.iter().take(lookahead) {
558 self.provider.aux_vectors.prefetch_hint(id.into_usize());
559 }
560
561 for (i, id) in id_buffer.iter().enumerate() {
562 if lookahead > 0 && i + lookahead < len {
564 self.provider
565 .aux_vectors
566 .prefetch_hint(id_buffer[i + lookahead].into_usize());
567 }
568
569 let vector = self.provider.aux_vectors.get_vector(id.into_usize())?;
570 let distance = self.computer.evaluate_similarity(vector);
571 on_neighbors(*id, distance);
572 }
573 }
574 Ok(())
575 };
576
577 std::future::ready(f())
578 }
579}
580
581impl<const NBITS: usize, V, D, Ctx> AsDeletionCheck for QuantAccessor<'_, NBITS, V, D, Ctx>
582where
583 V: AsyncFriendly,
584 D: AsyncFriendly + DeletionCheck,
585 Ctx: ExecutionContext,
586 Unsigned: Representation<NBITS>,
587{
588 type Checker = D;
589 fn as_deletion_check(&self) -> &D {
590 &self.provider.deleted
591 }
592}
593
594impl<'a, const NBITS: usize, D, Ctx, T>
602 SearchStrategy<'a, FullPrecisionProvider<T, SQStore<NBITS>, D, Ctx>, &'a [T]> for Quantized
603where
604 T: VectorRepr,
605 D: AsyncFriendly + DeletionCheck,
606 Ctx: ExecutionContext,
607 Unsigned: Representation<NBITS>,
608 QueryComputer<NBITS>: for<'b> PreprocessedDistanceFunction<CVRef<'b, NBITS>, f32>,
609{
610 type SearchAccessor = QuantAccessor<'a, NBITS, FullPrecisionStore<T>, D, Ctx>;
611 type SearchAccessorError = ANNError;
612
613 fn search_accessor(
614 &'a self,
615 provider: &'a FullPrecisionProvider<T, SQStore<NBITS>, D, Ctx>,
616 _context: &'a Ctx,
617 query: &'a [T],
618 ) -> Result<Self::SearchAccessor, Self::SearchAccessorError> {
619 let as_f32 = T::as_f32(query).into_ann_result()?;
620 QuantAccessor::new(provider, &as_f32, true)
621 }
622}
623
624impl<'a, const NBITS: usize, D, Ctx, T>
625 DefaultPostProcessor<'a, FullPrecisionProvider<T, SQStore<NBITS>, D, Ctx>, &'a [T]>
626 for Quantized
627where
628 T: VectorRepr,
629 D: AsyncFriendly + DeletionCheck,
630 Ctx: ExecutionContext,
631 Unsigned: Representation<NBITS>,
632 QueryComputer<NBITS>: for<'b> PreprocessedDistanceFunction<CVRef<'b, NBITS>, f32>,
633{
634 default_post_processor!(Pipeline<FilterStartPoints, Rerank>);
635}
636
637impl<'a, const NBITS: usize, D, Ctx, T>
641 SearchStrategy<'a, DefaultProvider<NoStore, SQStore<NBITS>, D, Ctx>, &'a [T]> for Quantized
642where
643 T: VectorRepr,
644 D: AsyncFriendly + DeletionCheck,
645 Ctx: ExecutionContext,
646 Unsigned: Representation<NBITS>,
647 QueryComputer<NBITS>: for<'b> PreprocessedDistanceFunction<CVRef<'b, NBITS>, f32>,
648{
649 type SearchAccessor = QuantAccessor<'a, NBITS, NoStore, D, Ctx>;
650 type SearchAccessorError = ANNError;
651
652 fn search_accessor(
653 &'a self,
654 provider: &'a DefaultProvider<NoStore, SQStore<NBITS>, D, Ctx>,
655 _context: &'a Ctx,
656 query: &'a [T],
657 ) -> Result<Self::SearchAccessor, Self::SearchAccessorError> {
658 let as_f32 = T::as_f32(query).into_ann_result()?;
659 QuantAccessor::new(provider, &as_f32, true)
660 }
661}
662
663impl<'a, const NBITS: usize, D, Ctx, T>
664 DefaultPostProcessor<'a, DefaultProvider<NoStore, SQStore<NBITS>, D, Ctx>, &'a [T]>
665 for Quantized
666where
667 T: VectorRepr,
668 D: AsyncFriendly + DeletionCheck,
669 Ctx: ExecutionContext,
670 Unsigned: Representation<NBITS>,
671 QueryComputer<NBITS>: for<'b> PreprocessedDistanceFunction<CVRef<'b, NBITS>, f32>,
672{
673 default_post_processor!(Pipeline<FilterStartPoints, RemoveDeletedIdsAndCopy>);
674}
675
676impl<const NBITS: usize, V, D, Ctx> PruneStrategy<DefaultProvider<V, SQStore<NBITS>, D, Ctx>>
677 for Quantized
678where
679 V: AsyncFriendly,
680 D: AsyncFriendly + DeletionCheck,
681 Ctx: ExecutionContext,
682 Unsigned: Representation<NBITS>,
683 DistanceComputer: for<'a, 'b> DistanceFunction<CVRef<'a, NBITS>, CVRef<'b, NBITS>, f32>,
684{
685 type PruneAccessor<'a> = PruneAccessor<'a, NBITS>;
686 type PruneAccessorError = ANNError;
687
688 fn prune_accessor<'a>(
689 &'a self,
690 provider: &'a DefaultProvider<V, SQStore<NBITS>, D, Ctx>,
691 _context: &'a Ctx,
692 _capacity: usize,
693 ) -> Result<Self::PruneAccessor<'a>, Self::PruneAccessorError> {
694 PruneAccessor::new(&provider.aux_vectors, provider.neighbors())
695 }
696}
697
698impl<'a, const NBITS: usize, V, D, Ctx, T>
699 InsertStrategy<'a, DefaultProvider<V, SQStore<NBITS>, D, Ctx>, &'a [T]> for Quantized
700where
701 T: VectorRepr,
702 V: AsyncFriendly,
703 D: AsyncFriendly + DeletionCheck,
704 Ctx: ExecutionContext,
705 Unsigned: Representation<NBITS>,
706 QueryComputer<NBITS>: for<'x> PreprocessedDistanceFunction<CVRef<'x, NBITS>, f32>,
707 DistanceComputer: for<'x, 'y> DistanceFunction<CVRef<'x, NBITS>, CVRef<'y, NBITS>, f32>,
708 Quantized: SearchStrategy<
709 'a,
710 DefaultProvider<V, SQStore<NBITS>, D, Ctx>,
711 &'a [T],
712 SearchAccessor = QuantAccessor<'a, NBITS, V, D, Ctx>,
713 >,
714{
715 type PruneStrategy = Self;
716
717 fn prune_strategy(&self) -> Self::PruneStrategy {
718 *self
719 }
720}
721
722impl<const NBITS: usize, V, D, Ctx, B>
723 glue::MultiInsertStrategy<DefaultProvider<V, SQStore<NBITS>, D, Ctx>, B> for Quantized
724where
725 V: AsyncFriendly,
726 D: AsyncFriendly + DeletionCheck,
727 Ctx: ExecutionContext,
728 B: glue::Batch,
729 Self: PruneStrategy<DefaultProvider<V, SQStore<NBITS>, D, Ctx>>
730 + for<'a> InsertStrategy<
731 'a,
732 DefaultProvider<V, SQStore<NBITS>, D, Ctx>,
733 B::Element<'a>,
734 PruneStrategy = Self,
735 >,
736{
737 type Seed = ();
738 type FinishError = diskann::error::Infallible;
739 type PruneStrategy = Self;
740 type InsertStrategy = Self;
741
742 fn insert_strategy(&self) -> Self::InsertStrategy {
743 *self
744 }
745
746 fn finish<Itr>(
747 &self,
748 _provider: &DefaultProvider<V, SQStore<NBITS>, D, Ctx>,
749 _ctx: &Ctx,
750 _batch: &std::sync::Arc<B>,
751 _ids: Itr,
752 ) -> impl std::future::Future<Output = Result<Self::Seed, Self::FinishError>> + Send
753 where
754 Itr: ExactSizeIterator<Item = u32> + Send,
755 {
756 std::future::ready(Ok(()))
757 }
758
759 fn seeded_prune_accessor<'a>(
760 &'a self,
761 provider: &'a DefaultProvider<V, SQStore<NBITS>, D, Ctx>,
762 context: &'a Ctx,
763 _seed: &'a (),
764 capacity: usize,
765 ) -> ANNResult<
766 <Self as PruneStrategy<DefaultProvider<V, SQStore<NBITS>, D, Ctx>>>::PruneAccessor<'a>,
767 > {
768 self.prune_accessor(provider, context, capacity)
769 .into_ann_result()
770 }
771}
772
773impl<const NBITS: usize> SaveWith<AsyncIndexMetadata> for SQStore<NBITS> {
778 type Ok = usize;
779 type Error = ANNError;
780
781 async fn save_with<P>(
782 &self,
783 write_provider: &P,
784 metadata: &AsyncIndexMetadata,
785 ) -> Result<Self::Ok, Self::Error>
786 where
787 P: StorageWriteProvider,
788 {
789 let sq_storage = storage::SQStorage::new(metadata.prefix());
790 let bytes_written =
791 storage::bin::save_to_bin(self, write_provider, sq_storage.compressed_data_path())?;
792 let quantizer_bytes_written = sq_storage.save_quantizer(&self.quantizer, write_provider)?;
793 Ok(bytes_written + quantizer_bytes_written)
794 }
795}
796
797impl<const NBITS: usize> LoadWith<AsyncQuantLoadContext> for SQStore<NBITS>
798where
799 Unsigned: Representation<NBITS>,
800{
801 type Error = ANNError;
802
803 async fn load_with<P>(read_provider: &P, ctx: &AsyncQuantLoadContext) -> ANNResult<Self>
804 where
805 P: StorageReadProvider,
806 {
807 let sq_storage = storage::SQStorage::new(ctx.metadata.prefix());
808 let quantizer = sq_storage.load_quantizer(read_provider)?;
809
810 storage::bin::load_from_bin(
811 read_provider,
812 sq_storage.compressed_data_path(),
813 |num_points, _pq_bytes| {
814 Ok(SQStore::<NBITS>::new(
815 quantizer,
816 num_points,
817 ctx.metric,
818 ctx.prefetch_lookahead,
819 ))
820 },
821 )
822 }
823}
824
825impl<const NBITS: usize> storage::bin::SetData for SQStore<NBITS>
827where
828 Unsigned: Representation<NBITS>,
829{
830 type Item = u8;
831
832 fn set_data(&mut self, i: usize, element: &[Self::Item]) -> ANNResult<()> {
833 unsafe { self.set_quant_vector(i, element) }
835 }
836}
837
838impl<const NBITS: usize> storage::bin::GetData for SQStore<NBITS> {
840 type Element = u8;
841 type Item<'a> = &'a [u8];
842
843 fn get_data(&self, i: usize) -> ANNResult<Self::Item<'_>> {
844 Ok(unsafe { self.data.get_slice(i) })
847 }
848
849 fn total(&self) -> usize {
851 self.data.max_vectors()
852 }
853
854 fn dim(&self) -> usize {
855 self.data.dim()
856 }
857}
858
859#[derive(Debug, Error)]
860pub enum SQError {
861 #[error("Issue with canonical layout of data: {0:?}")]
862 CanonicalLayoutError(#[from] NotCanonical),
863
864 #[error("Input contains NaN values.")]
865 InputContainsNaN(#[from] InputContainsNaN),
866
867 #[error("Input full-precision conversion error : {0}")]
868 FullPrecisionConversionErr(String),
869
870 #[error("Mean Norm is missing in the quantizer.")]
871 MeanNormMissing(#[from] MeanNormMissing),
872
873 #[error("Unsupported distance metric: {0:?}")]
874 UnsupportedDistanceMetric(Metric),
875
876 #[error("Error while loading quantizer proto struct from file: {0:?}")]
877 ProtoStorageError(#[from] crate::storage::protos::ProtoStorageError),
878
879 #[error("Error while converting proto struct to Scalar Qunatizer: {0:?}")]
880 QuantizerDecodeError(#[from] crate::storage::protos::ProtoConversionError),
881}
882
883impl From<SQError> for ANNError {
884 #[cold]
885 fn from(err: SQError) -> Self {
886 ANNError::log_sq_error(err)
887 }
888}
889
890#[cfg(test)]
891mod tests {
892 use crate::storage::VirtualStorageProvider;
893 use diskann::utils::ONE;
894 use diskann_quantization::scalar::train::ScalarQuantizationParameters;
895 use diskann_utils::views::MatrixView;
896 use diskann_vector::distance::Metric;
897 use rstest::rstest;
898
899 use super::*;
900
901 const NBITS: usize = 1;
902 const DIM: usize = 4;
903 const NPTS: usize = 5;
904 const DATA: [f32; 20] = [
905 0.286541, -0.079761, 0.373634, 0.878595, -0.131049, -0.131040, 0.883841, 0.429512,
906 -0.482576, 0.557701, -0.476350, -0.478727, 0.091383, -0.722600, -0.651460, -0.212363,
907 -0.510018, 0.158241, -0.457242, -0.711176,
908 ];
909 const V: [f32; DIM] = [DATA[0], DATA[1], DATA[2], DATA[3]];
910
911 fn make_store(metric: Metric) -> SQStore<NBITS> {
912 let quantizer = ScalarQuantizationParameters::default()
913 .train(MatrixView::try_from(&DATA, NPTS, DIM).unwrap());
914 SQStore::new(quantizer, 5, metric, None)
915 }
916
917 #[test]
918 fn test_dim() {
919 let store = make_store(Metric::L2);
920 assert_eq!(store.dim(), DIM);
921 }
922
923 #[test]
924 fn test_set_and_get_vector() {
925 let store = make_store(Metric::L2);
926 store.set_vector(0, &V).unwrap();
927 store.get_vector(0).unwrap();
928 }
930
931 #[test]
932 #[should_panic]
933 fn test_set_vector_wrong_dim_panic_in_debug() {
934 let store = make_store(Metric::L2);
935 let _: Result<_, SQError> = store.set_vector(0, &[1.0f32; DIM + 1]);
936 }
937
938 #[test]
939 #[should_panic]
940 fn test_get_vector_oob() {
941 let store = make_store(Metric::L2);
942 let _: Result<_, SQError> = store.get_vector(NPTS);
943 }
944
945 #[test]
946 fn test_prefetch_hint_ok() {
947 let store = make_store(Metric::L2);
948 store.prefetch_hint(NPTS - 1);
949 }
950
951 #[test]
952 #[should_panic]
953 fn test_prefetch_hint_oob() {
954 let store = make_store(Metric::L2);
955 store.prefetch_hint(NPTS);
956 }
957
958 #[test]
959 fn test_distance_computer_variants() {
960 let dc_l2 = make_store(Metric::L2).distance_computer().unwrap();
961 match dc_l2 {
962 DistanceComputer::SquaredL2(_) => {}
963 _ => panic!("expected SquaredL2 variant"),
964 }
965
966 let dc_ip = make_store(Metric::InnerProduct)
967 .distance_computer()
968 .unwrap();
969 match dc_ip {
970 DistanceComputer::InnerProduct(_) => {}
971 _ => panic!("expected InnerProduct variant"),
972 }
973
974 let dc_cosine_normalized = make_store(Metric::CosineNormalized)
975 .distance_computer()
976 .unwrap();
977 match dc_cosine_normalized {
978 DistanceComputer::CosineNormalized(_) => {}
979 _ => panic!("expected CosineNormalized variant"),
980 }
981
982 let dc_unsupported = make_store(Metric::Cosine).distance_computer().unwrap_err();
983 match dc_unsupported {
984 SQError::UnsupportedDistanceMetric(Metric::Cosine) => {}
985 _ => panic!("expected UnsupportedDistanceMetric error"),
986 }
987 }
988
989 #[rstest]
990 fn test_query_computer(
991 #[values(Metric::L2, Metric::InnerProduct, Metric::CosineNormalized)] metric: Metric,
992 #[values(false, true)] allow_rescale: bool,
993 ) {
994 let store = make_store(metric);
995 let q = [1.0_f32; DIM];
996 let result = store.query_computer(&q, allow_rescale);
997 assert!(
998 result.is_ok(),
999 "query_computer() failed for metric {:?} with allow_rescale={}",
1000 metric,
1001 allow_rescale
1002 );
1003 }
1004
1005 #[test]
1006 fn test_set_quant_vector() {
1007 let store = make_store(Metric::L2);
1008 let compressed_vec_len = CVRef::<NBITS>::canonical_bytes(DIM);
1009 let raw = vec![1u8; compressed_vec_len];
1010
1011 unsafe {
1012 store.set_quant_vector(0, &raw).unwrap();
1013 }
1014
1015 let slice = unsafe { store.data.get_slice(0) };
1017 assert_eq!(slice, raw.as_slice());
1018 }
1019
1020 #[test]
1021 #[should_panic]
1022 fn test_set_quant_vector_with_wrong_dim_panics() {
1023 let store = make_store(Metric::L2);
1024 let wrong_compressed_vec_len = CVRef::<NBITS>::canonical_bytes(DIM) + 1;
1025 let raw = vec![1u8; wrong_compressed_vec_len];
1026
1027 unsafe {
1028 store.set_quant_vector(0, &raw).unwrap();
1029 }
1030 }
1031
1032 #[rstest]
1033 fn test_distance_computer_cosine_normalized(
1034 #[values(Metric::L2, Metric::InnerProduct, Metric::CosineNormalized)] metric: Metric,
1035 ) {
1036 let store = make_store(metric);
1037 let v1 = [0.1, 0.2, 0.3, 0.4];
1039 let v2 = [0.4, 0.3, 0.2, 0.1];
1040 store.set_vector(0, &v1).unwrap();
1041 store.set_vector(1, &v2).unwrap();
1042
1043 let dc = store.distance_computer().unwrap();
1044 let x = store.get_vector(0).unwrap();
1045 let y = store.get_vector(1).unwrap();
1046
1047 let _ = dc.evaluate_similarity(x, y);
1049 }
1050
1051 #[tokio::test]
1052 async fn test_save_with_and_load_with() {
1053 let storage_provider = VirtualStorageProvider::new_memory();
1054 let store = make_store(Metric::InnerProduct);
1055
1056 let prefix = "/test";
1058 let metadata = AsyncIndexMetadata::new(prefix.to_string());
1059 let bytes_written = store.save_with(&storage_provider, &metadata).await.unwrap();
1060 let sq_storage = storage::SQStorage::new(prefix);
1061 assert!(bytes_written > 0);
1062 assert!(storage_provider.exists(sq_storage.compressed_data_path()),);
1063 assert!(storage_provider.exists(sq_storage.quantizer_path()));
1064
1065 let ctx = AsyncQuantLoadContext {
1067 metadata,
1068 num_frozen_points: ONE,
1069 metric: Metric::InnerProduct,
1070 prefetch_lookahead: None,
1071 is_disk_index: false,
1072 prefetch_cache_line_level: None,
1073 };
1074
1075 let loaded = SQStore::<NBITS>::load_with(&storage_provider, &ctx)
1076 .await
1077 .unwrap();
1078
1079 assert_eq!(loaded.dim(), store.dim());
1081 for i in 0..NPTS {
1083 let original = unsafe { store.data.get_slice(i) };
1084 let loaded = unsafe { loaded.data.get_slice(i) };
1085 assert_eq!(original, loaded);
1086 }
1087 }
1088}