1use diskann_wide::{arch::Target2, Architecture, ARCH};
7
8use 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#[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 arch.run2(F::default(), x, y)
71 }
72}
73
74#[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
90pub(super) trait PostOp<From, To> {
96 fn post_op(x: From) -> To;
97}
98
99macro_rules! use_simd_implementation {
101 ($functor:ty, $T:ty, $U:ty) => {
102 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 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 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 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 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 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#[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#[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#[derive(Debug, Clone, Copy, Default)]
248pub struct InnerProduct {}
249
250impl PostOp<f32, SimilarityScore<f32>> for InnerProduct {
251 #[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
281fn cosine_transformation(x: f32) -> f32 {
289 1.0 - x
290}
291
292#[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#[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 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#[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#[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 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 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 let simd_direct = T::evaluate(&x, &y);
505 assert_eq!(simd_direct, simd);
506 }
507 };
508
509 for dim in 0..max_dim {
511 run_tests(generate_corner_cases(dim));
512 }
513
514 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]
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 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 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 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 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 #[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 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 #[cfg(not(debug_assertions))]
661 const MAX_DIM: usize = 256;
662
663 #[cfg(debug_assertions)]
664 const MAX_DIM: usize = 160;
665
666 #[cfg(not(debug_assertions))]
668 const INTEGER_TRIALS: usize = 10000;
669
670 #[cfg(debug_assertions)]
671 const INTEGER_TRIALS: usize = 100;
672
673 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 assert_eq!(
713 callcount,
714 INTEGER_TRIALS + num_corner_cases * num_corner_cases
715 );
716 }
717 }
718
719 #[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 #[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 #[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 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 assert_eq!(
890 callcount,
891 INTEGER_TRIALS + left_corner_cases * right_corner_cases
892 );
893 }
894 }
895
896 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 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 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 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}