Skip to main content

diskann_vector/distance/
distance_provider.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6#[cfg(target_arch = "aarch64")]
7use diskann_wide::arch::aarch64::Neon;
8#[cfg(target_arch = "x86_64")]
9use diskann_wide::arch::x86_64::{V3, V4};
10use diskann_wide::{
11    arch::{Dispatched2, FTarget2, Scalar},
12    Architecture,
13};
14use half::f16;
15
16use super::{implementations::Specialize, Cosine, CosineNormalized, InnerProduct, SquaredL2};
17use crate::{distance::Metric, AsUnaligned, UnalignedSlice};
18
19/// Return a function pointer-like [`Distance`] to compute the requested metric.
20///
21/// If `dimension` is provided, then the returned function may **only** be used on
22/// slices with length `dimension`. Calling the returned function with a different sized
23/// slice **may** panic.
24///
25/// If `dimension` is not provided, then the returned function will work for all sizes.
26///
27/// The functions returned by `distance_comparer` do not have strict alignment
28/// requirements, though aligning your data *may* yield better memory performance.
29///
30/// # Metric Semantics
31///
32/// The values computed by the returned functions may be modified from the true mathematical
33/// definition of the metric to ensure that values closer to `-infinity` imply more similar.
34///
35/// * `L2`: Computes the squared L2 distance between vectors.
36/// * `InnerProduct`: Returns the **negative** inner-product.
37/// * `Cosine`: Returns `1 - cosine-similarity` and will work on un-normalized vectors.
38/// * `CosineNormalized`: Returns `1 - cosinesimilarity` with the hint that the provided
39///   vectors have norm 1. This allows for potentially more-efficient implementations but the
40///   results may be incorrect if called with unnormalized data.
41///
42///   When provided with integer arguments (for which normalization does not make sense), this
43///   behaves as if `Cosine` was provided.
44pub trait DistanceProvider<T>: Sized + 'static {
45    fn distance_comparer(metric: Metric, dimension: Option<usize>) -> Distance<Self, T>;
46}
47
48#[derive(Debug)]
49struct Unaligned<T>(std::marker::PhantomData<T>);
50
51impl<T> diskann_wide::lifetime::AddLifetime for Unaligned<T>
52where
53    T: 'static,
54{
55    type Of<'a> = UnalignedSlice<'a, T>;
56}
57
58/// A function pointer-like type for computing distances between `&[T]` and `&[U]`.
59///
60/// See: [`DistanceProvider`].
61#[derive(Debug, Clone, Copy)]
62pub struct Distance<T, U>
63where
64    T: 'static,
65    U: 'static,
66{
67    f: Dispatched2<f32, Unaligned<T>, Unaligned<U>>,
68}
69
70impl<T, U> Distance<T, U>
71where
72    T: 'static,
73    U: 'static,
74{
75    fn new(f: Dispatched2<f32, Unaligned<T>, Unaligned<U>>) -> Self {
76        Self { f }
77    }
78
79    /// Compute the distance between `x` and `y`.
80    ///
81    /// The actual distance computed depends on the metric supplied to [`DistanceProvider`].
82    ///
83    /// Additionally, if a dimension were given to [`DistanceProvider`], this function may
84    /// panic if provided with slices with a length not equal to this dimension.
85    #[inline]
86    pub fn call(&self, x: &[T], y: &[U]) -> f32 {
87        self.call_unaligned(x.as_unaligned(), y.as_unaligned())
88    }
89
90    /// Compute the distance between `x` and `y`.
91    ///
92    /// The actual distance computed depends on the metric supplied to [`DistanceProvider`].
93    ///
94    /// Additionally, if a dimension were given to [`DistanceProvider`], this function may
95    /// panic if provided with slices with a length not equal to this dimension.
96    #[inline]
97    pub fn call_unaligned(&self, x: UnalignedSlice<'_, T>, y: UnalignedSlice<'_, U>) -> f32 {
98        self.f.call(x, y)
99    }
100}
101
102impl<T, U> crate::DistanceFunction<&[T], &[U], f32> for Distance<T, U>
103where
104    T: 'static,
105    U: 'static,
106{
107    fn evaluate_similarity(&self, x: &[T], y: &[U]) -> f32 {
108        self.call(x, y)
109    }
110}
111
112impl<T, U> crate::DistanceFunction<UnalignedSlice<'_, T>, UnalignedSlice<'_, U>, f32>
113    for Distance<T, U>
114where
115    T: 'static,
116    U: 'static,
117{
118    fn evaluate_similarity(&self, x: UnalignedSlice<'_, T>, y: UnalignedSlice<'_, U>) -> f32 {
119        self.f.call(x, y)
120    }
121}
122
123////////////////////
124// Implementation //
125////////////////////
126
127// Implementation Notes
128//
129// Our implementation of `DistanceProvider` dispatches across:
130//
131// * Data Types
132// * Metric
133// * Dimensions
134// * Runtime Micro-architecture
135//
136// This is a combinatorial explosing of potentially compiled kernels. To get a handle on the
137// sheer number of compiled functions, we manually control the dimensional specialization on
138// a case-by-case basis.
139//
140// This is facilitated by the `specialize!` macro, which accepts a list of dimensions and
141// instantiates the necessary machinery.
142//
143// To explain the machiner a little, a [`Cons`] compile-time list is constructed. This type
144// might look like
145//
146// * `Cons<Spec<100>, Cons<Spec<64>, Spec<32>>>`: To specialize dimensions 100, 64, and 32.
147// * `Cons<Spec<100>, Null>`: To specialize just dimension 100.
148// * `Cons<Null, Null>`: To specialize no dimensions.
149//
150// The `TrySpecialize` trait is then used specialize a kernel `F` for an architecture `A`
151// with implementations
152//
153// * `Spec<N>`: Check if the requested dimension is equal to `N` and if so, return the
154//   specialized method.
155// * `Null`: Never specialize.
156// * `Cons<Head, Tail>`: Try to specialize using `Head` returning if successful. Otherwise,
157//   return the specialization of `Tail`.
158//
159//   This definition is what allows nested `Cons` structures to specialize multiple dimensions.
160//
161//   The `Cons` list also compiles a generic-dimensional fallback if none of the
162//   specializations match.
163//
164// The overall flow is
165//
166// 1. Enter the `DistanceProvider` implementation.
167//
168// 2. First dispatch across micro-architecture using `ArgumentTypes` to hold the data types.
169//    `ArgumentTypes` implements `Target2` to facilitate this dispatch.
170//
171// 3. The implementations of `Target2` for `ArgumentTypes` are performed by the `specialize!`
172//    macro, which creates a `Cons` list of requested specializations, switches across
173//    metrics and invokes `Cons:specialize` on the requested metric.
174
175macro_rules! provider {
176    ($T:ty, $U:ty) => {
177        impl DistanceProvider<$U> for $T {
178            fn distance_comparer(metric: Metric, dimension: Option<usize>) -> Distance<$T, $U> {
179                // Use the `no-features` variant because we do not care if the target gets
180                // compiled for higher micro-architecture levels.
181                //
182                // It's the returned kernel that matters.
183                diskann_wide::arch::dispatch2_no_features(
184                    ArgumentTypes::<$T, $U>::new(),
185                    metric,
186                    dimension,
187                )
188            }
189        }
190    };
191}
192
193provider!(f32, f32);
194provider!(f16, f16);
195provider!(f32, f16);
196provider!(i8, i8);
197provider!(u8, u8);
198
199/////////////////////////
200// Specialization List //
201/////////////////////////
202
203macro_rules! spec_list {
204    ($($Ns:literal),* $(,)?) => {
205        spec_list!(@value, $($Ns,)*)
206    };
207    (@value $(,)?) => {
208        Cons::new(Null, Null)
209    };
210    (@value, $N0:literal $(,)?) => {
211        Cons::new(Spec::<$N0>, Null)
212    };
213    (@value, $N0:literal, $N1:literal $(,)?) => {
214        Cons::new(Spec::<$N0>, Spec::<$N1>)
215    };
216    (@value, $N0:literal, $N1:literal, $($Ns:literal),+ $(,)?) => {
217        Cons::new(Spec::<$N0>, spec_list!(@value, $N1, $($Ns,)+))
218    };
219}
220
221struct ArgumentTypes<T: 'static, U: 'static>(std::marker::PhantomData<(T, U)>);
222
223impl<T, U> ArgumentTypes<T, U>
224where
225    T: 'static,
226    U: 'static,
227{
228    fn new() -> Self {
229        Self(std::marker::PhantomData)
230    }
231}
232
233macro_rules! specialize {
234    ($arch:ty, $T:ty, $U:ty, $($Ns:literal),* $(,)?) => {
235        impl diskann_wide::arch::Target2<
236            $arch,
237            Distance<$T, $U>,
238            Metric,
239            Option<usize>,
240        > for ArgumentTypes<$T, $U> {
241            fn run(
242                self,
243                arch: $arch,
244                metric: Metric,
245                dim: Option<usize>,
246            ) -> Distance<$T, $U> {
247                let spec = spec_list!($($Ns),*);
248                match metric {
249                    Metric::L2 => spec.specialize(arch, SquaredL2 {}, dim),
250                    Metric::Cosine => spec.specialize(arch, Cosine {}, dim),
251                    Metric::CosineNormalized => spec.specialize(arch, CosineNormalized {}, dim),
252                    Metric::InnerProduct => spec.specialize(arch, InnerProduct {}, dim),
253                }
254            }
255        }
256    };
257    // Integer types redirect `CosineNormalized` to `Cosine`.
258    (@integer, $arch:ty, $T:ty, $U:ty, $($Ns:literal),* $(,)?) => {
259        impl diskann_wide::arch::Target2<
260            $arch,
261            Distance<$T, $U>,
262            Metric,
263            Option<usize>,
264        > for ArgumentTypes<$T, $U> {
265            fn run(
266                self,
267                arch: $arch,
268                metric: Metric,
269                dim: Option<usize>,
270            ) -> Distance<$T, $U> {
271                let spec = spec_list!($($Ns),*);
272                match metric {
273                    Metric::L2 => spec.specialize(arch, SquaredL2 {}, dim),
274                    Metric::Cosine | Metric::CosineNormalized => {
275                        spec.specialize(arch, Cosine {}, dim)
276                    },
277                    Metric::InnerProduct => spec.specialize(arch, InnerProduct {}, dim),
278                }
279            }
280        }
281    };
282}
283
284specialize!(Scalar, f32, f32,);
285specialize!(Scalar, f32, f16,);
286specialize!(Scalar, f16, f16,);
287specialize!(@integer, Scalar, u8, u8,);
288specialize!(@integer, Scalar, i8, i8,);
289
290#[cfg(target_arch = "x86_64")]
291mod x86_64 {
292    use super::*;
293
294    specialize!(V3, f32, f32, 768, 384, 128, 100);
295    specialize!(V4, f32, f32, 768, 384, 128, 100);
296
297    specialize!(V3, f32, f16, 768, 384, 128, 100);
298    specialize!(V4, f32, f16, 768, 384, 128, 100);
299
300    specialize!(V3, f16, f16, 768, 384, 128, 100);
301    specialize!(V4, f16, f16, 768, 384, 128, 100);
302
303    specialize!(@integer, V3, u8, u8, 128);
304    specialize!(@integer, V4, u8, u8, 128);
305
306    specialize!(@integer, V3, i8, i8, 128, 100);
307    specialize!(@integer, V4, i8, i8, 128, 100);
308}
309
310#[cfg(target_arch = "aarch64")]
311mod aarch64 {
312    use super::*;
313
314    specialize!(Neon, f32, f32, 768, 384, 128, 100);
315    specialize!(Neon, f32, f16, 768, 384, 128, 100);
316    specialize!(Neon, f16, f16, 768, 384, 128, 100);
317
318    specialize!(@integer, Neon, u8, u8, 128);
319    specialize!(@integer, Neon, i8, i8, 128, 100);
320}
321
322/// Specialize a distance function `F` for the dimension `dim` if possible. Otherwise,
323/// return `None`.
324trait TrySpecialize<A, F, T, U>
325where
326    A: Architecture,
327    T: 'static,
328    U: 'static,
329{
330    fn try_specialize(&self, arch: A, dim: Option<usize>) -> Option<Distance<T, U>>;
331}
332
333/// Specialize a distance function for the requested dimensionality.
334struct Spec<const N: usize>;
335
336impl<A, F, const N: usize, T, U> TrySpecialize<A, F, T, U> for Spec<N>
337where
338    A: Architecture,
339    Specialize<N, F>: for<'a, 'b> FTarget2<A, f32, UnalignedSlice<'a, T>, UnalignedSlice<'b, U>>,
340    T: 'static,
341    U: 'static,
342{
343    fn try_specialize(&self, arch: A, dim: Option<usize>) -> Option<Distance<T, U>> {
344        if let Some(d) = dim {
345            if d == N {
346                return Some(Distance::new(
347                    // NOTE: This line here is what actually compiles the specialized kernel.
348                    arch.dispatch2::<Specialize<N, F>, f32, Unaligned<T>, Unaligned<U>>(),
349                ));
350            }
351        }
352        None
353    }
354}
355
356/// Don't specialize at all.
357struct Null;
358
359impl<A, F, T, U> TrySpecialize<A, F, T, U> for Null
360where
361    A: Architecture,
362    T: 'static,
363    U: 'static,
364{
365    fn try_specialize(&self, _arch: A, _dim: Option<usize>) -> Option<Distance<T, U>> {
366        None
367    }
368}
369
370/// A recursive compile-time list for building a list of specializations.
371struct Cons<Head, Tail> {
372    head: Head,
373    tail: Tail,
374}
375
376impl<Head, Tail> Cons<Head, Tail> {
377    const fn new(head: Head, tail: Tail) -> Self {
378        Self { head, tail }
379    }
380
381    /// Try to specialize `F`. If no such specialization is available, return a fallback
382    /// implementation.
383    fn specialize<A, F, T, U>(&self, arch: A, _f: F, dim: Option<usize>) -> Distance<T, U>
384    where
385        A: Architecture,
386        F: for<'a, 'b> FTarget2<A, f32, UnalignedSlice<'a, T>, UnalignedSlice<'b, U>>,
387        Head: TrySpecialize<A, F, T, U>,
388        Tail: TrySpecialize<A, F, T, U>,
389        T: 'static,
390        U: 'static,
391    {
392        if let Some(f) = self.try_specialize(arch, dim) {
393            f
394        } else {
395            Distance::new(arch.dispatch2::<F, f32, Unaligned<T>, Unaligned<U>>())
396        }
397    }
398}
399
400// Try `Head` and then `Tail`.
401impl<A, Head, Tail, F, T, U> TrySpecialize<A, F, T, U> for Cons<Head, Tail>
402where
403    A: Architecture,
404    Head: TrySpecialize<A, F, T, U>,
405    Tail: TrySpecialize<A, F, T, U>,
406    T: 'static,
407    U: 'static,
408{
409    fn try_specialize(&self, arch: A, dim: Option<usize>) -> Option<Distance<T, U>> {
410        if let Some(f) = self.head.try_specialize(arch, dim) {
411            Some(f)
412        } else {
413            self.tail.try_specialize(arch, dim)
414        }
415    }
416}
417
418///////////
419// Tests //
420///////////
421
422#[cfg(test)]
423mod test_unaligned_distance_provider {
424    use approx::assert_relative_eq;
425    use rand::{self, SeedableRng};
426
427    use super::*;
428    use crate::{
429        distance::{reference::ReferenceProvider, Metric},
430        test_util, SimilarityScore,
431    };
432
433    // Comparison Bounds
434    struct EpsilonAndRelative {
435        epsilon: f32,
436        max_relative: f32,
437    }
438
439    /// For now - these are rough bounds selected heuristically.
440    /// Eventually (once we have implementations using compensated arithmetic), we should
441    /// empirically derive bounds based on a combination of
442    ///
443    /// 1. Input Distribution
444    /// 2. Distance Function
445    /// 3. Dimensionality
446    ///
447    /// To ensure that these bounds are tight.
448    fn get_float_bounds(metric: Metric) -> EpsilonAndRelative {
449        match metric {
450            Metric::L2 => EpsilonAndRelative {
451                epsilon: 1e-5,
452                max_relative: 1e-5,
453            },
454            Metric::InnerProduct => EpsilonAndRelative {
455                epsilon: 1e-4,
456                max_relative: 1e-4,
457            },
458            Metric::Cosine => EpsilonAndRelative {
459                epsilon: 1e-4,
460                max_relative: 1e-4,
461            },
462            Metric::CosineNormalized => EpsilonAndRelative {
463                epsilon: 1e-4,
464                max_relative: 1e-4,
465            },
466        }
467    }
468
469    fn get_int_bounds(metric: Metric) -> EpsilonAndRelative {
470        match metric {
471            // Allow for some error when handling the normalization at the end.
472            Metric::Cosine | Metric::CosineNormalized => EpsilonAndRelative {
473                epsilon: 1e-6,
474                max_relative: 1e-6,
475            },
476            // These should be exact.
477            Metric::L2 | Metric::InnerProduct => EpsilonAndRelative {
478                epsilon: 0.0,
479                max_relative: 0.0,
480            },
481        }
482    }
483
484    fn do_test<T, Distribution>(
485        under_test: Distance<T, T>,
486        reference: fn(&[T], &[T]) -> SimilarityScore<f32>,
487        bounds: EpsilonAndRelative,
488        dim: usize,
489        distribution: Distribution,
490    ) where
491        T: test_util::CornerCases,
492        Distribution: test_util::GenerateRandomArguments<T> + Clone,
493    {
494        let mut rng = rand::rngs::StdRng::seed_from_u64(0xef0053c);
495
496        // Unwrap the SimilarityScore for the reference implementation.
497        let converted = |a: &[T], b: &[T]| -> f32 { reference(a, b).into_inner() };
498
499        let mut checker = test_util::Checker::<T, T, f32>::new(
500            |a, b| under_test.call(a, b),
501            converted,
502            |got: f32, expected: f32| {
503                assert_relative_eq!(
504                    got,
505                    expected,
506                    epsilon = bounds.epsilon,
507                    max_relative = bounds.max_relative
508                );
509            },
510        );
511
512        test_util::test_distance_function(
513            &mut checker,
514            distribution.clone(),
515            distribution.clone(),
516            dim,
517            10,
518            &mut rng,
519        );
520    }
521
522    fn all_metrics() -> [Metric; 4] {
523        [
524            Metric::L2,
525            Metric::InnerProduct,
526            Metric::Cosine,
527            Metric::CosineNormalized,
528        ]
529    }
530
531    /// The maximum dimension used for unaligned behavior checking with simple distances.
532    const MAX_DIM: usize = 256;
533
534    #[test]
535    fn test_unaligned_f32() {
536        let dist = rand_distr::Normal::new(0.0, 1.0).unwrap();
537        for metric in all_metrics() {
538            for dim in 0..MAX_DIM {
539                println!("Metric = {:?}, dim = {}", metric, dim);
540                let unaligned = <f32 as DistanceProvider<f32>>::distance_comparer(metric, None);
541                let simple = <f32 as ReferenceProvider<f32>>::reference_implementation(metric);
542                let bounds = get_float_bounds(metric);
543                do_test(unaligned, simple, bounds, dim, dist);
544            }
545        }
546    }
547
548    #[test]
549    fn test_unaligned_f16() {
550        let dist = rand_distr::Normal::new(0.0, 1.0).unwrap();
551        for metric in all_metrics() {
552            for dim in 0..MAX_DIM {
553                println!("Metric = {:?}, dim = {}", metric, dim);
554                let unaligned = <f16 as DistanceProvider<f16>>::distance_comparer(metric, None);
555                let simple = <f16 as ReferenceProvider<f16>>::reference_implementation(metric);
556                let bounds = get_float_bounds(metric);
557                do_test(unaligned, simple, bounds, dim, dist);
558            }
559        }
560    }
561
562    #[test]
563    fn test_unaligned_u8() {
564        let dist = rand::distr::StandardUniform {};
565        for metric in all_metrics() {
566            for dim in 0..MAX_DIM {
567                println!("Metric = {:?}, dim = {}", metric, dim);
568                let unaligned = <u8 as DistanceProvider<u8>>::distance_comparer(metric, None);
569                let simple = <u8 as ReferenceProvider<u8>>::reference_implementation(metric);
570                let bounds = get_int_bounds(metric);
571                do_test(unaligned, simple, bounds, dim, dist);
572            }
573        }
574    }
575
576    #[test]
577    fn test_unaligned_i8() {
578        let dist = rand::distr::StandardUniform {};
579        for metric in all_metrics() {
580            for dim in 0..MAX_DIM {
581                println!("Metric = {:?}, dim = {}", metric, dim);
582                let unaligned = <i8 as DistanceProvider<i8>>::distance_comparer(metric, None);
583                let simple = <i8 as ReferenceProvider<i8>>::reference_implementation(metric);
584
585                let bounds = get_int_bounds(metric);
586                do_test(unaligned, simple, bounds, dim, dist);
587            }
588        }
589    }
590}
591
592#[cfg(test)]
593mod distance_provider_f32_tests {
594    use approx::assert_abs_diff_eq;
595    use rand::{rngs::StdRng, Rng, SeedableRng};
596
597    use super::*;
598    use crate::{distance::reference, test_util::*};
599
600    #[repr(C, align(32))]
601    pub struct F32Slice112([f32; 112]);
602    #[repr(C, align(32))]
603    pub struct F32Slice104([f32; 104]);
604    #[repr(C, align(32))]
605    pub struct F32Slice128([f32; 128]);
606    #[repr(C, align(32))]
607    pub struct F32Slice256([f32; 256]);
608    #[repr(C, align(32))]
609    pub struct F32Slice4096([f32; 4096]);
610
611    pub fn get_turing_test_data_f32_dim(dim: usize) -> (Vec<f32>, Vec<f32>) {
612        let mut a_slice = vec![0.0f32; dim];
613        let mut b_slice = vec![0.0f32; dim];
614
615        let mut rng = StdRng::seed_from_u64(42);
616        for i in 0..dim {
617            a_slice[i] = rng.random_range(-1.0..1.0);
618            b_slice[i] = rng.random_range(-1.0..1.0);
619        }
620
621        ((a_slice), (b_slice))
622    }
623
624    #[test]
625    fn test_dist_l2_float_turing_104() {
626        let (a_data, b_data) = get_turing_test_data_f32_dim(104);
627        let (a_slice, b_slice) = (
628            F32Slice104(a_data.try_into().unwrap()),
629            F32Slice104(b_data.try_into().unwrap()),
630        );
631
632        let distance: f32 = compare_two_vec::<f32>(104, Metric::L2, &a_slice.0, &b_slice.0);
633
634        assert_abs_diff_eq!(
635            distance as f64,
636            no_vector_compare_f32_as_f64(&a_slice.0, &b_slice.0),
637            epsilon = 1e-4f64
638        );
639    }
640
641    #[test]
642    fn test_dist_l2_float_turing_112() {
643        let (a_data, b_data) = get_turing_test_data_f32_dim(112);
644        let (a_slice, b_slice) = (
645            F32Slice112(a_data.try_into().unwrap()),
646            F32Slice112(b_data.try_into().unwrap()),
647        );
648
649        let distance: f32 = compare_two_vec::<f32>(112, Metric::L2, &a_slice.0, &b_slice.0);
650
651        assert_abs_diff_eq!(
652            distance as f64,
653            no_vector_compare_f32_as_f64(&a_slice.0, &b_slice.0),
654            epsilon = 1e-4f64
655        );
656    }
657
658    #[test]
659    fn test_dist_l2_float_turing_128() {
660        let (a_data, b_data) = get_turing_test_data_f32_dim(128);
661        let (a_slice, b_slice) = (
662            F32Slice128(a_data.try_into().unwrap()),
663            F32Slice128(b_data.try_into().unwrap()),
664        );
665
666        let distance: f32 = compare_two_vec::<f32>(128, Metric::L2, &a_slice.0, &b_slice.0);
667
668        assert_abs_diff_eq!(
669            distance as f64,
670            no_vector_compare_f32_as_f64(&a_slice.0, &b_slice.0),
671            epsilon = 1e-4f64
672        );
673    }
674
675    #[test]
676    fn test_dist_l2_float_turing_256() {
677        let (a_data, b_data) = get_turing_test_data_f32_dim(256);
678        let (a_slice, b_slice) = (
679            F32Slice256(a_data.try_into().unwrap()),
680            F32Slice256(b_data.try_into().unwrap()),
681        );
682
683        let distance: f32 = compare_two_vec::<f32>(256, Metric::L2, &a_slice.0, &b_slice.0);
684
685        assert_abs_diff_eq!(
686            distance as f64,
687            no_vector_compare_f32_as_f64(&a_slice.0, &b_slice.0),
688            epsilon = 1e-3f64
689        );
690    }
691
692    #[test]
693    fn test_dist_l2_float_turing_4096() {
694        let (a_data, b_data) = get_turing_test_data_f32_dim(4096);
695        let (a_slice, b_slice) = (
696            F32Slice4096(a_data.try_into().unwrap()),
697            F32Slice4096(b_data.try_into().unwrap()),
698        );
699
700        let distance: f32 = compare_two_vec::<f32>(4096, Metric::L2, &a_slice.0, &b_slice.0);
701
702        assert_abs_diff_eq!(
703            distance as f64,
704            no_vector_compare_f32_as_f64(&a_slice.0, &b_slice.0),
705            epsilon = 1e-2f64
706        );
707    }
708
709    #[test]
710    fn test_dist_ip_float_turing_112() {
711        let (a_data, b_data) = get_turing_test_data_f32_dim(112);
712        let (a_slice, b_slice) = (
713            F32Slice112(a_data.try_into().unwrap()),
714            F32Slice112(b_data.try_into().unwrap()),
715        );
716
717        let distance: f32 =
718            compare_two_vec::<f32>(112, Metric::InnerProduct, &a_slice.0, &b_slice.0);
719
720        assert_abs_diff_eq!(
721            distance,
722            reference::reference_innerproduct_f32_similarity(&a_slice.0, &b_slice.0).into_inner(),
723            epsilon = 1e-4f32
724        );
725    }
726
727    #[test]
728    fn distance_test() {
729        #[repr(C, align(32))]
730        struct Vector32ByteAligned {
731            v: [f32; 512],
732        }
733
734        // two vectors are allocated in the contiguous heap memory
735        let two_vec = Box::new(Vector32ByteAligned {
736            v: [
737                69.02492, 78.84786, 63.125072, 90.90581, 79.2592, 70.81731, 3.0829668, 33.33287,
738                20.777142, 30.147898, 23.681915, 42.553043, 12.602162, 7.3808074, 19.157589,
739                65.6791, 76.44677, 76.89124, 86.40756, 84.70118, 87.86142, 16.126896, 5.1277637,
740                95.11038, 83.946945, 22.735607, 11.548555, 59.51482, 24.84603, 15.573776, 78.27185,
741                71.13179, 38.574017, 80.0228, 13.175261, 62.887978, 15.205181, 18.89392, 96.13162,
742                87.55455, 34.179806, 62.920044, 4.9305916, 54.349373, 21.731495, 14.982187,
743                40.262867, 20.15214, 36.61963, 72.450806, 55.565, 95.5375, 93.73356, 95.36308,
744                66.30762, 58.0397, 18.951357, 67.11702, 43.043316, 30.65622, 99.85361, 2.5889993,
745                27.844774, 39.72441, 46.463238, 71.303764, 90.45308, 36.390602, 63.344395,
746                26.427078, 35.99528, 82.35505, 32.529175, 23.165905, 74.73179, 9.856939, 59.38126,
747                35.714924, 79.81213, 46.704124, 24.47884, 36.01743, 0.46678782, 29.528152,
748                1.8980742, 24.68853, 75.58984, 98.72279, 68.62601, 11.890173, 49.49361, 55.45572,
749                72.71067, 34.107483, 51.357758, 76.400635, 81.32725, 66.45081, 17.848074,
750                62.398876, 94.20444, 2.10886, 17.416393, 64.88253, 29.000723, 62.434315, 53.907238,
751                70.51412, 78.70744, 55.181683, 64.45116, 23.419212, 53.68544, 43.506958, 46.89598,
752                35.905994, 64.51397, 91.95555, 20.322979, 74.80128, 97.548744, 58.312725, 78.81985,
753                31.911612, 14.445949, 49.85094, 70.87396, 40.06766, 7.129991, 78.48008, 75.21636,
754                93.623604, 95.95479, 29.571129, 22.721554, 26.73875, 52.075504, 56.783104,
755                94.65493, 61.778534, 85.72401, 85.369514, 29.922367, 41.410553, 94.12884,
756                80.276855, 55.604828, 54.70947, 74.07216, 44.61955, 31.38113, 68.48596, 34.56782,
757                14.424729, 48.204506, 9.675444, 32.01946, 92.32695, 36.292683, 78.31955, 98.05327,
758                14.343918, 46.017002, 95.90888, 82.63626, 16.873539, 3.698051, 7.8042626,
759                64.194405, 96.71023, 67.93692, 21.618402, 51.92182, 22.834194, 61.56986, 19.749891,
760                55.31206, 38.29552, 67.57593, 67.145836, 38.92673, 94.95708, 72.38746, 90.70901,
761                69.43995, 9.394085, 31.646872, 88.20112, 9.134722, 99.98214, 5.423498, 41.51995,
762                76.94409, 77.373276, 3.2966614, 9.611201, 57.231106, 30.747868, 76.10228, 91.98308,
763                70.893585, 0.9067178, 43.96515, 16.321218, 27.734184, 83.271835, 88.23312,
764                87.16445, 5.556643, 15.627432, 58.547127, 93.6459, 40.539192, 49.124157, 91.13276,
765                57.485855, 8.827019, 4.9690843, 46.511234, 53.91469, 97.71925, 20.135271,
766                23.353004, 70.92099, 93.38748, 87.520134, 51.684677, 29.89813, 9.110392, 65.809204,
767                34.16554, 93.398605, 84.58669, 96.409645, 9.876037, 94.767784, 99.21523, 1.9330144,
768                94.92429, 75.12728, 17.218828, 97.89164, 35.476578, 77.629456, 69.573746,
769                40.200542, 42.117836, 5.861628, 75.45282, 82.73633, 0.98086596, 77.24894,
770                11.248695, 61.070026, 52.692616, 80.5449, 80.76036, 29.270136, 67.60252, 48.782394,
771                95.18851, 83.47162, 52.068756, 46.66002, 90.12216, 15.515327, 33.694042, 96.963036,
772                73.49627, 62.805485, 44.715607, 59.98627, 3.8921833, 37.565327, 29.69184,
773                39.429665, 83.46899, 44.286453, 21.54851, 56.096413, 18.169249, 5.214751,
774                14.691341, 99.779335, 26.32643, 67.69903, 36.41243, 67.27333, 12.157213, 96.18984,
775                2.438283, 78.14289, 0.14715195, 98.769, 53.649532, 21.615898, 39.657497, 95.45616,
776                18.578386, 71.47976, 22.348118, 17.85519, 6.3717127, 62.176777, 22.033644,
777                23.178005, 79.44858, 89.70233, 37.21273, 71.86182, 21.284317, 52.908623, 30.095518,
778                63.64478, 77.55823, 80.04871, 15.133011, 30.439043, 70.16561, 4.4014096, 89.28944,
779                26.29093, 46.827854, 11.764729, 61.887516, 47.774887, 57.19503, 59.444664,
780                28.592825, 98.70386, 1.2497544, 82.28431, 46.76423, 83.746124, 53.032673, 86.53457,
781                99.42168, 90.184, 92.27852, 9.059965, 71.75723, 70.45299, 10.924053, 68.329704,
782                77.27232, 6.677854, 75.63629, 57.370533, 17.09031, 10.554659, 99.56178, 37.53221,
783                72.311104, 75.7565, 65.2042, 36.096478, 64.69502, 38.88497, 64.33723, 84.87812,
784                66.84958, 8.508932, 79.134, 83.431015, 66.72124, 61.801838, 64.30524, 37.194263,
785                77.94725, 89.705185, 23.643505, 19.505919, 48.40264, 43.01083, 21.171177,
786                18.717121, 10.805857, 69.66983, 77.85261, 57.323063, 3.28964, 38.758026, 5.349946,
787                7.46572, 57.485138, 30.822384, 33.9411, 95.53746, 65.57723, 42.1077, 28.591347,
788                11.917269, 5.031073, 31.835615, 19.34116, 85.71027, 87.4516, 1.3798475, 70.70583,
789                51.988052, 45.217144, 14.308596, 54.557167, 86.18323, 79.13666, 76.866745,
790                46.010685, 79.739235, 44.667603, 39.36416, 72.605896, 73.83187, 13.137412,
791                6.7911267, 63.952374, 10.082436, 86.00318, 99.760376, 92.84948, 63.786434,
792                3.4429908, 18.244314, 75.65299, 14.964747, 70.126366, 80.89449, 91.266655,
793                96.58798, 46.439327, 38.253975, 87.31036, 21.093178, 37.19671, 58.28973, 9.75231,
794                12.350321, 25.75115, 87.65073, 53.610504, 36.850048, 18.66356, 94.48941, 83.71898,
795                44.49315, 44.186737, 19.360733, 84.365974, 46.76272, 44.924366, 50.279808,
796                54.868866, 91.33004, 18.683397, 75.13282, 15.070831, 47.04839, 53.780903,
797                26.911152, 74.65651, 57.659935, 25.604189, 37.235474, 65.39667, 53.952206,
798                40.37131, 59.173275, 96.00756, 54.591274, 10.787476, 69.51549, 31.970142,
799                25.408005, 55.972492, 85.01888, 97.48981, 91.006134, 28.98619, 97.151276,
800                34.388496, 47.498177, 11.985874, 64.73775, 33.877014, 13.370312, 34.79146,
801                86.19321, 15.019405, 94.07832, 93.50433, 60.168625, 50.95409, 38.27827, 47.458614,
802                32.83715, 69.54998, 69.0361, 84.1418, 34.270298, 74.23852, 70.707466, 78.59845,
803                9.651399, 24.186779, 58.255756, 53.72362, 92.46477, 97.75528, 20.257462, 30.122698,
804                50.41517, 28.156603, 42.644154,
805            ],
806        });
807
808        let distance: f32 = compare::<f32>(256, Metric::L2, &two_vec.v);
809
810        assert_eq!(distance, 429141.2);
811    }
812
813    fn compare<T>(dim: usize, metric: Metric, v: &[T]) -> f32
814    where
815        T: DistanceProvider<T>,
816    {
817        let distance_comparer = T::distance_comparer(metric, Some(dim));
818        distance_comparer.call(&v[..dim], &v[dim..])
819    }
820
821    pub fn compare_two_vec<T>(dim: usize, metric: Metric, v1: &[T], v2: &[T]) -> f32
822    where
823        T: DistanceProvider<T>,
824    {
825        let distance_comparer = T::distance_comparer(metric, Some(dim));
826        distance_comparer.call(&v1[..dim], &v2[..dim])
827    }
828}
829
830#[cfg(test)]
831mod distance_provider_f16_tests {
832    use approx::assert_abs_diff_eq;
833
834    use super::{distance_provider_f32_tests::get_turing_test_data_f32_dim, *};
835    use crate::{
836        distance::distance_provider::distance_provider_f32_tests::compare_two_vec,
837        test_util::no_vector_compare_f16_as_f64,
838    };
839
840    #[repr(C, align(32))]
841    pub struct F16Slice112([f16; 112]);
842    #[repr(C, align(32))]
843    pub struct F16Slice104([f16; 104]);
844    #[repr(C, align(32))]
845    pub struct F16Slice128([f16; 128]);
846    #[repr(C, align(32))]
847    pub struct F16Slice256([f16; 256]);
848    #[repr(C, align(32))]
849    pub struct F16Slice4096([f16; 4096]);
850
851    fn get_turing_test_data_f16_dim(dim: usize) -> (Vec<f16>, Vec<f16>) {
852        let (a_slice, b_slice) = get_turing_test_data_f32_dim(dim);
853        let a_data = a_slice.iter().map(|x| f16::from_f32(*x)).collect();
854        let b_data = b_slice.iter().map(|x| f16::from_f32(*x)).collect();
855        (a_data, b_data)
856    }
857
858    #[test]
859    fn test_dist_l2_f16_turing_112() {
860        // two vectors are allocated in the contiguous heap memory
861        let (a_data, b_data) = get_turing_test_data_f16_dim(112);
862        let (a_slice, b_slice) = (
863            F16Slice112(a_data.try_into().unwrap()),
864            F16Slice112(b_data.try_into().unwrap()),
865        );
866
867        let distance: f32 = compare_two_vec::<f16>(112, Metric::L2, &a_slice.0, &b_slice.0);
868
869        // Note the variance between the full 32 bit precision and the 16 bit precision
870        assert_abs_diff_eq!(
871            distance as f64,
872            no_vector_compare_f16_as_f64(&a_slice.0, &b_slice.0),
873            epsilon = 1e-3f64
874        );
875    }
876
877    #[test]
878    fn test_dist_l2_f16_turing_104() {
879        // two vectors are allocated in the contiguous heap memory
880        let (a_data, b_data) = get_turing_test_data_f16_dim(104);
881        let (a_slice, b_slice) = (
882            F16Slice104(a_data.try_into().unwrap()),
883            F16Slice104(b_data.try_into().unwrap()),
884        );
885
886        let distance: f32 = compare_two_vec::<f16>(104, Metric::L2, &a_slice.0, &b_slice.0);
887
888        // Note the variance between the full 32 bit precision and the 16 bit precision
889        assert_abs_diff_eq!(
890            distance as f64,
891            no_vector_compare_f16_as_f64(&a_slice.0, &b_slice.0),
892            epsilon = 1e-3f64
893        );
894    }
895
896    #[test]
897    fn test_dist_l2_f16_turing_256() {
898        // two vectors are allocated in the contiguous heap memory
899        let (a_data, b_data) = get_turing_test_data_f16_dim(256);
900        let (a_slice, b_slice) = (
901            F16Slice256(a_data.try_into().unwrap()),
902            F16Slice256(b_data.try_into().unwrap()),
903        );
904
905        let distance: f32 = compare_two_vec::<f16>(256, Metric::L2, &a_slice.0, &b_slice.0);
906
907        // Note the variance between the full 32 bit precision and the 16 bit precision
908        assert_abs_diff_eq!(
909            distance as f64,
910            no_vector_compare_f16_as_f64(&a_slice.0, &b_slice.0),
911            epsilon = 1e-3f64
912        );
913    }
914
915    #[test]
916    fn test_dist_l2_f16_turing_128() {
917        // two vectors are allocated in the contiguous heap memory
918        let (a_data, b_data) = get_turing_test_data_f16_dim(128);
919        let (a_slice, b_slice) = (
920            F16Slice128(a_data.try_into().unwrap()),
921            F16Slice128(b_data.try_into().unwrap()),
922        );
923
924        let distance: f32 = compare_two_vec::<f16>(128, Metric::L2, &a_slice.0, &b_slice.0);
925
926        // Note the variance between the full 32 bit precision and the 16 bit precision
927        assert_abs_diff_eq!(
928            distance as f64,
929            no_vector_compare_f16_as_f64(&a_slice.0, &b_slice.0),
930            epsilon = 1e-3f64
931        );
932    }
933
934    #[test]
935    fn test_dist_l2_f16_turing_4096() {
936        // two vectors are allocated in the contiguous heap memory
937        let (a_data, b_data) = get_turing_test_data_f16_dim(4096);
938        let (a_slice, b_slice) = (
939            F16Slice4096(a_data.try_into().unwrap()),
940            F16Slice4096(b_data.try_into().unwrap()),
941        );
942
943        let distance: f32 = compare_two_vec::<f16>(4096, Metric::L2, &a_slice.0, &b_slice.0);
944
945        // Note the variance between the full 32 bit precision and the 16 bit precision
946        assert_abs_diff_eq!(
947            distance as f64,
948            no_vector_compare_f16_as_f64(&a_slice.0, &b_slice.0),
949            epsilon = 1e-2f64
950        );
951    }
952
953    #[test]
954    fn test_dist_l2_f16_produces_nan_distance_for_infinity_vectors() {
955        let a_data = vec![f16::INFINITY; 384];
956        let b_data = vec![f16::INFINITY; 384];
957
958        let distance: f32 = compare_two_vec::<f16>(384, Metric::L2, &a_data, &b_data);
959        assert!(distance.is_nan());
960    }
961}