1use diskann_vector::PureDistanceFunction;
111use diskann_wide::{ARCH, Architecture, arch::Target2};
112#[cfg(target_arch = "x86_64")]
113use diskann_wide::{
114 SIMDCast, SIMDDotProduct, SIMDMulAdd, SIMDReinterpret, SIMDSumTree, SIMDVector,
115};
116
117#[cfg(target_arch = "aarch64")]
118use diskann_wide::{SIMDDotProduct, SIMDSumTree, SIMDVector};
119
120use super::{Binary, BitSlice, BitTranspose, Dense, Representation, Unsigned};
121use crate::distances::{Hamming, InnerProduct, MV, MathematicalResult, SquaredL2, check_lengths};
122
123type USlice<'a, const N: usize, Perm = Dense> = BitSlice<'a, N, Unsigned, Perm>;
125
126macro_rules! retarget {
132 ($arch:path, $op:ty, ($N:literal, $M:literal)) => {
133 impl Target2<
134 $arch,
135 MathematicalResult<u32>,
136 USlice<'_, $N>,
137 USlice<'_, $M>,
138 > for $op {
139 #[inline(always)]
140 fn run(
141 self,
142 arch: $arch,
143 x: USlice<'_, $N>,
144 y: USlice<'_, $M>
145 ) -> MathematicalResult<u32> {
146 self.run(arch.retarget(), x, y)
147 }
148 }
149 };
150 ($arch:path, $op:ty, $N:literal) => {
151 retarget!($arch, $op, ($N, $N));
152 };
153 ($arch:path, $op:ty, $($args:tt),+ $(,)?) => {
154 $(retarget!($arch, $op, $args);)+
155 };
156}
157
158macro_rules! dispatch_pure {
160 ($op:ty, ($N:literal, $M:literal)) => {
161 impl PureDistanceFunction<USlice<'_, $N>, USlice<'_, $M>, MathematicalResult<u32>> for $op {
162 #[inline(always)]
163 fn evaluate(x: USlice<'_, $N>, y: USlice<'_, $M>) -> MathematicalResult<u32> {
164 (diskann_wide::ARCH).run2(Self, x, y)
165 }
166 }
167 };
168 ($op:ty, $N:literal) => {
169 dispatch_pure!($op, ($N, $N));
170 };
171 ($op:ty, $($args:tt),+ $(,)?) => {
172 $(dispatch_pure!($op, $args);)+
173 }
174}
175
176#[cfg(target_arch = "x86_64")]
183unsafe fn load_one<F, R>(ptr: *const u32, mut f: F) -> R
184where
185 F: FnMut(u32) -> R,
186{
187 f(unsafe { ptr.cast::<u8>().read_unaligned() }.into())
189}
190
191#[cfg(target_arch = "x86_64")]
198unsafe fn load_two<F, R>(ptr: *const u32, mut f: F) -> R
199where
200 F: FnMut(u32) -> R,
201{
202 f(unsafe { ptr.cast::<u16>().read_unaligned() }.into())
204}
205
206#[cfg(target_arch = "x86_64")]
213unsafe fn load_three<F, R>(ptr: *const u32, mut f: F) -> R
214where
215 F: FnMut(u32) -> R,
216{
217 let lo: u32 = unsafe { ptr.cast::<u16>().read_unaligned() }.into();
219 let hi: u32 = unsafe { ptr.cast::<u8>().add(2).read_unaligned() }.into();
221 f(lo | hi << 16)
222}
223
224#[cfg(target_arch = "x86_64")]
231unsafe fn load_four<F, R>(ptr: *const u32, mut f: F) -> R
232where
233 F: FnMut(u32) -> R,
234{
235 f(unsafe { ptr.read_unaligned() })
237}
238
239trait BitVectorOp<Repr>
251where
252 Repr: Representation<1>,
253{
254 fn on_u64(x: u64, y: u64) -> u32;
256
257 fn on_u8(x: u8, y: u8) -> u32;
262}
263
264impl BitVectorOp<Unsigned> for SquaredL2 {
266 #[inline(always)]
267 fn on_u64(x: u64, y: u64) -> u32 {
268 (x ^ y).count_ones()
269 }
270 #[inline(always)]
271 fn on_u8(x: u8, y: u8) -> u32 {
272 (x ^ y).count_ones()
273 }
274}
275
276impl BitVectorOp<Binary> for Hamming {
278 #[inline(always)]
279 fn on_u64(x: u64, y: u64) -> u32 {
280 (x ^ y).count_ones()
281 }
282 #[inline(always)]
283 fn on_u8(x: u8, y: u8) -> u32 {
284 (x ^ y).count_ones()
285 }
286}
287
288impl BitVectorOp<Unsigned> for InnerProduct {
296 #[inline(always)]
297 fn on_u64(x: u64, y: u64) -> u32 {
298 (x & y).count_ones()
299 }
300 #[inline(always)]
301 fn on_u8(x: u8, y: u8) -> u32 {
302 (x & y).count_ones()
303 }
304}
305
306#[inline(always)]
311fn bitvector_op<Op, Repr>(
312 x: BitSlice<'_, 1, Repr>,
313 y: BitSlice<'_, 1, Repr>,
314) -> MathematicalResult<u32>
315where
316 Repr: Representation<1>,
317 Op: BitVectorOp<Repr>,
318{
319 let len = check_lengths!(x, y)?;
320
321 let px: *const u64 = x.as_ptr().cast();
322 let py: *const u64 = y.as_ptr().cast();
323
324 let mut i = 0;
325 let mut s: u32 = 0;
326
327 let blocks = len / 64;
329 while i < blocks {
330 let vx = unsafe { px.add(i).read_unaligned() };
334
335 let vy = unsafe { py.add(i).read_unaligned() };
339
340 s += Op::on_u64(vx, vy);
341 i += 1;
342 }
343
344 i *= 8;
346 let px: *const u8 = x.as_ptr();
347 let py: *const u8 = y.as_ptr();
348
349 let blocks = len / 8;
350 while i < blocks {
351 let vx = unsafe { px.add(i).read_unaligned() };
354
355 let vy = unsafe { py.add(i).read_unaligned() };
359 s += Op::on_u8(vx, vy);
360 i += 1;
361 }
362
363 if i * 8 != len {
364 let vx = unsafe { px.add(i).read_unaligned() };
367
368 let vy = unsafe { py.add(i).read_unaligned() };
370 let m = (0x01u8 << (len - 8 * i)) - 1;
371
372 s += Op::on_u8(vx & m, vy & m)
373 }
374 Ok(MV::new(s))
375}
376
377impl PureDistanceFunction<BitSlice<'_, 1, Binary>, BitSlice<'_, 1, Binary>, MathematicalResult<u32>>
381 for Hamming
382{
383 fn evaluate(x: BitSlice<'_, 1, Binary>, y: BitSlice<'_, 1, Binary>) -> MathematicalResult<u32> {
384 bitvector_op::<Hamming, Binary>(x, y)
385 }
386}
387
388impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 8>> for SquaredL2
401where
402 A: Architecture,
403 diskann_vector::distance::SquaredL2: for<'a> Target2<A, MV<f32>, &'a [u8], &'a [u8]>,
404{
405 #[inline(always)]
406 fn run(self, arch: A, x: USlice<'_, 8>, y: USlice<'_, 8>) -> MathematicalResult<u32> {
407 check_lengths!(x, y)?;
408
409 let r: MV<f32> = <_ as Target2<_, _, _, _>>::run(
410 diskann_vector::distance::SquaredL2 {},
411 arch,
412 x.as_slice(),
413 y.as_slice(),
414 );
415
416 Ok(MV::new(r.into_inner() as u32))
417 }
418}
419
420#[cfg(target_arch = "x86_64")]
434impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
435 for SquaredL2
436{
437 #[inline(always)]
438 fn run(
439 self,
440 arch: diskann_wide::arch::x86_64::V3,
441 x: USlice<'_, 4>,
442 y: USlice<'_, 4>,
443 ) -> MathematicalResult<u32> {
444 let len = check_lengths!(x, y)?;
445
446 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
447 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
448 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
449
450 let px_u32: *const u32 = x.as_ptr().cast();
451 let py_u32: *const u32 = y.as_ptr().cast();
452
453 let mut i = 0;
454 let mut s: u32 = 0;
455
456 let blocks = len / 8;
458 if i < blocks {
459 let mut s0 = i32s::default(arch);
460 let mut s1 = i32s::default(arch);
461 let mut s2 = i32s::default(arch);
462 let mut s3 = i32s::default(arch);
463 let mask = u32s::splat(arch, 0x000f000f);
464 while i + 8 < blocks {
465 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
470
471 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
475
476 let wx: i16s = (vx & mask).reinterpret_simd();
477 let wy: i16s = (vy & mask).reinterpret_simd();
478 let d = wx - wy;
479 s0 = s0.dot_simd(d, d);
480
481 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
482 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
483 let d = wx - wy;
484 s1 = s1.dot_simd(d, d);
485
486 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
487 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
488 let d = wx - wy;
489 s2 = s2.dot_simd(d, d);
490
491 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
492 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
493 let d = wx - wy;
494 s3 = s3.dot_simd(d, d);
495
496 i += 8;
497 }
498
499 let remainder = blocks - i;
500
501 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
507
508 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
512
513 let wx: i16s = (vx & mask).reinterpret_simd();
514 let wy: i16s = (vy & mask).reinterpret_simd();
515 let d = wx - wy;
516 s0 = s0.dot_simd(d, d);
517
518 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
519 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
520 let d = wx - wy;
521 s1 = s1.dot_simd(d, d);
522
523 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
524 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
525 let d = wx - wy;
526 s2 = s2.dot_simd(d, d);
527
528 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
529 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
530 let d = wx - wy;
531 s3 = s3.dot_simd(d, d);
532
533 i += remainder;
534
535 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
536 }
537
538 i *= 8;
540
541 if i != len {
543 #[inline(never)]
545 fn fallback(x: USlice<'_, 4>, y: USlice<'_, 4>, from: usize) -> u32 {
546 let mut s: i32 = 0;
547 for i in from..x.len() {
548 let ix = unsafe { x.get_unchecked(i) } as i32;
550 let iy = unsafe { y.get_unchecked(i) } as i32;
552 let d = ix - iy;
553 s += d * d;
554 }
555 s as u32
556 }
557 s += fallback(x, y, i);
558 }
559
560 Ok(MV::new(s))
561 }
562}
563
564#[cfg(target_arch = "x86_64")]
578impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
579 for SquaredL2
580{
581 #[inline(always)]
582 fn run(
583 self,
584 arch: diskann_wide::arch::x86_64::V4,
585 x: USlice<'_, 4>,
586 y: USlice<'_, 4>,
587 ) -> MathematicalResult<u32> {
588 let len = check_lengths!(x, y)?;
589
590 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V4>::i32x16);
591 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V4>::u32x16);
592 diskann_wide::alias!(u8s = <diskann_wide::arch::x86_64::V4>::u8x64);
593 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V4>::i16x32);
594
595 let px_u32: *const u32 = x.as_ptr().cast();
596 let py_u32: *const u32 = y.as_ptr().cast();
597
598 let mut i = 0;
599 let mut s: u32 = 0;
600
601 let blocks = len.div_ceil(8);
606 if i < blocks {
607 let mut s0 = i32s::default(arch);
608 let mut s1 = i32s::default(arch);
609 let mut s2 = i32s::default(arch);
610 let mut s3 = i32s::default(arch);
611 let mask = u32s::splat(arch, 0x000f000f);
612 while i + 16 < blocks {
613 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
619
620 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
624
625 let wx: i16s = (vx & mask).reinterpret_simd();
626 let wy: i16s = (vy & mask).reinterpret_simd();
627 let d = wx - wy;
628 s0 = s0.dot_simd(d, d);
629
630 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
631 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
632 let d = wx - wy;
633 s1 = s1.dot_simd(d, d);
634
635 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
636 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
637 let d = wx - wy;
638 s2 = s2.dot_simd(d, d);
639
640 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
641 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
642 let d = wx - wy;
643 s3 = s3.dot_simd(d, d);
644
645 i += 16;
646 }
647
648 let remainder = len / 2 - 4 * i;
655
656 if remainder > 0 {
657 let vx =
663 unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
664 let vx: u32s = vx.reinterpret_simd();
665
666 let vy =
670 unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
671 let vy: u32s = vy.reinterpret_simd();
672
673 let wx: i16s = (vx & mask).reinterpret_simd();
674 let wy: i16s = (vy & mask).reinterpret_simd();
675 let d = wx - wy;
676 s0 = s0.dot_simd(d, d);
677
678 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
679 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
680 let d = wx - wy;
681 s1 = s1.dot_simd(d, d);
682
683 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
684 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
685 let d = wx - wy;
686 s2 = s2.dot_simd(d, d);
687
688 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
689 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
690 let d = wx - wy;
691 s3 = s3.dot_simd(d, d);
692 }
693
694 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
695 i = (4 * i) + remainder;
696 }
697
698 i *= 2;
700
701 debug_assert!(len - i <= 1);
703 if i != len {
704 let ix = unsafe { x.get_unchecked(i) } as i32;
706 let iy = unsafe { y.get_unchecked(i) } as i32;
708 let d = ix - iy;
709 s += (d * d) as u32;
710 }
711
712 Ok(MV::new(s))
713 }
714}
715
716#[cfg(target_arch = "aarch64")]
717impl
718 Target2<
719 diskann_wide::arch::aarch64::Neon,
720 MathematicalResult<u32>,
721 USlice<'_, 4>,
722 USlice<'_, 4>,
723 > for SquaredL2
724{
725 #[inline(always)]
726 fn run(
727 self,
728 arch: diskann_wide::arch::aarch64::Neon,
729 x: USlice<'_, 4>,
730 y: USlice<'_, 4>,
731 ) -> MathematicalResult<u32> {
732 let len = check_lengths!(x, y)?;
734
735 diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
736 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
737
738 let px_u8: *const u8 = x.as_ptr().cast();
739 let py_u8: *const u8 = y.as_ptr().cast();
740
741 let mut i = 0;
742 let mut s: u32 = 0;
743
744 let bytes = len / 2;
746 if i < bytes {
747 let mut s0 = u32s::default(arch);
748 let mut s1 = u32s::default(arch);
749 let mask = u8s::splat(arch, 0x0f);
750 while i + 16 <= bytes {
751 let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
755 let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
758
759 let lower_x: u8s = x_vec & mask;
761 let lower_y: u8s = y_vec & mask;
762 let d = arch.vabdq_u8(lower_x, lower_y);
763 s0 = s0.dot_simd(d, d);
764
765 let upper_x: u8s = (x_vec >> 4) & mask;
767 let upper_y: u8s = (y_vec >> 4) & mask;
768 let d = arch.vabdq_u8(upper_x, upper_y);
769 s1 = s1.dot_simd(d, d);
770
771 i += 16;
773 }
774
775 let remaining_bytes = len / 2 - i;
776
777 if remaining_bytes > 0 {
778 let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_bytes) };
782 let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_bytes) };
784
785 let lower_x: u8s = x_vec & mask;
787 let lower_y: u8s = y_vec & mask;
788 let d = arch.vabdq_u8(lower_x, lower_y);
789 s0 = s0.dot_simd(d, d);
790
791 let upper_x: u8s = (x_vec >> 4) & mask;
793 let upper_y: u8s = (y_vec >> 4) & mask;
794 let d = arch.vabdq_u8(upper_x, upper_y);
795 s1 = s1.dot_simd(d, d);
796
797 i += remaining_bytes;
798 }
799 s = (s0 + s1).sum_tree();
800 }
801
802 i *= 2;
804
805 debug_assert!(len - i <= 1);
807 if i != len {
808 let ix = unsafe { x.get_unchecked(i) } as i32;
810 let iy = unsafe { y.get_unchecked(i) } as i32;
812 let d = ix - iy;
813 s += (d * d) as u32;
814 }
815
816 Ok(MV::new(s))
817 }
818}
819
820#[cfg(target_arch = "aarch64")]
821impl
822 Target2<
823 diskann_wide::arch::aarch64::Neon,
824 MathematicalResult<u32>,
825 USlice<'_, 2>,
826 USlice<'_, 2>,
827 > for SquaredL2
828{
829 #[inline(always)]
830 fn run(
831 self,
832 arch: diskann_wide::arch::aarch64::Neon,
833 x: USlice<'_, 2>,
834 y: USlice<'_, 2>,
835 ) -> MathematicalResult<u32> {
836 let len = check_lengths!(x, y)?;
838
839 diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
840 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
841
842 let px_u8: *const u8 = x.as_ptr().cast();
843 let py_u8: *const u8 = y.as_ptr().cast();
844
845 let mut i = 0;
846 let mut s: u32 = 0;
847
848 let bytes = len / 4;
850 if i < bytes {
851 let mut s0 = u32s::default(arch);
852 let mut s1 = u32s::default(arch);
853 let mut s2 = u32s::default(arch);
854 let mut s3 = u32s::default(arch);
855 let mask = u8s::splat(arch, 0x03);
856 while i + 16 <= bytes {
857 let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
861 let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
864
865 let first_x: u8s = x_vec & mask;
867 let first_y: u8s = y_vec & mask;
868 let d = arch.vabdq_u8(first_x, first_y);
869 s0 = s0.dot_simd(d, d);
870
871 let second_x: u8s = (x_vec >> 2) & mask;
873 let second_y: u8s = (y_vec >> 2) & mask;
874 let d = arch.vabdq_u8(second_x, second_y);
875 s1 = s1.dot_simd(d, d);
876
877 let third_x: u8s = (x_vec >> 4) & mask;
879 let third_y: u8s = (y_vec >> 4) & mask;
880 let d = arch.vabdq_u8(third_x, third_y);
881 s2 = s2.dot_simd(d, d);
882
883 let fourth_x: u8s = (x_vec >> 6) & mask;
885 let fourth_y: u8s = (y_vec >> 6) & mask;
886 let d = arch.vabdq_u8(fourth_x, fourth_y);
887 s3 = s3.dot_simd(d, d);
888 i += 16;
890 }
891
892 let remaining_bytes = len / 4 - i;
893
894 if remaining_bytes > 0 {
895 let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_bytes) };
899 let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_bytes) };
901
902 let first_x: u8s = x_vec & mask;
904 let first_y: u8s = y_vec & mask;
905 let d = arch.vabdq_u8(first_x, first_y);
906 s0 = s0.dot_simd(d, d);
907
908 let second_x: u8s = (x_vec >> 2) & mask;
910 let second_y: u8s = (y_vec >> 2) & mask;
911 let d = arch.vabdq_u8(second_x, second_y);
912 s1 = s1.dot_simd(d, d);
913
914 let third_x: u8s = (x_vec >> 4) & mask;
916 let third_y: u8s = (y_vec >> 4) & mask;
917 let d = arch.vabdq_u8(third_x, third_y);
918 s2 = s2.dot_simd(d, d);
919
920 let fourth_x: u8s = (x_vec >> 6) & mask;
922 let fourth_y: u8s = (y_vec >> 6) & mask;
923 let d = arch.vabdq_u8(fourth_x, fourth_y);
924 s3 = s3.dot_simd(d, d);
925 i += remaining_bytes;
926 }
927 s = ((s0 + s1) + (s2 + s3)).sum_tree();
928 }
929
930 i *= 4;
932
933 debug_assert!(len - i <= 3);
935 if i != len {
936 #[inline(never)]
937 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
938 let mut s: i32 = 0;
939 for i in from..x.len() {
940 let ix = unsafe { x.get_unchecked(i) } as i32;
942 let iy = unsafe { y.get_unchecked(i) } as i32;
944 let d = ix - iy;
945 s += d * d;
946 }
947 s as u32
948 }
949 s += fallback(x, y, i);
950 }
951
952 Ok(MV::new(s))
953 }
954}
955
956#[cfg(target_arch = "x86_64")]
970impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
971 for SquaredL2
972{
973 #[inline(always)]
974 fn run(
975 self,
976 arch: diskann_wide::arch::x86_64::V3,
977 x: USlice<'_, 2>,
978 y: USlice<'_, 2>,
979 ) -> MathematicalResult<u32> {
980 let len = check_lengths!(x, y)?;
981
982 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
983 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
984 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
985
986 let px_u32: *const u32 = x.as_ptr().cast();
987 let py_u32: *const u32 = y.as_ptr().cast();
988
989 let mut i = 0;
990 let mut s: u32 = 0;
991
992 let blocks = len / 16;
994 if i < blocks {
995 let mut s0 = i32s::default(arch);
996 let mut s1 = i32s::default(arch);
997 let mut s2 = i32s::default(arch);
998 let mut s3 = i32s::default(arch);
999 let mask = u32s::splat(arch, 0x00030003);
1000 while i + 8 < blocks {
1001 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1006
1007 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1011
1012 let wx: i16s = (vx & mask).reinterpret_simd();
1013 let wy: i16s = (vy & mask).reinterpret_simd();
1014 let d = wx - wy;
1015 s0 = s0.dot_simd(d, d);
1016
1017 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1018 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1019 let d = wx - wy;
1020 s1 = s1.dot_simd(d, d);
1021
1022 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1023 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1024 let d = wx - wy;
1025 s2 = s2.dot_simd(d, d);
1026
1027 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1028 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1029 let d = wx - wy;
1030 s3 = s3.dot_simd(d, d);
1031
1032 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1033 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1034 let d = wx - wy;
1035 s0 = s0.dot_simd(d, d);
1036
1037 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1038 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1039 let d = wx - wy;
1040 s1 = s1.dot_simd(d, d);
1041
1042 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1043 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1044 let d = wx - wy;
1045 s2 = s2.dot_simd(d, d);
1046
1047 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1048 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1049 let d = wx - wy;
1050 s3 = s3.dot_simd(d, d);
1051
1052 i += 8;
1053 }
1054
1055 let remainder = blocks - i;
1056
1057 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1063
1064 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1068 let wx: i16s = (vx & mask).reinterpret_simd();
1069 let wy: i16s = (vy & mask).reinterpret_simd();
1070 let d = wx - wy;
1071 s0 = s0.dot_simd(d, d);
1072
1073 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1074 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1075 let d = wx - wy;
1076 s1 = s1.dot_simd(d, d);
1077
1078 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1079 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1080 let d = wx - wy;
1081 s2 = s2.dot_simd(d, d);
1082
1083 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1084 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1085 let d = wx - wy;
1086 s3 = s3.dot_simd(d, d);
1087
1088 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1089 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1090 let d = wx - wy;
1091 s0 = s0.dot_simd(d, d);
1092
1093 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1094 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1095 let d = wx - wy;
1096 s1 = s1.dot_simd(d, d);
1097
1098 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1099 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1100 let d = wx - wy;
1101 s2 = s2.dot_simd(d, d);
1102
1103 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1104 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1105 let d = wx - wy;
1106 s3 = s3.dot_simd(d, d);
1107
1108 i += remainder;
1109
1110 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1111 }
1112
1113 i *= 16;
1115
1116 if i != len {
1118 #[inline(never)]
1120 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
1121 let mut s: i32 = 0;
1122 for i in from..x.len() {
1123 let ix = unsafe { x.get_unchecked(i) } as i32;
1125 let iy = unsafe { y.get_unchecked(i) } as i32;
1127 let d = ix - iy;
1128 s += d * d;
1129 }
1130 s as u32
1131 }
1132 s += fallback(x, y, i);
1133 }
1134
1135 Ok(MV::new(s))
1136 }
1137}
1138
1139impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 1>, USlice<'_, 1>> for SquaredL2
1143where
1144 A: Architecture,
1145{
1146 fn run(self, _: A, x: USlice<'_, 1>, y: USlice<'_, 1>) -> MathematicalResult<u32> {
1147 bitvector_op::<Self, Unsigned>(x, y)
1148 }
1149}
1150
1151macro_rules! impl_fallback_l2 {
1153 ($N:literal) => {
1154 impl Target2<diskann_wide::arch::Scalar, MathematicalResult<u32>, USlice<'_, $N>, USlice<'_, $N>> for SquaredL2 {
1162 #[inline(never)]
1163 fn run(
1164 self,
1165 _: diskann_wide::arch::Scalar,
1166 x: USlice<'_, $N>,
1167 y: USlice<'_, $N>
1168 ) -> MathematicalResult<u32> {
1169 let len = check_lengths!(x, y)?;
1170
1171 let mut accum: i32 = 0;
1172 for i in 0..len {
1173 let ix: i32 = unsafe { x.get_unchecked(i) } as i32;
1175 let iy: i32 = unsafe { y.get_unchecked(i) } as i32;
1177 let diff = ix - iy;
1178 accum += diff * diff;
1179 }
1180 Ok(MV::new(accum as u32))
1181 }
1182 }
1183 };
1184 ($($N:literal),+ $(,)?) => {
1185 $(impl_fallback_l2!($N);)+
1186 };
1187}
1188
1189impl_fallback_l2!(7, 6, 5, 4, 3, 2);
1190
1191#[cfg(target_arch = "x86_64")]
1192retarget!(diskann_wide::arch::x86_64::V3, SquaredL2, 7, 6, 5, 3);
1193
1194#[cfg(target_arch = "x86_64")]
1195retarget!(diskann_wide::arch::x86_64::V4, SquaredL2, 7, 6, 5, 3, 2);
1196
1197dispatch_pure!(SquaredL2, 1, 2, 3, 4, 5, 6, 7, 8);
1198#[cfg(target_arch = "aarch64")]
1199retarget!(diskann_wide::arch::aarch64::Neon, SquaredL2, 7, 6, 5, 3,);
1200
1201impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 8>> for InnerProduct
1214where
1215 A: Architecture,
1216 diskann_vector::distance::InnerProduct: for<'a> Target2<A, MV<f32>, &'a [u8], &'a [u8]>,
1217{
1218 #[inline(always)]
1219 fn run(self, arch: A, x: USlice<'_, 8>, y: USlice<'_, 8>) -> MathematicalResult<u32> {
1220 check_lengths!(x, y)?;
1221 let r: MV<f32> = <_ as Target2<_, _, _, _>>::run(
1222 diskann_vector::distance::InnerProduct {},
1223 arch,
1224 x.as_slice(),
1225 y.as_slice(),
1226 );
1227
1228 Ok(MV::new(r.into_inner() as u32))
1229 }
1230}
1231
1232#[cfg(target_arch = "x86_64")]
1249impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
1250 for InnerProduct
1251{
1252 #[expect(non_camel_case_types)]
1253 #[inline(always)]
1254 fn run(
1255 self,
1256 arch: diskann_wide::arch::x86_64::V4,
1257 x: USlice<'_, 2>,
1258 y: USlice<'_, 2>,
1259 ) -> MathematicalResult<u32> {
1260 let len = check_lengths!(x, y)?;
1261
1262 type i32s = <diskann_wide::arch::x86_64::V4 as Architecture>::i32x16;
1263 type u32s = <diskann_wide::arch::x86_64::V4 as Architecture>::u32x16;
1264 type u8s = <diskann_wide::arch::x86_64::V4 as Architecture>::u8x64;
1265 type i8s = <diskann_wide::arch::x86_64::V4 as Architecture>::i8x64;
1266
1267 let px_u32: *const u32 = x.as_ptr().cast();
1268 let py_u32: *const u32 = y.as_ptr().cast();
1269
1270 let mut i = 0;
1271 let mut s: u32 = 0;
1272
1273 let blocks = len.div_ceil(16);
1275 if i < blocks {
1276 let mut s0 = i32s::default(arch);
1277 let mut s1 = i32s::default(arch);
1278 let mut s2 = i32s::default(arch);
1279 let mut s3 = i32s::default(arch);
1280 let mask = u32s::splat(arch, 0x03030303);
1281 while i + 16 < blocks {
1282 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1287
1288 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1292
1293 let wx: u8s = (vx & mask).reinterpret_simd();
1294 let wy: i8s = (vy & mask).reinterpret_simd();
1295 s0 = s0.dot_simd(wx, wy);
1296
1297 let wx: u8s = ((vx >> 2) & mask).reinterpret_simd();
1298 let wy: i8s = ((vy >> 2) & mask).reinterpret_simd();
1299 s1 = s1.dot_simd(wx, wy);
1300
1301 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1302 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1303 s2 = s2.dot_simd(wx, wy);
1304
1305 let wx: u8s = ((vx >> 6) & mask).reinterpret_simd();
1306 let wy: i8s = ((vy >> 6) & mask).reinterpret_simd();
1307 s3 = s3.dot_simd(wx, wy);
1308
1309 i += 16;
1310 }
1311
1312 let remainder = len / 4 - 4 * i;
1316
1317 let vx = unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
1322 let vx: u32s = vx.reinterpret_simd();
1323
1324 let vy = unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
1328 let vy: u32s = vy.reinterpret_simd();
1329
1330 let wx: u8s = (vx & mask).reinterpret_simd();
1331 let wy: i8s = (vy & mask).reinterpret_simd();
1332 s0 = s0.dot_simd(wx, wy);
1333
1334 let wx: u8s = ((vx >> 2) & mask).reinterpret_simd();
1335 let wy: i8s = ((vy >> 2) & mask).reinterpret_simd();
1336 s1 = s1.dot_simd(wx, wy);
1337
1338 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1339 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1340 s2 = s2.dot_simd(wx, wy);
1341
1342 let wx: u8s = ((vx >> 6) & mask).reinterpret_simd();
1343 let wy: i8s = ((vy >> 6) & mask).reinterpret_simd();
1344 s3 = s3.dot_simd(wx, wy);
1345
1346 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1347 i = (4 * i) + remainder;
1348 }
1349
1350 i *= 4;
1352
1353 debug_assert!(len - i <= 3);
1355 let rest = (len - i).min(3);
1356 if i != len {
1357 for j in 0..rest {
1358 let ix = unsafe { x.get_unchecked(i + j) } as u32;
1360 let iy = unsafe { y.get_unchecked(i + j) } as u32;
1362 s += ix * iy;
1363 }
1364 }
1365
1366 Ok(MV::new(s))
1367 }
1368}
1369
1370#[cfg(target_arch = "x86_64")]
1384impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
1385 for InnerProduct
1386{
1387 #[inline(always)]
1388 fn run(
1389 self,
1390 arch: diskann_wide::arch::x86_64::V3,
1391 x: USlice<'_, 4>,
1392 y: USlice<'_, 4>,
1393 ) -> MathematicalResult<u32> {
1394 let len = check_lengths!(x, y)?;
1395
1396 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1397 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
1398 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1399
1400 let px_u32: *const u32 = x.as_ptr().cast();
1401 let py_u32: *const u32 = y.as_ptr().cast();
1402
1403 let mut i = 0;
1404 let mut s: u32 = 0;
1405
1406 let blocks = len / 8;
1407 if i < blocks {
1408 let mut s0 = i32s::default(arch);
1409 let mut s1 = i32s::default(arch);
1410 let mut s2 = i32s::default(arch);
1411 let mut s3 = i32s::default(arch);
1412 let mask = u32s::splat(arch, 0x000f000f);
1413 while i + 8 < blocks {
1414 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1419
1420 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1424
1425 let wx: i16s = (vx & mask).reinterpret_simd();
1426 let wy: i16s = (vy & mask).reinterpret_simd();
1427 s0 = s0.dot_simd(wx, wy);
1428
1429 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1430 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1431 s1 = s1.dot_simd(wx, wy);
1432
1433 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1434 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1435 s2 = s2.dot_simd(wx, wy);
1436
1437 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1438 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1439 s3 = s3.dot_simd(wx, wy);
1440
1441 i += 8;
1442 }
1443
1444 let remainder = blocks - i;
1445
1446 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1452
1453 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1457
1458 let wx: i16s = (vx & mask).reinterpret_simd();
1459 let wy: i16s = (vy & mask).reinterpret_simd();
1460 s0 = s0.dot_simd(wx, wy);
1461
1462 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1463 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1464 s1 = s1.dot_simd(wx, wy);
1465
1466 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1467 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1468 s2 = s2.dot_simd(wx, wy);
1469
1470 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1471 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1472 s3 = s3.dot_simd(wx, wy);
1473
1474 i += remainder;
1475
1476 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1477 }
1478
1479 i *= 8;
1481
1482 if i != len {
1484 #[inline(never)]
1486 fn fallback(x: USlice<'_, 4>, y: USlice<'_, 4>, from: usize) -> u32 {
1487 let mut s: u32 = 0;
1488 for i in from..x.len() {
1489 let ix = unsafe { x.get_unchecked(i) } as u32;
1491 let iy = unsafe { y.get_unchecked(i) } as u32;
1493 s += ix * iy;
1494 }
1495 s
1496 }
1497 s += fallback(x, y, i);
1498 }
1499
1500 Ok(MV::new(s))
1501 }
1502}
1503
1504#[cfg(target_arch = "x86_64")]
1522impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
1523 for InnerProduct
1524{
1525 #[inline(always)]
1526 fn run(
1527 self,
1528 arch: diskann_wide::arch::x86_64::V4,
1529 x: USlice<'_, 4>,
1530 y: USlice<'_, 4>,
1531 ) -> MathematicalResult<u32> {
1532 let len = check_lengths!(x, y)?;
1533
1534 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V4>::i32x16);
1535 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V4>::u32x16);
1536 diskann_wide::alias!(u8s = <diskann_wide::arch::x86_64::V4>::u8x64);
1537 diskann_wide::alias!(i8s = <diskann_wide::arch::x86_64::V4>::i8x64);
1538
1539 let px_u32: *const u32 = x.as_ptr().cast();
1540 let py_u32: *const u32 = y.as_ptr().cast();
1541
1542 let mut i = 0;
1543 let mut s: u32 = 0;
1544
1545 let blocks = len.div_ceil(8);
1550 if i < blocks {
1551 let mut s0 = i32s::default(arch);
1552 let mut s1 = i32s::default(arch);
1553 let mask = u32s::splat(arch, 0x0f0f0f0f);
1554 while i + 16 < blocks {
1555 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1561
1562 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1566
1567 let wx: u8s = (vx & mask).reinterpret_simd();
1571 let wy: i8s = (vy & mask).reinterpret_simd();
1572 s0 = s0.dot_simd(wx, wy);
1573
1574 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1575 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1576 s1 = s1.dot_simd(wx, wy);
1577
1578 i += 16;
1579 }
1580
1581 let remainder = len / 2 - 4 * i;
1588
1589 if remainder > 0 {
1590 let vx =
1596 unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
1597 let vx: u32s = vx.reinterpret_simd();
1598
1599 let vy =
1603 unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
1604 let vy: u32s = vy.reinterpret_simd();
1605
1606 let wx: u8s = (vx & mask).reinterpret_simd();
1607 let wy: i8s = (vy & mask).reinterpret_simd();
1608 s0 = s0.dot_simd(wx, wy);
1609
1610 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1611 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1612 s1 = s1.dot_simd(wx, wy);
1613 }
1614
1615 s = (s0 + s1).sum_tree() as u32;
1616 i = (4 * i) + remainder;
1617 }
1618
1619 i *= 2;
1621
1622 debug_assert!(len - i <= 1);
1624 if i != len {
1625 let ix = unsafe { x.get_unchecked(i) } as u32;
1627 let iy = unsafe { y.get_unchecked(i) } as u32;
1629 s += ix * iy;
1630 }
1631
1632 Ok(MV::new(s))
1633 }
1634}
1635
1636#[cfg(target_arch = "x86_64")]
1650impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
1651 for InnerProduct
1652{
1653 #[inline(always)]
1654 fn run(
1655 self,
1656 arch: diskann_wide::arch::x86_64::V3,
1657 x: USlice<'_, 2>,
1658 y: USlice<'_, 2>,
1659 ) -> MathematicalResult<u32> {
1660 let len = check_lengths!(x, y)?;
1661
1662 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1663 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
1664 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1665
1666 let px_u32: *const u32 = x.as_ptr().cast();
1667 let py_u32: *const u32 = y.as_ptr().cast();
1668
1669 let mut i = 0;
1670 let mut s: u32 = 0;
1671
1672 let blocks = len / 16;
1674 if i < blocks {
1675 let mut s0 = i32s::default(arch);
1676 let mut s1 = i32s::default(arch);
1677 let mut s2 = i32s::default(arch);
1678 let mut s3 = i32s::default(arch);
1679 let mask = u32s::splat(arch, 0x00030003);
1680 while i + 8 < blocks {
1681 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1686
1687 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1691
1692 let wx: i16s = (vx & mask).reinterpret_simd();
1693 let wy: i16s = (vy & mask).reinterpret_simd();
1694 s0 = s0.dot_simd(wx, wy);
1695
1696 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1697 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1698 s1 = s1.dot_simd(wx, wy);
1699
1700 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1701 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1702 s2 = s2.dot_simd(wx, wy);
1703
1704 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1705 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1706 s3 = s3.dot_simd(wx, wy);
1707
1708 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1709 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1710 s0 = s0.dot_simd(wx, wy);
1711
1712 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1713 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1714 s1 = s1.dot_simd(wx, wy);
1715
1716 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1717 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1718 s2 = s2.dot_simd(wx, wy);
1719
1720 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1721 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1722 s3 = s3.dot_simd(wx, wy);
1723
1724 i += 8;
1725 }
1726
1727 let remainder = blocks - i;
1728
1729 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1735
1736 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1740 let wx: i16s = (vx & mask).reinterpret_simd();
1741 let wy: i16s = (vy & mask).reinterpret_simd();
1742 s0 = s0.dot_simd(wx, wy);
1743
1744 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1745 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1746 s1 = s1.dot_simd(wx, wy);
1747
1748 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1749 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1750 s2 = s2.dot_simd(wx, wy);
1751
1752 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1753 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1754 s3 = s3.dot_simd(wx, wy);
1755
1756 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1757 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1758 s0 = s0.dot_simd(wx, wy);
1759
1760 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1761 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1762 s1 = s1.dot_simd(wx, wy);
1763
1764 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1765 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1766 s2 = s2.dot_simd(wx, wy);
1767
1768 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1769 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1770 s3 = s3.dot_simd(wx, wy);
1771
1772 i += remainder;
1773
1774 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1775 }
1776
1777 i *= 16;
1779
1780 if i != len {
1782 #[inline(never)]
1784 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
1785 let mut s: u32 = 0;
1786 for i in from..x.len() {
1787 let ix = unsafe { x.get_unchecked(i) } as u32;
1789 let iy = unsafe { y.get_unchecked(i) } as u32;
1791 s += ix * iy;
1792 }
1793 s
1794 }
1795 s += fallback(x, y, i);
1796 }
1797
1798 Ok(MV::new(s))
1799 }
1800}
1801
1802#[cfg(target_arch = "aarch64")]
1803impl
1804 Target2<
1805 diskann_wide::arch::aarch64::Neon,
1806 MathematicalResult<u32>,
1807 USlice<'_, 4>,
1808 USlice<'_, 4>,
1809 > for InnerProduct
1810{
1811 #[inline(always)]
1812 fn run(
1813 self,
1814 arch: diskann_wide::arch::aarch64::Neon,
1815 x: USlice<'_, 4>,
1816 y: USlice<'_, 4>,
1817 ) -> MathematicalResult<u32> {
1818 let len = check_lengths!(x, y)?;
1819
1820 diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
1821 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
1822
1823 let px_u8: *const u8 = x.as_ptr().cast();
1824 let py_u8: *const u8 = y.as_ptr().cast();
1825
1826 let mut i = 0;
1827 let mut s: u32 = 0;
1828
1829 let bytes = len / 2;
1831 if i < bytes {
1832 let mut s0 = u32s::default(arch);
1833 let mut s1 = u32s::default(arch);
1834 let mask = u8s::splat(arch, 0x0f);
1835 while i + 16 <= bytes {
1836 let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
1839 let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
1842
1843 let first_x: u8s = x_vec & mask;
1846 let first_y: u8s = y_vec & mask;
1847 s0 = s0.dot_simd(first_x, first_y);
1848
1849 let second_x: u8s = (x_vec >> 4) & mask;
1851 let second_y: u8s = (y_vec >> 4) & mask;
1852 s1 = s1.dot_simd(second_x, second_y);
1853 i += 16;
1855 }
1856
1857 let remaining_bytes = len / 2 - i;
1858
1859 if remaining_bytes > 0 {
1860 let remaining_vec1 = remaining_bytes.min(16);
1861 let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_vec1) };
1865 let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_vec1) };
1868
1869 let first_x: u8s = x_vec & mask;
1870 let first_y: u8s = y_vec & mask;
1871 s0 = s0.dot_simd(first_x, first_y);
1872
1873 let second_x: u8s = (x_vec >> 4) & mask;
1875 let second_y: u8s = (y_vec >> 4) & mask;
1876 s1 = s1.dot_simd(second_x, second_y);
1877 i += remaining_bytes;
1878 }
1879 s = (s0 + s1).sum_tree();
1880 }
1881 i *= 2;
1883
1884 debug_assert!(len - i <= 1);
1886
1887 if i != len {
1888 let ix = unsafe { x.get_unchecked(i) } as i32;
1890 let iy = unsafe { y.get_unchecked(i) } as i32;
1892 s += (ix * iy) as u32;
1893 }
1894
1895 Ok(MV::new(s))
1896 }
1897}
1898
1899impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 1>, USlice<'_, 1>> for InnerProduct
1903where
1904 A: Architecture,
1905{
1906 #[inline(always)]
1907 fn run(self, _: A, x: USlice<'_, 1>, y: USlice<'_, 1>) -> MathematicalResult<u32> {
1908 bitvector_op::<Self, Unsigned>(x, y)
1909 }
1910}
1911
1912#[cfg(target_arch = "aarch64")]
1913impl
1914 Target2<
1915 diskann_wide::arch::aarch64::Neon,
1916 MathematicalResult<u32>,
1917 USlice<'_, 2>,
1918 USlice<'_, 2>,
1919 > for InnerProduct
1920{
1921 #[inline(always)]
1922 fn run(
1923 self,
1924 arch: diskann_wide::arch::aarch64::Neon,
1925 x: USlice<'_, 2>,
1926 y: USlice<'_, 2>,
1927 ) -> MathematicalResult<u32> {
1928 let len = check_lengths!(x, y)?;
1929
1930 diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
1931 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
1932
1933 let px_u8: *const u8 = x.as_ptr().cast();
1934 let py_u8: *const u8 = y.as_ptr().cast();
1935
1936 let mut i = 0;
1937 let mut s: u32 = 0;
1938
1939 let bytes = len / 4;
1941 if i < bytes {
1942 let mut s0 = u32s::default(arch);
1943 let mut s1 = u32s::default(arch);
1944 let mut s2 = u32s::default(arch);
1945 let mut s3 = u32s::default(arch);
1946 let mask = u8s::splat(arch, 0x03);
1947 while i + 16 <= bytes {
1948 let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
1950 let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
1952
1953 let first_x: u8s = x_vec & mask;
1954 let first_y: u8s = y_vec & mask;
1955 s0 = s0.dot_simd(first_x, first_y);
1956
1957 let second_x: u8s = (x_vec >> 2) & mask;
1958 let second_y: u8s = (y_vec >> 2) & mask;
1959 s1 = s1.dot_simd(second_x, second_y);
1960
1961 let third_x: u8s = (x_vec >> 4) & mask;
1962 let third_y: u8s = (y_vec >> 4) & mask;
1963 s2 = s2.dot_simd(third_x, third_y);
1964
1965 let fourth_x: u8s = (x_vec >> 6) & mask;
1966 let fourth_y: u8s = (y_vec >> 6) & mask;
1967 s3 = s3.dot_simd(fourth_x, fourth_y);
1968 i += 16;
1969 }
1970
1971 let remaining_bytes = bytes - i;
1972 if remaining_bytes > 0 {
1973 let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_bytes) };
1975 let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_bytes) };
1977
1978 let first_x: u8s = x_vec & mask;
1979 let first_y: u8s = y_vec & mask;
1980 s0 = s0.dot_simd(first_x, first_y);
1981
1982 let second_x: u8s = (x_vec >> 2) & mask;
1983 let second_y: u8s = (y_vec >> 2) & mask;
1984 s1 = s1.dot_simd(second_x, second_y);
1985
1986 let third_x: u8s = (x_vec >> 4) & mask;
1987 let third_y: u8s = (y_vec >> 4) & mask;
1988 s2 = s2.dot_simd(third_x, third_y);
1989
1990 let fourth_x: u8s = (x_vec >> 6) & mask;
1991 let fourth_y: u8s = (y_vec >> 6) & mask;
1992 s3 = s3.dot_simd(fourth_x, fourth_y);
1993 i += remaining_bytes;
1994 }
1995
1996 s = ((s0 + s1) + (s2 + s3)).sum_tree();
1997 }
1998
1999 i *= 4;
2001
2002 debug_assert!(len - i <= 3);
2004 if i != len {
2005 #[inline(never)]
2006 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
2007 let mut s: i32 = 0;
2008 for i in from..x.len() {
2009 let ix = unsafe { x.get_unchecked(i) } as i32;
2011 let iy = unsafe { y.get_unchecked(i) } as i32;
2013 s += ix * iy;
2014 }
2015 s as u32
2016 }
2017 s += fallback(x, y, i);
2018 }
2019
2020 Ok(MV::new(s))
2021 }
2022}
2023
2024macro_rules! impl_fallback_ip {
2026 (($N:literal, $M:literal)) => {
2027 impl Target2<diskann_wide::arch::Scalar, MathematicalResult<u32>, USlice<'_, $N>, USlice<'_, $M>> for InnerProduct {
2035 #[inline(never)]
2036 fn run(
2037 self,
2038 _: diskann_wide::arch::Scalar,
2039 x: USlice<'_, $N>,
2040 y: USlice<'_, $M>
2041 ) -> MathematicalResult<u32> {
2042 let len = check_lengths!(x, y)?;
2043
2044 let mut accum: u32 = 0;
2045 for i in 0..len {
2046 let ix = unsafe { x.get_unchecked(i) } as u32;
2048 let iy = unsafe { y.get_unchecked(i) } as u32;
2050 accum += ix * iy;
2051 }
2052 Ok(MV::new(accum))
2053 }
2054 }
2055 };
2056 ($N:literal) => {
2057 impl_fallback_ip!(($N, $N));
2058 };
2059 ($($args:tt),+ $(,)?) => {
2060 $(impl_fallback_ip!($args);)+
2061 };
2062}
2063
2064impl_fallback_ip!(7, 6, 5, 4, 3, 2, (8, 4), (8, 2), (8, 1));
2065
2066#[cfg(target_arch = "x86_64")]
2067retarget!(diskann_wide::arch::x86_64::V3, InnerProduct, 7, 6, 5, 3);
2068
2069#[cfg(target_arch = "x86_64")]
2070retarget!(
2071 diskann_wide::arch::x86_64::V4,
2072 InnerProduct,
2073 7,
2074 6,
2075 5,
2076 3,
2077 (8, 4),
2078 (8, 2),
2079 (8, 1)
2080);
2081
2082dispatch_pure!(
2083 InnerProduct,
2084 1,
2085 2,
2086 3,
2087 4,
2088 5,
2089 6,
2090 7,
2091 (8, 8),
2092 (8, 4),
2093 (8, 2),
2094 (8, 1)
2095);
2096
2097#[cfg(target_arch = "x86_64")]
2101impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 4>>
2102 for InnerProduct
2103{
2104 #[inline(always)]
2116 fn run(
2117 self,
2118 arch: diskann_wide::arch::x86_64::V3,
2119 x: USlice<'_, 8>,
2120 y: USlice<'_, 4>,
2121 ) -> MathematicalResult<u32> {
2122 use std::arch::x86_64::_mm256_maddubs_epi16;
2123
2124 let len = check_lengths!(x, y)?;
2125
2126 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
2127 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::x86_64::V3>::u8x16);
2128 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
2129 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
2130
2131 let px: *const u8 = x.as_ptr();
2132 let py: *const u8 = y.as_ptr();
2133
2134 let mut i: usize = 0;
2135 let mut s: u32 = 0;
2136
2137 #[inline(always)]
2138 fn unpack_half(input: u8s_16) -> u8s_32 {
2139 let combined = diskann_wide::LoHi::new(input, input >> 4).zip::<u8s_32>();
2140 combined & u8s_32::splat(input.arch(), (1u8 << 4) - 1)
2141 }
2142
2143 let blocks = len / 32;
2145 if blocks > 0 {
2146 let mut acc = i32s::default(arch);
2147
2148 let products = |x: u8s_32, y: u8s_32| -> i16s {
2149 i16s::from_underlying(arch, unsafe {
2151 _mm256_maddubs_epi16(x.to_underlying(), y.to_underlying())
2152 })
2153 };
2154
2155 let ones = i16s::splat(arch, 1);
2156
2157 while i + 4 <= blocks {
2163 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * i)) };
2167 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * i)) };
2169 let m0 = products(vx, unpack_half(vy));
2170
2171 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 1))) };
2174 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 1))) };
2176 let m1 = products(vx, unpack_half(vy));
2177
2178 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 2))) };
2181 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 2))) };
2183 let m2 = products(vx, unpack_half(vy));
2184
2185 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 3))) };
2188 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 3))) };
2190 let m3 = products(vx, unpack_half(vy));
2191
2192 acc = acc.dot_simd(m0 + m1 + m2 + m3, ones);
2193 i += 4;
2194 }
2195
2196 while i < blocks {
2198 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * i)) };
2201 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * i)) };
2203 acc = acc.dot_simd(products(vx, unpack_half(vy)), ones);
2204 i += 1;
2205 }
2206
2207 s = acc.sum_tree() as u32;
2208 }
2209
2210 i *= 32;
2212
2213 if i != len {
2215 #[inline(never)]
2216 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 4>, from: usize) -> u32 {
2217 let mut s: u32 = 0;
2218 for i in from..x.len() {
2219 let ix = unsafe { x.get_unchecked(i) } as u32;
2221 let iy = unsafe { y.get_unchecked(i) } as u32;
2223 s += ix * iy;
2224 }
2225 s
2226 }
2227 s += fallback(x, y, i);
2228 }
2229
2230 Ok(MV::new(s))
2231 }
2232}
2233
2234#[cfg(target_arch = "x86_64")]
2235impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 2>>
2236 for InnerProduct
2237{
2238 #[inline(always)]
2251 fn run(
2252 self,
2253 arch: diskann_wide::arch::x86_64::V3,
2254 x: USlice<'_, 8>,
2255 y: USlice<'_, 2>,
2256 ) -> MathematicalResult<u32> {
2257 use diskann_wide::SplitJoin;
2258 use std::arch::x86_64::_mm256_maddubs_epi16;
2259
2260 let len = check_lengths!(x, y)?;
2261
2262 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
2263 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::x86_64::V3>::u8x16);
2264 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
2265 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
2266
2267 let px: *const u8 = x.as_ptr();
2268 let py: *const u8 = y.as_ptr();
2269
2270 let mut i: usize = 0;
2271 let mut s: u32 = 0;
2272
2273 let blocks = len / 64;
2275 if blocks > 0 {
2276 let mut acc = i32s::default(arch);
2277
2278 let products = |x: u8s_32, y: u8s_32| -> i16s {
2279 i16s::from_underlying(arch, unsafe {
2281 _mm256_maddubs_epi16(x.to_underlying(), y.to_underlying())
2282 })
2283 };
2284
2285 #[inline(always)]
2286 fn unpack_sub<const N: u8>(input: u8s_16) -> u8s_32 {
2287 let combined = diskann_wide::LoHi::new(input, input >> N).zip::<u8s_32>();
2288 combined & u8s_32::splat(input.arch(), (1u8 << N) - 1)
2289 }
2290
2291 let unpack_crumbs = |x: u8s_16| -> (u8s_32, u8s_32) {
2292 let nibbles = unpack_sub::<4>(x);
2294
2295 let diskann_wide::LoHi { lo, hi } = nibbles.split();
2298 let lower = unpack_sub::<2>(lo);
2299 let upper = unpack_sub::<2>(hi);
2300
2301 (lower, upper)
2302 };
2303
2304 let ones = i16s::splat(arch, 1);
2305
2306 while i + 4 <= blocks {
2313 let (vx0, vx1, (vy0, vy1)) = unsafe {
2317 (
2318 u8s_32::load_simd(arch, px.add(64 * i)),
2319 u8s_32::load_simd(arch, px.add(64 * i + 32)),
2320 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * i))),
2321 )
2322 };
2323 let m0a = products(vx0, vy0);
2324 let m0b = products(vx1, vy1);
2325
2326 let (vx0, vx1, (vy0, vy1)) = unsafe {
2329 (
2330 u8s_32::load_simd(arch, px.add(64 * (i + 1))),
2331 u8s_32::load_simd(arch, px.add(64 * (i + 1) + 32)),
2332 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 1)))),
2333 )
2334 };
2335 let m1a = products(vx0, vy0);
2336 let m1b = products(vx1, vy1);
2337
2338 let (vx0, vx1, (vy0, vy1)) = unsafe {
2341 (
2342 u8s_32::load_simd(arch, px.add(64 * (i + 2))),
2343 u8s_32::load_simd(arch, px.add(64 * (i + 2) + 32)),
2344 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 2)))),
2345 )
2346 };
2347 let m2a = products(vx0, vy0);
2348 let m2b = products(vx1, vy1);
2349
2350 let (vx0, vx1, (vy0, vy1)) = unsafe {
2353 (
2354 u8s_32::load_simd(arch, px.add(64 * (i + 3))),
2355 u8s_32::load_simd(arch, px.add(64 * (i + 3) + 32)),
2356 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 3)))),
2357 )
2358 };
2359 let m3a = products(vx0, vy0);
2360 let m3b = products(vx1, vy1);
2361
2362 acc = acc.dot_simd((m0a + m0b + m1a + m1b) + (m2a + m2b + m3a + m3b), ones);
2363 i += 4;
2364 }
2365
2366 while i < blocks {
2368 let (vx0, vx1, (vy0, vy1)) = unsafe {
2371 (
2372 u8s_32::load_simd(arch, px.add(64 * i)),
2373 u8s_32::load_simd(arch, px.add(64 * i + 32)),
2374 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * i))),
2375 )
2376 };
2377 acc = acc.dot_simd(products(vx0, vy0) + products(vx1, vy1), ones);
2378 i += 1;
2379 }
2380
2381 s = acc.sum_tree() as u32;
2382 }
2383
2384 i *= 64;
2386
2387 if i != len {
2389 #[inline(never)]
2390 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 2>, from: usize) -> u32 {
2391 let mut s: u32 = 0;
2392 for i in from..x.len() {
2393 let (ix, iy) =
2395 unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2396 s += ix * iy;
2397 }
2398 s
2399 }
2400 s += fallback(x, y, i);
2401 }
2402
2403 Ok(MV::new(s))
2404 }
2405}
2406
2407#[cfg(target_arch = "x86_64")]
2408impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 1>>
2409 for InnerProduct
2410{
2411 #[inline(always)]
2425 fn run(
2426 self,
2427 arch: diskann_wide::arch::x86_64::V3,
2428 x: USlice<'_, 8>,
2429 y: USlice<'_, 1>,
2430 ) -> MathematicalResult<u32> {
2431 use diskann_wide::{FromInt, SIMDMask};
2432 use std::arch::x86_64::_mm256_sad_epu8;
2433
2434 let len = check_lengths!(x, y)?;
2435
2436 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
2437 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
2438
2439 type Mask32 = diskann_wide::BitMask<32, diskann_wide::arch::x86_64::V3>;
2440 type Mask8x32 = diskann_wide::arch::x86_64::v3::masks::mask8x32;
2441
2442 let px: *const u8 = x.as_ptr();
2443 let py: *const u8 = y.as_ptr();
2444
2445 let mut i: usize = 0;
2446 let mut s: u32 = 0;
2447
2448 let blocks = len / 32;
2450 if blocks > 0 {
2451 let mut acc = i32s::default(arch);
2452 let zero = u8s_32::default(arch);
2453
2454 let masked_sad = |vx: u8s_32, bits: u32| -> i32s {
2460 let byte_mask: Mask8x32 = Mask32::from_int(arch, bits).into();
2462
2463 let masked = vx & u8s_32::from_underlying(arch, byte_mask.to_underlying());
2465
2466 i32s::from_underlying(arch, unsafe {
2469 _mm256_sad_epu8(masked.to_underlying(), zero.to_underlying())
2470 })
2471 };
2472
2473 while i + 4 <= blocks {
2475 let s0 = unsafe {
2478 let vx = u8s_32::load_simd(arch, px.add(32 * i));
2479 let bits = (py.add(4 * i) as *const u32).read_unaligned();
2480 masked_sad(vx, bits)
2481 };
2482
2483 let s1 = unsafe {
2485 let vx = u8s_32::load_simd(arch, px.add(32 * (i + 1)));
2486 let bits = (py.add(4 * (i + 1)) as *const u32).read_unaligned();
2487 masked_sad(vx, bits)
2488 };
2489
2490 let s2 = unsafe {
2492 let vx = u8s_32::load_simd(arch, px.add(32 * (i + 2)));
2493 let bits = (py.add(4 * (i + 2)) as *const u32).read_unaligned();
2494 masked_sad(vx, bits)
2495 };
2496
2497 let s3 = unsafe {
2499 let vx = u8s_32::load_simd(arch, px.add(32 * (i + 3)));
2500 let bits = (py.add(4 * (i + 3)) as *const u32).read_unaligned();
2501 masked_sad(vx, bits)
2502 };
2503
2504 acc = acc + s0 + s1 + s2 + s3;
2505 i += 4;
2506 }
2507
2508 while i < blocks {
2510 let si = unsafe {
2513 let vx = u8s_32::load_simd(arch, px.add(32 * i));
2514 let bits = (py.add(4 * i) as *const u32).read_unaligned();
2515 masked_sad(vx, bits)
2516 };
2517 acc = acc + si;
2518 i += 1;
2519 }
2520
2521 s = acc.sum_tree() as u32;
2522 }
2523
2524 i *= 32;
2526
2527 if i != len {
2529 #[inline(never)]
2530 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 1>, from: usize) -> u32 {
2531 let mut s: u32 = 0;
2532 for i in from..x.len() {
2533 let (ix, iy) =
2535 unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2536 s += ix * iy;
2537 }
2538 s
2539 }
2540 s += fallback(x, y, i);
2541 }
2542
2543 Ok(MV::new(s))
2544 }
2545}
2546
2547#[cfg(target_arch = "aarch64")]
2548impl
2549 Target2<
2550 diskann_wide::arch::aarch64::Neon,
2551 MathematicalResult<u32>,
2552 USlice<'_, 8>,
2553 USlice<'_, 4>,
2554 > for InnerProduct
2555{
2556 #[inline(always)]
2557 fn run(
2558 self,
2559 arch: diskann_wide::arch::aarch64::Neon,
2560 x: USlice<'_, 8>,
2561 y: USlice<'_, 4>,
2562 ) -> MathematicalResult<u32> {
2563 let len = check_lengths!(x, y)?;
2565
2566 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::aarch64::Neon>::u8x16);
2567 #[expect(non_camel_case_types)]
2568 type u8s_8 = diskann_wide::arch::aarch64::u8x8;
2569 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
2570
2571 let px_u8: *const u8 = x.as_ptr().cast();
2572 let py_u8: *const u8 = y.as_ptr().cast();
2573
2574 let mut i = 0;
2575 let mut s: u32 = 0;
2576
2577 let y_bytes = len / 2;
2579 if i < y_bytes {
2580 let mut s0 = u32s::default(arch);
2581
2582 #[inline(always)]
2583 fn split_and_zip(input: u8s_8, arch: diskann_wide::arch::aarch64::Neon) -> u8s_16 {
2584 use diskann_wide::{LoHi, ZipUnzip};
2585 let lo = input;
2586 let hi = input >> 4;
2587 let halves = LoHi::new(lo, hi);
2588 let zipped_halves = u8s_16::zip(halves);
2589 zipped_halves & u8s_16::splat(arch, 0x0f)
2590 }
2591
2592 while i + 8 <= y_bytes {
2593 let y_vec = split_and_zip(unsafe { u8s_8::load_simd(arch, py_u8.add(i)) }, arch);
2595 let x_vec = unsafe { u8s_16::load_simd(arch, px_u8.add(i * 2)) };
2598
2599 s0 = s0.dot_simd(x_vec, y_vec);
2601
2602 i += 8;
2604 }
2605
2606 let remaining_bytes = len / 2 - i;
2607
2608 if remaining_bytes > 0 {
2609 let x_vec =
2611 unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 2), remaining_bytes * 2) };
2612
2613 let y_vec = split_and_zip(
2615 unsafe { u8s_8::load_simd_first(arch, py_u8.add(i), remaining_bytes) },
2616 arch,
2617 );
2618
2619 s0 = s0.dot_simd(x_vec, y_vec);
2620 i += remaining_bytes;
2621 }
2622 s = s0.sum_tree();
2623 }
2624
2625 i *= 2;
2627
2628 debug_assert!(len - i <= 1);
2630 if i != len {
2631 let ix = unsafe { x.get_unchecked(i) } as u32;
2633 let iy = unsafe { y.get_unchecked(i) } as u32;
2635 s += ix * iy;
2636 }
2637
2638 Ok(MV::new(s))
2639 }
2640}
2641
2642#[cfg(target_arch = "aarch64")]
2643impl
2644 Target2<
2645 diskann_wide::arch::aarch64::Neon,
2646 MathematicalResult<u32>,
2647 USlice<'_, 8>,
2648 USlice<'_, 2>,
2649 > for InnerProduct
2650{
2651 #[inline(always)]
2652 fn run(
2653 self,
2654 arch: diskann_wide::arch::aarch64::Neon,
2655 x: USlice<'_, 8>,
2656 y: USlice<'_, 2>,
2657 ) -> MathematicalResult<u32> {
2658 let len = check_lengths!(x, y)?;
2659
2660 #[expect(non_camel_case_types)]
2661 type u8s_8 = diskann_wide::arch::aarch64::u8x8;
2662 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::aarch64::Neon>::u8x16);
2663 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::aarch64::Neon>::u8x32);
2664 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
2665
2666 let px_u8: *const u8 = x.as_ptr().cast();
2667 let py_u8: *const u8 = y.as_ptr().cast();
2668
2669 let mut i = 0;
2670 let mut s: u32 = 0;
2671
2672 #[inline(always)]
2673 fn split_and_zip_four_bit(input: u8s_8, arch: diskann_wide::arch::aarch64::Neon) -> u8s_16 {
2674 use diskann_wide::{LoHi, ZipUnzip};
2675 let lo = input;
2676 let hi = input >> 4;
2677 let halves = LoHi::new(lo, hi);
2678 let zipped_halves = u8s_16::zip(halves);
2679 zipped_halves & u8s_16::splat(arch, 0x0f)
2680 }
2681
2682 #[inline(always)]
2683 fn split_and_zip_two_bits(
2684 input: u8s_8,
2685 arch: diskann_wide::arch::aarch64::Neon,
2686 ) -> (u8s_16, u8s_16) {
2687 use diskann_wide::{LoHi, SplitJoin, ZipUnzip};
2688 let four_bit_split = split_and_zip_four_bit(input, arch);
2689 let lo = four_bit_split;
2690 let hi = four_bit_split >> 2;
2691 let zipped = u8s_32::zip(LoHi::new(lo, hi));
2692 let LoHi { lo, hi } = zipped.split();
2693 let mask = u8s_16::splat(arch, 0x03);
2694 (lo & mask, hi & mask)
2695 }
2696
2697 let y_bytes = len / 4; if i < y_bytes {
2699 let mut s0 = u32s::default(arch);
2700 let mut s1 = u32s::default(arch);
2701
2702 while i + 8 <= y_bytes {
2703 let (y_vec1, y_vec2) =
2705 split_and_zip_two_bits(unsafe { u8s_8::load_simd(arch, py_u8.add(i)) }, arch);
2706
2707 let x_vec1 = unsafe { u8s_16::load_simd(arch, px_u8.add(4 * i)) };
2710 let x_vec2 = unsafe { u8s_16::load_simd(arch, px_u8.add(4 * i + 16)) };
2712
2713 s0 = s0.dot_simd(x_vec1, y_vec1);
2715
2716 s1 = s1.dot_simd(x_vec2, y_vec2);
2718
2719 i += 8;
2721 }
2722
2723 let remaining_y_bytes = len / 4 - i;
2724 let remaining_x_bytes = remaining_y_bytes * 4;
2725
2726 if remaining_y_bytes > 0 {
2727 let (y_vec1, y_vec2) = split_and_zip_two_bits(
2729 unsafe { u8s_8::load_simd_first(arch, py_u8.add(i), remaining_y_bytes) },
2730 arch,
2731 );
2732
2733 let x_first = remaining_x_bytes.min(16);
2736 let x_second = remaining_x_bytes.saturating_sub(16);
2737
2738 let x_vec1 = unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 4), x_first) };
2740 s0 = s0.dot_simd(x_vec1, y_vec1);
2741
2742 if x_second > 0 {
2743 let x_vec2 =
2745 unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 4 + 16), x_second) };
2746 s1 = s1.dot_simd(x_vec2, y_vec2);
2747 }
2748 i += remaining_y_bytes;
2749 }
2750 s = (s0 + s1).sum_tree();
2751 }
2752
2753 i *= 4;
2755
2756 debug_assert!(len - i <= 3);
2758 if i != len {
2759 #[inline(never)]
2760 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 2>, from: usize) -> u32 {
2761 let mut s: u32 = 0;
2762 for i in from..x.len() {
2763 let (ix, iy) =
2765 unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2766 s += ix * iy;
2767 }
2768 s
2769 }
2770 s += fallback(x, y, i)
2771 }
2772
2773 Ok(MV::new(s))
2774 }
2775}
2776
2777#[cfg(target_arch = "aarch64")]
2778impl
2779 Target2<
2780 diskann_wide::arch::aarch64::Neon,
2781 MathematicalResult<u32>,
2782 USlice<'_, 8>,
2783 USlice<'_, 1>,
2784 > for InnerProduct
2785{
2786 #[inline(always)]
2787 fn run(
2788 self,
2789 arch: diskann_wide::arch::aarch64::Neon,
2790 x: USlice<'_, 8>,
2791 y: USlice<'_, 1>,
2792 ) -> MathematicalResult<u32> {
2793 let len = check_lengths!(x, y)?;
2795
2796 #[expect(non_camel_case_types)]
2797 type u8s_8 = diskann_wide::arch::aarch64::u8x8;
2798 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::aarch64::Neon>::u8x16);
2799 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::aarch64::Neon>::u8x32);
2800 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
2801
2802 let px_u8: *const u8 = x.as_ptr().cast();
2803 let py_u8: *const u8 = y.as_ptr().cast();
2804
2805 let mut i = 0;
2806 let mut s: u32 = 0;
2807
2808 #[inline(always)]
2809 fn split_and_zip_four_bits(
2810 input: u8s_8,
2811 arch: diskann_wide::arch::aarch64::Neon,
2812 ) -> u8s_16 {
2813 use diskann_wide::{LoHi, ZipUnzip};
2814 let lo = input;
2815 let hi = input >> 4;
2816 let halves = LoHi::new(lo, hi);
2817 let zipped_halves = u8s_16::zip(halves);
2818 zipped_halves & u8s_16::splat(arch, 0x0f)
2819 }
2820
2821 #[inline(always)]
2822 fn split_and_zip_two_bits(
2823 input: u8s_8,
2824 arch: diskann_wide::arch::aarch64::Neon,
2825 ) -> (u8s_16, u8s_16) {
2826 use diskann_wide::{LoHi, SplitJoin, ZipUnzip};
2827 let four_bit_split = split_and_zip_four_bits(input, arch);
2828 let lo = four_bit_split;
2829 let hi = four_bit_split >> 2;
2830 let zipped = u8s_32::zip(LoHi::new(lo, hi));
2831 let LoHi { lo, hi } = zipped.split();
2832 let mask = u8s_16::splat(arch, 0x03);
2833 (lo & mask, hi & mask)
2834 }
2835
2836 #[inline(always)]
2837 fn split_and_zip_one_bit(
2838 input: u8s_8,
2839 arch: diskann_wide::arch::aarch64::Neon,
2840 ) -> (u8s_16, u8s_16, u8s_16, u8s_16) {
2841 use diskann_wide::{LoHi, SplitJoin, ZipUnzip};
2842 let (lower_two_bits, upper_two_bits) = split_and_zip_two_bits(input, arch);
2843 let vec1_lo = lower_two_bits;
2844 let vec1_hi = lower_two_bits >> 1;
2845 let vec2_lo = upper_two_bits;
2846 let vec2_hi = upper_two_bits >> 1;
2847 let vec1_zipped = u8s_32::zip(LoHi::new(vec1_lo, vec1_hi));
2848 let vec2_zipped = u8s_32::zip(LoHi::new(vec2_lo, vec2_hi));
2849 let LoHi {
2850 lo: vec1_lo,
2851 hi: vec1_hi,
2852 } = vec1_zipped.split();
2853 let LoHi {
2854 lo: vec2_lo,
2855 hi: vec2_hi,
2856 } = vec2_zipped.split();
2857 let mask = u8s_16::splat(arch, 0x01);
2858 (
2859 vec1_lo & mask,
2860 vec1_hi & mask,
2861 vec2_lo & mask,
2862 vec2_hi & mask,
2863 )
2864 }
2865
2866 let y_bytes = len / 8; if i < y_bytes {
2868 let mut s0 = u32s::default(arch);
2869 let mut s1 = u32s::default(arch);
2870 let mut s2 = u32s::default(arch);
2871 let mut s3 = u32s::default(arch);
2872
2873 while i + 8 <= y_bytes {
2874 let (y_vec1, y_vec2, y_vec3, y_vec4) =
2876 split_and_zip_one_bit(unsafe { u8s_8::load_simd(arch, py_u8.add(i)) }, arch);
2877
2878 let x_vec1 = unsafe { u8s_16::load_simd(arch, px_u8.add(8 * i)) };
2881 let x_vec2 = unsafe { u8s_16::load_simd(arch, px_u8.add(8 * i + 16)) };
2883 let x_vec3 = unsafe { u8s_16::load_simd(arch, px_u8.add(8 * i + 32)) };
2885 let x_vec4 = unsafe { u8s_16::load_simd(arch, px_u8.add(8 * i + 48)) };
2887
2888 s0 = s0.dot_simd(x_vec1, y_vec1);
2890
2891 s1 = s1.dot_simd(x_vec2, y_vec2);
2893
2894 s2 = s2.dot_simd(x_vec3, y_vec3);
2896
2897 s3 = s3.dot_simd(x_vec4, y_vec4);
2899
2900 i += 8;
2902 }
2903
2904 let remaining_y_bytes = len / 8 - i;
2905 let remaining_x_bytes = remaining_y_bytes * 8;
2906
2907 if remaining_y_bytes > 0 {
2908 let (y_vec1, y_vec2, y_vec3, y_vec4) = split_and_zip_one_bit(
2910 unsafe { u8s_8::load_simd_first(arch, py_u8.add(i), remaining_y_bytes) },
2911 arch,
2912 );
2913
2914 let x_first = remaining_x_bytes.min(16);
2917 let x_second = remaining_x_bytes.saturating_sub(16).min(16);
2918 let x_third = remaining_x_bytes.saturating_sub(32).min(16);
2919 let x_fourth = remaining_x_bytes.saturating_sub(48).min(16);
2920
2921 let x_vec1 = unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 8), x_first) };
2923 s0 = s0.dot_simd(x_vec1, y_vec1);
2924
2925 if x_second > 0 {
2926 let x_vec2 =
2928 unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 8 + 16), x_second) };
2929 s1 = s1.dot_simd(x_vec2, y_vec2);
2930 }
2931
2932 if x_third > 0 {
2933 let x_vec3 =
2935 unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 8 + 32), x_third) };
2936 s2 = s2.dot_simd(x_vec3, y_vec3);
2937 }
2938
2939 if x_fourth > 0 {
2940 let x_vec4 =
2942 unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 8 + 48), x_fourth) };
2943 s3 = s3.dot_simd(x_vec4, y_vec4);
2944 }
2945
2946 i += remaining_y_bytes;
2947 }
2948 s = ((s0 + s1) + (s2 + s3)).sum_tree();
2949 }
2950
2951 i *= 8;
2953
2954 debug_assert!(len - i <= 7);
2956 if i != len {
2957 #[inline(never)]
2958 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 1>, from: usize) -> u32 {
2959 let mut s: u32 = 0;
2960 for i in from..x.len() {
2961 let (ix, iy) =
2963 unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2964 s += ix * iy;
2965 }
2966 s
2967 }
2968 s += fallback(x, y, i)
2969 }
2970
2971 Ok(MV::new(s))
2972 }
2973}
2974
2975#[cfg(target_arch = "aarch64")]
2976retarget!(diskann_wide::arch::aarch64::Neon, InnerProduct, 7, 6, 5, 3);
2977
2978impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 4, BitTranspose>, USlice<'_, 1, Dense>>
3007 for InnerProduct
3008where
3009 A: Architecture,
3010{
3011 #[inline(always)]
3012 fn run(
3013 self,
3014 _: A,
3015 x: USlice<'_, 4, BitTranspose>,
3016 y: USlice<'_, 1, Dense>,
3017 ) -> MathematicalResult<u32> {
3018 let len = check_lengths!(x, y)?;
3019
3020 let px: *const u64 = x.as_ptr().cast();
3027 let py: *const u64 = y.as_ptr().cast();
3028
3029 let mut i = 0;
3030 let mut s: u32 = 0;
3031
3032 let blocks = len / 64;
3033 while i < blocks {
3034 let bits = unsafe { py.add(i).read_unaligned() };
3036
3037 let b0 = unsafe { px.add(4 * i).read_unaligned() };
3043 s += (bits & b0).count_ones();
3044
3045 let b1 = unsafe { px.add(4 * i + 1).read_unaligned() };
3047 s += (bits & b1).count_ones() << 1;
3048
3049 let b2 = unsafe { px.add(4 * i + 2).read_unaligned() };
3051 s += (bits & b2).count_ones() << 2;
3052
3053 let b3 = unsafe { px.add(4 * i + 3).read_unaligned() };
3055 s += (bits & b3).count_ones() << 3;
3056
3057 i += 1;
3058 }
3059
3060 if 64 * i == len {
3062 return Ok(MV::new(s));
3063 }
3064
3065 let k = i * 8;
3067
3068 let py = unsafe { py.cast::<u8>().add(k) };
3073 let bytes_remaining = y.bytes() - k;
3074 let mut bits: u64 = 0;
3075
3076 for j in 0..bytes_remaining.min(8) {
3079 bits += (unsafe { py.add(j).read() } as u64) << (8 * j);
3082 }
3083
3084 bits &= (0x01u64 << (len - (64 * i))) - 1;
3087
3088 let b0 = unsafe { px.add(4 * i).read_unaligned() };
3093 s += (bits & b0).count_ones();
3094
3095 let b1 = unsafe { px.add(4 * i + 1).read_unaligned() };
3097 s += (bits & b1).count_ones() << 1;
3098
3099 let b2 = unsafe { px.add(4 * i + 2).read_unaligned() };
3101 s += (bits & b2).count_ones() << 2;
3102
3103 let b3 = unsafe { px.add(4 * i + 3).read_unaligned() };
3105 s += (bits & b3).count_ones() << 3;
3106
3107 Ok(MV::new(s))
3108 }
3109}
3110
3111impl
3112 PureDistanceFunction<USlice<'_, 4, BitTranspose>, USlice<'_, 1, Dense>, MathematicalResult<u32>>
3113 for InnerProduct
3114{
3115 fn evaluate(
3116 x: USlice<'_, 4, BitTranspose>,
3117 y: USlice<'_, 1, Dense>,
3118 ) -> MathematicalResult<u32> {
3119 (diskann_wide::ARCH).run2(Self, x, y)
3120 }
3121}
3122
3123#[cfg(target_arch = "x86_64")]
3154impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 1>>
3155 for InnerProduct
3156{
3157 #[inline(always)]
3158 fn run(
3159 self,
3160 arch: diskann_wide::arch::x86_64::V3,
3161 x: &[f32],
3162 y: USlice<'_, 1>,
3163 ) -> MathematicalResult<f32> {
3164 let len = check_lengths!(x, y)?;
3165
3166 use std::arch::x86_64::*;
3167
3168 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
3169 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
3170
3171 let values = f32s::from_array(arch, [0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
3174
3175 let variable_shifts = u32s::from_array(arch, [0, 1, 2, 3, 4, 5, 6, 7]);
3177
3178 let px: *const f32 = x.as_ptr();
3179 let py: *const u32 = y.as_ptr().cast();
3180
3181 let mut i = 0;
3182 let mut s = f32s::default(arch);
3183
3184 let prep = |v: u32| -> u32s { u32s::splat(arch, v) >> variable_shifts };
3185 let to_f32 = |v: u32s| -> f32s {
3186 f32s::from_underlying(arch, unsafe {
3189 _mm256_permutevar_ps(values.to_underlying(), v.to_underlying())
3190 })
3191 };
3192
3193 let blocks = len / 32;
3195 if i < blocks {
3196 let mut s0 = f32s::default(arch);
3197 let mut s1 = f32s::default(arch);
3198
3199 while i < blocks {
3200 let iy = prep(unsafe { py.add(i).read_unaligned() });
3202
3203 let ix0 = unsafe { f32s::load_simd(arch, px.add(32 * i)) };
3205 let ix1 = unsafe { f32s::load_simd(arch, px.add(32 * i + 8)) };
3207 let ix2 = unsafe { f32s::load_simd(arch, px.add(32 * i + 16)) };
3209 let ix3 = unsafe { f32s::load_simd(arch, px.add(32 * i + 24)) };
3211
3212 s0 = ix0.mul_add_simd(to_f32(iy), s0);
3213 s1 = ix1.mul_add_simd(to_f32(iy >> 8), s1);
3214 s0 = ix2.mul_add_simd(to_f32(iy >> 16), s0);
3215 s1 = ix3.mul_add_simd(to_f32(iy >> 24), s1);
3216
3217 i += 1;
3218 }
3219 s = s0 + s1;
3220 }
3221
3222 let remainder = len % 32;
3223 if remainder != 0 {
3224 let tail = if len % 8 == 0 { 8 } else { len % 8 };
3225
3226 let py = unsafe { py.add(blocks) };
3229
3230 if remainder <= 8 {
3231 unsafe {
3234 load_one(py, |iy| {
3235 let iy = prep(iy);
3236 let ix = f32s::load_simd_first(arch, px.add(32 * blocks), tail);
3237 s = ix.mul_add_simd(to_f32(iy), s);
3238 })
3239 }
3240 } else if remainder <= 16 {
3241 unsafe {
3244 load_two(py, |iy| {
3245 let iy = prep(iy);
3246 let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
3247 let ix1 = f32s::load_simd_first(arch, px.add(32 * blocks + 8), tail);
3248 s = ix0.mul_add_simd(to_f32(iy), s);
3249 s = ix1.mul_add_simd(to_f32(iy >> 8), s);
3250 })
3251 }
3252 } else if remainder <= 24 {
3253 unsafe {
3256 load_three(py, |iy| {
3257 let iy = prep(iy);
3258
3259 let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
3260 let ix1 = f32s::load_simd(arch, px.add(32 * blocks + 8));
3261 let ix2 = f32s::load_simd_first(arch, px.add(32 * blocks + 16), tail);
3262
3263 s = ix0.mul_add_simd(to_f32(iy), s);
3264 s = ix1.mul_add_simd(to_f32(iy >> 8), s);
3265 s = ix2.mul_add_simd(to_f32(iy >> 16), s);
3266 })
3267 }
3268 } else {
3269 unsafe {
3272 load_four(py, |iy| {
3273 let iy = prep(iy);
3274
3275 let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
3276 let ix1 = f32s::load_simd(arch, px.add(32 * blocks + 8));
3277 let ix2 = f32s::load_simd(arch, px.add(32 * blocks + 16));
3278 let ix3 = f32s::load_simd_first(arch, px.add(32 * blocks + 24), tail);
3279
3280 s = ix0.mul_add_simd(to_f32(iy), s);
3281 s = ix1.mul_add_simd(to_f32(iy >> 8), s);
3282 s = ix2.mul_add_simd(to_f32(iy >> 16), s);
3283 s = ix3.mul_add_simd(to_f32(iy >> 24), s);
3284 })
3285 }
3286 }
3287 }
3288
3289 Ok(MV::new(s.sum_tree()))
3290 }
3291}
3292
3293#[cfg(target_arch = "x86_64")]
3297impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 2>>
3298 for InnerProduct
3299{
3300 #[inline(always)]
3301 fn run(
3302 self,
3303 arch: diskann_wide::arch::x86_64::V3,
3304 x: &[f32],
3305 y: USlice<'_, 2>,
3306 ) -> MathematicalResult<f32> {
3307 let len = check_lengths!(x, y)?;
3308
3309 use std::arch::x86_64::*;
3310
3311 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
3312 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
3313
3314 let values = f32s::from_array(arch, [0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0]);
3318
3319 let variable_shifts = u32s::from_array(arch, [0, 2, 4, 6, 8, 10, 12, 14]);
3321
3322 let px: *const f32 = x.as_ptr();
3323 let py: *const u32 = y.as_ptr().cast();
3324
3325 let mut i = 0;
3326 let mut s = f32s::default(arch);
3327
3328 let prep = |v: u32| -> u32s { u32s::splat(arch, v) >> variable_shifts };
3329 let to_f32 = |v: u32s| -> f32s {
3330 f32s::from_underlying(arch, unsafe {
3333 _mm256_permutevar_ps(values.to_underlying(), v.to_underlying())
3334 })
3335 };
3336
3337 let blocks = len / 16;
3338 if blocks != 0 {
3339 let mut s0 = f32s::default(arch);
3340 let mut s1 = f32s::default(arch);
3341
3342 while i + 2 <= blocks {
3344 let iy = prep(unsafe { py.add(i).read_unaligned() });
3347
3348 let (ix0, ix1) = unsafe {
3351 (
3352 f32s::load_simd(arch, px.add(16 * i)),
3353 f32s::load_simd(arch, px.add(16 * i + 8)),
3354 )
3355 };
3356
3357 s0 = ix0.mul_add_simd(to_f32(iy), s0);
3358 s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
3359
3360 let iy = prep(unsafe { py.add(i + 1).read_unaligned() });
3363
3364 let (ix0, ix1) = unsafe {
3366 (
3367 f32s::load_simd(arch, px.add(16 * (i + 1))),
3368 f32s::load_simd(arch, px.add(16 * (i + 1) + 8)),
3369 )
3370 };
3371
3372 s0 = ix0.mul_add_simd(to_f32(iy), s0);
3373 s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
3374
3375 i += 2;
3376 }
3377
3378 if i < blocks {
3380 let iy = prep(unsafe { py.add(i).read_unaligned() });
3383
3384 let (ix0, ix1) = unsafe {
3386 (
3387 f32s::load_simd(arch, px.add(16 * i)),
3388 f32s::load_simd(arch, px.add(16 * i + 8)),
3389 )
3390 };
3391
3392 s0 = ix0.mul_add_simd(to_f32(iy), s0);
3393 s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
3394 }
3395
3396 s = s0 + s1;
3397 }
3398
3399 let remainder = len % 16;
3400 if remainder != 0 {
3401 let tail = if len % 8 == 0 { 8 } else { len % 8 };
3402 let py = unsafe { py.add(blocks) };
3405
3406 if remainder <= 4 {
3407 unsafe {
3410 load_one(py, |iy| {
3411 let iy = prep(iy);
3412 let ix = f32s::load_simd_first(arch, px.add(16 * blocks), tail);
3413 s = ix.mul_add_simd(to_f32(iy), s);
3414 });
3415 }
3416 } else if remainder <= 8 {
3417 unsafe {
3420 load_two(py, |iy| {
3421 let iy = prep(iy);
3422 let ix = f32s::load_simd_first(arch, px.add(16 * blocks), tail);
3423 s = ix.mul_add_simd(to_f32(iy), s);
3424 });
3425 }
3426 } else if remainder <= 12 {
3427 unsafe {
3430 load_three(py, |iy| {
3431 let iy = prep(iy);
3432 let ix0 = f32s::load_simd(arch, px.add(16 * blocks));
3433 let ix1 = f32s::load_simd_first(arch, px.add(16 * blocks + 8), tail);
3434 s = ix0.mul_add_simd(to_f32(iy), s);
3435 s = ix1.mul_add_simd(to_f32(iy >> 16), s);
3436 });
3437 }
3438 } else {
3439 unsafe {
3442 load_four(py, |iy| {
3443 let iy = prep(iy);
3444 let ix0 = f32s::load_simd(arch, px.add(16 * blocks));
3445 let ix1 = f32s::load_simd_first(arch, px.add(16 * blocks + 8), tail);
3446 s = ix0.mul_add_simd(to_f32(iy), s);
3447 s = ix1.mul_add_simd(to_f32(iy >> 16), s);
3448 });
3449 }
3450 }
3451 }
3452
3453 Ok(MV::new(s.sum_tree()))
3454 }
3455}
3456
3457#[cfg(target_arch = "x86_64")]
3462impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 4>>
3463 for InnerProduct
3464{
3465 #[inline(always)]
3466 fn run(
3467 self,
3468 arch: diskann_wide::arch::x86_64::V3,
3469 x: &[f32],
3470 y: USlice<'_, 4>,
3471 ) -> MathematicalResult<f32> {
3472 let len = check_lengths!(x, y)?;
3473
3474 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
3475 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
3476
3477 let variable_shifts = i32s::from_array(arch, [0, 4, 8, 12, 16, 20, 24, 28]);
3478 let mask = i32s::splat(arch, 0x0f);
3479
3480 let to_f32 = |v: u32| -> f32s {
3481 ((i32s::splat(arch, v as i32) >> variable_shifts) & mask).simd_cast()
3482 };
3483
3484 let px: *const f32 = x.as_ptr();
3485 let py: *const u32 = y.as_ptr().cast();
3486
3487 let mut i = 0;
3488 let mut s = f32s::default(arch);
3489
3490 let blocks = len / 8;
3491 while i < blocks {
3492 let ix = unsafe { f32s::load_simd(arch, px.add(8 * i)) };
3494 let iy = to_f32(unsafe { py.add(i).read_unaligned() });
3496 s = ix.mul_add_simd(iy, s);
3497
3498 i += 1;
3499 }
3500
3501 let remainder = len % 8;
3502 if remainder != 0 {
3503 let f = |iy| {
3504 let ix = unsafe { f32s::load_simd_first(arch, px.add(8 * blocks), remainder) };
3508 s = ix.mul_add_simd(to_f32(iy), s);
3509 };
3510
3511 let py = unsafe { py.add(blocks) };
3514
3515 if remainder <= 2 {
3516 unsafe { load_one(py, f) };
3518 } else if remainder <= 4 {
3519 unsafe { load_two(py, f) };
3521 } else if remainder <= 6 {
3522 unsafe { load_three(py, f) };
3524 } else {
3525 unsafe { load_four(py, f) };
3527 }
3528 }
3529
3530 Ok(MV::new(s.sum_tree()))
3531 }
3532}
3533
3534impl<const N: usize>
3535 Target2<diskann_wide::arch::Scalar, MathematicalResult<f32>, &[f32], USlice<'_, N>>
3536 for InnerProduct
3537where
3538 Unsigned: Representation<N>,
3539{
3540 #[inline(always)]
3543 fn run(
3544 self,
3545 _: diskann_wide::arch::Scalar,
3546 x: &[f32],
3547 y: USlice<'_, N>,
3548 ) -> MathematicalResult<f32> {
3549 check_lengths!(x, y)?;
3550
3551 let mut s = 0.0;
3552 for (i, x) in x.iter().enumerate() {
3553 let y = unsafe { y.get_unchecked(i) } as f32;
3556 s += x * y;
3557 }
3558
3559 Ok(MV::new(s))
3560 }
3561}
3562
3563macro_rules! ip_retarget {
3565 ($arch:path, $N:literal) => {
3566 impl Target2<$arch, MathematicalResult<f32>, &[f32], USlice<'_, $N>>
3567 for InnerProduct
3568 {
3569 #[inline(always)]
3570 fn run(
3571 self,
3572 arch: $arch,
3573 x: &[f32],
3574 y: USlice<'_, $N>,
3575 ) -> MathematicalResult<f32> {
3576 self.run(arch.retarget(), x, y)
3577 }
3578 }
3579 };
3580 ($arch:path, $($Ns:literal),*) => {
3581 $(ip_retarget!($arch, $Ns);)*
3582 }
3583}
3584
3585#[cfg(target_arch = "x86_64")]
3586ip_retarget!(diskann_wide::arch::x86_64::V3, 3, 5, 6, 7, 8);
3587
3588#[cfg(target_arch = "x86_64")]
3589ip_retarget!(diskann_wide::arch::x86_64::V4, 1, 2, 3, 4, 5, 6, 7, 8);
3590
3591#[cfg(target_arch = "aarch64")]
3592ip_retarget!(diskann_wide::arch::aarch64::Neon, 1, 2, 3, 4, 5, 6, 7, 8);
3593
3594macro_rules! dispatch_full_ip {
3597 ($N:literal) => {
3598 impl PureDistanceFunction<&[f32], USlice<'_, $N>, MathematicalResult<f32>>
3602 for InnerProduct
3603 {
3604 fn evaluate(x: &[f32], y: USlice<'_, $N>) -> MathematicalResult<f32> {
3605 Self.run(ARCH, x, y)
3606 }
3607 }
3608 };
3609 ($($Ns:literal),*) => {
3610 $(dispatch_full_ip!($Ns);)*
3611 }
3612}
3613
3614dispatch_full_ip!(1, 2, 3, 4, 5, 6, 7, 8);
3615
3616#[cfg(test)]
3621mod tests {
3622 use std::{collections::HashMap, fmt::Display, sync::LazyLock};
3623
3624 use diskann_utils::{Reborrow, lazy_format};
3625 use rand::{
3626 Rng, SeedableRng,
3627 distr::{Distribution, Uniform},
3628 rngs::StdRng,
3629 seq::IndexedRandom,
3630 };
3631
3632 use super::*;
3633 use crate::bits::{BoxedBitSlice, Representation, Unsigned};
3634
3635 type MR = MathematicalResult<u32>;
3636
3637 #[inline(always)]
3638 fn should_check_this_dimension(dim: usize) -> bool {
3639 if cfg!(miri) {
3640 return dim.is_power_of_two()
3641 || (dim > 1 && (dim - 1).is_power_of_two())
3642 || (dim < 64 && (dim % 8 == 7));
3643 }
3644
3645 true
3646 }
3647
3648 fn test_bitslice_distances<const NBITS: usize, R>(
3658 dim_max: usize,
3659 trials_per_dim: usize,
3660 evaluate_l2: &dyn Fn(USlice<'_, NBITS>, USlice<'_, NBITS>) -> MR,
3661 evaluate_ip: &dyn Fn(USlice<'_, NBITS>, USlice<'_, NBITS>) -> MR,
3662 context: &str,
3663 rng: &mut R,
3664 ) where
3665 Unsigned: Representation<NBITS>,
3666 R: Rng,
3667 {
3668 let domain = Unsigned::domain_const::<NBITS>();
3669 let min: i64 = *domain.start();
3670 let max: i64 = *domain.end();
3671
3672 let dist = Uniform::new_inclusive(min, max).unwrap();
3673
3674 for dim in 0..dim_max {
3675 if !should_check_this_dimension(dim) {
3676 continue;
3677 }
3678
3679 let mut x_reference: Vec<u8> = vec![0; dim];
3680 let mut y_reference: Vec<u8> = vec![0; dim];
3681
3682 let mut x = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(dim);
3683 let mut y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(dim);
3684
3685 for trial in 0..trials_per_dim {
3686 x_reference
3687 .iter_mut()
3688 .for_each(|i| *i = dist.sample(rng).try_into().unwrap());
3689 y_reference
3690 .iter_mut()
3691 .for_each(|i| *i = dist.sample(rng).try_into().unwrap());
3692
3693 x.as_mut_slice().fill(u8::MAX);
3696 y.as_mut_slice().fill(u8::MAX);
3697
3698 for i in 0..dim {
3699 x.set(i, x_reference[i].into()).unwrap();
3700 y.set(i, y_reference[i].into()).unwrap();
3701 }
3702
3703 let expected: MV<f32> =
3705 diskann_vector::distance::SquaredL2::evaluate(&*x_reference, &*y_reference);
3706
3707 let got = evaluate_l2(x.reborrow(), y.reborrow()).unwrap();
3708
3709 assert_eq!(
3711 expected.into_inner(),
3712 got.into_inner() as f32,
3713 "failed SquaredL2 for NBITS = {}, dim = {}, trial = {} -- context {}",
3714 NBITS,
3715 dim,
3716 trial,
3717 context,
3718 );
3719
3720 let expected: MV<f32> =
3722 diskann_vector::distance::InnerProduct::evaluate(&*x_reference, &*y_reference);
3723
3724 let got = evaluate_ip(x.reborrow(), y.reborrow()).unwrap();
3725
3726 assert_eq!(
3728 expected.into_inner(),
3729 got.into_inner() as f32,
3730 "faild InnerProduct for NBITS = {}, dim = {}, trial = {} -- context {}",
3731 NBITS,
3732 dim,
3733 trial,
3734 context,
3735 );
3736 }
3737 }
3738
3739 let x = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(10);
3741 let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(11);
3742
3743 assert!(
3744 evaluate_l2(x.reborrow(), y.reborrow()).is_err(),
3745 "context: {}",
3746 context
3747 );
3748 assert!(
3749 evaluate_l2(y.reborrow(), x.reborrow()).is_err(),
3750 "context: {}",
3751 context
3752 );
3753
3754 assert!(
3755 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
3756 "context: {}",
3757 context
3758 );
3759 assert!(
3760 evaluate_ip(y.reborrow(), x.reborrow()).is_err(),
3761 "context: {}",
3762 context
3763 );
3764 }
3765
3766 cfg_if::cfg_if! {
3767 if #[cfg(miri)] {
3768 const MAX_DIM: usize = 132;
3769 const TRIALS_PER_DIM: usize = 1;
3770 } else {
3771 const MAX_DIM: usize = 256;
3772 const TRIALS_PER_DIM: usize = 20;
3773 }
3774 }
3775
3776 static BITSLICE_TEST_BOUNDS: LazyLock<HashMap<Key, Bounds>> = LazyLock::new(|| {
3786 use ArchKey::{Neon, Scalar, X86_64_V3, X86_64_V4};
3787 [
3788 (Key::new(1, Scalar), Bounds::new(64, 64)),
3789 (Key::new(1, X86_64_V3), Bounds::new(256, 256)),
3790 (Key::new(1, X86_64_V4), Bounds::new(256, 256)),
3791 (Key::new(1, Neon), Bounds::new(64, 64)),
3792 (Key::new(2, Scalar), Bounds::new(64, 64)),
3793 (Key::new(2, X86_64_V3), Bounds::new(512, 300)),
3795 (Key::new(2, X86_64_V4), Bounds::new(768, 600)), (Key::new(2, Neon), Bounds::new(256, 256)),
3797 (Key::new(3, Scalar), Bounds::new(64, 64)),
3798 (Key::new(3, X86_64_V3), Bounds::new(256, 96)),
3799 (Key::new(3, X86_64_V4), Bounds::new(256, 96)),
3800 (Key::new(3, Neon), Bounds::new(64, 64)),
3801 (Key::new(4, Scalar), Bounds::new(64, 64)),
3802 (Key::new(4, X86_64_V3), Bounds::new(256, 150)),
3804 (Key::new(4, X86_64_V4), Bounds::new(512, 300)),
3805 (Key::new(4, Neon), Bounds::new(128, 128)),
3806 (Key::new(5, Scalar), Bounds::new(64, 64)),
3807 (Key::new(5, X86_64_V3), Bounds::new(256, 96)),
3808 (Key::new(5, X86_64_V4), Bounds::new(256, 96)),
3809 (Key::new(5, Neon), Bounds::new(64, 64)),
3810 (Key::new(6, Scalar), Bounds::new(64, 64)),
3811 (Key::new(6, X86_64_V3), Bounds::new(256, 96)),
3812 (Key::new(6, X86_64_V4), Bounds::new(256, 96)),
3813 (Key::new(6, Neon), Bounds::new(64, 64)),
3814 (Key::new(7, Scalar), Bounds::new(64, 64)),
3815 (Key::new(7, X86_64_V3), Bounds::new(256, 96)),
3816 (Key::new(7, X86_64_V4), Bounds::new(256, 96)),
3817 (Key::new(7, Neon), Bounds::new(64, 64)),
3818 (Key::new(8, Scalar), Bounds::new(64, 64)),
3819 (Key::new(8, X86_64_V3), Bounds::new(256, 96)),
3820 (Key::new(8, X86_64_V4), Bounds::new(256, 96)),
3821 (Key::new(8, Neon), Bounds::new(256, 256)),
3822 ]
3823 .into_iter()
3824 .collect()
3825 });
3826
3827 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
3828 enum ArchKey {
3829 Scalar,
3830 #[expect(non_camel_case_types)]
3831 X86_64_V3,
3832 #[expect(non_camel_case_types)]
3833 X86_64_V4,
3834 Neon,
3835 }
3836
3837 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
3838 struct Key {
3839 nbits: usize,
3840 arch: ArchKey,
3841 }
3842
3843 impl Key {
3844 fn new(nbits: usize, arch: ArchKey) -> Self {
3845 Self { nbits, arch }
3846 }
3847 }
3848
3849 #[derive(Debug, Clone, Copy)]
3850 struct Bounds {
3851 standard: usize,
3852 miri: usize,
3853 }
3854
3855 impl Bounds {
3856 fn new(standard: usize, miri: usize) -> Self {
3857 Self { standard, miri }
3858 }
3859
3860 fn get(&self) -> usize {
3861 if cfg!(miri) { self.miri } else { self.standard }
3862 }
3863 }
3864
3865 macro_rules! test_bitslice {
3866 ($name:ident, $nbits:literal, $seed:literal) => {
3867 #[test]
3868 fn $name() {
3869 let mut rng = StdRng::seed_from_u64($seed);
3870
3871 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::Scalar)].get();
3872
3873 test_bitslice_distances::<$nbits, _>(
3874 max_dim,
3875 TRIALS_PER_DIM,
3876 &|x, y| SquaredL2::evaluate(x, y),
3877 &|x, y| InnerProduct::evaluate(x, y),
3878 "pure distance function",
3879 &mut rng,
3880 );
3881
3882 test_bitslice_distances::<$nbits, _>(
3883 max_dim,
3884 TRIALS_PER_DIM,
3885 &|x, y| diskann_wide::arch::Scalar::new().run2(SquaredL2, x, y),
3886 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
3887 "scalar arch",
3888 &mut rng,
3889 );
3890
3891 #[cfg(target_arch = "x86_64")]
3893 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
3894 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::X86_64_V3)].get();
3895 test_bitslice_distances::<$nbits, _>(
3896 max_dim,
3897 TRIALS_PER_DIM,
3898 &|x, y| arch.run2(SquaredL2, x, y),
3899 &|x, y| arch.run2(InnerProduct, x, y),
3900 "x86-64-v3",
3901 &mut rng,
3902 );
3903 }
3904
3905 #[cfg(target_arch = "x86_64")]
3906 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
3907 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::X86_64_V4)].get();
3908 test_bitslice_distances::<$nbits, _>(
3909 max_dim,
3910 TRIALS_PER_DIM,
3911 &|x, y| arch.run2(SquaredL2, x, y),
3912 &|x, y| arch.run2(InnerProduct, x, y),
3913 "x86-64-v4",
3914 &mut rng,
3915 );
3916 }
3917
3918 #[cfg(target_arch = "aarch64")]
3919 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
3920 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::Neon)].get();
3921 test_bitslice_distances::<$nbits, _>(
3922 max_dim,
3923 TRIALS_PER_DIM,
3924 &|x, y| arch.run2(SquaredL2, x, y),
3925 &|x, y| arch.run2(InnerProduct, x, y),
3926 "neon",
3927 &mut rng,
3928 );
3929 }
3930 }
3931 };
3932 }
3933
3934 test_bitslice!(test_bitslice_distances_8bit, 8, 0xf0330c6d880e08ff);
3935 test_bitslice!(test_bitslice_distances_7bit, 7, 0x98aa7f2d4c83844f);
3936 test_bitslice!(test_bitslice_distances_6bit, 6, 0xf2f7ad7a37764b4c);
3937 test_bitslice!(test_bitslice_distances_5bit, 5, 0xae878d14973fb43f);
3938 test_bitslice!(test_bitslice_distances_4bit, 4, 0x8d6dbb8a6b19a4f8);
3939 test_bitslice!(test_bitslice_distances_3bit, 3, 0x8f56767236e58da2);
3940 test_bitslice!(test_bitslice_distances_2bit, 2, 0xb04f741a257b61af);
3941 test_bitslice!(test_bitslice_distances_1bit, 1, 0x820ea031c379eab5);
3942
3943 fn test_hamming_distances<R>(dim_max: usize, trials_per_dim: usize, rng: &mut R)
3948 where
3949 R: Rng,
3950 {
3951 let dist: [i8; 2] = [-1, 1];
3952
3953 for dim in 0..dim_max {
3954 if !should_check_this_dimension(dim) {
3955 continue;
3956 }
3957
3958 let mut x_reference: Vec<i8> = vec![1; dim];
3959 let mut y_reference: Vec<i8> = vec![1; dim];
3960
3961 let mut x = BoxedBitSlice::<1, Binary>::new_boxed(dim);
3962 let mut y = BoxedBitSlice::<1, Binary>::new_boxed(dim);
3963
3964 for _ in 0..trials_per_dim {
3965 x_reference
3966 .iter_mut()
3967 .for_each(|i| *i = *dist.choose(rng).unwrap());
3968 y_reference
3969 .iter_mut()
3970 .for_each(|i| *i = *dist.choose(rng).unwrap());
3971
3972 x.as_mut_slice().fill(u8::MAX);
3975 y.as_mut_slice().fill(u8::MAX);
3976
3977 for i in 0..dim {
3978 x.set(i, x_reference[i].into()).unwrap();
3979 y.set(i, y_reference[i].into()).unwrap();
3980 }
3981
3982 let expected: MV<f32> =
3988 diskann_vector::distance::SquaredL2::evaluate(&*x_reference, &*y_reference);
3989 let got: MV<u32> = Hamming::evaluate(x.reborrow(), y.reborrow()).unwrap();
3990 assert_eq!(4.0 * (got.into_inner() as f32), expected.into_inner());
3991 }
3992 }
3993
3994 let x = BoxedBitSlice::<1, Binary>::new_boxed(10);
3995 let y = BoxedBitSlice::<1, Binary>::new_boxed(11);
3996 assert!(Hamming::evaluate(x.reborrow(), y.reborrow()).is_err());
3997 assert!(Hamming::evaluate(y.reborrow(), x.reborrow()).is_err());
3998 }
3999
4000 #[test]
4001 fn test_hamming_distance() {
4002 let mut rng = StdRng::seed_from_u64(0x2160419161246d97);
4003 test_hamming_distances(MAX_DIM, TRIALS_PER_DIM, &mut rng);
4004 }
4005
4006 fn test_bit_transpose_distances<R>(
4011 dim_max: usize,
4012 trials_per_dim: usize,
4013 evaluate_ip: &dyn Fn(USlice<'_, 4, BitTranspose>, USlice<'_, 1>) -> MR,
4014 context: &str,
4015 rng: &mut R,
4016 ) where
4017 R: Rng,
4018 {
4019 let dist_4bit = {
4020 let domain = Unsigned::domain_const::<4>();
4021 Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
4022 };
4023
4024 let dist_1bit = {
4025 let domain = Unsigned::domain_const::<1>();
4026 Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
4027 };
4028
4029 for dim in 0..dim_max {
4030 if !should_check_this_dimension(dim) {
4031 continue;
4032 }
4033
4034 let mut x_reference: Vec<u8> = vec![0; dim];
4035 let mut y_reference: Vec<u8> = vec![0; dim];
4036
4037 let mut x = BoxedBitSlice::<4, Unsigned, BitTranspose>::new_boxed(dim);
4038 let mut y = BoxedBitSlice::<1, Unsigned, Dense>::new_boxed(dim);
4039
4040 for trial in 0..trials_per_dim {
4041 x_reference
4042 .iter_mut()
4043 .for_each(|i| *i = dist_4bit.sample(rng).try_into().unwrap());
4044 y_reference
4045 .iter_mut()
4046 .for_each(|i| *i = dist_1bit.sample(rng).try_into().unwrap());
4047
4048 x.as_mut_slice().fill(u8::MAX);
4050 y.as_mut_slice().fill(u8::MAX);
4051
4052 for i in 0..dim {
4053 x.set(i, x_reference[i].into()).unwrap();
4054 y.set(i, y_reference[i].into()).unwrap();
4055 }
4056
4057 let expected: MV<f32> =
4059 diskann_vector::distance::InnerProduct::evaluate(&*x_reference, &*y_reference);
4060
4061 let got = evaluate_ip(x.reborrow(), y.reborrow());
4062
4063 assert_eq!(
4065 expected.into_inner(),
4066 got.unwrap().into_inner() as f32,
4067 "faild InnerProduct for dim = {}, trial = {} -- context {}",
4068 dim,
4069 trial,
4070 context,
4071 );
4072 }
4073 }
4074
4075 let x = BoxedBitSlice::<4, Unsigned, BitTranspose>::new_boxed(10);
4076 let y = BoxedBitSlice::<1, Unsigned>::new_boxed(11);
4077 assert!(
4078 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
4079 "context: {}",
4080 context
4081 );
4082
4083 let y = BoxedBitSlice::<1, Unsigned>::new_boxed(9);
4084 assert!(
4085 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
4086 "context: {}",
4087 context
4088 );
4089 }
4090
4091 #[test]
4092 fn test_bit_transpose_distance() {
4093 let mut rng = StdRng::seed_from_u64(0xe20e26e926d4b853);
4094
4095 test_bit_transpose_distances(
4096 MAX_DIM,
4097 TRIALS_PER_DIM,
4098 &|x, y| InnerProduct::evaluate(x, y),
4099 "pure distance function",
4100 &mut rng,
4101 );
4102
4103 test_bit_transpose_distances(
4104 MAX_DIM,
4105 TRIALS_PER_DIM,
4106 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
4107 "scalar",
4108 &mut rng,
4109 );
4110
4111 #[cfg(target_arch = "x86_64")]
4113 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
4114 test_bit_transpose_distances(
4115 MAX_DIM,
4116 TRIALS_PER_DIM,
4117 &|x, y| arch.run2(InnerProduct, x, y),
4118 "x86-64-v3",
4119 &mut rng,
4120 );
4121 }
4122
4123 #[cfg(target_arch = "x86_64")]
4125 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
4126 test_bit_transpose_distances(
4127 MAX_DIM,
4128 TRIALS_PER_DIM,
4129 &|x, y| arch.run2(InnerProduct, x, y),
4130 "x86-64-v4",
4131 &mut rng,
4132 );
4133 }
4134
4135 #[cfg(target_arch = "aarch64")]
4137 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4138 test_bit_transpose_distances(
4139 MAX_DIM,
4140 TRIALS_PER_DIM,
4141 &|x, y| arch.run2(InnerProduct, x, y),
4142 "neon",
4143 &mut rng,
4144 );
4145 }
4146 }
4147
4148 fn test_full_distances<const NBITS: usize>(
4153 dim_max: usize,
4154 trials_per_dim: usize,
4155 evaluate_ip: &dyn Fn(&[f32], USlice<'_, NBITS>) -> MathematicalResult<f32>,
4156 context: &str,
4157 rng: &mut impl Rng,
4158 ) where
4159 Unsigned: Representation<NBITS>,
4160 {
4161 let dist_float = [-2.0, -1.0, 0.0, 1.0, 2.0];
4163 let dist_bit = {
4164 let domain = Unsigned::domain_const::<NBITS>();
4165 Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
4166 };
4167
4168 for dim in 0..dim_max {
4169 if !should_check_this_dimension(dim) {
4170 continue;
4171 }
4172
4173 let mut x: Vec<f32> = vec![0.0; dim];
4174
4175 let mut y_reference: Vec<u8> = vec![0; dim];
4176 let mut y = BoxedBitSlice::<NBITS, Unsigned, Dense>::new_boxed(dim);
4177
4178 for trial in 0..trials_per_dim {
4179 x.iter_mut()
4180 .for_each(|i| *i = *dist_float.choose(rng).unwrap());
4181 y_reference
4182 .iter_mut()
4183 .for_each(|i| *i = dist_bit.sample(rng).try_into().unwrap());
4184
4185 y.as_mut_slice().fill(u8::MAX);
4187
4188 let mut expected = 0.0;
4189 for i in 0..dim {
4190 y.set(i, y_reference[i].into()).unwrap();
4191 expected += y_reference[i] as f32 * x[i];
4192 }
4193
4194 let got = evaluate_ip(&x, y.reborrow()).unwrap();
4196
4197 assert_eq!(
4199 expected,
4200 got.into_inner(),
4201 "faild InnerProduct for dim = {}, trial = {} -- context {}",
4202 dim,
4203 trial,
4204 context,
4205 );
4206
4207 let scalar: MV<f32> = InnerProduct
4210 .run(diskann_wide::arch::Scalar, x.as_slice(), y.reborrow())
4211 .unwrap();
4212 assert_eq!(got.into_inner(), scalar.into_inner());
4213 }
4214 }
4215
4216 let x = vec![0.0; 10];
4218 let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(11);
4219 assert!(
4220 evaluate_ip(x.as_slice(), y.reborrow()).is_err(),
4221 "context: {}",
4222 context
4223 );
4224
4225 let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(9);
4226 assert!(
4227 evaluate_ip(x.as_slice(), y.reborrow()).is_err(),
4228 "context: {}",
4229 context
4230 );
4231 }
4232
4233 macro_rules! test_full {
4234 ($name:ident, $nbits:literal, $seed:literal) => {
4235 #[test]
4236 fn $name() {
4237 let mut rng = StdRng::seed_from_u64($seed);
4238
4239 test_full_distances::<$nbits>(
4240 MAX_DIM,
4241 TRIALS_PER_DIM,
4242 &|x, y| InnerProduct::evaluate(x, y),
4243 "pure distance function",
4244 &mut rng,
4245 );
4246
4247 test_full_distances::<$nbits>(
4248 MAX_DIM,
4249 TRIALS_PER_DIM,
4250 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
4251 "scalar",
4252 &mut rng,
4253 );
4254
4255 #[cfg(target_arch = "x86_64")]
4257 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
4258 test_full_distances::<$nbits>(
4259 MAX_DIM,
4260 TRIALS_PER_DIM,
4261 &|x, y| arch.run2(InnerProduct, x, y),
4262 "x86-64-v3",
4263 &mut rng,
4264 );
4265 }
4266
4267 #[cfg(target_arch = "x86_64")]
4268 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
4269 test_full_distances::<$nbits>(
4270 MAX_DIM,
4271 TRIALS_PER_DIM,
4272 &|x, y| arch.run2(InnerProduct, x, y),
4273 "x86-64-v4",
4274 &mut rng,
4275 );
4276 }
4277
4278 #[cfg(target_arch = "aarch64")]
4279 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4280 test_full_distances::<$nbits>(
4281 MAX_DIM,
4282 TRIALS_PER_DIM,
4283 &|x, y| arch.run2(InnerProduct, x, y),
4284 "neon",
4285 &mut rng,
4286 );
4287 }
4288 }
4289 };
4290 }
4291
4292 test_full!(test_full_distance_1bit, 1, 0xe20e26e926d4b853);
4293 test_full!(test_full_distance_2bit, 2, 0xae9542700aecbf68);
4294 test_full!(test_full_distance_3bit, 3, 0xfffd04b26bb6068c);
4295 test_full!(test_full_distance_4bit, 4, 0x86db49fd1a1704ba);
4296 test_full!(test_full_distance_5bit, 5, 0x3a35dc7fa7931c41);
4297 test_full!(test_full_distance_6bit, 6, 0x1f69de79e418d336);
4298 test_full!(test_full_distance_7bit, 7, 0x3fcf17b82dadc5ab);
4299 test_full!(test_full_distance_8bit, 8, 0x85dcaf48b1399db2);
4300
4301 struct HetCase<const M: usize> {
4308 x_vals: Vec<i64>,
4309 y_vals: Vec<i64>,
4310 }
4311
4312 impl<const M: usize> HetCase<M>
4313 where
4314 Unsigned: Representation<M>,
4315 {
4316 fn new(dim: usize, fill: impl FnMut(usize) -> (i64, i64)) -> Self {
4317 let (x_vals, y_vals) = (0..dim).map(fill).unzip();
4318 Self { x_vals, y_vals }
4319 }
4320
4321 fn check_with(
4322 &self,
4323 label: impl Display,
4324 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4325 ) {
4326 let dim = self.x_vals.len();
4327 let mut x = BoxedBitSlice::<8, Unsigned>::new_boxed(dim);
4328 let mut y = BoxedBitSlice::<M, Unsigned>::new_boxed(dim);
4329 x.as_mut_slice().fill(u8::MAX);
4331 y.as_mut_slice().fill(u8::MAX);
4332 for (i, (&xv, &yv)) in self.x_vals.iter().zip(&self.y_vals).enumerate() {
4333 x.set(i, xv).unwrap();
4334 y.set(i, yv).unwrap();
4335 }
4336 let expected: u32 = self
4337 .x_vals
4338 .iter()
4339 .zip(&self.y_vals)
4340 .map(|(&a, &b)| a as u32 * b as u32)
4341 .sum();
4342 let got = evaluate(x.reborrow(), y.reborrow()).unwrap().into_inner();
4343 assert_eq!(expected, got, "{} failed for dim = {}", label, dim);
4344 }
4345 }
4346
4347 fn fuzz_heterogeneous_ip<const M: usize>(
4349 dim_max: usize,
4350 trials_per_dim: usize,
4351 max_val: i64,
4352 evaluate_ip: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4353 context: &str,
4354 rng: &mut impl Rng,
4355 ) where
4356 Unsigned: Representation<M>,
4357 {
4358 let dist_8bit = Uniform::new_inclusive(0i64, 255i64).unwrap();
4359 let dist_mbit = Uniform::new_inclusive(0i64, max_val).unwrap();
4360
4361 for dim in 0..dim_max {
4362 for trial in 0..trials_per_dim {
4363 HetCase::<M>::new(dim, |_| {
4364 (dist_8bit.sample(&mut *rng), dist_mbit.sample(&mut *rng))
4365 })
4366 .check_with(
4367 lazy_format!("IP(8,{}) dim={dim}, trial={trial} -- {context}", M),
4368 evaluate_ip,
4369 );
4370 }
4371
4372 let x = BoxedBitSlice::<8, Unsigned>::new_boxed(dim);
4374 let y = BoxedBitSlice::<M, Unsigned>::new_boxed(dim + 1);
4375 assert!(
4376 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
4377 "context: {}",
4378 context,
4379 );
4380 }
4381 }
4382
4383 fn het_test_max_values<const M: usize>(
4386 max_val: i64,
4387 context: &str,
4388 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4389 ) where
4390 Unsigned: Representation<M>,
4391 {
4392 let dims = [127, 128, 129, 255, 256, 512, 768, 896, 3072];
4393 for &dim in &dims {
4394 let case = HetCase::<M>::new(dim, |_| (255, max_val));
4395 case.check_with(lazy_format!("max-value {context} dim={dim}"), evaluate);
4396 }
4397 }
4398
4399 fn het_test_known_answers<const M: usize>(
4401 max_val: i64,
4402 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4403 ) where
4404 Unsigned: Representation<M>,
4405 {
4406 HetCase::<M>::new(64, |_| (200, max_val)).check_with("vpmaddubsw operand-order", evaluate);
4409
4410 let y_val = (max_val / 2).max(1);
4412 HetCase::<M>::new(128, |i| ((i % 256) as i64, y_val))
4413 .check_with("ascending-x constant-y", evaluate);
4414
4415 HetCase::<M>::new(1, |_| (200, max_val)).check_with("single element", evaluate);
4417 }
4418
4419 fn het_test_edge_cases<const M: usize>(
4421 max_val: i64,
4422 block_size: usize,
4423 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4424 ) where
4425 Unsigned: Representation<M>,
4426 {
4427 let y_half = (max_val / 2).max(1);
4428
4429 HetCase::<M>::new(64, |_| (0, max_val)).check_with("x-zero y-nonzero", evaluate);
4431 HetCase::<M>::new(64, |_| (255, 0)).check_with("y-zero x-nonzero", evaluate);
4432
4433 for dim in 0..=(block_size + 1) {
4435 HetCase::<M>::new(dim, |_| (3, y_half)).check_with("uniform fill", evaluate);
4436 }
4437
4438 for &dim in &[block_size, 2 * block_size, 4 * block_size, 8 * block_size] {
4440 HetCase::<M>::new(dim, |_| (100, max_val)).check_with("exact block boundary", evaluate);
4441 }
4442
4443 HetCase::<M>::new(300, |i| ((i % 256) as i64, 1))
4445 .check_with("x-varies y-constant", evaluate);
4446
4447 HetCase::<M>::new(300, |i| (1, (i as i64) % (max_val + 1)))
4449 .check_with("x-constant y-varies", evaluate);
4450
4451 HetCase::<M>::new(128, |i| if i % 2 == 0 { (255, max_val) } else { (0, 0) })
4453 .check_with("alternating pattern", evaluate);
4454
4455 HetCase::<M>::new(128, |i| if i % 2 == 0 { (0, 0) } else { (255, max_val) })
4457 .check_with("opposite alternating", evaluate);
4458
4459 HetCase::<M>::new(1024, |_| (255, max_val)).check_with("large accumulation", evaluate);
4461
4462 for x_val in [128i64, 170, 200, 240, 255] {
4464 HetCase::<M>::new(block_size, move |_| (x_val, y_half))
4465 .check_with(lazy_format!("x > 127 (x_val={x_val})"), evaluate);
4466 }
4467
4468 HetCase::<M>::new(block_size - 1, |i| {
4470 (
4471 ((i * 7 + 3) % 256) as i64,
4472 ((i * 11 + 5) as i64) % (max_val + 1),
4473 )
4474 })
4475 .check_with("dim=block_size-1 (all scalar)", evaluate);
4476
4477 let unroll4 = 4 * block_size;
4479 for &dim in &[
4480 unroll4,
4481 unroll4 + 1,
4482 unroll4 + block_size,
4483 unroll4 + block_size + 1,
4484 ] {
4485 HetCase::<M>::new(dim, |i| {
4486 (((i + 1) % 256) as i64, ((i + 1) as i64) % (max_val + 1))
4487 })
4488 .check_with("unroll boundary", evaluate);
4489 }
4490 }
4491
4492 macro_rules! heterogeneous_ip_tests_8xM {
4493 (
4494 mod_name: $mod:ident,
4495 M: $M:literal,
4496 max_val: $max_val:literal,
4497 block_size: $block_size:literal,
4498 seed_fuzz: $seed_fuzz:literal,
4499 ) => {
4500 mod $mod {
4501 use super::*;
4502
4503 #[test]
4504 fn all_ip_dispatches() {
4505 let mut rng = StdRng::seed_from_u64($seed_fuzz);
4506
4507 fuzz_heterogeneous_ip::<$M>(
4508 MAX_DIM,
4509 TRIALS_PER_DIM,
4510 $max_val,
4511 &|x, y| InnerProduct::evaluate(x, y),
4512 "pure distance function",
4513 &mut rng,
4514 );
4515 fuzz_heterogeneous_ip::<$M>(
4516 MAX_DIM,
4517 TRIALS_PER_DIM,
4518 $max_val,
4519 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
4520 "scalar arch",
4521 &mut rng,
4522 );
4523 #[cfg(target_arch = "x86_64")]
4524 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
4525 fuzz_heterogeneous_ip::<$M>(
4526 MAX_DIM,
4527 TRIALS_PER_DIM,
4528 $max_val,
4529 &|x, y| arch.run2(InnerProduct, x, y),
4530 "x86-64-v3",
4531 &mut rng,
4532 );
4533 }
4534 #[cfg(target_arch = "x86_64")]
4535 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
4536 fuzz_heterogeneous_ip::<$M>(
4537 MAX_DIM,
4538 TRIALS_PER_DIM,
4539 $max_val,
4540 &|x, y| arch.run2(InnerProduct, x, y),
4541 "x86-64-v4",
4542 &mut rng,
4543 );
4544 }
4545 #[cfg(target_arch = "aarch64")]
4546 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4547 fuzz_heterogeneous_ip::<$M>(
4548 MAX_DIM,
4549 TRIALS_PER_DIM,
4550 $max_val,
4551 &|x, y| arch.run2(InnerProduct, x, y),
4552 "neon",
4553 &mut rng,
4554 );
4555 }
4556 }
4557
4558 #[test]
4559 fn max_values() {
4560 het_test_max_values::<$M>($max_val, "dispatch", &|x, y| {
4561 InnerProduct::evaluate(x, y)
4562 });
4563 #[cfg(target_arch = "x86_64")]
4564 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
4565 het_test_max_values::<$M>($max_val, "V3", &|x, y| {
4566 arch.run2(InnerProduct, x, y)
4567 });
4568 }
4569 #[cfg(target_arch = "aarch64")]
4570 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4571 het_test_max_values::<$M>($max_val, "neon", &|x, y| {
4572 arch.run2(InnerProduct, x, y)
4573 });
4574 }
4575 }
4576
4577 #[test]
4578 fn known_answers() {
4579 het_test_known_answers::<$M>($max_val, &|x, y| InnerProduct::evaluate(x, y));
4580 #[cfg(target_arch = "aarch64")]
4581 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4582 het_test_known_answers::<$M>($max_val, &|x, y| {
4583 arch.run2(InnerProduct, x, y)
4584 });
4585 }
4586 }
4587
4588 #[test]
4589 fn edge_cases() {
4590 het_test_edge_cases::<$M>($max_val, $block_size, &|x, y| {
4591 InnerProduct::evaluate(x, y)
4592 });
4593 #[cfg(target_arch = "aarch64")]
4594 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4595 het_test_edge_cases::<$M>($max_val, $block_size, &|x, y| {
4596 arch.run2(InnerProduct, x, y)
4597 });
4598 }
4599 }
4600 }
4601 };
4602 }
4603
4604 heterogeneous_ip_tests_8xM! {
4605 mod_name: heterogeneous_ip_8x4,
4606 M: 4,
4607 max_val: 15,
4608 block_size: 32,
4609 seed_fuzz: 0xd3a7f1c09b2e4856,
4610 }
4611
4612 heterogeneous_ip_tests_8xM! {
4613 mod_name: heterogeneous_ip_8x2,
4614 M: 2,
4615 max_val: 3,
4616 block_size: 64,
4617 seed_fuzz: 0x82c4a6e809f1d3b5,
4618 }
4619
4620 heterogeneous_ip_tests_8xM! {
4621 mod_name: heterogeneous_ip_8x1,
4622 M: 1,
4623 max_val: 1,
4624 block_size: 32,
4625 seed_fuzz: 0x1b17_a5e7c2d0f839,
4626 }
4627}