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