Skip to main content

diskann_vector/distance/
implementations.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6use diskann_wide::{arch::Target2, Architecture, ARCH};
7
8/// Experimental traits for distance functions.
9use super::simd;
10use crate::{
11    AsUnaligned, Half, MathematicalValue, PureDistanceFunction, SimilarityScore, UnalignedSlice,
12};
13
14macro_rules! architecture_hook {
15    ($functor:ty, $impl:path) => {
16        impl<A, T, L, R> diskann_wide::arch::Target2<A, T, L, R> for $functor
17        where
18            A: Architecture,
19            L: AsUnaligned,
20            R: AsUnaligned,
21            $impl: simd::SIMDSchema<L::Element, R::Element, A>,
22            Self: PostOp<<$impl as simd::SIMDSchema<L::Element, R::Element, A>>::Return, T>,
23        {
24            #[inline(always)]
25            fn run(self, arch: A, left: L, right: R) -> T {
26                Self::post_op(simd::simd_op(
27                    &$impl,
28                    arch,
29                    left.as_unaligned(),
30                    right.as_unaligned(),
31                ))
32            }
33        }
34
35        impl<A, T, L, R> diskann_wide::arch::FTarget2<A, T, L, R> for $functor
36        where
37            A: Architecture,
38            L: AsUnaligned,
39            R: AsUnaligned,
40            Self: diskann_wide::arch::Target2<A, T, L, R>,
41        {
42            #[inline(always)]
43            fn run(arch: A, left: L, right: R) -> T {
44                arch.run2(Self::default(), left, right)
45            }
46        }
47    };
48}
49
50/// A utility for specializing distance computations for fixed-length slices.
51#[derive(Debug, Clone, Copy)]
52pub(crate) struct Specialize<const N: usize, F>(std::marker::PhantomData<F>);
53
54impl<A, T, L, R, const N: usize, F>
55    diskann_wide::arch::FTarget2<A, T, UnalignedSlice<'_, L>, UnalignedSlice<'_, R>>
56    for Specialize<N, F>
57where
58    A: Architecture,
59    F: for<'a, 'b> diskann_wide::arch::Target2<A, T, UnalignedSlice<'a, L>, UnalignedSlice<'b, R>>
60        + Default,
61{
62    #[inline(always)]
63    fn run(arch: A, x: UnalignedSlice<'_, L>, y: UnalignedSlice<'_, R>) -> T {
64        if (x.len() != N) | (y.len() != N) {
65            fail_length_check(x, y, N);
66        }
67
68        // The validation of `x.len()` and `y.len()` is sufficient (and indeed necessary)
69        // to trigger constant propagation and unrolling.
70        arch.run2(F::default(), x, y)
71    }
72}
73
74// Outline the panic formatting and keep the calling convention the same as
75// the top function. This keeps code generation extremely lightweight.
76#[inline(never)]
77#[allow(clippy::panic)]
78fn fail_length_check<L, R>(x: UnalignedSlice<'_, L>, y: UnalignedSlice<'_, R>, len: usize) -> ! {
79    let message = if x.len() != len {
80        ("first", x.len())
81    } else {
82        ("second", y.len())
83    };
84    panic!(
85        "expected {} argument to have length {}, instead it has length {}",
86        message.0, len, message.1
87    );
88}
89
90/// An internal trait to transform the result of the low-level SIMD ops into a value
91/// expected by the rest of DiskANN.
92///
93/// Keep this trait private as it is likely to either change or be removed completely in the
94/// near future once better integer implementations come online.
95pub(super) trait PostOp<From, To> {
96    fn post_op(x: From) -> To;
97}
98
99/// Provide explicit dynamic and sized implementations for a distance functor.
100macro_rules! use_simd_implementation {
101    ($functor:ty, $T:ty, $U:ty) => {
102        //////////////////////
103        // Similarity Score //
104        //////////////////////
105
106        // Dynamically Sized.
107        impl PureDistanceFunction<&[$T], &[$U], SimilarityScore<f32>> for $functor {
108            #[inline]
109            fn evaluate(x: &[$T], y: &[$U]) -> SimilarityScore<f32> {
110                <$functor>::default().run(ARCH, x, y)
111            }
112        }
113
114        // Statically Sized
115        impl<const N: usize> PureDistanceFunction<&[$T; N], &[$U; N], SimilarityScore<f32>>
116            for $functor
117        {
118            #[inline]
119            fn evaluate(x: &[$T; N], y: &[$U; N]) -> SimilarityScore<f32> {
120                <$functor>::default().run(ARCH, x, y)
121            }
122        }
123
124        ////////////////////////
125        // Mathematical Value //
126        ////////////////////////
127
128        // Dynamically Sized.
129        impl PureDistanceFunction<&[$T], &[$U], MathematicalValue<f32>> for $functor {
130            #[inline]
131            fn evaluate(x: &[$T], y: &[$U]) -> MathematicalValue<f32> {
132                <$functor>::default().run(ARCH, x, y)
133            }
134        }
135        // Statically Sized
136        impl<const N: usize> PureDistanceFunction<&[$T; N], &[$U; N], MathematicalValue<f32>>
137            for $functor
138        {
139            #[inline]
140            fn evaluate(x: &[$T; N], y: &[$U; N]) -> MathematicalValue<f32> {
141                <$functor>::default().run(ARCH, x, y)
142            }
143        }
144
145        /////////
146        // f32 //
147        /////////
148
149        // Dynamically Sized
150        impl PureDistanceFunction<&[$T], &[$U], f32> for $functor {
151            #[inline(always)]
152            fn evaluate(x: &[$T], y: &[$U]) -> f32 {
153                <$functor>::default().run(ARCH, x, y)
154            }
155        }
156
157        // Statically Sized
158        impl<const N: usize> PureDistanceFunction<&[$T; N], &[$U; N], f32> for $functor {
159            #[inline]
160            fn evaluate(x: &[$T; N], y: &[$U; N]) -> f32 {
161                <$functor>::default().run(ARCH, x, y)
162            }
163        }
164    };
165}
166
167///////////////
168// SquaredL2 //
169///////////////
170
171/// Compute the squared L2 distance between two vectors.
172#[derive(Debug, Clone, Copy, Default)]
173pub struct SquaredL2 {}
174
175impl PostOp<f32, SimilarityScore<f32>> for SquaredL2 {
176    #[inline(always)]
177    fn post_op(x: f32) -> SimilarityScore<f32> {
178        SimilarityScore::new(x)
179    }
180}
181
182impl PostOp<f32, f32> for SquaredL2 {
183    #[inline(always)]
184    fn post_op(x: f32) -> f32 {
185        x
186    }
187}
188
189impl PostOp<f32, MathematicalValue<f32>> for SquaredL2 {
190    #[inline(always)]
191    fn post_op(x: f32) -> MathematicalValue<f32> {
192        MathematicalValue::new(x)
193    }
194}
195
196architecture_hook!(SquaredL2, simd::L2);
197use_simd_implementation!(SquaredL2, f32, f32);
198use_simd_implementation!(SquaredL2, f32, Half);
199use_simd_implementation!(SquaredL2, Half, Half);
200use_simd_implementation!(SquaredL2, i8, i8);
201use_simd_implementation!(SquaredL2, u8, u8);
202
203////////////
204// FullL2 //
205////////////
206
207/// Computes the full L2 distance between two vectors.
208///
209/// Unlike `SquaredL2`, this function-like object will perform compute the full L2 distance
210/// including the trailing square root.
211#[derive(Debug, Clone, Copy, Default)]
212pub struct FullL2 {}
213
214impl PostOp<f32, SimilarityScore<f32>> for FullL2 {
215    #[inline(always)]
216    fn post_op(x: f32) -> SimilarityScore<f32> {
217        SimilarityScore::new(x.sqrt())
218    }
219}
220
221impl PostOp<f32, f32> for FullL2 {
222    #[inline(always)]
223    fn post_op(x: f32) -> f32 {
224        x.sqrt()
225    }
226}
227
228impl PostOp<f32, MathematicalValue<f32>> for FullL2 {
229    #[inline(always)]
230    fn post_op(x: f32) -> MathematicalValue<f32> {
231        MathematicalValue::new(x.sqrt())
232    }
233}
234
235architecture_hook!(FullL2, simd::L2);
236use_simd_implementation!(FullL2, f32, f32);
237use_simd_implementation!(FullL2, f32, Half);
238use_simd_implementation!(FullL2, Half, Half);
239use_simd_implementation!(FullL2, i8, i8);
240use_simd_implementation!(FullL2, u8, u8);
241
242//////////////////
243// InnerProduct //
244//////////////////
245
246/// Compute the inner product between two vectors.
247#[derive(Debug, Clone, Copy, Default)]
248pub struct InnerProduct {}
249
250impl PostOp<f32, SimilarityScore<f32>> for InnerProduct {
251    // The low-level operations compute the mathematical dot product.
252    // Similarity scores used in DiskANN expect the InnerProduct to be negated.
253    // This PostOp does that negation.
254    #[inline(always)]
255    fn post_op(x: f32) -> SimilarityScore<f32> {
256        SimilarityScore::new(-x)
257    }
258}
259
260impl PostOp<f32, MathematicalValue<f32>> for InnerProduct {
261    #[inline(always)]
262    fn post_op(x: f32) -> MathematicalValue<f32> {
263        MathematicalValue::new(x)
264    }
265}
266
267impl PostOp<f32, f32> for InnerProduct {
268    #[inline(always)]
269    fn post_op(x: f32) -> f32 {
270        <Self as PostOp<f32, SimilarityScore<f32>>>::post_op(x).into_inner()
271    }
272}
273
274architecture_hook!(InnerProduct, simd::IP);
275use_simd_implementation!(InnerProduct, f32, f32);
276use_simd_implementation!(InnerProduct, f32, Half);
277use_simd_implementation!(InnerProduct, Half, Half);
278use_simd_implementation!(InnerProduct, i8, i8);
279use_simd_implementation!(InnerProduct, u8, u8);
280
281////////////
282// Cosine //
283////////////
284
285/// Perform the conversion `x -> 1 - x`.
286///
287/// Don't clamp the output - assume the output is clamped from the inner computation.
288fn cosine_transformation(x: f32) -> f32 {
289    1.0 - x
290}
291
292/// Compute the cosine similarity between two vectors.
293#[derive(Debug, Clone, Copy, Default)]
294pub struct Cosine {}
295
296impl PostOp<f32, SimilarityScore<f32>> for Cosine {
297    fn post_op(x: f32) -> SimilarityScore<f32> {
298        debug_assert!(x >= -1.0);
299        debug_assert!(x <= 1.0);
300        SimilarityScore::new(cosine_transformation(x))
301    }
302}
303
304impl PostOp<f32, MathematicalValue<f32>> for Cosine {
305    fn post_op(x: f32) -> MathematicalValue<f32> {
306        debug_assert!(x >= -1.0);
307        debug_assert!(x <= 1.0);
308        MathematicalValue::new(x)
309    }
310}
311
312impl PostOp<f32, f32> for Cosine {
313    fn post_op(x: f32) -> f32 {
314        <Self as PostOp<f32, SimilarityScore<f32>>>::post_op(x).into_inner()
315    }
316}
317
318architecture_hook!(Cosine, simd::CosineStateless);
319use_simd_implementation!(Cosine, f32, f32);
320use_simd_implementation!(Cosine, f32, Half);
321use_simd_implementation!(Cosine, Half, Half);
322use_simd_implementation!(Cosine, i8, i8);
323use_simd_implementation!(Cosine, u8, u8);
324
325//////////////////////
326// CosineNormalized //
327//////////////////////
328
329/// Compute the cosine similarity between two normalized vectors.
330#[derive(Debug, Clone, Copy, Default)]
331pub struct CosineNormalized {}
332
333impl PostOp<f32, SimilarityScore<f32>> for CosineNormalized {
334    #[inline(always)]
335    fn post_op(x: f32) -> SimilarityScore<f32> {
336        // If the vectors are assumed to be normalized, then the implementation of
337        // normalized cosine can be expressed in terms of an inner product inner loop.
338        //
339        // Don't use `clamp` at the end since the simple non-vector implementations do not
340        // clamp their outputs.
341        SimilarityScore::new(cosine_transformation(x))
342    }
343}
344
345impl PostOp<f32, MathematicalValue<f32>> for CosineNormalized {
346    #[inline(always)]
347    fn post_op(x: f32) -> MathematicalValue<f32> {
348        MathematicalValue::new(x)
349    }
350}
351
352impl PostOp<f32, f32> for CosineNormalized {
353    #[inline(always)]
354    fn post_op(x: f32) -> f32 {
355        <Self as PostOp<f32, SimilarityScore<f32>>>::post_op(x).into_inner()
356    }
357}
358
359architecture_hook!(CosineNormalized, simd::IP);
360use_simd_implementation!(CosineNormalized, f32, f32);
361use_simd_implementation!(CosineNormalized, f32, Half);
362use_simd_implementation!(CosineNormalized, Half, Half);
363
364////////////
365// L1Norm //
366////////////
367
368/// Compute the L1 norm of a vector.
369#[derive(Debug, Clone, Copy, Default)]
370pub struct L1NormFunctor {}
371
372impl PostOp<f32, f32> for L1NormFunctor {
373    #[inline(always)]
374    fn post_op(x: f32) -> f32 {
375        x
376    }
377}
378
379architecture_hook!(L1NormFunctor, simd::L1Norm);
380
381impl PureDistanceFunction<&[f32], &[f32], f32> for L1NormFunctor {
382    #[inline]
383    fn evaluate(x: &[f32], y: &[f32]) -> f32 {
384        L1NormFunctor::default().run(ARCH, x, y)
385    }
386}
387
388////////////
389// Tests //
390////////////
391
392#[cfg(test)]
393mod tests {
394
395    use std::hash::{Hash, Hasher};
396
397    use approx::assert_relative_eq;
398    use rand::{Rng, SeedableRng};
399
400    use super::*;
401    use crate::{
402        distance::{
403            reference::{self, ReferenceProvider},
404            Metric,
405        },
406        test_util::{self, Normalize},
407    };
408
409    pub fn as_function_pointer<T, Left, Right, Return>(x: &[Left], y: &[Right]) -> Return
410    where
411        T: for<'a, 'b> PureDistanceFunction<&'a [Left], &'b [Right], Return>,
412    {
413        T::evaluate(x, y)
414    }
415
416    fn simd_provider(metric: Metric) -> fn(&[f32], &[f32]) -> f32 {
417        match metric {
418            Metric::L2 => as_function_pointer::<SquaredL2, _, _, _>,
419            Metric::InnerProduct => as_function_pointer::<InnerProduct, _, _, _>,
420            Metric::Cosine => as_function_pointer::<Cosine, _, _, _>,
421            Metric::CosineNormalized => as_function_pointer::<CosineNormalized, _, _, _>,
422        }
423    }
424
425    fn random_normal_arguments(dim: usize, lo: f32, hi: f32, seed: u64) -> (Vec<f32>, Vec<f32>) {
426        let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
427        let x: Vec<f32> = (0..dim).map(|_| rng.random_range(lo..hi)).collect();
428        let y: Vec<f32> = (0..dim).map(|_| rng.random_range(lo..hi)).collect();
429        (x, y)
430    }
431
432    struct LeftRightPair {
433        pub x: Vec<f32>,
434        pub y: Vec<f32>,
435    }
436
437    fn generate_corner_cases(dim: usize) -> Vec<LeftRightPair> {
438        let mut output = Vec::<LeftRightPair>::new();
439        let fixed_values = [0.0, -5.0, 5.0, 10.0];
440
441        for va in fixed_values.iter() {
442            for vb in fixed_values.iter() {
443                let x: Vec<f32> = vec![*va; dim];
444                let y: Vec<f32> = vec![*vb; dim];
445                output.push(LeftRightPair { x, y });
446            }
447        }
448        output
449    }
450
451    fn collect_random_arguments(
452        dim: usize,
453        num_trials: usize,
454        lo: f32,
455        hi: f32,
456        mut seed: u64,
457    ) -> Vec<LeftRightPair> {
458        (0..num_trials)
459            .map(|_| {
460                let (x, y) = random_normal_arguments(dim, lo, hi, seed);
461
462                // update the seed.
463                let mut hasher = std::hash::DefaultHasher::new();
464                seed.hash(&mut hasher);
465                seed = hasher.finish();
466
467                LeftRightPair { x, y }
468            })
469            .collect()
470    }
471
472    fn test_pure_functions_impl<T>(metric: Metric, _func: T, normalize: bool)
473    where
474        T: for<'a, 'b> PureDistanceFunction<&'a [f32], &'b [f32], f32> + Clone,
475    {
476        let epsilon: f32 = 1e-4;
477        let max_relative: f32 = 1e-4;
478
479        let max_dim = 256;
480        let num_trials = 10;
481
482        let f_reference = <f32 as ReferenceProvider<f32>>::reference_implementation(metric);
483        let f_simd = simd_provider(metric);
484
485        // Inner test that loops over a vector of arguments.
486        let run_tests = |argument_pairs: Vec<LeftRightPair>| {
487            for LeftRightPair { mut x, mut y } in argument_pairs {
488                if normalize {
489                    x.normalize();
490                    y.normalize();
491                }
492
493                let reference: f32 = f_reference(&x, &y).into_inner();
494                let simd = f_simd(&x, &y);
495
496                assert_relative_eq!(
497                    reference,
498                    simd,
499                    epsilon = epsilon,
500                    max_relative = max_relative
501                );
502
503                // Compute via direct call.
504                let simd_direct = T::evaluate(&x, &y);
505                assert_eq!(simd_direct, simd);
506            }
507        };
508
509        // Corner Cases
510        for dim in 0..max_dim {
511            run_tests(generate_corner_cases(dim));
512        }
513
514        // Generated tests
515        for dim in 0..max_dim {
516            run_tests(collect_random_arguments(
517                dim, num_trials, -10.0, 10.0, 0x5643,
518            ));
519        }
520    }
521
522    #[test]
523    fn test_pure_functions() {
524        println!("L2");
525        test_pure_functions_impl(Metric::L2, SquaredL2 {}, false);
526        println!("InnerProduct");
527        test_pure_functions_impl(Metric::InnerProduct, InnerProduct {}, false);
528        println!("Cosine");
529        test_pure_functions_impl(Metric::Cosine, Cosine {}, false);
530        println!("CosineNormalized");
531        test_pure_functions_impl(Metric::CosineNormalized, CosineNormalized {}, true);
532    }
533
534    /// Test that the constant function pointer implementation returns the same result as
535    /// non-sized counterpart..
536    #[test]
537    fn test_specialize() {
538        use diskann_wide::arch::FTarget2;
539
540        const DIM: usize = 123;
541        let (x, y) = random_normal_arguments(DIM, -100.0, 100.0, 0x023457AA);
542
543        let reference: f32 = SquaredL2::evaluate(x.as_slice(), y.as_slice());
544        let evaluated: f32 = Specialize::<DIM, SquaredL2>::run(
545            diskann_wide::ARCH,
546            x.as_slice().as_unaligned(),
547            y.as_slice().as_unaligned(),
548        );
549
550        // Equality should be exact.
551        assert_eq!(reference, evaluated);
552    }
553
554    #[test]
555    #[should_panic]
556    fn test_function_pointer_const_panics_left() {
557        use diskann_wide::arch::FTarget2;
558
559        const DIM: usize = 34;
560        let x = vec![0.0f32; DIM + 1];
561        let y = vec![0.0f32; DIM];
562        // Since `x` does not have the correct dimensions, this should panic.
563        let _: f32 = Specialize::<DIM, SquaredL2>::run(
564            diskann_wide::ARCH,
565            x.as_slice().as_unaligned(),
566            y.as_slice().as_unaligned(),
567        );
568    }
569
570    #[test]
571    #[should_panic]
572    fn test_function_pointer_const_panics_right() {
573        use diskann_wide::arch::FTarget2;
574
575        const DIM: usize = 34;
576        let x = vec![0.0f32; DIM];
577        let y = vec![0.0f32; DIM + 1];
578        // Since `y` does not have the correct dimensions, this should panic.
579        let _: f32 = Specialize::<DIM, SquaredL2>::run(
580            diskann_wide::ARCH,
581            x.as_slice().as_unaligned(),
582            y.as_slice().as_unaligned(),
583        );
584    }
585
586    ////////////////////
587    // Test Version 2 //
588    ////////////////////
589
590    trait GetInner {
591        fn get_inner(self) -> f32;
592    }
593
594    impl GetInner for f32 {
595        fn get_inner(self) -> f32 {
596            self
597        }
598    }
599
600    impl GetInner for SimilarityScore<f32> {
601        fn get_inner(self) -> f32 {
602            self.into_inner()
603        }
604    }
605
606    impl GetInner for MathematicalValue<f32> {
607        fn get_inner(self) -> f32 {
608            self.into_inner()
609        }
610    }
611
612    // Comparison Bounds
613    #[derive(Clone, Copy)]
614    struct EpsilonAndRelative {
615        epsilon: f32,
616        max_relative: f32,
617    }
618
619    #[allow(clippy::too_many_arguments)]
620    fn run_test<L, R, To, Distribution, Callback>(
621        under_test: fn(&[L], &[R]) -> To,
622        reference: fn(&[L], &[R]) -> To,
623        bounds: EpsilonAndRelative,
624        dim: usize,
625        num_trials: usize,
626        distribution: Distribution,
627        rng: &mut impl Rng,
628        mut cb: Callback,
629    ) where
630        L: test_util::CornerCases,
631        R: test_util::CornerCases,
632        Distribution:
633            test_util::GenerateRandomArguments<L> + test_util::GenerateRandomArguments<R> + Clone,
634        To: GetInner + Copy,
635        Callback: FnMut(To, To),
636    {
637        let mut checker =
638            test_util::Checker::<L, R, To>::new(under_test, reference, |got, expected| {
639                // Invoke the callback with the received numbers.
640                cb(got, expected);
641                assert_relative_eq!(
642                    got.get_inner(),
643                    expected.get_inner(),
644                    epsilon = bounds.epsilon,
645                    max_relative = bounds.max_relative
646                );
647            });
648
649        test_util::test_distance_function(
650            &mut checker,
651            distribution.clone(),
652            distribution.clone(),
653            dim,
654            num_trials,
655            rng,
656        );
657    }
658
659    /// The maximum dimension tested for these tests.
660    #[cfg(not(debug_assertions))]
661    const MAX_DIM: usize = 256;
662
663    #[cfg(debug_assertions)]
664    const MAX_DIM: usize = 160;
665
666    // Decrease the number of trials in debug mode to keep test run-time down.
667    #[cfg(not(debug_assertions))]
668    const INTEGER_TRIALS: usize = 10000;
669
670    #[cfg(debug_assertions)]
671    const INTEGER_TRIALS: usize = 100;
672
673    ////////////////////
674    // Integer Tester //
675    ////////////////////
676
677    // For integer tests - we expect exact reproducibility with the reference
678    // implementations.
679    fn run_integer_test<T, R>(
680        under_test: fn(&[T], &[T]) -> R,
681        reference: fn(&[T], &[T]) -> R,
682        rng: &mut impl Rng,
683    ) where
684        T: test_util::CornerCases,
685        R: GetInner + Copy,
686        rand::distr::StandardUniform: test_util::GenerateRandomArguments<T> + Clone,
687    {
688        let distribution = rand::distr::StandardUniform {};
689        let num_corner_cases = <T as test_util::CornerCases>::corner_cases().len();
690
691        for dim in 0..MAX_DIM {
692            let mut callcount = 0;
693            let callback = |_, _| {
694                callcount += 1;
695            };
696
697            run_test(
698                under_test,
699                reference,
700                EpsilonAndRelative {
701                    epsilon: 0.0,
702                    max_relative: 0.0,
703                },
704                dim,
705                INTEGER_TRIALS,
706                distribution,
707                rng,
708                callback,
709            );
710
711            // Make sure the expected number of callbacks were made.
712            assert_eq!(
713                callcount,
714                INTEGER_TRIALS + num_corner_cases * num_corner_cases
715            );
716        }
717    }
718
719    //////////////////
720    // L2 - Integer //
721    //////////////////
722
723    #[test]
724    fn test_l2_i8_mathematical() {
725        let mut rng = rand::rngs::StdRng::seed_from_u64(0x2bb701074c2b81c9);
726        run_integer_test(
727            as_function_pointer::<FullL2, i8, i8, MathematicalValue<f32>>,
728            reference::reference_l2_i8_mathematical,
729            &mut rng,
730        );
731    }
732
733    #[test]
734    fn test_l2_u8_mathematical() {
735        let mut rng = rand::rngs::StdRng::seed_from_u64(0x9284ced6d080808c);
736        run_integer_test(
737            as_function_pointer::<FullL2, u8, u8, MathematicalValue<f32>>,
738            reference::reference_l2_u8_mathematical,
739            &mut rng,
740        );
741    }
742
743    #[test]
744    fn test_l2_i8_similarity() {
745        let mut rng = rand::rngs::StdRng::seed_from_u64(0xb196fecc4def04fa);
746        run_integer_test(
747            as_function_pointer::<FullL2, i8, i8, SimilarityScore<f32>>,
748            reference::reference_l2_i8_similarity,
749            &mut rng,
750        );
751    }
752
753    #[test]
754    fn test_l2_u8_similarity() {
755        let mut rng = rand::rngs::StdRng::seed_from_u64(0x07f6463e4a654aea);
756        run_integer_test(
757            as_function_pointer::<FullL2, u8, u8, SimilarityScore<f32>>,
758            reference::reference_l2_u8_similarity,
759            &mut rng,
760        );
761    }
762
763    ////////////////////////////
764    // InnerProduct - Integer //
765    ////////////////////////////
766
767    #[test]
768    fn test_innerproduct_i8_mathematical() {
769        let mut rng = rand::rngs::StdRng::seed_from_u64(0x2c1b1bddda5774be);
770        run_integer_test(
771            as_function_pointer::<InnerProduct, i8, i8, MathematicalValue<f32>>,
772            reference::reference_innerproduct_i8_mathematical,
773            &mut rng,
774        );
775    }
776
777    #[test]
778    fn test_innerproduct_u8_mathematical() {
779        let mut rng = rand::rngs::StdRng::seed_from_u64(0x757e363832d7f215);
780        run_integer_test(
781            as_function_pointer::<InnerProduct, u8, u8, MathematicalValue<f32>>,
782            reference::reference_innerproduct_u8_mathematical,
783            &mut rng,
784        );
785    }
786
787    #[test]
788    fn test_innerproduct_i8_similarity() {
789        let mut rng = rand::rngs::StdRng::seed_from_u64(0x4788ce0b991eb15a);
790        run_integer_test(
791            as_function_pointer::<InnerProduct, i8, i8, SimilarityScore<f32>>,
792            reference::reference_innerproduct_i8_similarity,
793            &mut rng,
794        );
795    }
796
797    #[test]
798    fn test_innerproduct_u8_similarity() {
799        let mut rng = rand::rngs::StdRng::seed_from_u64(0x4994adb68f814d96);
800        run_integer_test(
801            as_function_pointer::<InnerProduct, u8, u8, SimilarityScore<f32>>,
802            reference::reference_innerproduct_u8_similarity,
803            &mut rng,
804        );
805    }
806
807    //////////////////////
808    // Cosine - Integer //
809    //////////////////////
810
811    #[test]
812    fn test_cosine_i8_mathematical() {
813        let mut rng = rand::rngs::StdRng::seed_from_u64(0xedef81c780491ada);
814        run_integer_test(
815            as_function_pointer::<Cosine, i8, i8, MathematicalValue<f32>>,
816            reference::reference_cosine_i8_mathematical,
817            &mut rng,
818        );
819    }
820
821    #[test]
822    fn test_cosine_u8_mathematical() {
823        let mut rng = rand::rngs::StdRng::seed_from_u64(0x107cee2adcc58b73);
824        run_integer_test(
825            as_function_pointer::<Cosine, u8, u8, MathematicalValue<f32>>,
826            reference::reference_cosine_u8_mathematical,
827            &mut rng,
828        );
829    }
830
831    #[test]
832    fn test_cosine_i8_similarity() {
833        let mut rng = rand::rngs::StdRng::seed_from_u64(0x02d95c1cc0843647);
834        run_integer_test(
835            as_function_pointer::<Cosine, i8, i8, SimilarityScore<f32>>,
836            reference::reference_cosine_i8_similarity,
837            &mut rng,
838        );
839    }
840
841    #[test]
842    fn test_cosine_u8_similarity() {
843        let mut rng = rand::rngs::StdRng::seed_from_u64(0xf5ea1974bf8d8b3b);
844        run_integer_test(
845            as_function_pointer::<Cosine, u8, u8, SimilarityScore<f32>>,
846            reference::reference_cosine_u8_similarity,
847            &mut rng,
848        );
849    }
850
851    //////////////////
852    // Float Tester //
853    //////////////////
854
855    // For integer tests - we expect exact reproducibility with the reference
856    // implementations.
857    fn run_float_test<L, R, To, Dist>(
858        under_test: fn(&[L], &[R]) -> To,
859        reference: fn(&[L], &[R]) -> To,
860        rng: &mut impl Rng,
861        distribution: Dist,
862        bounds: EpsilonAndRelative,
863    ) where
864        L: test_util::CornerCases,
865        R: test_util::CornerCases,
866        To: GetInner + Copy,
867        Dist: test_util::GenerateRandomArguments<L> + test_util::GenerateRandomArguments<R> + Clone,
868    {
869        let left_corner_cases = <L as test_util::CornerCases>::corner_cases().len();
870        let right_corner_cases = <R as test_util::CornerCases>::corner_cases().len();
871        for dim in 0..MAX_DIM {
872            let mut callcount = 0;
873            let callback = |_, _| {
874                callcount += 1;
875            };
876
877            run_test(
878                under_test,
879                reference,
880                bounds,
881                dim,
882                INTEGER_TRIALS,
883                distribution.clone(),
884                rng,
885                callback,
886            );
887
888            // Make sure the expected number of callbacks were made.
889            assert_eq!(
890                callcount,
891                INTEGER_TRIALS + left_corner_cases * right_corner_cases
892            );
893        }
894    }
895
896    ////////////////
897    // L2 - Float //
898    ////////////////
899
900    fn expected_l2_errors() -> EpsilonAndRelative {
901        EpsilonAndRelative {
902            epsilon: 0.0,
903            max_relative: 1.2e-6,
904        }
905    }
906
907    #[test]
908    fn test_l2_f32_mathematical() {
909        let mut rng = rand::rngs::StdRng::seed_from_u64(0x6d22d320bdf35aec);
910        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
911        run_float_test(
912            as_function_pointer::<FullL2, f32, f32, MathematicalValue<f32>>,
913            reference::reference_l2_f32_mathematical,
914            &mut rng,
915            distribution,
916            expected_l2_errors(),
917        );
918    }
919
920    #[test]
921    fn test_l2_f16_mathematical() {
922        let mut rng = rand::rngs::StdRng::seed_from_u64(0x755819460c190db4);
923        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
924        run_float_test(
925            as_function_pointer::<FullL2, Half, Half, MathematicalValue<f32>>,
926            reference::reference_l2_f16_mathematical,
927            &mut rng,
928            distribution,
929            expected_l2_errors(),
930        );
931    }
932
933    #[test]
934    fn test_l2_f32xf16_mathematical() {
935        let mut rng = rand::rngs::StdRng::seed_from_u64(0x755819460c190db4);
936        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
937
938        run_float_test(
939            as_function_pointer::<FullL2, f32, Half, MathematicalValue<f32>>,
940            reference::reference_l2_f32xf16_mathematical,
941            &mut rng,
942            distribution,
943            expected_l2_errors(),
944        );
945    }
946
947    #[test]
948    fn test_l2_f32_similarity() {
949        let mut rng = rand::rngs::StdRng::seed_from_u64(0xbfc5f4b42b5bc0c1);
950        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
951        run_float_test(
952            as_function_pointer::<FullL2, f32, f32, SimilarityScore<f32>>,
953            reference::reference_l2_f32_similarity,
954            &mut rng,
955            distribution,
956            expected_l2_errors(),
957        );
958    }
959
960    #[test]
961    fn test_l2_f16_similarity() {
962        let mut rng = rand::rngs::StdRng::seed_from_u64(0x9d3809d84f54e4b6);
963        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
964        run_float_test(
965            as_function_pointer::<FullL2, Half, Half, SimilarityScore<f32>>,
966            reference::reference_l2_f16_similarity,
967            &mut rng,
968            distribution,
969            expected_l2_errors(),
970        );
971    }
972
973    #[test]
974    fn test_l2_f32xf16_similarity() {
975        let mut rng = rand::rngs::StdRng::seed_from_u64(0x755819460c190db4);
976        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
977
978        run_float_test(
979            as_function_pointer::<FullL2, f32, Half, SimilarityScore<f32>>,
980            reference::reference_l2_f32xf16_similarity,
981            &mut rng,
982            distribution,
983            expected_l2_errors(),
984        );
985    }
986
987    ///////////////////////////
988    // InnerProduct - Floats //
989    ///////////////////////////
990
991    fn expected_innerproduct_errors() -> EpsilonAndRelative {
992        EpsilonAndRelative {
993            epsilon: 2.5e-5,
994            max_relative: 1.6e-5,
995        }
996    }
997
998    #[test]
999    fn test_innerproduct_f32_mathematical() {
1000        let mut rng = rand::rngs::StdRng::seed_from_u64(0x1ef6ac3b65869792);
1001        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1002        run_float_test(
1003            as_function_pointer::<InnerProduct, f32, f32, MathematicalValue<f32>>,
1004            reference::reference_innerproduct_f32_mathematical,
1005            &mut rng,
1006            distribution,
1007            expected_innerproduct_errors(),
1008        );
1009    }
1010
1011    #[test]
1012    fn test_innerproduct_f16_mathematical() {
1013        let mut rng = rand::rngs::StdRng::seed_from_u64(0x24c51e4b825b0329);
1014        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1015        run_float_test(
1016            as_function_pointer::<InnerProduct, Half, Half, MathematicalValue<f32>>,
1017            reference::reference_innerproduct_f16_mathematical,
1018            &mut rng,
1019            distribution,
1020            expected_innerproduct_errors(),
1021        );
1022    }
1023
1024    #[test]
1025    fn test_innerproduct_f32xf16_mathematical() {
1026        let mut rng = rand::rngs::StdRng::seed_from_u64(0x24c51e4b825b0329);
1027        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1028        run_float_test(
1029            as_function_pointer::<InnerProduct, f32, Half, MathematicalValue<f32>>,
1030            reference::reference_innerproduct_f32xf16_mathematical,
1031            &mut rng,
1032            distribution,
1033            expected_innerproduct_errors(),
1034        );
1035    }
1036
1037    #[test]
1038    fn test_innerproduct_f32_similarity() {
1039        let mut rng = rand::rngs::StdRng::seed_from_u64(0x40326b22a57db0d7);
1040        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1041        run_float_test(
1042            as_function_pointer::<InnerProduct, f32, f32, SimilarityScore<f32>>,
1043            reference::reference_innerproduct_f32_similarity,
1044            &mut rng,
1045            distribution,
1046            expected_innerproduct_errors(),
1047        );
1048    }
1049
1050    #[test]
1051    fn test_innerproduct_f16_similarity() {
1052        let mut rng = rand::rngs::StdRng::seed_from_u64(0xfb8cff47bcbc9528);
1053        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1054        run_float_test(
1055            as_function_pointer::<InnerProduct, Half, Half, SimilarityScore<f32>>,
1056            reference::reference_innerproduct_f16_similarity,
1057            &mut rng,
1058            distribution,
1059            expected_innerproduct_errors(),
1060        );
1061    }
1062
1063    #[test]
1064    fn test_innerproduct_f32xf16_similarity() {
1065        let mut rng = rand::rngs::StdRng::seed_from_u64(0x24c51e4b825b0329);
1066        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1067        run_float_test(
1068            as_function_pointer::<InnerProduct, f32, Half, SimilarityScore<f32>>,
1069            reference::reference_innerproduct_f32xf16_similarity,
1070            &mut rng,
1071            distribution,
1072            expected_innerproduct_errors(),
1073        );
1074    }
1075
1076    /////////////////////
1077    // Cosine - Floats //
1078    /////////////////////
1079
1080    fn expected_cosine_errors() -> EpsilonAndRelative {
1081        EpsilonAndRelative {
1082            epsilon: 3e-7,
1083            max_relative: 5e-6,
1084        }
1085    }
1086
1087    #[test]
1088    fn test_cosine_f32_mathematical() {
1089        let mut rng = rand::rngs::StdRng::seed_from_u64(0xca6eaac942999500);
1090        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1091        run_float_test(
1092            as_function_pointer::<Cosine, f32, f32, MathematicalValue<f32>>,
1093            reference::reference_cosine_f32_mathematical,
1094            &mut rng,
1095            distribution,
1096            expected_cosine_errors(),
1097        );
1098    }
1099
1100    #[test]
1101    fn test_cosine_f16_mathematical() {
1102        let mut rng = rand::rngs::StdRng::seed_from_u64(0xa736c789aa16ce86);
1103        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1104        run_float_test(
1105            as_function_pointer::<Cosine, Half, Half, MathematicalValue<f32>>,
1106            reference::reference_cosine_f16_mathematical,
1107            &mut rng,
1108            distribution,
1109            expected_cosine_errors(),
1110        );
1111    }
1112
1113    #[test]
1114    fn test_cosine_f32xf16_mathematical() {
1115        let mut rng = rand::rngs::StdRng::seed_from_u64(0xac550231088a0d5c);
1116        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1117        run_float_test(
1118            as_function_pointer::<Cosine, f32, Half, MathematicalValue<f32>>,
1119            reference::reference_cosine_f32xf16_mathematical,
1120            &mut rng,
1121            distribution,
1122            expected_cosine_errors(),
1123        );
1124    }
1125
1126    #[test]
1127    fn test_cosine_f32_similarity() {
1128        let mut rng = rand::rngs::StdRng::seed_from_u64(0x4a09ad987a6204f3);
1129        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1130        run_float_test(
1131            as_function_pointer::<Cosine, f32, f32, SimilarityScore<f32>>,
1132            reference::reference_cosine_f32_similarity,
1133            &mut rng,
1134            distribution,
1135            expected_cosine_errors(),
1136        );
1137    }
1138
1139    #[test]
1140    fn test_cosine_f16_similarity() {
1141        let mut rng = rand::rngs::StdRng::seed_from_u64(0x77a48d1914f850f2);
1142        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1143        run_float_test(
1144            as_function_pointer::<Cosine, Half, Half, SimilarityScore<f32>>,
1145            reference::reference_cosine_f16_similarity,
1146            &mut rng,
1147            distribution,
1148            expected_cosine_errors(),
1149        );
1150    }
1151
1152    #[test]
1153    fn test_cosine_f32xf16_similarity() {
1154        let mut rng = rand::rngs::StdRng::seed_from_u64(0xbd7471b815655ca1);
1155        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1156        run_float_test(
1157            as_function_pointer::<Cosine, f32, Half, SimilarityScore<f32>>,
1158            reference::reference_cosine_f32xf16_similarity,
1159            &mut rng,
1160            distribution,
1161            expected_cosine_errors(),
1162        );
1163    }
1164
1165    ///////////////////////////////
1166    // CosineNormalized - Floats //
1167    ///////////////////////////////
1168
1169    fn expected_cosine_normalized_errors() -> EpsilonAndRelative {
1170        EpsilonAndRelative {
1171            epsilon: 3e-7,
1172            max_relative: 5e-6,
1173        }
1174    }
1175
1176    #[test]
1177    fn test_cosine_normalized_f32_mathematical() {
1178        let mut rng = rand::rngs::StdRng::seed_from_u64(0x1fda98112747f8dd);
1179        let distribution = rand_distr::Normal::new(-1.0, 1.0).unwrap();
1180        run_float_test(
1181            as_function_pointer::<CosineNormalized, f32, f32, MathematicalValue<f32>>,
1182            reference::reference_cosine_normalized_f32_mathematical,
1183            &mut rng,
1184            test_util::Normalized(distribution),
1185            expected_cosine_normalized_errors(),
1186        );
1187    }
1188
1189    #[test]
1190    fn test_cosine_normalized_f16_mathematical() {
1191        let mut rng = rand::rngs::StdRng::seed_from_u64(0x5e8c5d5e19cdd840);
1192        let distribution = rand_distr::Normal::new(-1.0, 1.0).unwrap();
1193        run_float_test(
1194            as_function_pointer::<CosineNormalized, Half, Half, MathematicalValue<f32>>,
1195            reference::reference_cosine_normalized_f16_mathematical,
1196            &mut rng,
1197            test_util::Normalized(distribution),
1198            expected_cosine_normalized_errors(),
1199        );
1200    }
1201
1202    #[test]
1203    fn test_cosine_normalized_f32xf16_mathematical() {
1204        let mut rng = rand::rngs::StdRng::seed_from_u64(0x3fd01e1c11c9bc45);
1205        let distribution = rand_distr::Normal::new(-1.0, 1.0).unwrap();
1206        run_float_test(
1207            as_function_pointer::<CosineNormalized, f32, Half, MathematicalValue<f32>>,
1208            reference::reference_cosine_normalized_f32xf16_mathematical,
1209            &mut rng,
1210            test_util::Normalized(distribution),
1211            expected_cosine_normalized_errors(),
1212        );
1213    }
1214
1215    #[test]
1216    fn test_cosine_normalized_f32_similarity() {
1217        let mut rng = rand::rngs::StdRng::seed_from_u64(0x9446d057870e5605);
1218        let distribution = rand_distr::Normal::new(-1.0, 1.0).unwrap();
1219        run_float_test(
1220            as_function_pointer::<CosineNormalized, f32, f32, SimilarityScore<f32>>,
1221            reference::reference_cosine_normalized_f32_similarity,
1222            &mut rng,
1223            test_util::Normalized(distribution),
1224            expected_cosine_normalized_errors(),
1225        );
1226    }
1227
1228    #[test]
1229    fn test_cosine_normalized_f16_similarity() {
1230        let mut rng = rand::rngs::StdRng::seed_from_u64(0x885c371801f18174);
1231        let distribution = rand_distr::Normal::new(-1.0, 1.0).unwrap();
1232        run_float_test(
1233            as_function_pointer::<CosineNormalized, Half, Half, SimilarityScore<f32>>,
1234            reference::reference_cosine_normalized_f16_similarity,
1235            &mut rng,
1236            test_util::Normalized(distribution),
1237            expected_cosine_normalized_errors(),
1238        );
1239    }
1240
1241    #[test]
1242    fn test_cosine_normalized_f32xf16_similarity() {
1243        let mut rng = rand::rngs::StdRng::seed_from_u64(0x1c356c92d0522c0f);
1244        let distribution = rand_distr::Normal::new(-1.0, 1.0).unwrap();
1245        run_float_test(
1246            as_function_pointer::<CosineNormalized, f32, Half, SimilarityScore<f32>>,
1247            reference::reference_cosine_normalized_f32xf16_similarity,
1248            &mut rng,
1249            test_util::Normalized(distribution),
1250            expected_cosine_normalized_errors(),
1251        );
1252    }
1253}