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::{
119 SIMDDotProduct, SIMDMulAdd, SIMDPartialEq, SIMDSelect, SIMDSumTree, SIMDVector,
120};
121
122use super::{Binary, BitSlice, BitTranspose, Dense, Representation, Unsigned};
123use crate::distances::{Hamming, InnerProduct, MV, MathematicalResult, SquaredL2, check_lengths};
124
125type USlice<'a, const N: usize, Perm = Dense> = BitSlice<'a, N, Unsigned, Perm>;
127
128macro_rules! retarget {
134 ($arch:path, $op:ty, ($N:literal, $M:literal)) => {
135 impl Target2<
136 $arch,
137 MathematicalResult<u32>,
138 USlice<'_, $N>,
139 USlice<'_, $M>,
140 > for $op {
141 #[inline(always)]
142 fn run(
143 self,
144 arch: $arch,
145 x: USlice<'_, $N>,
146 y: USlice<'_, $M>
147 ) -> MathematicalResult<u32> {
148 self.run(arch.retarget(), x, y)
149 }
150 }
151 };
152 ($arch:path, $op:ty, $N:literal) => {
153 retarget!($arch, $op, ($N, $N));
154 };
155 ($arch:path, $op:ty, $($args:tt),+ $(,)?) => {
156 $(retarget!($arch, $op, $args);)+
157 };
158}
159
160macro_rules! dispatch_pure {
162 ($op:ty, ($N:literal, $M:literal)) => {
163 impl PureDistanceFunction<USlice<'_, $N>, USlice<'_, $M>, MathematicalResult<u32>> for $op {
164 #[inline(always)]
165 fn evaluate(x: USlice<'_, $N>, y: USlice<'_, $M>) -> MathematicalResult<u32> {
166 (diskann_wide::ARCH).run2(Self, x, y)
167 }
168 }
169 };
170 ($op:ty, $N:literal) => {
171 dispatch_pure!($op, ($N, $N));
172 };
173 ($op:ty, $($args:tt),+ $(,)?) => {
174 $(dispatch_pure!($op, $args);)+
175 }
176}
177
178#[cfg(target_arch = "x86_64")]
185unsafe fn load_one<F, R>(ptr: *const u32, mut f: F) -> R
186where
187 F: FnMut(u32) -> R,
188{
189 f(unsafe { ptr.cast::<u8>().read_unaligned() }.into())
191}
192
193#[cfg(target_arch = "x86_64")]
200unsafe fn load_two<F, R>(ptr: *const u32, mut f: F) -> R
201where
202 F: FnMut(u32) -> R,
203{
204 f(unsafe { ptr.cast::<u16>().read_unaligned() }.into())
206}
207
208#[cfg(target_arch = "x86_64")]
215unsafe fn load_three<F, R>(ptr: *const u32, mut f: F) -> R
216where
217 F: FnMut(u32) -> R,
218{
219 let lo: u32 = unsafe { ptr.cast::<u16>().read_unaligned() }.into();
221 let hi: u32 = unsafe { ptr.cast::<u8>().add(2).read_unaligned() }.into();
223 f(lo | hi << 16)
224}
225
226#[cfg(target_arch = "x86_64")]
233unsafe fn load_four<F, R>(ptr: *const u32, mut f: F) -> R
234where
235 F: FnMut(u32) -> R,
236{
237 f(unsafe { ptr.read_unaligned() })
239}
240
241trait BitVectorOp<Repr>
253where
254 Repr: Representation<1>,
255{
256 fn on_u64(x: u64, y: u64) -> u32;
258
259 fn on_u8(x: u8, y: u8) -> u32;
264}
265
266impl BitVectorOp<Unsigned> for SquaredL2 {
268 #[inline(always)]
269 fn on_u64(x: u64, y: u64) -> u32 {
270 (x ^ y).count_ones()
271 }
272 #[inline(always)]
273 fn on_u8(x: u8, y: u8) -> u32 {
274 (x ^ y).count_ones()
275 }
276}
277
278impl BitVectorOp<Binary> for Hamming {
280 #[inline(always)]
281 fn on_u64(x: u64, y: u64) -> u32 {
282 (x ^ y).count_ones()
283 }
284 #[inline(always)]
285 fn on_u8(x: u8, y: u8) -> u32 {
286 (x ^ y).count_ones()
287 }
288}
289
290impl BitVectorOp<Unsigned> for InnerProduct {
298 #[inline(always)]
299 fn on_u64(x: u64, y: u64) -> u32 {
300 (x & y).count_ones()
301 }
302 #[inline(always)]
303 fn on_u8(x: u8, y: u8) -> u32 {
304 (x & y).count_ones()
305 }
306}
307
308#[inline(always)]
313fn bitvector_op<Op, Repr>(
314 x: BitSlice<'_, 1, Repr>,
315 y: BitSlice<'_, 1, Repr>,
316) -> MathematicalResult<u32>
317where
318 Repr: Representation<1>,
319 Op: BitVectorOp<Repr>,
320{
321 let len = check_lengths!(x, y)?;
322
323 let px: *const u64 = x.as_ptr().cast();
324 let py: *const u64 = y.as_ptr().cast();
325
326 let mut i = 0;
327 let mut s: u32 = 0;
328
329 let blocks = len / 64;
331 while i < blocks {
332 let vx = unsafe { px.add(i).read_unaligned() };
336
337 let vy = unsafe { py.add(i).read_unaligned() };
341
342 s += Op::on_u64(vx, vy);
343 i += 1;
344 }
345
346 i *= 8;
348 let px: *const u8 = x.as_ptr();
349 let py: *const u8 = y.as_ptr();
350
351 let blocks = len / 8;
352 while i < blocks {
353 let vx = unsafe { px.add(i).read_unaligned() };
356
357 let vy = unsafe { py.add(i).read_unaligned() };
361 s += Op::on_u8(vx, vy);
362 i += 1;
363 }
364
365 if i * 8 != len {
366 let vx = unsafe { px.add(i).read_unaligned() };
369
370 let vy = unsafe { py.add(i).read_unaligned() };
372 let m = (0x01u8 << (len - 8 * i)) - 1;
373
374 s += Op::on_u8(vx & m, vy & m)
375 }
376 Ok(MV::new(s))
377}
378
379impl PureDistanceFunction<BitSlice<'_, 1, Binary>, BitSlice<'_, 1, Binary>, MathematicalResult<u32>>
383 for Hamming
384{
385 fn evaluate(x: BitSlice<'_, 1, Binary>, y: BitSlice<'_, 1, Binary>) -> MathematicalResult<u32> {
386 bitvector_op::<Hamming, Binary>(x, y)
387 }
388}
389
390impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 8>> for SquaredL2
403where
404 A: Architecture,
405 diskann_vector::distance::SquaredL2: for<'a> Target2<A, MV<f32>, &'a [u8], &'a [u8]>,
406{
407 #[inline(always)]
408 fn run(self, arch: A, x: USlice<'_, 8>, y: USlice<'_, 8>) -> MathematicalResult<u32> {
409 check_lengths!(x, y)?;
410
411 let r: MV<f32> = <_ as Target2<_, _, _, _>>::run(
412 diskann_vector::distance::SquaredL2 {},
413 arch,
414 x.as_slice(),
415 y.as_slice(),
416 );
417
418 Ok(MV::new(r.into_inner() as u32))
419 }
420}
421
422#[cfg(target_arch = "x86_64")]
436impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
437 for SquaredL2
438{
439 #[inline(always)]
440 fn run(
441 self,
442 arch: diskann_wide::arch::x86_64::V3,
443 x: USlice<'_, 4>,
444 y: USlice<'_, 4>,
445 ) -> MathematicalResult<u32> {
446 let len = check_lengths!(x, y)?;
447
448 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
449 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
450 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
451
452 let px_u32: *const u32 = x.as_ptr().cast();
453 let py_u32: *const u32 = y.as_ptr().cast();
454
455 let mut i = 0;
456 let mut s: u32 = 0;
457
458 let blocks = len / 8;
460 if i < blocks {
461 let mut s0 = i32s::default(arch);
462 let mut s1 = i32s::default(arch);
463 let mut s2 = i32s::default(arch);
464 let mut s3 = i32s::default(arch);
465 let mask = u32s::splat(arch, 0x000f000f);
466 while i + 8 < blocks {
467 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
472
473 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
477
478 let wx: i16s = (vx & mask).reinterpret_simd();
479 let wy: i16s = (vy & mask).reinterpret_simd();
480 let d = wx - wy;
481 s0 = s0.dot_simd(d, d);
482
483 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
484 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
485 let d = wx - wy;
486 s1 = s1.dot_simd(d, d);
487
488 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
489 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
490 let d = wx - wy;
491 s2 = s2.dot_simd(d, d);
492
493 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
494 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
495 let d = wx - wy;
496 s3 = s3.dot_simd(d, d);
497
498 i += 8;
499 }
500
501 let remainder = blocks - i;
502
503 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
509
510 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
514
515 let wx: i16s = (vx & mask).reinterpret_simd();
516 let wy: i16s = (vy & mask).reinterpret_simd();
517 let d = wx - wy;
518 s0 = s0.dot_simd(d, d);
519
520 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
521 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
522 let d = wx - wy;
523 s1 = s1.dot_simd(d, d);
524
525 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
526 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
527 let d = wx - wy;
528 s2 = s2.dot_simd(d, d);
529
530 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
531 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
532 let d = wx - wy;
533 s3 = s3.dot_simd(d, d);
534
535 i += remainder;
536
537 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
538 }
539
540 i *= 8;
542
543 if i != len {
545 #[inline(never)]
547 fn fallback(x: USlice<'_, 4>, y: USlice<'_, 4>, from: usize) -> u32 {
548 let mut s: i32 = 0;
549 for i in from..x.len() {
550 let ix = unsafe { x.get_unchecked(i) } as i32;
552 let iy = unsafe { y.get_unchecked(i) } as i32;
554 let d = ix - iy;
555 s += d * d;
556 }
557 s as u32
558 }
559 s += fallback(x, y, i);
560 }
561
562 Ok(MV::new(s))
563 }
564}
565
566#[cfg(target_arch = "x86_64")]
580impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
581 for SquaredL2
582{
583 #[inline(always)]
584 fn run(
585 self,
586 arch: diskann_wide::arch::x86_64::V4,
587 x: USlice<'_, 4>,
588 y: USlice<'_, 4>,
589 ) -> MathematicalResult<u32> {
590 let len = check_lengths!(x, y)?;
591
592 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V4>::i32x16);
593 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V4>::u32x16);
594 diskann_wide::alias!(u8s = <diskann_wide::arch::x86_64::V4>::u8x64);
595 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V4>::i16x32);
596
597 let px_u32: *const u32 = x.as_ptr().cast();
598 let py_u32: *const u32 = y.as_ptr().cast();
599
600 let mut i = 0;
601 let mut s: u32 = 0;
602
603 let blocks = len.div_ceil(8);
608 if i < blocks {
609 let mut s0 = i32s::default(arch);
610 let mut s1 = i32s::default(arch);
611 let mut s2 = i32s::default(arch);
612 let mut s3 = i32s::default(arch);
613 let mask = u32s::splat(arch, 0x000f000f);
614 while i + 16 < blocks {
615 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
621
622 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
626
627 let wx: i16s = (vx & mask).reinterpret_simd();
628 let wy: i16s = (vy & mask).reinterpret_simd();
629 let d = wx - wy;
630 s0 = s0.dot_simd(d, d);
631
632 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
633 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
634 let d = wx - wy;
635 s1 = s1.dot_simd(d, d);
636
637 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
638 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
639 let d = wx - wy;
640 s2 = s2.dot_simd(d, d);
641
642 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
643 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
644 let d = wx - wy;
645 s3 = s3.dot_simd(d, d);
646
647 i += 16;
648 }
649
650 let remainder = len / 2 - 4 * i;
657
658 if remainder > 0 {
659 let vx =
665 unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
666 let vx: u32s = vx.reinterpret_simd();
667
668 let vy =
672 unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
673 let vy: u32s = vy.reinterpret_simd();
674
675 let wx: i16s = (vx & mask).reinterpret_simd();
676 let wy: i16s = (vy & mask).reinterpret_simd();
677 let d = wx - wy;
678 s0 = s0.dot_simd(d, d);
679
680 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
681 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
682 let d = wx - wy;
683 s1 = s1.dot_simd(d, d);
684
685 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
686 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
687 let d = wx - wy;
688 s2 = s2.dot_simd(d, d);
689
690 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
691 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
692 let d = wx - wy;
693 s3 = s3.dot_simd(d, d);
694 }
695
696 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
697 i = (4 * i) + remainder;
698 }
699
700 i *= 2;
702
703 debug_assert!(len - i <= 1);
705 if i != len {
706 let ix = unsafe { x.get_unchecked(i) } as i32;
708 let iy = unsafe { y.get_unchecked(i) } as i32;
710 let d = ix - iy;
711 s += (d * d) as u32;
712 }
713
714 Ok(MV::new(s))
715 }
716}
717
718#[cfg(target_arch = "aarch64")]
719impl
720 Target2<
721 diskann_wide::arch::aarch64::Neon,
722 MathematicalResult<u32>,
723 USlice<'_, 4>,
724 USlice<'_, 4>,
725 > for SquaredL2
726{
727 #[inline(always)]
728 fn run(
729 self,
730 arch: diskann_wide::arch::aarch64::Neon,
731 x: USlice<'_, 4>,
732 y: USlice<'_, 4>,
733 ) -> MathematicalResult<u32> {
734 let len = check_lengths!(x, y)?;
736
737 diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
738 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
739
740 let px_u8: *const u8 = x.as_ptr().cast();
741 let py_u8: *const u8 = y.as_ptr().cast();
742
743 let mut i = 0;
744 let mut s: u32 = 0;
745
746 let bytes = len / 2;
748 if i < bytes {
749 let mut s0 = u32s::default(arch);
750 let mut s1 = u32s::default(arch);
751 let mask = u8s::splat(arch, 0x0f);
752 while i + 16 <= bytes {
753 let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
757 let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
760
761 let lower_x: u8s = x_vec & mask;
763 let lower_y: u8s = y_vec & mask;
764 let d = arch.vabdq_u8(lower_x, lower_y);
765 s0 = s0.dot_simd(d, d);
766
767 let upper_x: u8s = (x_vec >> 4) & mask;
769 let upper_y: u8s = (y_vec >> 4) & mask;
770 let d = arch.vabdq_u8(upper_x, upper_y);
771 s1 = s1.dot_simd(d, d);
772
773 i += 16;
775 }
776
777 let remaining_bytes = len / 2 - i;
778
779 if remaining_bytes > 0 {
780 let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_bytes) };
784 let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_bytes) };
786
787 let lower_x: u8s = x_vec & mask;
789 let lower_y: u8s = y_vec & mask;
790 let d = arch.vabdq_u8(lower_x, lower_y);
791 s0 = s0.dot_simd(d, d);
792
793 let upper_x: u8s = (x_vec >> 4) & mask;
795 let upper_y: u8s = (y_vec >> 4) & mask;
796 let d = arch.vabdq_u8(upper_x, upper_y);
797 s1 = s1.dot_simd(d, d);
798
799 i += remaining_bytes;
800 }
801 s = (s0 + s1).sum_tree();
802 }
803
804 i *= 2;
806
807 debug_assert!(len - i <= 1);
809 if i != len {
810 let ix = unsafe { x.get_unchecked(i) } as i32;
812 let iy = unsafe { y.get_unchecked(i) } as i32;
814 let d = ix - iy;
815 s += (d * d) as u32;
816 }
817
818 Ok(MV::new(s))
819 }
820}
821
822#[cfg(target_arch = "aarch64")]
823impl
824 Target2<
825 diskann_wide::arch::aarch64::Neon,
826 MathematicalResult<u32>,
827 USlice<'_, 2>,
828 USlice<'_, 2>,
829 > for SquaredL2
830{
831 #[inline(always)]
832 fn run(
833 self,
834 arch: diskann_wide::arch::aarch64::Neon,
835 x: USlice<'_, 2>,
836 y: USlice<'_, 2>,
837 ) -> MathematicalResult<u32> {
838 let len = check_lengths!(x, y)?;
840
841 diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
842 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
843
844 let px_u8: *const u8 = x.as_ptr().cast();
845 let py_u8: *const u8 = y.as_ptr().cast();
846
847 let mut i = 0;
848 let mut s: u32 = 0;
849
850 let bytes = len / 4;
852 if i < bytes {
853 let mut s0 = u32s::default(arch);
854 let mut s1 = u32s::default(arch);
855 let mut s2 = u32s::default(arch);
856 let mut s3 = u32s::default(arch);
857 let mask = u8s::splat(arch, 0x03);
858 while i + 16 <= bytes {
859 let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
863 let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
866
867 let first_x: u8s = x_vec & mask;
869 let first_y: u8s = y_vec & mask;
870 let d = arch.vabdq_u8(first_x, first_y);
871 s0 = s0.dot_simd(d, d);
872
873 let second_x: u8s = (x_vec >> 2) & mask;
875 let second_y: u8s = (y_vec >> 2) & mask;
876 let d = arch.vabdq_u8(second_x, second_y);
877 s1 = s1.dot_simd(d, d);
878
879 let third_x: u8s = (x_vec >> 4) & mask;
881 let third_y: u8s = (y_vec >> 4) & mask;
882 let d = arch.vabdq_u8(third_x, third_y);
883 s2 = s2.dot_simd(d, d);
884
885 let fourth_x: u8s = (x_vec >> 6) & mask;
887 let fourth_y: u8s = (y_vec >> 6) & mask;
888 let d = arch.vabdq_u8(fourth_x, fourth_y);
889 s3 = s3.dot_simd(d, d);
890 i += 16;
892 }
893
894 let remaining_bytes = len / 4 - i;
895
896 if remaining_bytes > 0 {
897 let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_bytes) };
901 let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_bytes) };
903
904 let first_x: u8s = x_vec & mask;
906 let first_y: u8s = y_vec & mask;
907 let d = arch.vabdq_u8(first_x, first_y);
908 s0 = s0.dot_simd(d, d);
909
910 let second_x: u8s = (x_vec >> 2) & mask;
912 let second_y: u8s = (y_vec >> 2) & mask;
913 let d = arch.vabdq_u8(second_x, second_y);
914 s1 = s1.dot_simd(d, d);
915
916 let third_x: u8s = (x_vec >> 4) & mask;
918 let third_y: u8s = (y_vec >> 4) & mask;
919 let d = arch.vabdq_u8(third_x, third_y);
920 s2 = s2.dot_simd(d, d);
921
922 let fourth_x: u8s = (x_vec >> 6) & mask;
924 let fourth_y: u8s = (y_vec >> 6) & mask;
925 let d = arch.vabdq_u8(fourth_x, fourth_y);
926 s3 = s3.dot_simd(d, d);
927 i += remaining_bytes;
928 }
929 s = ((s0 + s1) + (s2 + s3)).sum_tree();
930 }
931
932 i *= 4;
934
935 debug_assert!(len - i <= 3);
937 if i != len {
938 #[inline(never)]
939 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
940 let mut s: i32 = 0;
941 for i in from..x.len() {
942 let ix = unsafe { x.get_unchecked(i) } as i32;
944 let iy = unsafe { y.get_unchecked(i) } as i32;
946 let d = ix - iy;
947 s += d * d;
948 }
949 s as u32
950 }
951 s += fallback(x, y, i);
952 }
953
954 Ok(MV::new(s))
955 }
956}
957
958#[cfg(target_arch = "x86_64")]
972impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
973 for SquaredL2
974{
975 #[inline(always)]
976 fn run(
977 self,
978 arch: diskann_wide::arch::x86_64::V3,
979 x: USlice<'_, 2>,
980 y: USlice<'_, 2>,
981 ) -> MathematicalResult<u32> {
982 let len = check_lengths!(x, y)?;
983
984 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
985 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
986 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
987
988 let px_u32: *const u32 = x.as_ptr().cast();
989 let py_u32: *const u32 = y.as_ptr().cast();
990
991 let mut i = 0;
992 let mut s: u32 = 0;
993
994 let blocks = len / 16;
996 if i < blocks {
997 let mut s0 = i32s::default(arch);
998 let mut s1 = i32s::default(arch);
999 let mut s2 = i32s::default(arch);
1000 let mut s3 = i32s::default(arch);
1001 let mask = u32s::splat(arch, 0x00030003);
1002 while i + 8 < blocks {
1003 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1008
1009 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1013
1014 let wx: i16s = (vx & mask).reinterpret_simd();
1015 let wy: i16s = (vy & mask).reinterpret_simd();
1016 let d = wx - wy;
1017 s0 = s0.dot_simd(d, d);
1018
1019 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1020 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1021 let d = wx - wy;
1022 s1 = s1.dot_simd(d, d);
1023
1024 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1025 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1026 let d = wx - wy;
1027 s2 = s2.dot_simd(d, d);
1028
1029 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1030 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1031 let d = wx - wy;
1032 s3 = s3.dot_simd(d, d);
1033
1034 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1035 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1036 let d = wx - wy;
1037 s0 = s0.dot_simd(d, d);
1038
1039 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1040 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1041 let d = wx - wy;
1042 s1 = s1.dot_simd(d, d);
1043
1044 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1045 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1046 let d = wx - wy;
1047 s2 = s2.dot_simd(d, d);
1048
1049 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1050 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1051 let d = wx - wy;
1052 s3 = s3.dot_simd(d, d);
1053
1054 i += 8;
1055 }
1056
1057 let remainder = blocks - i;
1058
1059 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1065
1066 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1070 let wx: i16s = (vx & mask).reinterpret_simd();
1071 let wy: i16s = (vy & mask).reinterpret_simd();
1072 let d = wx - wy;
1073 s0 = s0.dot_simd(d, d);
1074
1075 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1076 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1077 let d = wx - wy;
1078 s1 = s1.dot_simd(d, d);
1079
1080 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1081 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1082 let d = wx - wy;
1083 s2 = s2.dot_simd(d, d);
1084
1085 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1086 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1087 let d = wx - wy;
1088 s3 = s3.dot_simd(d, d);
1089
1090 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1091 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1092 let d = wx - wy;
1093 s0 = s0.dot_simd(d, d);
1094
1095 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1096 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1097 let d = wx - wy;
1098 s1 = s1.dot_simd(d, d);
1099
1100 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1101 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1102 let d = wx - wy;
1103 s2 = s2.dot_simd(d, d);
1104
1105 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1106 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1107 let d = wx - wy;
1108 s3 = s3.dot_simd(d, d);
1109
1110 i += remainder;
1111
1112 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1113 }
1114
1115 i *= 16;
1117
1118 if i != len {
1120 #[inline(never)]
1122 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
1123 let mut s: i32 = 0;
1124 for i in from..x.len() {
1125 let ix = unsafe { x.get_unchecked(i) } as i32;
1127 let iy = unsafe { y.get_unchecked(i) } as i32;
1129 let d = ix - iy;
1130 s += d * d;
1131 }
1132 s as u32
1133 }
1134 s += fallback(x, y, i);
1135 }
1136
1137 Ok(MV::new(s))
1138 }
1139}
1140
1141impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 1>, USlice<'_, 1>> for SquaredL2
1145where
1146 A: Architecture,
1147{
1148 fn run(self, _: A, x: USlice<'_, 1>, y: USlice<'_, 1>) -> MathematicalResult<u32> {
1149 bitvector_op::<Self, Unsigned>(x, y)
1150 }
1151}
1152
1153macro_rules! impl_fallback_l2 {
1155 ($N:literal) => {
1156 impl Target2<diskann_wide::arch::Scalar, MathematicalResult<u32>, USlice<'_, $N>, USlice<'_, $N>> for SquaredL2 {
1164 #[inline(never)]
1165 fn run(
1166 self,
1167 _: diskann_wide::arch::Scalar,
1168 x: USlice<'_, $N>,
1169 y: USlice<'_, $N>
1170 ) -> MathematicalResult<u32> {
1171 let len = check_lengths!(x, y)?;
1172
1173 let mut accum: i32 = 0;
1174 for i in 0..len {
1175 let ix: i32 = unsafe { x.get_unchecked(i) } as i32;
1177 let iy: i32 = unsafe { y.get_unchecked(i) } as i32;
1179 let diff = ix - iy;
1180 accum += diff * diff;
1181 }
1182 Ok(MV::new(accum as u32))
1183 }
1184 }
1185 };
1186 ($($N:literal),+ $(,)?) => {
1187 $(impl_fallback_l2!($N);)+
1188 };
1189}
1190
1191impl_fallback_l2!(7, 6, 5, 4, 3, 2);
1192
1193#[cfg(target_arch = "x86_64")]
1194retarget!(diskann_wide::arch::x86_64::V3, SquaredL2, 7, 6, 5, 3);
1195
1196#[cfg(target_arch = "x86_64")]
1197retarget!(diskann_wide::arch::x86_64::V4, SquaredL2, 7, 6, 5, 3, 2);
1198
1199dispatch_pure!(SquaredL2, 1, 2, 3, 4, 5, 6, 7, 8);
1200#[cfg(target_arch = "aarch64")]
1201retarget!(diskann_wide::arch::aarch64::Neon, SquaredL2, 7, 6, 5, 3,);
1202
1203impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 8>> for InnerProduct
1216where
1217 A: Architecture,
1218 diskann_vector::distance::InnerProduct: for<'a> Target2<A, MV<f32>, &'a [u8], &'a [u8]>,
1219{
1220 #[inline(always)]
1221 fn run(self, arch: A, x: USlice<'_, 8>, y: USlice<'_, 8>) -> MathematicalResult<u32> {
1222 check_lengths!(x, y)?;
1223 let r: MV<f32> = <_ as Target2<_, _, _, _>>::run(
1224 diskann_vector::distance::InnerProduct {},
1225 arch,
1226 x.as_slice(),
1227 y.as_slice(),
1228 );
1229
1230 Ok(MV::new(r.into_inner() as u32))
1231 }
1232}
1233
1234#[cfg(target_arch = "x86_64")]
1251impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
1252 for InnerProduct
1253{
1254 #[expect(non_camel_case_types)]
1255 #[inline(always)]
1256 fn run(
1257 self,
1258 arch: diskann_wide::arch::x86_64::V4,
1259 x: USlice<'_, 2>,
1260 y: USlice<'_, 2>,
1261 ) -> MathematicalResult<u32> {
1262 let len = check_lengths!(x, y)?;
1263
1264 type i32s = <diskann_wide::arch::x86_64::V4 as Architecture>::i32x16;
1265 type u32s = <diskann_wide::arch::x86_64::V4 as Architecture>::u32x16;
1266 type u8s = <diskann_wide::arch::x86_64::V4 as Architecture>::u8x64;
1267 type i8s = <diskann_wide::arch::x86_64::V4 as Architecture>::i8x64;
1268
1269 let px_u32: *const u32 = x.as_ptr().cast();
1270 let py_u32: *const u32 = y.as_ptr().cast();
1271
1272 let mut i = 0;
1273 let mut s: u32 = 0;
1274
1275 let blocks = len.div_ceil(16);
1277 if i < blocks {
1278 let mut s0 = i32s::default(arch);
1279 let mut s1 = i32s::default(arch);
1280 let mut s2 = i32s::default(arch);
1281 let mut s3 = i32s::default(arch);
1282 let mask = u32s::splat(arch, 0x03030303);
1283 while i + 16 < blocks {
1284 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1289
1290 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1294
1295 let wx: u8s = (vx & mask).reinterpret_simd();
1296 let wy: i8s = (vy & mask).reinterpret_simd();
1297 s0 = s0.dot_simd(wx, wy);
1298
1299 let wx: u8s = ((vx >> 2) & mask).reinterpret_simd();
1300 let wy: i8s = ((vy >> 2) & mask).reinterpret_simd();
1301 s1 = s1.dot_simd(wx, wy);
1302
1303 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1304 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1305 s2 = s2.dot_simd(wx, wy);
1306
1307 let wx: u8s = ((vx >> 6) & mask).reinterpret_simd();
1308 let wy: i8s = ((vy >> 6) & mask).reinterpret_simd();
1309 s3 = s3.dot_simd(wx, wy);
1310
1311 i += 16;
1312 }
1313
1314 let remainder = len / 4 - 4 * i;
1318
1319 let vx = unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
1324 let vx: u32s = vx.reinterpret_simd();
1325
1326 let vy = unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
1330 let vy: u32s = vy.reinterpret_simd();
1331
1332 let wx: u8s = (vx & mask).reinterpret_simd();
1333 let wy: i8s = (vy & mask).reinterpret_simd();
1334 s0 = s0.dot_simd(wx, wy);
1335
1336 let wx: u8s = ((vx >> 2) & mask).reinterpret_simd();
1337 let wy: i8s = ((vy >> 2) & mask).reinterpret_simd();
1338 s1 = s1.dot_simd(wx, wy);
1339
1340 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1341 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1342 s2 = s2.dot_simd(wx, wy);
1343
1344 let wx: u8s = ((vx >> 6) & mask).reinterpret_simd();
1345 let wy: i8s = ((vy >> 6) & mask).reinterpret_simd();
1346 s3 = s3.dot_simd(wx, wy);
1347
1348 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1349 i = (4 * i) + remainder;
1350 }
1351
1352 i *= 4;
1354
1355 debug_assert!(len - i <= 3);
1357 let rest = (len - i).min(3);
1358 if i != len {
1359 for j in 0..rest {
1360 let ix = unsafe { x.get_unchecked(i + j) } as u32;
1362 let iy = unsafe { y.get_unchecked(i + j) } as u32;
1364 s += ix * iy;
1365 }
1366 }
1367
1368 Ok(MV::new(s))
1369 }
1370}
1371
1372#[cfg(target_arch = "x86_64")]
1386impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
1387 for InnerProduct
1388{
1389 #[inline(always)]
1390 fn run(
1391 self,
1392 arch: diskann_wide::arch::x86_64::V3,
1393 x: USlice<'_, 4>,
1394 y: USlice<'_, 4>,
1395 ) -> MathematicalResult<u32> {
1396 let len = check_lengths!(x, y)?;
1397
1398 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1399 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
1400 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1401
1402 let px_u32: *const u32 = x.as_ptr().cast();
1403 let py_u32: *const u32 = y.as_ptr().cast();
1404
1405 let mut i = 0;
1406 let mut s: u32 = 0;
1407
1408 let blocks = len / 8;
1409 if i < blocks {
1410 let mut s0 = i32s::default(arch);
1411 let mut s1 = i32s::default(arch);
1412 let mut s2 = i32s::default(arch);
1413 let mut s3 = i32s::default(arch);
1414 let mask = u32s::splat(arch, 0x000f000f);
1415 while i + 8 < blocks {
1416 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1421
1422 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1426
1427 let wx: i16s = (vx & mask).reinterpret_simd();
1428 let wy: i16s = (vy & mask).reinterpret_simd();
1429 s0 = s0.dot_simd(wx, wy);
1430
1431 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1432 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1433 s1 = s1.dot_simd(wx, wy);
1434
1435 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1436 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1437 s2 = s2.dot_simd(wx, wy);
1438
1439 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1440 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1441 s3 = s3.dot_simd(wx, wy);
1442
1443 i += 8;
1444 }
1445
1446 let remainder = blocks - i;
1447
1448 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1454
1455 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1459
1460 let wx: i16s = (vx & mask).reinterpret_simd();
1461 let wy: i16s = (vy & mask).reinterpret_simd();
1462 s0 = s0.dot_simd(wx, wy);
1463
1464 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1465 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1466 s1 = s1.dot_simd(wx, wy);
1467
1468 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1469 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1470 s2 = s2.dot_simd(wx, wy);
1471
1472 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1473 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1474 s3 = s3.dot_simd(wx, wy);
1475
1476 i += remainder;
1477
1478 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1479 }
1480
1481 i *= 8;
1483
1484 if i != len {
1486 #[inline(never)]
1488 fn fallback(x: USlice<'_, 4>, y: USlice<'_, 4>, from: usize) -> u32 {
1489 let mut s: u32 = 0;
1490 for i in from..x.len() {
1491 let ix = unsafe { x.get_unchecked(i) } as u32;
1493 let iy = unsafe { y.get_unchecked(i) } as u32;
1495 s += ix * iy;
1496 }
1497 s
1498 }
1499 s += fallback(x, y, i);
1500 }
1501
1502 Ok(MV::new(s))
1503 }
1504}
1505
1506#[cfg(target_arch = "x86_64")]
1524impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
1525 for InnerProduct
1526{
1527 #[inline(always)]
1528 fn run(
1529 self,
1530 arch: diskann_wide::arch::x86_64::V4,
1531 x: USlice<'_, 4>,
1532 y: USlice<'_, 4>,
1533 ) -> MathematicalResult<u32> {
1534 let len = check_lengths!(x, y)?;
1535
1536 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V4>::i32x16);
1537 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V4>::u32x16);
1538 diskann_wide::alias!(u8s = <diskann_wide::arch::x86_64::V4>::u8x64);
1539 diskann_wide::alias!(i8s = <diskann_wide::arch::x86_64::V4>::i8x64);
1540
1541 let px_u32: *const u32 = x.as_ptr().cast();
1542 let py_u32: *const u32 = y.as_ptr().cast();
1543
1544 let mut i = 0;
1545 let mut s: u32 = 0;
1546
1547 let blocks = len.div_ceil(8);
1552 if i < blocks {
1553 let mut s0 = i32s::default(arch);
1554 let mut s1 = i32s::default(arch);
1555 let mask = u32s::splat(arch, 0x0f0f0f0f);
1556 while i + 16 < blocks {
1557 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1563
1564 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1568
1569 let wx: u8s = (vx & mask).reinterpret_simd();
1573 let wy: i8s = (vy & mask).reinterpret_simd();
1574 s0 = s0.dot_simd(wx, wy);
1575
1576 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1577 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1578 s1 = s1.dot_simd(wx, wy);
1579
1580 i += 16;
1581 }
1582
1583 let remainder = len / 2 - 4 * i;
1590
1591 if remainder > 0 {
1592 let vx =
1598 unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
1599 let vx: u32s = vx.reinterpret_simd();
1600
1601 let vy =
1605 unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
1606 let vy: u32s = vy.reinterpret_simd();
1607
1608 let wx: u8s = (vx & mask).reinterpret_simd();
1609 let wy: i8s = (vy & mask).reinterpret_simd();
1610 s0 = s0.dot_simd(wx, wy);
1611
1612 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1613 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1614 s1 = s1.dot_simd(wx, wy);
1615 }
1616
1617 s = (s0 + s1).sum_tree() as u32;
1618 i = (4 * i) + remainder;
1619 }
1620
1621 i *= 2;
1623
1624 debug_assert!(len - i <= 1);
1626 if i != len {
1627 let ix = unsafe { x.get_unchecked(i) } as u32;
1629 let iy = unsafe { y.get_unchecked(i) } as u32;
1631 s += ix * iy;
1632 }
1633
1634 Ok(MV::new(s))
1635 }
1636}
1637
1638#[cfg(target_arch = "x86_64")]
1652impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
1653 for InnerProduct
1654{
1655 #[inline(always)]
1656 fn run(
1657 self,
1658 arch: diskann_wide::arch::x86_64::V3,
1659 x: USlice<'_, 2>,
1660 y: USlice<'_, 2>,
1661 ) -> MathematicalResult<u32> {
1662 let len = check_lengths!(x, y)?;
1663
1664 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1665 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
1666 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1667
1668 let px_u32: *const u32 = x.as_ptr().cast();
1669 let py_u32: *const u32 = y.as_ptr().cast();
1670
1671 let mut i = 0;
1672 let mut s: u32 = 0;
1673
1674 let blocks = len / 16;
1676 if i < blocks {
1677 let mut s0 = i32s::default(arch);
1678 let mut s1 = i32s::default(arch);
1679 let mut s2 = i32s::default(arch);
1680 let mut s3 = i32s::default(arch);
1681 let mask = u32s::splat(arch, 0x00030003);
1682 while i + 8 < blocks {
1683 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1688
1689 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1693
1694 let wx: i16s = (vx & mask).reinterpret_simd();
1695 let wy: i16s = (vy & mask).reinterpret_simd();
1696 s0 = s0.dot_simd(wx, wy);
1697
1698 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1699 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1700 s1 = s1.dot_simd(wx, wy);
1701
1702 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1703 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1704 s2 = s2.dot_simd(wx, wy);
1705
1706 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1707 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1708 s3 = s3.dot_simd(wx, wy);
1709
1710 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1711 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1712 s0 = s0.dot_simd(wx, wy);
1713
1714 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1715 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1716 s1 = s1.dot_simd(wx, wy);
1717
1718 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1719 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1720 s2 = s2.dot_simd(wx, wy);
1721
1722 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1723 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1724 s3 = s3.dot_simd(wx, wy);
1725
1726 i += 8;
1727 }
1728
1729 let remainder = blocks - i;
1730
1731 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1737
1738 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1742 let wx: i16s = (vx & mask).reinterpret_simd();
1743 let wy: i16s = (vy & mask).reinterpret_simd();
1744 s0 = s0.dot_simd(wx, wy);
1745
1746 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1747 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1748 s1 = s1.dot_simd(wx, wy);
1749
1750 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1751 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1752 s2 = s2.dot_simd(wx, wy);
1753
1754 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1755 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1756 s3 = s3.dot_simd(wx, wy);
1757
1758 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1759 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1760 s0 = s0.dot_simd(wx, wy);
1761
1762 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1763 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1764 s1 = s1.dot_simd(wx, wy);
1765
1766 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1767 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1768 s2 = s2.dot_simd(wx, wy);
1769
1770 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1771 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1772 s3 = s3.dot_simd(wx, wy);
1773
1774 i += remainder;
1775
1776 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1777 }
1778
1779 i *= 16;
1781
1782 if i != len {
1784 #[inline(never)]
1786 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
1787 let mut s: u32 = 0;
1788 for i in from..x.len() {
1789 let ix = unsafe { x.get_unchecked(i) } as u32;
1791 let iy = unsafe { y.get_unchecked(i) } as u32;
1793 s += ix * iy;
1794 }
1795 s
1796 }
1797 s += fallback(x, y, i);
1798 }
1799
1800 Ok(MV::new(s))
1801 }
1802}
1803
1804#[cfg(target_arch = "aarch64")]
1805impl
1806 Target2<
1807 diskann_wide::arch::aarch64::Neon,
1808 MathematicalResult<u32>,
1809 USlice<'_, 4>,
1810 USlice<'_, 4>,
1811 > for InnerProduct
1812{
1813 #[inline(always)]
1814 fn run(
1815 self,
1816 arch: diskann_wide::arch::aarch64::Neon,
1817 x: USlice<'_, 4>,
1818 y: USlice<'_, 4>,
1819 ) -> MathematicalResult<u32> {
1820 let len = check_lengths!(x, y)?;
1821
1822 diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
1823 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
1824
1825 let px_u8: *const u8 = x.as_ptr().cast();
1826 let py_u8: *const u8 = y.as_ptr().cast();
1827
1828 let mut i = 0;
1829 let mut s: u32 = 0;
1830
1831 let bytes = len / 2;
1833 if i < bytes {
1834 let mut s0 = u32s::default(arch);
1835 let mut s1 = u32s::default(arch);
1836 let mask = u8s::splat(arch, 0x0f);
1837 while i + 16 <= bytes {
1838 let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
1841 let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
1844
1845 let first_x: u8s = x_vec & mask;
1848 let first_y: u8s = y_vec & mask;
1849 s0 = s0.dot_simd(first_x, first_y);
1850
1851 let second_x: u8s = (x_vec >> 4) & mask;
1853 let second_y: u8s = (y_vec >> 4) & mask;
1854 s1 = s1.dot_simd(second_x, second_y);
1855 i += 16;
1857 }
1858
1859 let remaining_bytes = len / 2 - i;
1860
1861 if remaining_bytes > 0 {
1862 let remaining_vec1 = remaining_bytes.min(16);
1863 let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_vec1) };
1867 let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_vec1) };
1870
1871 let first_x: u8s = x_vec & mask;
1872 let first_y: u8s = y_vec & mask;
1873 s0 = s0.dot_simd(first_x, first_y);
1874
1875 let second_x: u8s = (x_vec >> 4) & mask;
1877 let second_y: u8s = (y_vec >> 4) & mask;
1878 s1 = s1.dot_simd(second_x, second_y);
1879 i += remaining_bytes;
1880 }
1881 s = (s0 + s1).sum_tree();
1882 }
1883 i *= 2;
1885
1886 debug_assert!(len - i <= 1);
1888
1889 if i != len {
1890 let ix = unsafe { x.get_unchecked(i) } as i32;
1892 let iy = unsafe { y.get_unchecked(i) } as i32;
1894 s += (ix * iy) as u32;
1895 }
1896
1897 Ok(MV::new(s))
1898 }
1899}
1900
1901impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 1>, USlice<'_, 1>> for InnerProduct
1905where
1906 A: Architecture,
1907{
1908 #[inline(always)]
1909 fn run(self, _: A, x: USlice<'_, 1>, y: USlice<'_, 1>) -> MathematicalResult<u32> {
1910 bitvector_op::<Self, Unsigned>(x, y)
1911 }
1912}
1913
1914#[cfg(target_arch = "aarch64")]
1915impl
1916 Target2<
1917 diskann_wide::arch::aarch64::Neon,
1918 MathematicalResult<u32>,
1919 USlice<'_, 2>,
1920 USlice<'_, 2>,
1921 > for InnerProduct
1922{
1923 #[inline(always)]
1924 fn run(
1925 self,
1926 arch: diskann_wide::arch::aarch64::Neon,
1927 x: USlice<'_, 2>,
1928 y: USlice<'_, 2>,
1929 ) -> MathematicalResult<u32> {
1930 let len = check_lengths!(x, y)?;
1931
1932 diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
1933 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
1934
1935 let px_u8: *const u8 = x.as_ptr().cast();
1936 let py_u8: *const u8 = y.as_ptr().cast();
1937
1938 let mut i = 0;
1939 let mut s: u32 = 0;
1940
1941 let bytes = len / 4;
1943 if i < bytes {
1944 let mut s0 = u32s::default(arch);
1945 let mut s1 = u32s::default(arch);
1946 let mut s2 = u32s::default(arch);
1947 let mut s3 = u32s::default(arch);
1948 let mask = u8s::splat(arch, 0x03);
1949 while i + 16 <= bytes {
1950 let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
1952 let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
1954
1955 let first_x: u8s = x_vec & mask;
1956 let first_y: u8s = y_vec & mask;
1957 s0 = s0.dot_simd(first_x, first_y);
1958
1959 let second_x: u8s = (x_vec >> 2) & mask;
1960 let second_y: u8s = (y_vec >> 2) & mask;
1961 s1 = s1.dot_simd(second_x, second_y);
1962
1963 let third_x: u8s = (x_vec >> 4) & mask;
1964 let third_y: u8s = (y_vec >> 4) & mask;
1965 s2 = s2.dot_simd(third_x, third_y);
1966
1967 let fourth_x: u8s = (x_vec >> 6) & mask;
1968 let fourth_y: u8s = (y_vec >> 6) & mask;
1969 s3 = s3.dot_simd(fourth_x, fourth_y);
1970 i += 16;
1971 }
1972
1973 let remaining_bytes = bytes - i;
1974 if remaining_bytes > 0 {
1975 let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_bytes) };
1977 let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_bytes) };
1979
1980 let first_x: u8s = x_vec & mask;
1981 let first_y: u8s = y_vec & mask;
1982 s0 = s0.dot_simd(first_x, first_y);
1983
1984 let second_x: u8s = (x_vec >> 2) & mask;
1985 let second_y: u8s = (y_vec >> 2) & mask;
1986 s1 = s1.dot_simd(second_x, second_y);
1987
1988 let third_x: u8s = (x_vec >> 4) & mask;
1989 let third_y: u8s = (y_vec >> 4) & mask;
1990 s2 = s2.dot_simd(third_x, third_y);
1991
1992 let fourth_x: u8s = (x_vec >> 6) & mask;
1993 let fourth_y: u8s = (y_vec >> 6) & mask;
1994 s3 = s3.dot_simd(fourth_x, fourth_y);
1995 i += remaining_bytes;
1996 }
1997
1998 s = ((s0 + s1) + (s2 + s3)).sum_tree();
1999 }
2000
2001 i *= 4;
2003
2004 debug_assert!(len - i <= 3);
2006 if i != len {
2007 #[inline(never)]
2008 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
2009 let mut s: i32 = 0;
2010 for i in from..x.len() {
2011 let ix = unsafe { x.get_unchecked(i) } as i32;
2013 let iy = unsafe { y.get_unchecked(i) } as i32;
2015 s += ix * iy;
2016 }
2017 s as u32
2018 }
2019 s += fallback(x, y, i);
2020 }
2021
2022 Ok(MV::new(s))
2023 }
2024}
2025
2026macro_rules! impl_fallback_ip {
2028 (($N:literal, $M:literal)) => {
2029 impl Target2<diskann_wide::arch::Scalar, MathematicalResult<u32>, USlice<'_, $N>, USlice<'_, $M>> for InnerProduct {
2037 #[inline(never)]
2038 fn run(
2039 self,
2040 _: diskann_wide::arch::Scalar,
2041 x: USlice<'_, $N>,
2042 y: USlice<'_, $M>
2043 ) -> MathematicalResult<u32> {
2044 let len = check_lengths!(x, y)?;
2045
2046 let mut accum: u32 = 0;
2047 for i in 0..len {
2048 let ix = unsafe { x.get_unchecked(i) } as u32;
2050 let iy = unsafe { y.get_unchecked(i) } as u32;
2052 accum += ix * iy;
2053 }
2054 Ok(MV::new(accum))
2055 }
2056 }
2057 };
2058 ($N:literal) => {
2059 impl_fallback_ip!(($N, $N));
2060 };
2061 ($($args:tt),+ $(,)?) => {
2062 $(impl_fallback_ip!($args);)+
2063 };
2064}
2065
2066impl_fallback_ip!(7, 6, 5, 4, 3, 2, (8, 4), (8, 2), (8, 1));
2067
2068#[cfg(target_arch = "x86_64")]
2069retarget!(diskann_wide::arch::x86_64::V3, InnerProduct, 7, 6, 5, 3);
2070
2071#[cfg(target_arch = "x86_64")]
2072retarget!(
2073 diskann_wide::arch::x86_64::V4,
2074 InnerProduct,
2075 7,
2076 6,
2077 5,
2078 3,
2079 (8, 4),
2080 (8, 2),
2081 (8, 1)
2082);
2083
2084dispatch_pure!(
2085 InnerProduct,
2086 1,
2087 2,
2088 3,
2089 4,
2090 5,
2091 6,
2092 7,
2093 (8, 8),
2094 (8, 4),
2095 (8, 2),
2096 (8, 1)
2097);
2098
2099#[cfg(target_arch = "x86_64")]
2103impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 4>>
2104 for InnerProduct
2105{
2106 #[inline(always)]
2118 fn run(
2119 self,
2120 arch: diskann_wide::arch::x86_64::V3,
2121 x: USlice<'_, 8>,
2122 y: USlice<'_, 4>,
2123 ) -> MathematicalResult<u32> {
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!(i8s_32 = <diskann_wide::arch::x86_64::V3>::i8x32);
2130 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
2131
2132 let px: *const u8 = x.as_ptr();
2133 let py: *const u8 = y.as_ptr();
2134
2135 let mut i: usize = 0;
2136 let mut s: u32 = 0;
2137
2138 #[inline(always)]
2139 fn unpack_half(input: u8s_16) -> u8s_32 {
2140 let combined = diskann_wide::LoHi::new(input, input >> 4).zip::<u8s_32>();
2141 combined & u8s_32::splat(input.arch(), (1u8 << 4) - 1)
2142 }
2143
2144 let blocks = len / 32;
2146 if blocks > 0 {
2147 let mut acc = i32s::default(arch);
2148
2149 let products = |x: u8s_32, y: u8s_32| -> i16s {
2150 let signed_y = i8s_32::from_underlying(arch, y.to_underlying());
2151 arch.mm256_maddubs_epi16(x, signed_y)
2152 };
2153
2154 let ones = i16s::splat(arch, 1);
2155
2156 while i + 4 <= blocks {
2162 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * i)) };
2166 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * i)) };
2168 let m0 = products(vx, unpack_half(vy));
2169
2170 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 1))) };
2173 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 1))) };
2175 let m1 = products(vx, unpack_half(vy));
2176
2177 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 2))) };
2180 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 2))) };
2182 let m2 = products(vx, unpack_half(vy));
2183
2184 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 3))) };
2187 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 3))) };
2189 let m3 = products(vx, unpack_half(vy));
2190
2191 acc = acc.dot_simd(m0 + m1 + m2 + m3, ones);
2192 i += 4;
2193 }
2194
2195 while i < blocks {
2197 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * i)) };
2200 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * i)) };
2202 acc = acc.dot_simd(products(vx, unpack_half(vy)), ones);
2203 i += 1;
2204 }
2205
2206 s = acc.sum_tree() as u32;
2207 }
2208
2209 i *= 32;
2211
2212 if i != len {
2214 #[inline(never)]
2215 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 4>, from: usize) -> u32 {
2216 let mut s: u32 = 0;
2217 for i in from..x.len() {
2218 let ix = unsafe { x.get_unchecked(i) } as u32;
2220 let iy = unsafe { y.get_unchecked(i) } as u32;
2222 s += ix * iy;
2223 }
2224 s
2225 }
2226 s += fallback(x, y, i);
2227 }
2228
2229 Ok(MV::new(s))
2230 }
2231}
2232
2233#[cfg(target_arch = "x86_64")]
2234impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 2>>
2235 for InnerProduct
2236{
2237 #[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
2259 let len = check_lengths!(x, y)?;
2260
2261 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
2262 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::x86_64::V3>::u8x16);
2263 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
2264 diskann_wide::alias!(i8s_32 = <diskann_wide::arch::x86_64::V3>::i8x32);
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 let signed_y = i8s_32::from_underlying(arch, y.to_underlying());
2280 arch.mm256_maddubs_epi16(x, signed_y)
2281 };
2282
2283 #[inline(always)]
2284 fn unpack_sub<const N: u8>(input: u8s_16) -> u8s_32 {
2285 let combined = diskann_wide::LoHi::new(input, input >> N).zip::<u8s_32>();
2286 combined & u8s_32::splat(input.arch(), (1u8 << N) - 1)
2287 }
2288
2289 let unpack_crumbs = |x: u8s_16| -> (u8s_32, u8s_32) {
2290 let nibbles = unpack_sub::<4>(x);
2292
2293 let diskann_wide::LoHi { lo, hi } = nibbles.split();
2296 let lower = unpack_sub::<2>(lo);
2297 let upper = unpack_sub::<2>(hi);
2298
2299 (lower, upper)
2300 };
2301
2302 let ones = i16s::splat(arch, 1);
2303
2304 while i + 4 <= blocks {
2311 let (vx0, vx1, (vy0, vy1)) = unsafe {
2315 (
2316 u8s_32::load_simd(arch, px.add(64 * i)),
2317 u8s_32::load_simd(arch, px.add(64 * i + 32)),
2318 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * i))),
2319 )
2320 };
2321 let m0a = products(vx0, vy0);
2322 let m0b = products(vx1, vy1);
2323
2324 let (vx0, vx1, (vy0, vy1)) = unsafe {
2327 (
2328 u8s_32::load_simd(arch, px.add(64 * (i + 1))),
2329 u8s_32::load_simd(arch, px.add(64 * (i + 1) + 32)),
2330 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 1)))),
2331 )
2332 };
2333 let m1a = products(vx0, vy0);
2334 let m1b = products(vx1, vy1);
2335
2336 let (vx0, vx1, (vy0, vy1)) = unsafe {
2339 (
2340 u8s_32::load_simd(arch, px.add(64 * (i + 2))),
2341 u8s_32::load_simd(arch, px.add(64 * (i + 2) + 32)),
2342 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 2)))),
2343 )
2344 };
2345 let m2a = products(vx0, vy0);
2346 let m2b = products(vx1, vy1);
2347
2348 let (vx0, vx1, (vy0, vy1)) = unsafe {
2351 (
2352 u8s_32::load_simd(arch, px.add(64 * (i + 3))),
2353 u8s_32::load_simd(arch, px.add(64 * (i + 3) + 32)),
2354 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 3)))),
2355 )
2356 };
2357 let m3a = products(vx0, vy0);
2358 let m3b = products(vx1, vy1);
2359
2360 acc = acc.dot_simd((m0a + m0b + m1a + m1b) + (m2a + m2b + m3a + m3b), ones);
2361 i += 4;
2362 }
2363
2364 while i < blocks {
2366 let (vx0, vx1, (vy0, vy1)) = unsafe {
2369 (
2370 u8s_32::load_simd(arch, px.add(64 * i)),
2371 u8s_32::load_simd(arch, px.add(64 * i + 32)),
2372 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * i))),
2373 )
2374 };
2375 acc = acc.dot_simd(products(vx0, vy0) + products(vx1, vy1), ones);
2376 i += 1;
2377 }
2378
2379 s = acc.sum_tree() as u32;
2380 }
2381
2382 i *= 64;
2384
2385 if i != len {
2387 #[inline(never)]
2388 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 2>, from: usize) -> u32 {
2389 let mut s: u32 = 0;
2390 for i in from..x.len() {
2391 let (ix, iy) =
2393 unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2394 s += ix * iy;
2395 }
2396 s
2397 }
2398 s += fallback(x, y, i);
2399 }
2400
2401 Ok(MV::new(s))
2402 }
2403}
2404
2405#[cfg(target_arch = "x86_64")]
2406impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 1>>
2407 for InnerProduct
2408{
2409 #[inline(always)]
2423 fn run(
2424 self,
2425 arch: diskann_wide::arch::x86_64::V3,
2426 x: USlice<'_, 8>,
2427 y: USlice<'_, 1>,
2428 ) -> MathematicalResult<u32> {
2429 use diskann_wide::{FromInt, SIMDMask};
2430 use std::arch::x86_64::_mm256_sad_epu8;
2431
2432 let len = check_lengths!(x, y)?;
2433
2434 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
2435 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
2436
2437 type Mask32 = diskann_wide::BitMask<32, diskann_wide::arch::x86_64::V3>;
2438 type Mask8x32 = diskann_wide::arch::x86_64::v3::masks::mask8x32;
2439
2440 let px: *const u8 = x.as_ptr();
2441 let py: *const u8 = y.as_ptr();
2442
2443 let mut i: usize = 0;
2444 let mut s: u32 = 0;
2445
2446 let blocks = len / 32;
2448 if blocks > 0 {
2449 let mut acc = i32s::default(arch);
2450 let zero = u8s_32::default(arch);
2451
2452 let masked_sad = |vx: u8s_32, bits: u32| -> i32s {
2458 let byte_mask: Mask8x32 = Mask32::from_int(arch, bits).into();
2460
2461 let masked = vx & u8s_32::from_underlying(arch, byte_mask.to_underlying());
2463
2464 i32s::from_underlying(arch, unsafe {
2467 _mm256_sad_epu8(masked.to_underlying(), zero.to_underlying())
2468 })
2469 };
2470
2471 while i + 4 <= blocks {
2473 let s0 = unsafe {
2476 let vx = u8s_32::load_simd(arch, px.add(32 * i));
2477 let bits = (py.add(4 * i) as *const u32).read_unaligned();
2478 masked_sad(vx, bits)
2479 };
2480
2481 let s1 = unsafe {
2483 let vx = u8s_32::load_simd(arch, px.add(32 * (i + 1)));
2484 let bits = (py.add(4 * (i + 1)) as *const u32).read_unaligned();
2485 masked_sad(vx, bits)
2486 };
2487
2488 let s2 = unsafe {
2490 let vx = u8s_32::load_simd(arch, px.add(32 * (i + 2)));
2491 let bits = (py.add(4 * (i + 2)) as *const u32).read_unaligned();
2492 masked_sad(vx, bits)
2493 };
2494
2495 let s3 = unsafe {
2497 let vx = u8s_32::load_simd(arch, px.add(32 * (i + 3)));
2498 let bits = (py.add(4 * (i + 3)) as *const u32).read_unaligned();
2499 masked_sad(vx, bits)
2500 };
2501
2502 acc = acc + s0 + s1 + s2 + s3;
2503 i += 4;
2504 }
2505
2506 while i < blocks {
2508 let si = unsafe {
2511 let vx = u8s_32::load_simd(arch, px.add(32 * i));
2512 let bits = (py.add(4 * i) as *const u32).read_unaligned();
2513 masked_sad(vx, bits)
2514 };
2515 acc = acc + si;
2516 i += 1;
2517 }
2518
2519 s = acc.sum_tree() as u32;
2520 }
2521
2522 i *= 32;
2524
2525 if i != len {
2527 #[inline(never)]
2528 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 1>, from: usize) -> u32 {
2529 let mut s: u32 = 0;
2530 for i in from..x.len() {
2531 let (ix, iy) =
2533 unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2534 s += ix * iy;
2535 }
2536 s
2537 }
2538 s += fallback(x, y, i);
2539 }
2540
2541 Ok(MV::new(s))
2542 }
2543}
2544
2545#[cfg(target_arch = "aarch64")]
2546impl
2547 Target2<
2548 diskann_wide::arch::aarch64::Neon,
2549 MathematicalResult<u32>,
2550 USlice<'_, 8>,
2551 USlice<'_, 4>,
2552 > for InnerProduct
2553{
2554 #[inline(always)]
2555 fn run(
2556 self,
2557 arch: diskann_wide::arch::aarch64::Neon,
2558 x: USlice<'_, 8>,
2559 y: USlice<'_, 4>,
2560 ) -> MathematicalResult<u32> {
2561 let len = check_lengths!(x, y)?;
2563
2564 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::aarch64::Neon>::u8x16);
2565 #[expect(non_camel_case_types)]
2566 type u8s_8 = diskann_wide::arch::aarch64::u8x8;
2567 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
2568
2569 let px_u8: *const u8 = x.as_ptr().cast();
2570 let py_u8: *const u8 = y.as_ptr().cast();
2571
2572 let mut i = 0;
2573 let mut s: u32 = 0;
2574
2575 let y_bytes = len / 2;
2577 if i < y_bytes {
2578 let mut s0 = u32s::default(arch);
2579
2580 #[inline(always)]
2581 fn split_and_zip(input: u8s_8, arch: diskann_wide::arch::aarch64::Neon) -> u8s_16 {
2582 use diskann_wide::{LoHi, ZipUnzip};
2583 let lo = input;
2584 let hi = input >> 4;
2585 let halves = LoHi::new(lo, hi);
2586 let zipped_halves = u8s_16::zip(halves);
2587 zipped_halves & u8s_16::splat(arch, 0x0f)
2588 }
2589
2590 while i + 8 <= y_bytes {
2591 let y_vec = split_and_zip(unsafe { u8s_8::load_simd(arch, py_u8.add(i)) }, arch);
2593 let x_vec = unsafe { u8s_16::load_simd(arch, px_u8.add(i * 2)) };
2596
2597 s0 = s0.dot_simd(x_vec, y_vec);
2599
2600 i += 8;
2602 }
2603
2604 let remaining_bytes = len / 2 - i;
2605
2606 if remaining_bytes > 0 {
2607 let x_vec =
2609 unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 2), remaining_bytes * 2) };
2610
2611 let y_vec = split_and_zip(
2613 unsafe { u8s_8::load_simd_first(arch, py_u8.add(i), remaining_bytes) },
2614 arch,
2615 );
2616
2617 s0 = s0.dot_simd(x_vec, y_vec);
2618 i += remaining_bytes;
2619 }
2620 s = s0.sum_tree();
2621 }
2622
2623 i *= 2;
2625
2626 debug_assert!(len - i <= 1);
2628 if i != len {
2629 let ix = unsafe { x.get_unchecked(i) } as u32;
2631 let iy = unsafe { y.get_unchecked(i) } as u32;
2633 s += ix * iy;
2634 }
2635
2636 Ok(MV::new(s))
2637 }
2638}
2639
2640#[cfg(target_arch = "aarch64")]
2641impl
2642 Target2<
2643 diskann_wide::arch::aarch64::Neon,
2644 MathematicalResult<u32>,
2645 USlice<'_, 8>,
2646 USlice<'_, 2>,
2647 > for InnerProduct
2648{
2649 #[inline(always)]
2650 fn run(
2651 self,
2652 arch: diskann_wide::arch::aarch64::Neon,
2653 x: USlice<'_, 8>,
2654 y: USlice<'_, 2>,
2655 ) -> MathematicalResult<u32> {
2656 let len = check_lengths!(x, y)?;
2657
2658 #[expect(non_camel_case_types)]
2659 type u8s_8 = diskann_wide::arch::aarch64::u8x8;
2660 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::aarch64::Neon>::u8x16);
2661 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::aarch64::Neon>::u8x32);
2662 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
2663
2664 let px_u8: *const u8 = x.as_ptr().cast();
2665 let py_u8: *const u8 = y.as_ptr().cast();
2666
2667 let mut i = 0;
2668 let mut s: u32 = 0;
2669
2670 #[inline(always)]
2671 fn split_and_zip_four_bit(input: u8s_8, arch: diskann_wide::arch::aarch64::Neon) -> u8s_16 {
2672 use diskann_wide::{LoHi, ZipUnzip};
2673 let lo = input;
2674 let hi = input >> 4;
2675 let halves = LoHi::new(lo, hi);
2676 let zipped_halves = u8s_16::zip(halves);
2677 zipped_halves & u8s_16::splat(arch, 0x0f)
2678 }
2679
2680 #[inline(always)]
2681 fn split_and_zip_two_bits(
2682 input: u8s_8,
2683 arch: diskann_wide::arch::aarch64::Neon,
2684 ) -> (u8s_16, u8s_16) {
2685 use diskann_wide::{LoHi, SplitJoin, ZipUnzip};
2686 let four_bit_split = split_and_zip_four_bit(input, arch);
2687 let lo = four_bit_split;
2688 let hi = four_bit_split >> 2;
2689 let zipped = u8s_32::zip(LoHi::new(lo, hi));
2690 let LoHi { lo, hi } = zipped.split();
2691 let mask = u8s_16::splat(arch, 0x03);
2692 (lo & mask, hi & mask)
2693 }
2694
2695 let y_bytes = len / 4; if i < y_bytes {
2697 let mut s0 = u32s::default(arch);
2698 let mut s1 = u32s::default(arch);
2699
2700 while i + 8 <= y_bytes {
2701 let (y_vec1, y_vec2) =
2703 split_and_zip_two_bits(unsafe { u8s_8::load_simd(arch, py_u8.add(i)) }, arch);
2704
2705 let x_vec1 = unsafe { u8s_16::load_simd(arch, px_u8.add(4 * i)) };
2708 let x_vec2 = unsafe { u8s_16::load_simd(arch, px_u8.add(4 * i + 16)) };
2710
2711 s0 = s0.dot_simd(x_vec1, y_vec1);
2713
2714 s1 = s1.dot_simd(x_vec2, y_vec2);
2716
2717 i += 8;
2719 }
2720
2721 let remaining_y_bytes = len / 4 - i;
2722 let remaining_x_bytes = remaining_y_bytes * 4;
2723
2724 if remaining_y_bytes > 0 {
2725 let (y_vec1, y_vec2) = split_and_zip_two_bits(
2727 unsafe { u8s_8::load_simd_first(arch, py_u8.add(i), remaining_y_bytes) },
2728 arch,
2729 );
2730
2731 let x_first = remaining_x_bytes.min(16);
2734 let x_second = remaining_x_bytes.saturating_sub(16);
2735
2736 let x_vec1 = unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 4), x_first) };
2738 s0 = s0.dot_simd(x_vec1, y_vec1);
2739
2740 if x_second > 0 {
2741 let x_vec2 =
2743 unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 4 + 16), x_second) };
2744 s1 = s1.dot_simd(x_vec2, y_vec2);
2745 }
2746 i += remaining_y_bytes;
2747 }
2748 s = (s0 + s1).sum_tree();
2749 }
2750
2751 i *= 4;
2753
2754 debug_assert!(len - i <= 3);
2756 if i != len {
2757 #[inline(never)]
2758 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 2>, from: usize) -> u32 {
2759 let mut s: u32 = 0;
2760 for i in from..x.len() {
2761 let (ix, iy) =
2763 unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2764 s += ix * iy;
2765 }
2766 s
2767 }
2768 s += fallback(x, y, i)
2769 }
2770
2771 Ok(MV::new(s))
2772 }
2773}
2774
2775#[cfg(target_arch = "aarch64")]
2776impl
2777 Target2<
2778 diskann_wide::arch::aarch64::Neon,
2779 MathematicalResult<u32>,
2780 USlice<'_, 8>,
2781 USlice<'_, 1>,
2782 > for InnerProduct
2783{
2784 #[inline(always)]
2785 fn run(
2786 self,
2787 arch: diskann_wide::arch::aarch64::Neon,
2788 x: USlice<'_, 8>,
2789 y: USlice<'_, 1>,
2790 ) -> MathematicalResult<u32> {
2791 let len = check_lengths!(x, y)?;
2793
2794 #[expect(non_camel_case_types)]
2795 type u8s_8 = diskann_wide::arch::aarch64::u8x8;
2796 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::aarch64::Neon>::u8x16);
2797 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::aarch64::Neon>::u8x32);
2798 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
2799
2800 let px_u8: *const u8 = x.as_ptr().cast();
2801 let py_u8: *const u8 = y.as_ptr().cast();
2802
2803 let mut i = 0;
2804 let mut s: u32 = 0;
2805
2806 #[inline(always)]
2807 fn split_and_zip_four_bits(
2808 input: u8s_8,
2809 arch: diskann_wide::arch::aarch64::Neon,
2810 ) -> u8s_16 {
2811 use diskann_wide::{LoHi, ZipUnzip};
2812 let lo = input;
2813 let hi = input >> 4;
2814 let halves = LoHi::new(lo, hi);
2815 let zipped_halves = u8s_16::zip(halves);
2816 zipped_halves & u8s_16::splat(arch, 0x0f)
2817 }
2818
2819 #[inline(always)]
2820 fn split_and_zip_two_bits(
2821 input: u8s_8,
2822 arch: diskann_wide::arch::aarch64::Neon,
2823 ) -> (u8s_16, u8s_16) {
2824 use diskann_wide::{LoHi, SplitJoin, ZipUnzip};
2825 let four_bit_split = split_and_zip_four_bits(input, arch);
2826 let lo = four_bit_split;
2827 let hi = four_bit_split >> 2;
2828 let zipped = u8s_32::zip(LoHi::new(lo, hi));
2829 let LoHi { lo, hi } = zipped.split();
2830 let mask = u8s_16::splat(arch, 0x03);
2831 (lo & mask, hi & mask)
2832 }
2833
2834 #[inline(always)]
2835 fn split_and_zip_one_bit(
2836 input: u8s_8,
2837 arch: diskann_wide::arch::aarch64::Neon,
2838 ) -> (u8s_16, u8s_16, u8s_16, u8s_16) {
2839 use diskann_wide::{LoHi, SplitJoin, ZipUnzip};
2840 let (lower_two_bits, upper_two_bits) = split_and_zip_two_bits(input, arch);
2841 let vec1_lo = lower_two_bits;
2842 let vec1_hi = lower_two_bits >> 1;
2843 let vec2_lo = upper_two_bits;
2844 let vec2_hi = upper_two_bits >> 1;
2845 let vec1_zipped = u8s_32::zip(LoHi::new(vec1_lo, vec1_hi));
2846 let vec2_zipped = u8s_32::zip(LoHi::new(vec2_lo, vec2_hi));
2847 let LoHi {
2848 lo: vec1_lo,
2849 hi: vec1_hi,
2850 } = vec1_zipped.split();
2851 let LoHi {
2852 lo: vec2_lo,
2853 hi: vec2_hi,
2854 } = vec2_zipped.split();
2855 let mask = u8s_16::splat(arch, 0x01);
2856 (
2857 vec1_lo & mask,
2858 vec1_hi & mask,
2859 vec2_lo & mask,
2860 vec2_hi & mask,
2861 )
2862 }
2863
2864 let y_bytes = len / 8; if i < y_bytes {
2866 let mut s0 = u32s::default(arch);
2867 let mut s1 = u32s::default(arch);
2868 let mut s2 = u32s::default(arch);
2869 let mut s3 = u32s::default(arch);
2870
2871 while i + 8 <= y_bytes {
2872 let (y_vec1, y_vec2, y_vec3, y_vec4) =
2874 split_and_zip_one_bit(unsafe { u8s_8::load_simd(arch, py_u8.add(i)) }, arch);
2875
2876 let x_vec1 = unsafe { u8s_16::load_simd(arch, px_u8.add(8 * i)) };
2879 let x_vec2 = unsafe { u8s_16::load_simd(arch, px_u8.add(8 * i + 16)) };
2881 let x_vec3 = unsafe { u8s_16::load_simd(arch, px_u8.add(8 * i + 32)) };
2883 let x_vec4 = unsafe { u8s_16::load_simd(arch, px_u8.add(8 * i + 48)) };
2885
2886 s0 = s0.dot_simd(x_vec1, y_vec1);
2888
2889 s1 = s1.dot_simd(x_vec2, y_vec2);
2891
2892 s2 = s2.dot_simd(x_vec3, y_vec3);
2894
2895 s3 = s3.dot_simd(x_vec4, y_vec4);
2897
2898 i += 8;
2900 }
2901
2902 let remaining_y_bytes = len / 8 - i;
2903 let remaining_x_bytes = remaining_y_bytes * 8;
2904
2905 if remaining_y_bytes > 0 {
2906 let (y_vec1, y_vec2, y_vec3, y_vec4) = split_and_zip_one_bit(
2908 unsafe { u8s_8::load_simd_first(arch, py_u8.add(i), remaining_y_bytes) },
2909 arch,
2910 );
2911
2912 let x_first = remaining_x_bytes.min(16);
2915 let x_second = remaining_x_bytes.saturating_sub(16).min(16);
2916 let x_third = remaining_x_bytes.saturating_sub(32).min(16);
2917 let x_fourth = remaining_x_bytes.saturating_sub(48).min(16);
2918
2919 let x_vec1 = unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 8), x_first) };
2921 s0 = s0.dot_simd(x_vec1, y_vec1);
2922
2923 if x_second > 0 {
2924 let x_vec2 =
2926 unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 8 + 16), x_second) };
2927 s1 = s1.dot_simd(x_vec2, y_vec2);
2928 }
2929
2930 if x_third > 0 {
2931 let x_vec3 =
2933 unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 8 + 32), x_third) };
2934 s2 = s2.dot_simd(x_vec3, y_vec3);
2935 }
2936
2937 if x_fourth > 0 {
2938 let x_vec4 =
2940 unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 8 + 48), x_fourth) };
2941 s3 = s3.dot_simd(x_vec4, y_vec4);
2942 }
2943
2944 i += remaining_y_bytes;
2945 }
2946 s = ((s0 + s1) + (s2 + s3)).sum_tree();
2947 }
2948
2949 i *= 8;
2951
2952 debug_assert!(len - i <= 7);
2954 if i != len {
2955 #[inline(never)]
2956 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 1>, from: usize) -> u32 {
2957 let mut s: u32 = 0;
2958 for i in from..x.len() {
2959 let (ix, iy) =
2961 unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2962 s += ix * iy;
2963 }
2964 s
2965 }
2966 s += fallback(x, y, i)
2967 }
2968
2969 Ok(MV::new(s))
2970 }
2971}
2972
2973#[cfg(target_arch = "aarch64")]
2974retarget!(diskann_wide::arch::aarch64::Neon, InnerProduct, 7, 6, 5, 3);
2975
2976impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 4, BitTranspose>, USlice<'_, 1, Dense>>
3005 for InnerProduct
3006where
3007 A: Architecture,
3008{
3009 #[inline(always)]
3010 fn run(
3011 self,
3012 _: A,
3013 x: USlice<'_, 4, BitTranspose>,
3014 y: USlice<'_, 1, Dense>,
3015 ) -> MathematicalResult<u32> {
3016 let len = check_lengths!(x, y)?;
3017
3018 let px: *const u64 = x.as_ptr().cast();
3025 let py: *const u64 = y.as_ptr().cast();
3026
3027 let mut i = 0;
3028 let mut s: u32 = 0;
3029
3030 let blocks = len / 64;
3031 while i < blocks {
3032 let bits = unsafe { py.add(i).read_unaligned() };
3034
3035 let b0 = unsafe { px.add(4 * i).read_unaligned() };
3041 s += (bits & b0).count_ones();
3042
3043 let b1 = unsafe { px.add(4 * i + 1).read_unaligned() };
3045 s += (bits & b1).count_ones() << 1;
3046
3047 let b2 = unsafe { px.add(4 * i + 2).read_unaligned() };
3049 s += (bits & b2).count_ones() << 2;
3050
3051 let b3 = unsafe { px.add(4 * i + 3).read_unaligned() };
3053 s += (bits & b3).count_ones() << 3;
3054
3055 i += 1;
3056 }
3057
3058 if 64 * i == len {
3060 return Ok(MV::new(s));
3061 }
3062
3063 let k = i * 8;
3065
3066 let py = unsafe { py.cast::<u8>().add(k) };
3071 let bytes_remaining = y.bytes() - k;
3072 let mut bits: u64 = 0;
3073
3074 for j in 0..bytes_remaining.min(8) {
3077 bits += (unsafe { py.add(j).read() } as u64) << (8 * j);
3080 }
3081
3082 bits &= (0x01u64 << (len - (64 * i))) - 1;
3085
3086 let b0 = unsafe { px.add(4 * i).read_unaligned() };
3091 s += (bits & b0).count_ones();
3092
3093 let b1 = unsafe { px.add(4 * i + 1).read_unaligned() };
3095 s += (bits & b1).count_ones() << 1;
3096
3097 let b2 = unsafe { px.add(4 * i + 2).read_unaligned() };
3099 s += (bits & b2).count_ones() << 2;
3100
3101 let b3 = unsafe { px.add(4 * i + 3).read_unaligned() };
3103 s += (bits & b3).count_ones() << 3;
3104
3105 Ok(MV::new(s))
3106 }
3107}
3108
3109impl
3110 PureDistanceFunction<USlice<'_, 4, BitTranspose>, USlice<'_, 1, Dense>, MathematicalResult<u32>>
3111 for InnerProduct
3112{
3113 fn evaluate(
3114 x: USlice<'_, 4, BitTranspose>,
3115 y: USlice<'_, 1, Dense>,
3116 ) -> MathematicalResult<u32> {
3117 (diskann_wide::ARCH).run2(Self, x, y)
3118 }
3119}
3120
3121#[cfg(target_arch = "x86_64")]
3152impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 1>>
3153 for InnerProduct
3154{
3155 #[inline(always)]
3156 fn run(
3157 self,
3158 arch: diskann_wide::arch::x86_64::V3,
3159 x: &[f32],
3160 y: USlice<'_, 1>,
3161 ) -> MathematicalResult<f32> {
3162 let len = check_lengths!(x, y)?;
3163
3164 use std::arch::x86_64::*;
3165
3166 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
3167 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
3168
3169 let values = f32s::from_array(arch, [0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
3172
3173 let variable_shifts = u32s::from_array(arch, [0, 1, 2, 3, 4, 5, 6, 7]);
3175
3176 let px: *const f32 = x.as_ptr();
3177 let py: *const u32 = y.as_ptr().cast();
3178
3179 let mut i = 0;
3180 let mut s = f32s::default(arch);
3181
3182 let prep = |v: u32| -> u32s { u32s::splat(arch, v) >> variable_shifts };
3183 let to_f32 = |v: u32s| -> f32s {
3184 f32s::from_underlying(arch, unsafe {
3187 _mm256_permutevar_ps(values.to_underlying(), v.to_underlying())
3188 })
3189 };
3190
3191 let blocks = len / 32;
3193 if i < blocks {
3194 let mut s0 = f32s::default(arch);
3195 let mut s1 = f32s::default(arch);
3196
3197 while i < blocks {
3198 let iy = prep(unsafe { py.add(i).read_unaligned() });
3200
3201 let ix0 = unsafe { f32s::load_simd(arch, px.add(32 * i)) };
3203 let ix1 = unsafe { f32s::load_simd(arch, px.add(32 * i + 8)) };
3205 let ix2 = unsafe { f32s::load_simd(arch, px.add(32 * i + 16)) };
3207 let ix3 = unsafe { f32s::load_simd(arch, px.add(32 * i + 24)) };
3209
3210 s0 = ix0.mul_add_simd(to_f32(iy), s0);
3211 s1 = ix1.mul_add_simd(to_f32(iy >> 8), s1);
3212 s0 = ix2.mul_add_simd(to_f32(iy >> 16), s0);
3213 s1 = ix3.mul_add_simd(to_f32(iy >> 24), s1);
3214
3215 i += 1;
3216 }
3217 s = s0 + s1;
3218 }
3219
3220 let remainder = len % 32;
3221 if remainder != 0 {
3222 let tail = if len % 8 == 0 { 8 } else { len % 8 };
3223
3224 let py = unsafe { py.add(blocks) };
3227
3228 if remainder <= 8 {
3229 unsafe {
3232 load_one(py, |iy| {
3233 let iy = prep(iy);
3234 let ix = f32s::load_simd_first(arch, px.add(32 * blocks), tail);
3235 s = ix.mul_add_simd(to_f32(iy), s);
3236 })
3237 }
3238 } else if remainder <= 16 {
3239 unsafe {
3242 load_two(py, |iy| {
3243 let iy = prep(iy);
3244 let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
3245 let ix1 = f32s::load_simd_first(arch, px.add(32 * blocks + 8), tail);
3246 s = ix0.mul_add_simd(to_f32(iy), s);
3247 s = ix1.mul_add_simd(to_f32(iy >> 8), s);
3248 })
3249 }
3250 } else if remainder <= 24 {
3251 unsafe {
3254 load_three(py, |iy| {
3255 let iy = prep(iy);
3256
3257 let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
3258 let ix1 = f32s::load_simd(arch, px.add(32 * blocks + 8));
3259 let ix2 = f32s::load_simd_first(arch, px.add(32 * blocks + 16), tail);
3260
3261 s = ix0.mul_add_simd(to_f32(iy), s);
3262 s = ix1.mul_add_simd(to_f32(iy >> 8), s);
3263 s = ix2.mul_add_simd(to_f32(iy >> 16), s);
3264 })
3265 }
3266 } else {
3267 unsafe {
3270 load_four(py, |iy| {
3271 let iy = prep(iy);
3272
3273 let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
3274 let ix1 = f32s::load_simd(arch, px.add(32 * blocks + 8));
3275 let ix2 = f32s::load_simd(arch, px.add(32 * blocks + 16));
3276 let ix3 = f32s::load_simd_first(arch, px.add(32 * blocks + 24), tail);
3277
3278 s = ix0.mul_add_simd(to_f32(iy), s);
3279 s = ix1.mul_add_simd(to_f32(iy >> 8), s);
3280 s = ix2.mul_add_simd(to_f32(iy >> 16), s);
3281 s = ix3.mul_add_simd(to_f32(iy >> 24), s);
3282 })
3283 }
3284 }
3285 }
3286
3287 Ok(MV::new(s.sum_tree()))
3288 }
3289}
3290
3291#[cfg(target_arch = "x86_64")]
3295impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 2>>
3296 for InnerProduct
3297{
3298 #[inline(always)]
3299 fn run(
3300 self,
3301 arch: diskann_wide::arch::x86_64::V3,
3302 x: &[f32],
3303 y: USlice<'_, 2>,
3304 ) -> MathematicalResult<f32> {
3305 let len = check_lengths!(x, y)?;
3306
3307 use std::arch::x86_64::*;
3308
3309 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
3310 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
3311
3312 let values = f32s::from_array(arch, [0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0]);
3316
3317 let variable_shifts = u32s::from_array(arch, [0, 2, 4, 6, 8, 10, 12, 14]);
3319
3320 let px: *const f32 = x.as_ptr();
3321 let py: *const u32 = y.as_ptr().cast();
3322
3323 let mut i = 0;
3324 let mut s = f32s::default(arch);
3325
3326 let prep = |v: u32| -> u32s { u32s::splat(arch, v) >> variable_shifts };
3327 let to_f32 = |v: u32s| -> f32s {
3328 f32s::from_underlying(arch, unsafe {
3331 _mm256_permutevar_ps(values.to_underlying(), v.to_underlying())
3332 })
3333 };
3334
3335 let blocks = len / 16;
3336 if blocks != 0 {
3337 let mut s0 = f32s::default(arch);
3338 let mut s1 = f32s::default(arch);
3339
3340 while i + 2 <= blocks {
3342 let iy = prep(unsafe { py.add(i).read_unaligned() });
3345
3346 let (ix0, ix1) = unsafe {
3349 (
3350 f32s::load_simd(arch, px.add(16 * i)),
3351 f32s::load_simd(arch, px.add(16 * i + 8)),
3352 )
3353 };
3354
3355 s0 = ix0.mul_add_simd(to_f32(iy), s0);
3356 s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
3357
3358 let iy = prep(unsafe { py.add(i + 1).read_unaligned() });
3361
3362 let (ix0, ix1) = unsafe {
3364 (
3365 f32s::load_simd(arch, px.add(16 * (i + 1))),
3366 f32s::load_simd(arch, px.add(16 * (i + 1) + 8)),
3367 )
3368 };
3369
3370 s0 = ix0.mul_add_simd(to_f32(iy), s0);
3371 s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
3372
3373 i += 2;
3374 }
3375
3376 if i < blocks {
3378 let iy = prep(unsafe { py.add(i).read_unaligned() });
3381
3382 let (ix0, ix1) = unsafe {
3384 (
3385 f32s::load_simd(arch, px.add(16 * i)),
3386 f32s::load_simd(arch, px.add(16 * i + 8)),
3387 )
3388 };
3389
3390 s0 = ix0.mul_add_simd(to_f32(iy), s0);
3391 s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
3392 }
3393
3394 s = s0 + s1;
3395 }
3396
3397 let remainder = len % 16;
3398 if remainder != 0 {
3399 let tail = if len % 8 == 0 { 8 } else { len % 8 };
3400 let py = unsafe { py.add(blocks) };
3403
3404 if remainder <= 4 {
3405 unsafe {
3408 load_one(py, |iy| {
3409 let iy = prep(iy);
3410 let ix = f32s::load_simd_first(arch, px.add(16 * blocks), tail);
3411 s = ix.mul_add_simd(to_f32(iy), s);
3412 });
3413 }
3414 } else if remainder <= 8 {
3415 unsafe {
3418 load_two(py, |iy| {
3419 let iy = prep(iy);
3420 let ix = f32s::load_simd_first(arch, px.add(16 * blocks), tail);
3421 s = ix.mul_add_simd(to_f32(iy), s);
3422 });
3423 }
3424 } else if remainder <= 12 {
3425 unsafe {
3428 load_three(py, |iy| {
3429 let iy = prep(iy);
3430 let ix0 = f32s::load_simd(arch, px.add(16 * blocks));
3431 let ix1 = f32s::load_simd_first(arch, px.add(16 * blocks + 8), tail);
3432 s = ix0.mul_add_simd(to_f32(iy), s);
3433 s = ix1.mul_add_simd(to_f32(iy >> 16), s);
3434 });
3435 }
3436 } else {
3437 unsafe {
3440 load_four(py, |iy| {
3441 let iy = prep(iy);
3442 let ix0 = f32s::load_simd(arch, px.add(16 * blocks));
3443 let ix1 = f32s::load_simd_first(arch, px.add(16 * blocks + 8), tail);
3444 s = ix0.mul_add_simd(to_f32(iy), s);
3445 s = ix1.mul_add_simd(to_f32(iy >> 16), s);
3446 });
3447 }
3448 }
3449 }
3450
3451 Ok(MV::new(s.sum_tree()))
3452 }
3453}
3454
3455#[cfg(target_arch = "x86_64")]
3458impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 3>>
3459 for InnerProduct
3460{
3461 #[inline(always)]
3462 fn run(
3463 self,
3464 arch: diskann_wide::arch::x86_64::V3,
3465 x: &[f32],
3466 y: USlice<'_, 3>,
3467 ) -> MathematicalResult<f32> {
3468 let len = check_lengths!(x, y)?;
3469 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
3470 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
3471
3472 let shifts = i32s::from_array(arch, [0, 3, 6, 9, 12, 15, 18, 21]);
3473 let mask = i32s::splat(arch, 7);
3474 let decode =
3475 |word: u32| -> f32s { ((i32s::splat(arch, word as i32) >> shifts) & mask).simd_cast() };
3476 let px = x.as_ptr();
3477 let py = y.as_ptr();
3478 let mut s0 = f32s::default(arch);
3479 let mut s1 = f32s::default(arch);
3480 let mut s2 = f32s::default(arch);
3481 let mut s3 = f32s::default(arch);
3482 let mut i = 0;
3483
3484 while len - i > 32 {
3485 unsafe {
3488 let packed = py.add(3 * (i / 8));
3489 let c0 = decode(packed.cast::<u32>().read_unaligned());
3490 let c1 = decode(packed.add(3).cast::<u32>().read_unaligned());
3491 let c2 = decode(packed.add(6).cast::<u32>().read_unaligned());
3492 let c3 = decode(packed.add(9).cast::<u32>().read_unaligned());
3493 s0 = f32s::load_simd(arch, px.add(i)).mul_add_simd(c0, s0);
3494 s1 = f32s::load_simd(arch, px.add(i + 8)).mul_add_simd(c1, s1);
3495 s2 = f32s::load_simd(arch, px.add(i + 16)).mul_add_simd(c2, s2);
3496 s3 = f32s::load_simd(arch, px.add(i + 24)).mul_add_simd(c3, s3);
3497 }
3498 i += 32;
3499 }
3500 if len - i > 16 {
3503 unsafe {
3506 let packed = py.add(3 * (i / 8));
3507 let c0 = decode(packed.cast::<u32>().read_unaligned());
3508 let c1 = decode(packed.add(3).cast::<u32>().read_unaligned());
3509 s0 = f32s::load_simd(arch, px.add(i)).mul_add_simd(c0, s0);
3510 s1 = f32s::load_simd(arch, px.add(i + 8)).mul_add_simd(c1, s1);
3511 }
3512 i += 16;
3513 }
3514 if len - i > 8 {
3515 unsafe {
3518 let codes = decode(py.add(3 * (i / 8)).cast::<u32>().read_unaligned());
3519 s0 = f32s::load_simd(arch, px.add(i)).mul_add_simd(codes, s0);
3520 }
3521 i += 8;
3522 }
3523 let tail = len - i;
3524 if tail != 0 {
3525 let finish = |word| {
3526 let query = unsafe { f32s::load_simd_first(arch, px.add(i), tail) };
3528 s0 = query.mul_add_simd(decode(word), s0);
3529 };
3530 unsafe {
3533 let packed = py.add(3 * (i / 8)).cast::<u32>();
3534 match tail {
3535 1..=2 => load_one(packed, finish),
3536 3..=5 => load_two(packed, finish),
3537 _ => load_three(packed, finish),
3538 }
3539 }
3540 }
3541 let sum = (s0 + s1) + (s2 + s3);
3542 Ok(MV::new(sum.sum_tree()))
3543 }
3544}
3545
3546#[cfg(target_arch = "x86_64")]
3551impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 4>>
3552 for InnerProduct
3553{
3554 #[inline(always)]
3555 fn run(
3556 self,
3557 arch: diskann_wide::arch::x86_64::V3,
3558 x: &[f32],
3559 y: USlice<'_, 4>,
3560 ) -> MathematicalResult<f32> {
3561 let len = check_lengths!(x, y)?;
3562
3563 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
3564 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
3565
3566 let variable_shifts = i32s::from_array(arch, [0, 4, 8, 12, 16, 20, 24, 28]);
3567 let mask = i32s::splat(arch, 0x0f);
3568
3569 let to_f32 = |v: u32| -> f32s {
3570 ((i32s::splat(arch, v as i32) >> variable_shifts) & mask).simd_cast()
3571 };
3572
3573 let px: *const f32 = x.as_ptr();
3574 let py: *const u32 = y.as_ptr().cast();
3575
3576 let mut i = 0;
3577 let mut s = f32s::default(arch);
3578
3579 let blocks = len / 8;
3580 while i < blocks {
3581 let ix = unsafe { f32s::load_simd(arch, px.add(8 * i)) };
3583 let iy = to_f32(unsafe { py.add(i).read_unaligned() });
3585 s = ix.mul_add_simd(iy, s);
3586
3587 i += 1;
3588 }
3589
3590 let remainder = len % 8;
3591 if remainder != 0 {
3592 let f = |iy| {
3593 let ix = unsafe { f32s::load_simd_first(arch, px.add(8 * blocks), remainder) };
3597 s = ix.mul_add_simd(to_f32(iy), s);
3598 };
3599
3600 let py = unsafe { py.add(blocks) };
3603
3604 if remainder <= 2 {
3605 unsafe { load_one(py, f) };
3607 } else if remainder <= 4 {
3608 unsafe { load_two(py, f) };
3610 } else if remainder <= 6 {
3611 unsafe { load_three(py, f) };
3613 } else {
3614 unsafe { load_four(py, f) };
3616 }
3617 }
3618
3619 Ok(MV::new(s.sum_tree()))
3620 }
3621}
3622
3623#[cfg(target_arch = "aarch64")]
3624impl Target2<diskann_wide::arch::aarch64::Neon, MathematicalResult<f32>, &[f32], USlice<'_, 1>>
3625 for InnerProduct
3626{
3627 #[inline(always)]
3628 fn run(
3629 self,
3630 arch: diskann_wide::arch::aarch64::Neon,
3631 x: &[f32],
3632 y: USlice<'_, 1>,
3633 ) -> MathematicalResult<f32> {
3634 let len = check_lengths!(x, y)?;
3636
3637 diskann_wide::alias!(f32s_4 = <diskann_wide::arch::aarch64::Neon>::f32x4);
3638 diskann_wide::alias!(u32s_4 = <diskann_wide::arch::aarch64::Neon>::u32x4);
3639
3640 let px_f32: *const f32 = x.as_ptr();
3641 let py_u8: *const u8 = y.as_ptr().cast();
3642
3643 let mut i = 0;
3644 let mut s: f32 = 0.0;
3645
3646 let y_bytes = len / 8; if i < y_bytes {
3648 let mut s0 = f32s_4::default(arch);
3649 let mut s1 = f32s_4::default(arch);
3650 let mut s2 = f32s_4::default(arch);
3651 let mut s3 = f32s_4::default(arch);
3652 let bits0 = u32s_4::from_array(arch, [1, 2, 4, 8]);
3653 let bits1 = u32s_4::from_array(arch, [16, 32, 64, 128]);
3654 let bits2 = u32s_4::from_array(arch, [256, 512, 1024, 2048]);
3655 let bits3 = u32s_4::from_array(arch, [4096, 8192, 16384, 32768]);
3656 let bits4 = u32s_4::from_array(arch, [65536, 131072, 262144, 524288]);
3657 let bits5 = u32s_4::from_array(arch, [1048576, 2097152, 4194304, 8388608]);
3658 let bits6 = u32s_4::from_array(arch, [16777216, 33554432, 67108864, 134217728]);
3659 let bits7 = u32s_4::from_array(arch, [268435456, 536870912, 1073741824, 2147483648]);
3660 let zero_u32 = u32s_4::splat(arch, 0);
3661 let zero_f32 = f32s_4::splat(arch, 0.0);
3662
3663 while i + 4 <= y_bytes {
3664 let y_word = unsafe { py_u8.add(i).cast::<u32>().read_unaligned() };
3666 let y_words = u32s_4::splat(arch, y_word);
3667
3668 let x_ptr = unsafe { px_f32.add(8 * i) };
3670 let x0 = unsafe { f32s_4::load_simd(arch, x_ptr) };
3672 let x1 = unsafe { f32s_4::load_simd(arch, x_ptr.add(4)) };
3674 let x2 = unsafe { f32s_4::load_simd(arch, x_ptr.add(8)) };
3676 let x3 = unsafe { f32s_4::load_simd(arch, x_ptr.add(12)) };
3678 let x4 = unsafe { f32s_4::load_simd(arch, x_ptr.add(16)) };
3680 let x5 = unsafe { f32s_4::load_simd(arch, x_ptr.add(20)) };
3682 let x6 = unsafe { f32s_4::load_simd(arch, x_ptr.add(24)) };
3684 let x7 = unsafe { f32s_4::load_simd(arch, x_ptr.add(28)) };
3686
3687 s0 = s0 + (y_words & bits0).eq_simd(zero_u32).select(zero_f32, x0);
3688 s1 = s1 + (y_words & bits1).eq_simd(zero_u32).select(zero_f32, x1);
3689 s2 = s2 + (y_words & bits2).eq_simd(zero_u32).select(zero_f32, x2);
3690 s3 = s3 + (y_words & bits3).eq_simd(zero_u32).select(zero_f32, x3);
3691 s0 = s0 + (y_words & bits4).eq_simd(zero_u32).select(zero_f32, x4);
3692 s1 = s1 + (y_words & bits5).eq_simd(zero_u32).select(zero_f32, x5);
3693 s2 = s2 + (y_words & bits6).eq_simd(zero_u32).select(zero_f32, x6);
3694 s3 = s3 + (y_words & bits7).eq_simd(zero_u32).select(zero_f32, x7);
3695
3696 i += 4;
3697 }
3698
3699 while i < y_bytes {
3700 let y_word = unsafe { *py_u8.add(i) };
3702 let y_words = u32s_4::splat(arch, y_word as u32);
3703
3704 let x_ptr = unsafe { px_f32.add(8 * i) };
3706 let x0 = unsafe { f32s_4::load_simd(arch, x_ptr) };
3708 let x1 = unsafe { f32s_4::load_simd(arch, x_ptr.add(4)) };
3710
3711 s0 = s0 + (y_words & bits0).eq_simd(zero_u32).select(zero_f32, x0);
3712 s1 = s1 + (y_words & bits1).eq_simd(zero_u32).select(zero_f32, x1);
3713 i += 1;
3714 }
3715 s = ((s0 + s1) + (s2 + s3)).sum_tree();
3716 }
3717
3718 i *= 8;
3720
3721 debug_assert!(len - i <= 7);
3723 if i != len {
3724 #[inline(never)]
3725 fn fallback(x: &[f32], y: USlice<'_, 1>, from: usize) -> f32 {
3726 let mut s: f32 = 0.0;
3727 for i in from..x.len() {
3728 let (ix, iy) = unsafe { (*x.get_unchecked(i), y.get_unchecked(i) as f32) };
3730 s += ix * iy;
3731 }
3732 s
3733 }
3734 s += fallback(x, y, i)
3735 }
3736
3737 Ok(MV::new(s))
3738 }
3739}
3740
3741#[cfg(target_arch = "aarch64")]
3742impl Target2<diskann_wide::arch::aarch64::Neon, MathematicalResult<f32>, &[f32], USlice<'_, 2>>
3743 for InnerProduct
3744{
3745 #[inline(always)]
3746 fn run(
3747 self,
3748 arch: diskann_wide::arch::aarch64::Neon,
3749 x: &[f32],
3750 y: USlice<'_, 2>,
3751 ) -> MathematicalResult<f32> {
3752 let len = check_lengths!(x, y)?;
3754
3755 diskann_wide::alias!(u8s_8 = type diskann_wide::arch::aarch64::u8x8);
3756 diskann_wide::alias!(u16s_8 = type diskann_wide::arch::aarch64::u16x8);
3757 diskann_wide::alias!(f32s_8 = <diskann_wide::arch::aarch64::Neon>::f32x8);
3758
3759 let px_f32: *const f32 = x.as_ptr();
3760 let py_u8: *const u8 = y.as_ptr();
3761
3762 let mut i = 0;
3763 let mut s: f32 = 0.0;
3764
3765 let y_bytes = len / 4; if i < y_bytes {
3767 let mut s0 = f32s_8::default(arch);
3768 let mut s1 = f32s_8::default(arch);
3769 let mut s2 = f32s_8::default(arch);
3770 let mut s3 = f32s_8::default(arch);
3771
3772 let mask = u8s_8::splat(arch, 0x03);
3773
3774 while i + 8 <= y_bytes {
3775 let y_vec = unsafe { u8s_8::load_simd(arch, py_u8.add(i)) };
3777
3778 let x_base = unsafe { px_f32.add(4 * i) };
3781 let x_lo = unsafe { arch.vld4q_f32(x_base) };
3784 let x_hi_base = unsafe { x_base.add(16) };
3787 let x_hi = unsafe { arch.vld4q_f32(x_hi_base) };
3790
3791 let x_vec1 = f32s_8::new(x_lo[0], x_hi[0]);
3792 let y_vec1: f32s_8 = (y_vec & mask).into();
3793 s0 = x_vec1.mul_add_simd(y_vec1, s0);
3794
3795 let x_vec2 = f32s_8::new(x_lo[1], x_hi[1]);
3796 let y_vec2: f32s_8 = ((y_vec >> 2) & mask).into();
3797 s1 = x_vec2.mul_add_simd(y_vec2, s1);
3798
3799 let x_vec3 = f32s_8::new(x_lo[2], x_hi[2]);
3800 let y_vec3: f32s_8 = ((y_vec >> 4) & mask).into();
3801 s2 = x_vec3.mul_add_simd(y_vec3, s2);
3802
3803 let x_vec4 = f32s_8::new(x_lo[3], x_hi[3]);
3804 let y_vec4: f32s_8 = ((y_vec >> 6) & mask).into();
3805 s3 = x_vec4.mul_add_simd(y_vec4, s3);
3806
3807 i += 8;
3808 }
3809
3810 while i + 1 < y_bytes {
3813 let mask_u16 = u16s_8::splat(arch, 0x0003);
3814 let y_u16 = unsafe { py_u8.add(i).cast::<u16>().read_unaligned() };
3817 let y_raw = u16s_8::splat(arch, y_u16);
3818 let shifts = u16s_8::from_array(arch, [0, 2, 4, 6, 8, 10, 12, 14]);
3819 let y_vec: f32s_8 = ((y_raw >> shifts) & mask_u16).into();
3820 let x_vec = unsafe { f32s_8::load_simd(arch, px_f32.add(4 * i)) };
3823 s0 = x_vec.mul_add_simd(y_vec, s0);
3824 i += 2;
3825 }
3826
3827 s = ((s0 + s1) + (s2 + s3)).sum_tree();
3828 }
3829
3830 i *= 4;
3832
3833 debug_assert!(len - i <= 7);
3835 if i != len {
3836 #[inline(never)]
3837 fn fallback(x: &[f32], y: USlice<'_, 2>, from: usize) -> f32 {
3838 let mut s: f32 = 0.0;
3839 for i in from..x.len() {
3840 let (ix, iy) = unsafe { (*x.get_unchecked(i), y.get_unchecked(i) as f32) };
3842 s += ix * iy;
3843 }
3844 s
3845 }
3846 s += fallback(x, y, i);
3847 }
3848
3849 Ok(MV::new(s))
3850 }
3851}
3852
3853#[cfg(target_arch = "aarch64")]
3854impl Target2<diskann_wide::arch::aarch64::Neon, MathematicalResult<f32>, &[f32], USlice<'_, 4>>
3855 for InnerProduct
3856{
3857 #[inline(always)]
3858 fn run(
3859 self,
3860 arch: diskann_wide::arch::aarch64::Neon,
3861 x: &[f32],
3862 y: USlice<'_, 4>,
3863 ) -> MathematicalResult<f32> {
3864 let len = check_lengths!(x, y)?;
3866
3867 diskann_wide::alias!(u8s_8 = type diskann_wide::arch::aarch64::u8x8);
3868 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::aarch64::Neon>::u8x16);
3869 diskann_wide::alias!(f32s_8 = <diskann_wide::arch::aarch64::Neon>::f32x8);
3870 diskann_wide::alias!(f32s_16 = <diskann_wide::arch::aarch64::Neon>::f32x16);
3871
3872 let px_f32: *const f32 = x.as_ptr();
3873 let py_u8: *const u8 = y.as_ptr().cast();
3874
3875 let mut i = 0;
3876 let mut s: f32 = 0.0;
3877
3878 let y_bytes = len / 2; if i < y_bytes {
3880 let mut s0 = f32s_8::default(arch);
3881 let mut s1 = f32s_8::default(arch);
3882 let mask = u8s_8::splat(arch, 0x0f);
3883
3884 while i + 8 <= y_bytes {
3885 let y_vec = unsafe { u8s_8::load_simd(arch, py_u8.add(i)) };
3887 let x_lo_base = unsafe { px_f32.add(2 * i) };
3890 let [x_vec_1, x_vec_2] = unsafe { arch.vld2q_f32(x_lo_base) };
3893 let x_hi_base = unsafe { px_f32.add(2 * i + 8) };
3896 let [x_vec_3, x_vec_4] = unsafe { arch.vld2q_f32(x_hi_base) };
3899
3900 let x_vec_even = f32s_8::new(x_vec_1, x_vec_3);
3901 let x_vec_odd = f32s_8::new(x_vec_2, x_vec_4);
3902
3903 let y_vec_even: f32s_8 = (y_vec & mask).into();
3904 let y_vec_odd: f32s_8 = (y_vec >> 4).into();
3905
3906 s0 = x_vec_even.mul_add_simd(y_vec_even, s0);
3907 s1 = x_vec_odd.mul_add_simd(y_vec_odd, s1);
3908
3909 i += 8;
3910 }
3911
3912 #[inline(always)]
3913 fn split_and_zip(input: u8s_8) -> u8s_16 {
3914 use diskann_wide::{LoHi, ZipUnzip};
3915 let zipped = u8s_16::zip(LoHi::new(input, input >> 4));
3916 zipped & u8s_16::splat(input.arch(), 0x0f)
3917 }
3918
3919 let remaining_y_bytes = len / 2 - i;
3920
3921 let mut s_grouped = f32s_16::new(s0, s1);
3922
3923 if remaining_y_bytes > 0 {
3926 let y_vec_u8s_8 =
3929 unsafe { u8s_8::load_simd_first(arch, py_u8.add(i), remaining_y_bytes) };
3930 let y_vec_u8s_16 = split_and_zip(y_vec_u8s_8);
3931 let y_vec: f32s_16 = y_vec_u8s_16.into();
3932
3933 let x_vec = unsafe {
3936 f32s_16::load_simd_first(arch, px_f32.add(2 * i), remaining_y_bytes * 2)
3937 };
3938
3939 s_grouped = x_vec.mul_add_simd(y_vec, s_grouped);
3940
3941 i += remaining_y_bytes;
3942 }
3943 s = s_grouped.sum_tree();
3944 }
3945
3946 i *= 2;
3948
3949 debug_assert!(len - i <= 1);
3951 if i != len {
3952 #[inline(never)]
3953 fn fallback(x: &[f32], y: USlice<'_, 4>, from: usize) -> f32 {
3954 let mut s: f32 = 0.0;
3955 for i in from..x.len() {
3956 let (ix, iy) = unsafe { (*x.get_unchecked(i), y.get_unchecked(i) as f32) };
3958 s += ix * iy;
3959 }
3960 s
3961 }
3962 s += fallback(x, y, i);
3963 }
3964
3965 Ok(MV::new(s))
3966 }
3967}
3968
3969impl<const N: usize>
3970 Target2<diskann_wide::arch::Scalar, MathematicalResult<f32>, &[f32], USlice<'_, N>>
3971 for InnerProduct
3972where
3973 Unsigned: Representation<N>,
3974{
3975 #[inline(always)]
3978 fn run(
3979 self,
3980 _: diskann_wide::arch::Scalar,
3981 x: &[f32],
3982 y: USlice<'_, N>,
3983 ) -> MathematicalResult<f32> {
3984 check_lengths!(x, y)?;
3985
3986 let mut s = 0.0;
3987 for (i, x) in x.iter().enumerate() {
3988 let y = unsafe { y.get_unchecked(i) } as f32;
3991 s += x * y;
3992 }
3993
3994 Ok(MV::new(s))
3995 }
3996}
3997
3998macro_rules! ip_retarget {
4000 ($arch:path, $N:literal) => {
4001 impl Target2<$arch, MathematicalResult<f32>, &[f32], USlice<'_, $N>>
4002 for InnerProduct
4003 {
4004 #[inline(always)]
4005 fn run(
4006 self,
4007 arch: $arch,
4008 x: &[f32],
4009 y: USlice<'_, $N>,
4010 ) -> MathematicalResult<f32> {
4011 self.run(arch.retarget(), x, y)
4012 }
4013 }
4014 };
4015 ($arch:path, $($Ns:literal),*) => {
4016 $(ip_retarget!($arch, $Ns);)*
4017 }
4018}
4019
4020#[cfg(target_arch = "x86_64")]
4021ip_retarget!(diskann_wide::arch::x86_64::V3, 5, 6, 7, 8);
4022
4023#[cfg(target_arch = "x86_64")]
4024ip_retarget!(diskann_wide::arch::x86_64::V4, 1, 2, 3, 4, 5, 6, 7, 8);
4025
4026#[cfg(target_arch = "aarch64")]
4027ip_retarget!(diskann_wide::arch::aarch64::Neon, 3, 5, 6, 7, 8);
4028
4029macro_rules! dispatch_full_ip {
4032 ($N:literal) => {
4033 impl PureDistanceFunction<&[f32], USlice<'_, $N>, MathematicalResult<f32>>
4037 for InnerProduct
4038 {
4039 fn evaluate(x: &[f32], y: USlice<'_, $N>) -> MathematicalResult<f32> {
4040 Self.run(ARCH, x, y)
4041 }
4042 }
4043 };
4044 ($($Ns:literal),*) => {
4045 $(dispatch_full_ip!($Ns);)*
4046 }
4047}
4048
4049dispatch_full_ip!(1, 2, 3, 4, 5, 6, 7, 8);
4050
4051#[cfg(test)]
4056mod tests {
4057 use std::{collections::HashMap, fmt::Display, sync::LazyLock};
4058
4059 use diskann_utils::{Reborrow, lazy_format};
4060 use rand::{
4061 Rng, SeedableRng,
4062 distr::{Distribution, Uniform},
4063 rngs::StdRng,
4064 seq::IndexedRandom,
4065 };
4066
4067 use super::*;
4068 use crate::bits::{BoxedBitSlice, Representation, Unsigned};
4069
4070 type MR = MathematicalResult<u32>;
4071
4072 #[inline(always)]
4073 fn should_check_this_dimension(dim: usize) -> bool {
4074 if cfg!(miri) {
4075 return dim.is_power_of_two()
4076 || (dim > 1 && (dim - 1).is_power_of_two())
4077 || (dim < 64 && (dim % 8 == 7));
4078 }
4079
4080 true
4081 }
4082
4083 fn test_bitslice_distances<const NBITS: usize, R>(
4093 dim_max: usize,
4094 trials_per_dim: usize,
4095 evaluate_l2: &dyn Fn(USlice<'_, NBITS>, USlice<'_, NBITS>) -> MR,
4096 evaluate_ip: &dyn Fn(USlice<'_, NBITS>, USlice<'_, NBITS>) -> MR,
4097 context: &str,
4098 rng: &mut R,
4099 ) where
4100 Unsigned: Representation<NBITS>,
4101 R: Rng,
4102 {
4103 let domain = Unsigned::domain_const::<NBITS>();
4104 let min: i64 = *domain.start();
4105 let max: i64 = *domain.end();
4106
4107 let dist = Uniform::new_inclusive(min, max).unwrap();
4108
4109 for dim in 0..dim_max {
4110 if !should_check_this_dimension(dim) {
4111 continue;
4112 }
4113
4114 let mut x_reference: Vec<u8> = vec![0; dim];
4115 let mut y_reference: Vec<u8> = vec![0; dim];
4116
4117 let mut x = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(dim);
4118 let mut y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(dim);
4119
4120 for trial in 0..trials_per_dim {
4121 x_reference
4122 .iter_mut()
4123 .for_each(|i| *i = dist.sample(rng).try_into().unwrap());
4124 y_reference
4125 .iter_mut()
4126 .for_each(|i| *i = dist.sample(rng).try_into().unwrap());
4127
4128 x.as_mut_slice().fill(u8::MAX);
4131 y.as_mut_slice().fill(u8::MAX);
4132
4133 for i in 0..dim {
4134 x.set(i, x_reference[i].into()).unwrap();
4135 y.set(i, y_reference[i].into()).unwrap();
4136 }
4137
4138 let expected: MV<f32> =
4140 diskann_vector::distance::SquaredL2::evaluate(&*x_reference, &*y_reference);
4141
4142 let got = evaluate_l2(x.reborrow(), y.reborrow()).unwrap();
4143
4144 assert_eq!(
4146 expected.into_inner(),
4147 got.into_inner() as f32,
4148 "failed SquaredL2 for NBITS = {}, dim = {}, trial = {} -- context {}",
4149 NBITS,
4150 dim,
4151 trial,
4152 context,
4153 );
4154
4155 let expected: MV<f32> =
4157 diskann_vector::distance::InnerProduct::evaluate(&*x_reference, &*y_reference);
4158
4159 let got = evaluate_ip(x.reborrow(), y.reborrow()).unwrap();
4160
4161 assert_eq!(
4163 expected.into_inner(),
4164 got.into_inner() as f32,
4165 "faild InnerProduct for NBITS = {}, dim = {}, trial = {} -- context {}",
4166 NBITS,
4167 dim,
4168 trial,
4169 context,
4170 );
4171 }
4172 }
4173
4174 let x = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(10);
4176 let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(11);
4177
4178 assert!(
4179 evaluate_l2(x.reborrow(), y.reborrow()).is_err(),
4180 "context: {}",
4181 context
4182 );
4183 assert!(
4184 evaluate_l2(y.reborrow(), x.reborrow()).is_err(),
4185 "context: {}",
4186 context
4187 );
4188
4189 assert!(
4190 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
4191 "context: {}",
4192 context
4193 );
4194 assert!(
4195 evaluate_ip(y.reborrow(), x.reborrow()).is_err(),
4196 "context: {}",
4197 context
4198 );
4199 }
4200
4201 cfg_if::cfg_if! {
4202 if #[cfg(miri)] {
4203 const MAX_DIM: usize = 132;
4204 const TRIALS_PER_DIM: usize = 1;
4205 } else {
4206 const MAX_DIM: usize = 256;
4207 const TRIALS_PER_DIM: usize = 20;
4208 }
4209 }
4210
4211 static BITSLICE_TEST_BOUNDS: LazyLock<HashMap<Key, Bounds>> = LazyLock::new(|| {
4221 use ArchKey::{Neon, Scalar, X86_64_V3, X86_64_V4};
4222 [
4223 (Key::new(1, Scalar), Bounds::new(64, 64)),
4224 (Key::new(1, X86_64_V3), Bounds::new(256, 256)),
4225 (Key::new(1, X86_64_V4), Bounds::new(256, 256)),
4226 (Key::new(1, Neon), Bounds::new(64, 64)),
4227 (Key::new(2, Scalar), Bounds::new(64, 64)),
4228 (Key::new(2, X86_64_V3), Bounds::new(512, 300)),
4230 (Key::new(2, X86_64_V4), Bounds::new(768, 600)), (Key::new(2, Neon), Bounds::new(256, 256)),
4232 (Key::new(3, Scalar), Bounds::new(64, 64)),
4233 (Key::new(3, X86_64_V3), Bounds::new(256, 96)),
4234 (Key::new(3, X86_64_V4), Bounds::new(256, 96)),
4235 (Key::new(3, Neon), Bounds::new(64, 64)),
4236 (Key::new(4, Scalar), Bounds::new(64, 64)),
4237 (Key::new(4, X86_64_V3), Bounds::new(256, 150)),
4239 (Key::new(4, X86_64_V4), Bounds::new(512, 300)),
4240 (Key::new(4, Neon), Bounds::new(128, 128)),
4241 (Key::new(5, Scalar), Bounds::new(64, 64)),
4242 (Key::new(5, X86_64_V3), Bounds::new(256, 96)),
4243 (Key::new(5, X86_64_V4), Bounds::new(256, 96)),
4244 (Key::new(5, Neon), Bounds::new(64, 64)),
4245 (Key::new(6, Scalar), Bounds::new(64, 64)),
4246 (Key::new(6, X86_64_V3), Bounds::new(256, 96)),
4247 (Key::new(6, X86_64_V4), Bounds::new(256, 96)),
4248 (Key::new(6, Neon), Bounds::new(64, 64)),
4249 (Key::new(7, Scalar), Bounds::new(64, 64)),
4250 (Key::new(7, X86_64_V3), Bounds::new(256, 96)),
4251 (Key::new(7, X86_64_V4), Bounds::new(256, 96)),
4252 (Key::new(7, Neon), Bounds::new(64, 64)),
4253 (Key::new(8, Scalar), Bounds::new(64, 64)),
4254 (Key::new(8, X86_64_V3), Bounds::new(256, 96)),
4255 (Key::new(8, X86_64_V4), Bounds::new(256, 96)),
4256 (Key::new(8, Neon), Bounds::new(256, 256)),
4257 ]
4258 .into_iter()
4259 .collect()
4260 });
4261
4262 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
4263 enum ArchKey {
4264 Scalar,
4265 #[expect(non_camel_case_types)]
4266 X86_64_V3,
4267 #[expect(non_camel_case_types)]
4268 X86_64_V4,
4269 Neon,
4270 }
4271
4272 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
4273 struct Key {
4274 nbits: usize,
4275 arch: ArchKey,
4276 }
4277
4278 impl Key {
4279 fn new(nbits: usize, arch: ArchKey) -> Self {
4280 Self { nbits, arch }
4281 }
4282 }
4283
4284 #[derive(Debug, Clone, Copy)]
4285 struct Bounds {
4286 standard: usize,
4287 miri: usize,
4288 }
4289
4290 impl Bounds {
4291 fn new(standard: usize, miri: usize) -> Self {
4292 Self { standard, miri }
4293 }
4294
4295 fn get(&self) -> usize {
4296 if cfg!(miri) { self.miri } else { self.standard }
4297 }
4298 }
4299
4300 macro_rules! test_bitslice {
4301 ($name:ident, $nbits:literal, $seed:literal) => {
4302 #[test]
4303 fn $name() {
4304 let mut rng = StdRng::seed_from_u64($seed);
4305
4306 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::Scalar)].get();
4307
4308 test_bitslice_distances::<$nbits, _>(
4309 max_dim,
4310 TRIALS_PER_DIM,
4311 &|x, y| SquaredL2::evaluate(x, y),
4312 &|x, y| InnerProduct::evaluate(x, y),
4313 "pure distance function",
4314 &mut rng,
4315 );
4316
4317 test_bitslice_distances::<$nbits, _>(
4318 max_dim,
4319 TRIALS_PER_DIM,
4320 &|x, y| diskann_wide::arch::Scalar::new().run2(SquaredL2, x, y),
4321 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
4322 "scalar arch",
4323 &mut rng,
4324 );
4325
4326 #[cfg(target_arch = "x86_64")]
4328 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
4329 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::X86_64_V3)].get();
4330 test_bitslice_distances::<$nbits, _>(
4331 max_dim,
4332 TRIALS_PER_DIM,
4333 &|x, y| arch.run2(SquaredL2, x, y),
4334 &|x, y| arch.run2(InnerProduct, x, y),
4335 "x86-64-v3",
4336 &mut rng,
4337 );
4338 }
4339
4340 #[cfg(target_arch = "x86_64")]
4341 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
4342 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::X86_64_V4)].get();
4343 test_bitslice_distances::<$nbits, _>(
4344 max_dim,
4345 TRIALS_PER_DIM,
4346 &|x, y| arch.run2(SquaredL2, x, y),
4347 &|x, y| arch.run2(InnerProduct, x, y),
4348 "x86-64-v4",
4349 &mut rng,
4350 );
4351 }
4352
4353 #[cfg(target_arch = "aarch64")]
4354 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4355 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::Neon)].get();
4356 test_bitslice_distances::<$nbits, _>(
4357 max_dim,
4358 TRIALS_PER_DIM,
4359 &|x, y| arch.run2(SquaredL2, x, y),
4360 &|x, y| arch.run2(InnerProduct, x, y),
4361 "neon",
4362 &mut rng,
4363 );
4364 }
4365 }
4366 };
4367 }
4368
4369 test_bitslice!(test_bitslice_distances_8bit, 8, 0xf0330c6d880e08ff);
4370 test_bitslice!(test_bitslice_distances_7bit, 7, 0x98aa7f2d4c83844f);
4371 test_bitslice!(test_bitslice_distances_6bit, 6, 0xf2f7ad7a37764b4c);
4372 test_bitslice!(test_bitslice_distances_5bit, 5, 0xae878d14973fb43f);
4373 test_bitslice!(test_bitslice_distances_4bit, 4, 0x8d6dbb8a6b19a4f8);
4374 test_bitslice!(test_bitslice_distances_3bit, 3, 0x8f56767236e58da2);
4375 test_bitslice!(test_bitslice_distances_2bit, 2, 0xb04f741a257b61af);
4376 test_bitslice!(test_bitslice_distances_1bit, 1, 0x820ea031c379eab5);
4377
4378 fn test_hamming_distances<R>(dim_max: usize, trials_per_dim: usize, rng: &mut R)
4383 where
4384 R: Rng,
4385 {
4386 let dist: [i8; 2] = [-1, 1];
4387
4388 for dim in 0..dim_max {
4389 if !should_check_this_dimension(dim) {
4390 continue;
4391 }
4392
4393 let mut x_reference: Vec<i8> = vec![1; dim];
4394 let mut y_reference: Vec<i8> = vec![1; dim];
4395
4396 let mut x = BoxedBitSlice::<1, Binary>::new_boxed(dim);
4397 let mut y = BoxedBitSlice::<1, Binary>::new_boxed(dim);
4398
4399 for _ in 0..trials_per_dim {
4400 x_reference
4401 .iter_mut()
4402 .for_each(|i| *i = *dist.choose(rng).unwrap());
4403 y_reference
4404 .iter_mut()
4405 .for_each(|i| *i = *dist.choose(rng).unwrap());
4406
4407 x.as_mut_slice().fill(u8::MAX);
4410 y.as_mut_slice().fill(u8::MAX);
4411
4412 for i in 0..dim {
4413 x.set(i, x_reference[i].into()).unwrap();
4414 y.set(i, y_reference[i].into()).unwrap();
4415 }
4416
4417 let expected: MV<f32> =
4423 diskann_vector::distance::SquaredL2::evaluate(&*x_reference, &*y_reference);
4424 let got: MV<u32> = Hamming::evaluate(x.reborrow(), y.reborrow()).unwrap();
4425 assert_eq!(4.0 * (got.into_inner() as f32), expected.into_inner());
4426 }
4427 }
4428
4429 let x = BoxedBitSlice::<1, Binary>::new_boxed(10);
4430 let y = BoxedBitSlice::<1, Binary>::new_boxed(11);
4431 assert!(Hamming::evaluate(x.reborrow(), y.reborrow()).is_err());
4432 assert!(Hamming::evaluate(y.reborrow(), x.reborrow()).is_err());
4433 }
4434
4435 #[test]
4436 fn test_hamming_distance() {
4437 let mut rng = StdRng::seed_from_u64(0x2160419161246d97);
4438 test_hamming_distances(MAX_DIM, TRIALS_PER_DIM, &mut rng);
4439 }
4440
4441 fn test_bit_transpose_distances<R>(
4446 dim_max: usize,
4447 trials_per_dim: usize,
4448 evaluate_ip: &dyn Fn(USlice<'_, 4, BitTranspose>, USlice<'_, 1>) -> MR,
4449 context: &str,
4450 rng: &mut R,
4451 ) where
4452 R: Rng,
4453 {
4454 let dist_4bit = {
4455 let domain = Unsigned::domain_const::<4>();
4456 Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
4457 };
4458
4459 let dist_1bit = {
4460 let domain = Unsigned::domain_const::<1>();
4461 Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
4462 };
4463
4464 for dim in 0..dim_max {
4465 if !should_check_this_dimension(dim) {
4466 continue;
4467 }
4468
4469 let mut x_reference: Vec<u8> = vec![0; dim];
4470 let mut y_reference: Vec<u8> = vec![0; dim];
4471
4472 let mut x = BoxedBitSlice::<4, Unsigned, BitTranspose>::new_boxed(dim);
4473 let mut y = BoxedBitSlice::<1, Unsigned, Dense>::new_boxed(dim);
4474
4475 for trial in 0..trials_per_dim {
4476 x_reference
4477 .iter_mut()
4478 .for_each(|i| *i = dist_4bit.sample(rng).try_into().unwrap());
4479 y_reference
4480 .iter_mut()
4481 .for_each(|i| *i = dist_1bit.sample(rng).try_into().unwrap());
4482
4483 x.as_mut_slice().fill(u8::MAX);
4485 y.as_mut_slice().fill(u8::MAX);
4486
4487 for i in 0..dim {
4488 x.set(i, x_reference[i].into()).unwrap();
4489 y.set(i, y_reference[i].into()).unwrap();
4490 }
4491
4492 let expected: MV<f32> =
4494 diskann_vector::distance::InnerProduct::evaluate(&*x_reference, &*y_reference);
4495
4496 let got = evaluate_ip(x.reborrow(), y.reborrow());
4497
4498 assert_eq!(
4500 expected.into_inner(),
4501 got.unwrap().into_inner() as f32,
4502 "faild InnerProduct for dim = {}, trial = {} -- context {}",
4503 dim,
4504 trial,
4505 context,
4506 );
4507 }
4508 }
4509
4510 let x = BoxedBitSlice::<4, Unsigned, BitTranspose>::new_boxed(10);
4511 let y = BoxedBitSlice::<1, Unsigned>::new_boxed(11);
4512 assert!(
4513 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
4514 "context: {}",
4515 context
4516 );
4517
4518 let y = BoxedBitSlice::<1, Unsigned>::new_boxed(9);
4519 assert!(
4520 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
4521 "context: {}",
4522 context
4523 );
4524 }
4525
4526 #[test]
4527 fn test_bit_transpose_distance() {
4528 let mut rng = StdRng::seed_from_u64(0xe20e26e926d4b853);
4529
4530 test_bit_transpose_distances(
4531 MAX_DIM,
4532 TRIALS_PER_DIM,
4533 &|x, y| InnerProduct::evaluate(x, y),
4534 "pure distance function",
4535 &mut rng,
4536 );
4537
4538 test_bit_transpose_distances(
4539 MAX_DIM,
4540 TRIALS_PER_DIM,
4541 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
4542 "scalar",
4543 &mut rng,
4544 );
4545
4546 #[cfg(target_arch = "x86_64")]
4548 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
4549 test_bit_transpose_distances(
4550 MAX_DIM,
4551 TRIALS_PER_DIM,
4552 &|x, y| arch.run2(InnerProduct, x, y),
4553 "x86-64-v3",
4554 &mut rng,
4555 );
4556 }
4557
4558 #[cfg(target_arch = "x86_64")]
4560 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
4561 test_bit_transpose_distances(
4562 MAX_DIM,
4563 TRIALS_PER_DIM,
4564 &|x, y| arch.run2(InnerProduct, x, y),
4565 "x86-64-v4",
4566 &mut rng,
4567 );
4568 }
4569
4570 #[cfg(target_arch = "aarch64")]
4572 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4573 test_bit_transpose_distances(
4574 MAX_DIM,
4575 TRIALS_PER_DIM,
4576 &|x, y| arch.run2(InnerProduct, x, y),
4577 "neon",
4578 &mut rng,
4579 );
4580 }
4581 }
4582
4583 fn test_full_distances<const NBITS: usize>(
4588 dim_max: usize,
4589 trials_per_dim: usize,
4590 evaluate_ip: &dyn Fn(&[f32], USlice<'_, NBITS>) -> MathematicalResult<f32>,
4591 context: &str,
4592 rng: &mut impl Rng,
4593 ) where
4594 Unsigned: Representation<NBITS>,
4595 {
4596 let dist_float = [-2.0, -1.0, 0.0, 1.0, 2.0];
4598 let dist_bit = {
4599 let domain = Unsigned::domain_const::<NBITS>();
4600 Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
4601 };
4602
4603 for dim in 0..dim_max {
4604 if !should_check_this_dimension(dim) {
4605 continue;
4606 }
4607
4608 let mut x: Vec<f32> = vec![0.0; dim];
4609
4610 let mut y_reference: Vec<u8> = vec![0; dim];
4611 let mut y = BoxedBitSlice::<NBITS, Unsigned, Dense>::new_boxed(dim);
4612
4613 for trial in 0..trials_per_dim {
4614 x.iter_mut()
4615 .for_each(|i| *i = *dist_float.choose(rng).unwrap());
4616 y_reference
4617 .iter_mut()
4618 .for_each(|i| *i = dist_bit.sample(rng).try_into().unwrap());
4619
4620 y.as_mut_slice().fill(u8::MAX);
4622
4623 let mut expected = 0.0;
4624 for i in 0..dim {
4625 y.set(i, y_reference[i].into()).unwrap();
4626 expected += y_reference[i] as f32 * x[i];
4627 }
4628
4629 let got = evaluate_ip(&x, y.reborrow()).unwrap();
4631
4632 assert_eq!(
4634 expected,
4635 got.into_inner(),
4636 "faild InnerProduct for dim = {}, trial = {} -- context {}",
4637 dim,
4638 trial,
4639 context,
4640 );
4641
4642 let scalar: MV<f32> = InnerProduct
4645 .run(diskann_wide::arch::Scalar, x.as_slice(), y.reborrow())
4646 .unwrap();
4647 assert_eq!(got.into_inner(), scalar.into_inner());
4648 }
4649 }
4650
4651 let x = vec![0.0; 10];
4653 let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(11);
4654 assert!(
4655 evaluate_ip(x.as_slice(), y.reborrow()).is_err(),
4656 "context: {}",
4657 context
4658 );
4659
4660 let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(9);
4661 assert!(
4662 evaluate_ip(x.as_slice(), y.reborrow()).is_err(),
4663 "context: {}",
4664 context
4665 );
4666 }
4667
4668 macro_rules! test_full {
4669 ($name:ident, $nbits:literal, $seed:literal) => {
4670 #[test]
4671 fn $name() {
4672 let mut rng = StdRng::seed_from_u64($seed);
4673
4674 test_full_distances::<$nbits>(
4675 MAX_DIM,
4676 TRIALS_PER_DIM,
4677 &|x, y| InnerProduct::evaluate(x, y),
4678 "pure distance function",
4679 &mut rng,
4680 );
4681
4682 test_full_distances::<$nbits>(
4683 MAX_DIM,
4684 TRIALS_PER_DIM,
4685 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
4686 "scalar",
4687 &mut rng,
4688 );
4689
4690 #[cfg(target_arch = "x86_64")]
4692 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
4693 test_full_distances::<$nbits>(
4694 MAX_DIM,
4695 TRIALS_PER_DIM,
4696 &|x, y| arch.run2(InnerProduct, x, y),
4697 "x86-64-v3",
4698 &mut rng,
4699 );
4700 }
4701
4702 #[cfg(target_arch = "x86_64")]
4703 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
4704 test_full_distances::<$nbits>(
4705 MAX_DIM,
4706 TRIALS_PER_DIM,
4707 &|x, y| arch.run2(InnerProduct, x, y),
4708 "x86-64-v4",
4709 &mut rng,
4710 );
4711 }
4712
4713 #[cfg(target_arch = "aarch64")]
4714 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4715 test_full_distances::<$nbits>(
4716 MAX_DIM,
4717 TRIALS_PER_DIM,
4718 &|x, y| arch.run2(InnerProduct, x, y),
4719 "neon",
4720 &mut rng,
4721 );
4722 }
4723 }
4724 };
4725 }
4726
4727 test_full!(test_full_distance_1bit, 1, 0xe20e26e926d4b853);
4728 test_full!(test_full_distance_2bit, 2, 0xae9542700aecbf68);
4729 test_full!(test_full_distance_3bit, 3, 0xfffd04b26bb6068c);
4730 test_full!(test_full_distance_4bit, 4, 0x86db49fd1a1704ba);
4731 test_full!(test_full_distance_5bit, 5, 0x3a35dc7fa7931c41);
4732 test_full!(test_full_distance_6bit, 6, 0x1f69de79e418d336);
4733 test_full!(test_full_distance_7bit, 7, 0x3fcf17b82dadc5ab);
4734 test_full!(test_full_distance_8bit, 8, 0x85dcaf48b1399db2);
4735
4736 struct HetCase<const M: usize> {
4743 x_vals: Vec<i64>,
4744 y_vals: Vec<i64>,
4745 }
4746
4747 impl<const M: usize> HetCase<M>
4748 where
4749 Unsigned: Representation<M>,
4750 {
4751 fn new(dim: usize, fill: impl FnMut(usize) -> (i64, i64)) -> Self {
4752 let (x_vals, y_vals) = (0..dim).map(fill).unzip();
4753 Self { x_vals, y_vals }
4754 }
4755
4756 fn check_with(
4757 &self,
4758 label: impl Display,
4759 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4760 ) {
4761 let dim = self.x_vals.len();
4762 let mut x = BoxedBitSlice::<8, Unsigned>::new_boxed(dim);
4763 let mut y = BoxedBitSlice::<M, Unsigned>::new_boxed(dim);
4764 x.as_mut_slice().fill(u8::MAX);
4766 y.as_mut_slice().fill(u8::MAX);
4767 for (i, (&xv, &yv)) in self.x_vals.iter().zip(&self.y_vals).enumerate() {
4768 x.set(i, xv).unwrap();
4769 y.set(i, yv).unwrap();
4770 }
4771 let expected: u32 = self
4772 .x_vals
4773 .iter()
4774 .zip(&self.y_vals)
4775 .map(|(&a, &b)| a as u32 * b as u32)
4776 .sum();
4777 let got = evaluate(x.reborrow(), y.reborrow()).unwrap().into_inner();
4778 assert_eq!(expected, got, "{} failed for dim = {}", label, dim);
4779 }
4780 }
4781
4782 fn fuzz_heterogeneous_ip<const M: usize>(
4784 dim_max: usize,
4785 trials_per_dim: usize,
4786 max_val: i64,
4787 evaluate_ip: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4788 context: &str,
4789 rng: &mut impl Rng,
4790 ) where
4791 Unsigned: Representation<M>,
4792 {
4793 let dist_8bit = Uniform::new_inclusive(0i64, 255i64).unwrap();
4794 let dist_mbit = Uniform::new_inclusive(0i64, max_val).unwrap();
4795
4796 for dim in 0..dim_max {
4797 for trial in 0..trials_per_dim {
4798 HetCase::<M>::new(dim, |_| {
4799 (dist_8bit.sample(&mut *rng), dist_mbit.sample(&mut *rng))
4800 })
4801 .check_with(
4802 lazy_format!("IP(8,{}) dim={dim}, trial={trial} -- {context}", M),
4803 evaluate_ip,
4804 );
4805 }
4806
4807 let x = BoxedBitSlice::<8, Unsigned>::new_boxed(dim);
4809 let y = BoxedBitSlice::<M, Unsigned>::new_boxed(dim + 1);
4810 assert!(
4811 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
4812 "context: {}",
4813 context,
4814 );
4815 }
4816 }
4817
4818 fn het_test_max_values<const M: usize>(
4821 max_val: i64,
4822 context: &str,
4823 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4824 ) where
4825 Unsigned: Representation<M>,
4826 {
4827 let dims = [127, 128, 129, 255, 256, 512, 768, 896, 3072];
4828 for &dim in &dims {
4829 let case = HetCase::<M>::new(dim, |_| (255, max_val));
4830 case.check_with(lazy_format!("max-value {context} dim={dim}"), evaluate);
4831 }
4832 }
4833
4834 fn het_test_known_answers<const M: usize>(
4836 max_val: i64,
4837 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4838 ) where
4839 Unsigned: Representation<M>,
4840 {
4841 HetCase::<M>::new(64, |_| (200, max_val)).check_with("vpmaddubsw operand-order", evaluate);
4844
4845 let y_val = (max_val / 2).max(1);
4847 HetCase::<M>::new(128, |i| ((i % 256) as i64, y_val))
4848 .check_with("ascending-x constant-y", evaluate);
4849
4850 HetCase::<M>::new(1, |_| (200, max_val)).check_with("single element", evaluate);
4852 }
4853
4854 fn het_test_edge_cases<const M: usize>(
4856 max_val: i64,
4857 block_size: usize,
4858 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4859 ) where
4860 Unsigned: Representation<M>,
4861 {
4862 let y_half = (max_val / 2).max(1);
4863
4864 HetCase::<M>::new(64, |_| (0, max_val)).check_with("x-zero y-nonzero", evaluate);
4866 HetCase::<M>::new(64, |_| (255, 0)).check_with("y-zero x-nonzero", evaluate);
4867
4868 for dim in 0..=(block_size + 1) {
4870 HetCase::<M>::new(dim, |_| (3, y_half)).check_with("uniform fill", evaluate);
4871 }
4872
4873 for &dim in &[block_size, 2 * block_size, 4 * block_size, 8 * block_size] {
4875 HetCase::<M>::new(dim, |_| (100, max_val)).check_with("exact block boundary", evaluate);
4876 }
4877
4878 HetCase::<M>::new(300, |i| ((i % 256) as i64, 1))
4880 .check_with("x-varies y-constant", evaluate);
4881
4882 HetCase::<M>::new(300, |i| (1, (i as i64) % (max_val + 1)))
4884 .check_with("x-constant y-varies", evaluate);
4885
4886 HetCase::<M>::new(128, |i| if i % 2 == 0 { (255, max_val) } else { (0, 0) })
4888 .check_with("alternating pattern", evaluate);
4889
4890 HetCase::<M>::new(128, |i| if i % 2 == 0 { (0, 0) } else { (255, max_val) })
4892 .check_with("opposite alternating", evaluate);
4893
4894 HetCase::<M>::new(1024, |_| (255, max_val)).check_with("large accumulation", evaluate);
4896
4897 for x_val in [128i64, 170, 200, 240, 255] {
4899 HetCase::<M>::new(block_size, move |_| (x_val, y_half))
4900 .check_with(lazy_format!("x > 127 (x_val={x_val})"), evaluate);
4901 }
4902
4903 HetCase::<M>::new(block_size - 1, |i| {
4905 (
4906 ((i * 7 + 3) % 256) as i64,
4907 ((i * 11 + 5) as i64) % (max_val + 1),
4908 )
4909 })
4910 .check_with("dim=block_size-1 (all scalar)", evaluate);
4911
4912 let unroll4 = 4 * block_size;
4914 for &dim in &[
4915 unroll4,
4916 unroll4 + 1,
4917 unroll4 + block_size,
4918 unroll4 + block_size + 1,
4919 ] {
4920 HetCase::<M>::new(dim, |i| {
4921 (((i + 1) % 256) as i64, ((i + 1) as i64) % (max_val + 1))
4922 })
4923 .check_with("unroll boundary", evaluate);
4924 }
4925 }
4926
4927 macro_rules! heterogeneous_ip_tests_8xM {
4928 (
4929 mod_name: $mod:ident,
4930 M: $M:literal,
4931 max_val: $max_val:literal,
4932 block_size: $block_size:literal,
4933 seed_fuzz: $seed_fuzz:literal,
4934 ) => {
4935 mod $mod {
4936 use super::*;
4937
4938 #[test]
4939 fn all_ip_dispatches() {
4940 let mut rng = StdRng::seed_from_u64($seed_fuzz);
4941
4942 fuzz_heterogeneous_ip::<$M>(
4943 MAX_DIM,
4944 TRIALS_PER_DIM,
4945 $max_val,
4946 &|x, y| InnerProduct::evaluate(x, y),
4947 "pure distance function",
4948 &mut rng,
4949 );
4950 fuzz_heterogeneous_ip::<$M>(
4951 MAX_DIM,
4952 TRIALS_PER_DIM,
4953 $max_val,
4954 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
4955 "scalar arch",
4956 &mut rng,
4957 );
4958 #[cfg(target_arch = "x86_64")]
4959 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
4960 fuzz_heterogeneous_ip::<$M>(
4961 MAX_DIM,
4962 TRIALS_PER_DIM,
4963 $max_val,
4964 &|x, y| arch.run2(InnerProduct, x, y),
4965 "x86-64-v3",
4966 &mut rng,
4967 );
4968 }
4969 #[cfg(target_arch = "x86_64")]
4970 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
4971 fuzz_heterogeneous_ip::<$M>(
4972 MAX_DIM,
4973 TRIALS_PER_DIM,
4974 $max_val,
4975 &|x, y| arch.run2(InnerProduct, x, y),
4976 "x86-64-v4",
4977 &mut rng,
4978 );
4979 }
4980 #[cfg(target_arch = "aarch64")]
4981 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4982 fuzz_heterogeneous_ip::<$M>(
4983 MAX_DIM,
4984 TRIALS_PER_DIM,
4985 $max_val,
4986 &|x, y| arch.run2(InnerProduct, x, y),
4987 "neon",
4988 &mut rng,
4989 );
4990 }
4991 }
4992
4993 #[test]
4994 fn max_values() {
4995 het_test_max_values::<$M>($max_val, "dispatch", &|x, y| {
4996 InnerProduct::evaluate(x, y)
4997 });
4998 #[cfg(target_arch = "x86_64")]
4999 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
5000 het_test_max_values::<$M>($max_val, "V3", &|x, y| {
5001 arch.run2(InnerProduct, x, y)
5002 });
5003 }
5004 #[cfg(target_arch = "aarch64")]
5005 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
5006 het_test_max_values::<$M>($max_val, "neon", &|x, y| {
5007 arch.run2(InnerProduct, x, y)
5008 });
5009 }
5010 }
5011
5012 #[test]
5013 fn known_answers() {
5014 het_test_known_answers::<$M>($max_val, &|x, y| InnerProduct::evaluate(x, y));
5015 #[cfg(target_arch = "aarch64")]
5016 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
5017 het_test_known_answers::<$M>($max_val, &|x, y| {
5018 arch.run2(InnerProduct, x, y)
5019 });
5020 }
5021 }
5022
5023 #[test]
5024 fn edge_cases() {
5025 het_test_edge_cases::<$M>($max_val, $block_size, &|x, y| {
5026 InnerProduct::evaluate(x, y)
5027 });
5028 #[cfg(target_arch = "aarch64")]
5029 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
5030 het_test_edge_cases::<$M>($max_val, $block_size, &|x, y| {
5031 arch.run2(InnerProduct, x, y)
5032 });
5033 }
5034 }
5035 }
5036 };
5037 }
5038
5039 heterogeneous_ip_tests_8xM! {
5040 mod_name: heterogeneous_ip_8x4,
5041 M: 4,
5042 max_val: 15,
5043 block_size: 32,
5044 seed_fuzz: 0xd3a7f1c09b2e4856,
5045 }
5046
5047 heterogeneous_ip_tests_8xM! {
5048 mod_name: heterogeneous_ip_8x2,
5049 M: 2,
5050 max_val: 3,
5051 block_size: 64,
5052 seed_fuzz: 0x82c4a6e809f1d3b5,
5053 }
5054
5055 heterogeneous_ip_tests_8xM! {
5056 mod_name: heterogeneous_ip_8x1,
5057 M: 1,
5058 max_val: 1,
5059 block_size: 32,
5060 seed_fuzz: 0x1b17_a5e7c2d0f839,
5061 }
5062}