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