1use diskann_vector::PureDistanceFunction;
111use diskann_wide::{ARCH, Architecture, arch::Target2};
112#[cfg(target_arch = "x86_64")]
113use diskann_wide::{
114 SIMDCast, SIMDDotProduct, SIMDMulAdd, SIMDReinterpret, SIMDSumTree, SIMDVector,
115};
116
117#[cfg(target_arch = "aarch64")]
118use diskann_wide::{SIMDDotProduct, SIMDSumTree, SIMDVector};
119
120use super::{Binary, BitSlice, BitTranspose, Dense, Representation, Unsigned};
121use crate::distances::{Hamming, InnerProduct, MV, MathematicalResult, SquaredL2, check_lengths};
122
123type USlice<'a, const N: usize, Perm = Dense> = BitSlice<'a, N, Unsigned, Perm>;
125
126macro_rules! retarget {
132 ($arch:path, $op:ty, ($N:literal, $M:literal)) => {
133 impl Target2<
134 $arch,
135 MathematicalResult<u32>,
136 USlice<'_, $N>,
137 USlice<'_, $M>,
138 > for $op {
139 #[inline(always)]
140 fn run(
141 self,
142 arch: $arch,
143 x: USlice<'_, $N>,
144 y: USlice<'_, $M>
145 ) -> MathematicalResult<u32> {
146 self.run(arch.retarget(), x, y)
147 }
148 }
149 };
150 ($arch:path, $op:ty, $N:literal) => {
151 retarget!($arch, $op, ($N, $N));
152 };
153 ($arch:path, $op:ty, $($args:tt),+ $(,)?) => {
154 $(retarget!($arch, $op, $args);)+
155 };
156}
157
158macro_rules! dispatch_pure {
160 ($op:ty, ($N:literal, $M:literal)) => {
161 impl PureDistanceFunction<USlice<'_, $N>, USlice<'_, $M>, MathematicalResult<u32>> for $op {
162 #[inline(always)]
163 fn evaluate(x: USlice<'_, $N>, y: USlice<'_, $M>) -> MathematicalResult<u32> {
164 (diskann_wide::ARCH).run2(Self, x, y)
165 }
166 }
167 };
168 ($op:ty, $N:literal) => {
169 dispatch_pure!($op, ($N, $N));
170 };
171 ($op:ty, $($args:tt),+ $(,)?) => {
172 $(dispatch_pure!($op, $args);)+
173 }
174}
175
176#[cfg(target_arch = "x86_64")]
183unsafe fn load_one<F, R>(ptr: *const u32, mut f: F) -> R
184where
185 F: FnMut(u32) -> R,
186{
187 f(unsafe { ptr.cast::<u8>().read_unaligned() }.into())
189}
190
191#[cfg(target_arch = "x86_64")]
198unsafe fn load_two<F, R>(ptr: *const u32, mut f: F) -> R
199where
200 F: FnMut(u32) -> R,
201{
202 f(unsafe { ptr.cast::<u16>().read_unaligned() }.into())
204}
205
206#[cfg(target_arch = "x86_64")]
213unsafe fn load_three<F, R>(ptr: *const u32, mut f: F) -> R
214where
215 F: FnMut(u32) -> R,
216{
217 let lo: u32 = unsafe { ptr.cast::<u16>().read_unaligned() }.into();
219 let hi: u32 = unsafe { ptr.cast::<u8>().add(2).read_unaligned() }.into();
221 f(lo | hi << 16)
222}
223
224#[cfg(target_arch = "x86_64")]
231unsafe fn load_four<F, R>(ptr: *const u32, mut f: F) -> R
232where
233 F: FnMut(u32) -> R,
234{
235 f(unsafe { ptr.read_unaligned() })
237}
238
239trait BitVectorOp<Repr>
251where
252 Repr: Representation<1>,
253{
254 fn on_u64(x: u64, y: u64) -> u32;
256
257 fn on_u8(x: u8, y: u8) -> u32;
262}
263
264impl BitVectorOp<Unsigned> for SquaredL2 {
266 #[inline(always)]
267 fn on_u64(x: u64, y: u64) -> u32 {
268 (x ^ y).count_ones()
269 }
270 #[inline(always)]
271 fn on_u8(x: u8, y: u8) -> u32 {
272 (x ^ y).count_ones()
273 }
274}
275
276impl BitVectorOp<Binary> for Hamming {
278 #[inline(always)]
279 fn on_u64(x: u64, y: u64) -> u32 {
280 (x ^ y).count_ones()
281 }
282 #[inline(always)]
283 fn on_u8(x: u8, y: u8) -> u32 {
284 (x ^ y).count_ones()
285 }
286}
287
288impl BitVectorOp<Unsigned> for InnerProduct {
296 #[inline(always)]
297 fn on_u64(x: u64, y: u64) -> u32 {
298 (x & y).count_ones()
299 }
300 #[inline(always)]
301 fn on_u8(x: u8, y: u8) -> u32 {
302 (x & y).count_ones()
303 }
304}
305
306#[inline(always)]
311fn bitvector_op<Op, Repr>(
312 x: BitSlice<'_, 1, Repr>,
313 y: BitSlice<'_, 1, Repr>,
314) -> MathematicalResult<u32>
315where
316 Repr: Representation<1>,
317 Op: BitVectorOp<Repr>,
318{
319 let len = check_lengths!(x, y)?;
320
321 let px: *const u64 = x.as_ptr().cast();
322 let py: *const u64 = y.as_ptr().cast();
323
324 let mut i = 0;
325 let mut s: u32 = 0;
326
327 let blocks = len / 64;
329 while i < blocks {
330 let vx = unsafe { px.add(i).read_unaligned() };
334
335 let vy = unsafe { py.add(i).read_unaligned() };
339
340 s += Op::on_u64(vx, vy);
341 i += 1;
342 }
343
344 i *= 8;
346 let px: *const u8 = x.as_ptr();
347 let py: *const u8 = y.as_ptr();
348
349 let blocks = len / 8;
350 while i < blocks {
351 let vx = unsafe { px.add(i).read_unaligned() };
354
355 let vy = unsafe { py.add(i).read_unaligned() };
359 s += Op::on_u8(vx, vy);
360 i += 1;
361 }
362
363 if i * 8 != len {
364 let vx = unsafe { px.add(i).read_unaligned() };
367
368 let vy = unsafe { py.add(i).read_unaligned() };
370 let m = (0x01u8 << (len - 8 * i)) - 1;
371
372 s += Op::on_u8(vx & m, vy & m)
373 }
374 Ok(MV::new(s))
375}
376
377impl PureDistanceFunction<BitSlice<'_, 1, Binary>, BitSlice<'_, 1, Binary>, MathematicalResult<u32>>
381 for Hamming
382{
383 fn evaluate(x: BitSlice<'_, 1, Binary>, y: BitSlice<'_, 1, Binary>) -> MathematicalResult<u32> {
384 bitvector_op::<Hamming, Binary>(x, y)
385 }
386}
387
388impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 8>> for SquaredL2
401where
402 A: Architecture,
403 diskann_vector::distance::SquaredL2: for<'a> Target2<A, MV<f32>, &'a [u8], &'a [u8]>,
404{
405 #[inline(always)]
406 fn run(self, arch: A, x: USlice<'_, 8>, y: USlice<'_, 8>) -> MathematicalResult<u32> {
407 check_lengths!(x, y)?;
408
409 let r: MV<f32> = <_ as Target2<_, _, _, _>>::run(
410 diskann_vector::distance::SquaredL2 {},
411 arch,
412 x.as_slice(),
413 y.as_slice(),
414 );
415
416 Ok(MV::new(r.into_inner() as u32))
417 }
418}
419
420#[cfg(target_arch = "x86_64")]
434impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
435 for SquaredL2
436{
437 #[inline(always)]
438 fn run(
439 self,
440 arch: diskann_wide::arch::x86_64::V3,
441 x: USlice<'_, 4>,
442 y: USlice<'_, 4>,
443 ) -> MathematicalResult<u32> {
444 let len = check_lengths!(x, y)?;
445
446 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
447 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
448 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
449
450 let px_u32: *const u32 = x.as_ptr().cast();
451 let py_u32: *const u32 = y.as_ptr().cast();
452
453 let mut i = 0;
454 let mut s: u32 = 0;
455
456 let blocks = len / 8;
458 if i < blocks {
459 let mut s0 = i32s::default(arch);
460 let mut s1 = i32s::default(arch);
461 let mut s2 = i32s::default(arch);
462 let mut s3 = i32s::default(arch);
463 let mask = u32s::splat(arch, 0x000f000f);
464 while i + 8 < blocks {
465 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
470
471 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
475
476 let wx: i16s = (vx & mask).reinterpret_simd();
477 let wy: i16s = (vy & mask).reinterpret_simd();
478 let d = wx - wy;
479 s0 = s0.dot_simd(d, d);
480
481 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
482 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
483 let d = wx - wy;
484 s1 = s1.dot_simd(d, d);
485
486 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
487 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
488 let d = wx - wy;
489 s2 = s2.dot_simd(d, d);
490
491 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
492 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
493 let d = wx - wy;
494 s3 = s3.dot_simd(d, d);
495
496 i += 8;
497 }
498
499 let remainder = blocks - i;
500
501 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
507
508 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
512
513 let wx: i16s = (vx & mask).reinterpret_simd();
514 let wy: i16s = (vy & mask).reinterpret_simd();
515 let d = wx - wy;
516 s0 = s0.dot_simd(d, d);
517
518 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
519 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
520 let d = wx - wy;
521 s1 = s1.dot_simd(d, d);
522
523 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
524 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
525 let d = wx - wy;
526 s2 = s2.dot_simd(d, d);
527
528 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
529 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
530 let d = wx - wy;
531 s3 = s3.dot_simd(d, d);
532
533 i += remainder;
534
535 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
536 }
537
538 i *= 8;
540
541 if i != len {
543 #[inline(never)]
545 fn fallback(x: USlice<'_, 4>, y: USlice<'_, 4>, from: usize) -> u32 {
546 let mut s: i32 = 0;
547 for i in from..x.len() {
548 let ix = unsafe { x.get_unchecked(i) } as i32;
550 let iy = unsafe { y.get_unchecked(i) } as i32;
552 let d = ix - iy;
553 s += d * d;
554 }
555 s as u32
556 }
557 s += fallback(x, y, i);
558 }
559
560 Ok(MV::new(s))
561 }
562}
563
564#[cfg(target_arch = "x86_64")]
578impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
579 for SquaredL2
580{
581 #[inline(always)]
582 fn run(
583 self,
584 arch: diskann_wide::arch::x86_64::V4,
585 x: USlice<'_, 4>,
586 y: USlice<'_, 4>,
587 ) -> MathematicalResult<u32> {
588 let len = check_lengths!(x, y)?;
589
590 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V4>::i32x16);
591 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V4>::u32x16);
592 diskann_wide::alias!(u8s = <diskann_wide::arch::x86_64::V4>::u8x64);
593 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V4>::i16x32);
594
595 let px_u32: *const u32 = x.as_ptr().cast();
596 let py_u32: *const u32 = y.as_ptr().cast();
597
598 let mut i = 0;
599 let mut s: u32 = 0;
600
601 let blocks = len.div_ceil(8);
606 if i < blocks {
607 let mut s0 = i32s::default(arch);
608 let mut s1 = i32s::default(arch);
609 let mut s2 = i32s::default(arch);
610 let mut s3 = i32s::default(arch);
611 let mask = u32s::splat(arch, 0x000f000f);
612 while i + 16 < blocks {
613 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
619
620 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
624
625 let wx: i16s = (vx & mask).reinterpret_simd();
626 let wy: i16s = (vy & mask).reinterpret_simd();
627 let d = wx - wy;
628 s0 = s0.dot_simd(d, d);
629
630 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
631 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
632 let d = wx - wy;
633 s1 = s1.dot_simd(d, d);
634
635 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
636 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
637 let d = wx - wy;
638 s2 = s2.dot_simd(d, d);
639
640 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
641 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
642 let d = wx - wy;
643 s3 = s3.dot_simd(d, d);
644
645 i += 16;
646 }
647
648 let remainder = len / 2 - 4 * i;
655
656 if remainder > 0 {
657 let vx =
663 unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
664 let vx: u32s = vx.reinterpret_simd();
665
666 let vy =
670 unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
671 let vy: u32s = vy.reinterpret_simd();
672
673 let wx: i16s = (vx & mask).reinterpret_simd();
674 let wy: i16s = (vy & mask).reinterpret_simd();
675 let d = wx - wy;
676 s0 = s0.dot_simd(d, d);
677
678 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
679 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
680 let d = wx - wy;
681 s1 = s1.dot_simd(d, d);
682
683 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
684 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
685 let d = wx - wy;
686 s2 = s2.dot_simd(d, d);
687
688 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
689 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
690 let d = wx - wy;
691 s3 = s3.dot_simd(d, d);
692 }
693
694 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
695 i = (4 * i) + remainder;
696 }
697
698 i *= 2;
700
701 debug_assert!(len - i <= 1);
703 if i != len {
704 let ix = unsafe { x.get_unchecked(i) } as i32;
706 let iy = unsafe { y.get_unchecked(i) } as i32;
708 let d = ix - iy;
709 s += (d * d) as u32;
710 }
711
712 Ok(MV::new(s))
713 }
714}
715
716#[cfg(target_arch = "x86_64")]
730impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
731 for SquaredL2
732{
733 #[inline(always)]
734 fn run(
735 self,
736 arch: diskann_wide::arch::x86_64::V3,
737 x: USlice<'_, 2>,
738 y: USlice<'_, 2>,
739 ) -> MathematicalResult<u32> {
740 let len = check_lengths!(x, y)?;
741
742 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
743 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
744 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
745
746 let px_u32: *const u32 = x.as_ptr().cast();
747 let py_u32: *const u32 = y.as_ptr().cast();
748
749 let mut i = 0;
750 let mut s: u32 = 0;
751
752 let blocks = len / 16;
754 if i < blocks {
755 let mut s0 = i32s::default(arch);
756 let mut s1 = i32s::default(arch);
757 let mut s2 = i32s::default(arch);
758 let mut s3 = i32s::default(arch);
759 let mask = u32s::splat(arch, 0x00030003);
760 while i + 8 < blocks {
761 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
766
767 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
771
772 let wx: i16s = (vx & mask).reinterpret_simd();
773 let wy: i16s = (vy & mask).reinterpret_simd();
774 let d = wx - wy;
775 s0 = s0.dot_simd(d, d);
776
777 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
778 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
779 let d = wx - wy;
780 s1 = s1.dot_simd(d, d);
781
782 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
783 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
784 let d = wx - wy;
785 s2 = s2.dot_simd(d, d);
786
787 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
788 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
789 let d = wx - wy;
790 s3 = s3.dot_simd(d, d);
791
792 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
793 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
794 let d = wx - wy;
795 s0 = s0.dot_simd(d, d);
796
797 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
798 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
799 let d = wx - wy;
800 s1 = s1.dot_simd(d, d);
801
802 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
803 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
804 let d = wx - wy;
805 s2 = s2.dot_simd(d, d);
806
807 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
808 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
809 let d = wx - wy;
810 s3 = s3.dot_simd(d, d);
811
812 i += 8;
813 }
814
815 let remainder = blocks - i;
816
817 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
823
824 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
828 let wx: i16s = (vx & mask).reinterpret_simd();
829 let wy: i16s = (vy & mask).reinterpret_simd();
830 let d = wx - wy;
831 s0 = s0.dot_simd(d, d);
832
833 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
834 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
835 let d = wx - wy;
836 s1 = s1.dot_simd(d, d);
837
838 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
839 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
840 let d = wx - wy;
841 s2 = s2.dot_simd(d, d);
842
843 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
844 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
845 let d = wx - wy;
846 s3 = s3.dot_simd(d, d);
847
848 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
849 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
850 let d = wx - wy;
851 s0 = s0.dot_simd(d, d);
852
853 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
854 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
855 let d = wx - wy;
856 s1 = s1.dot_simd(d, d);
857
858 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
859 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
860 let d = wx - wy;
861 s2 = s2.dot_simd(d, d);
862
863 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
864 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
865 let d = wx - wy;
866 s3 = s3.dot_simd(d, d);
867
868 i += remainder;
869
870 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
871 }
872
873 i *= 16;
875
876 if i != len {
878 #[inline(never)]
880 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
881 let mut s: i32 = 0;
882 for i in from..x.len() {
883 let ix = unsafe { x.get_unchecked(i) } as i32;
885 let iy = unsafe { y.get_unchecked(i) } as i32;
887 let d = ix - iy;
888 s += d * d;
889 }
890 s as u32
891 }
892 s += fallback(x, y, i);
893 }
894
895 Ok(MV::new(s))
896 }
897}
898
899impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 1>, USlice<'_, 1>> for SquaredL2
903where
904 A: Architecture,
905{
906 fn run(self, _: A, x: USlice<'_, 1>, y: USlice<'_, 1>) -> MathematicalResult<u32> {
907 bitvector_op::<Self, Unsigned>(x, y)
908 }
909}
910
911macro_rules! impl_fallback_l2 {
913 ($N:literal) => {
914 impl Target2<diskann_wide::arch::Scalar, MathematicalResult<u32>, USlice<'_, $N>, USlice<'_, $N>> for SquaredL2 {
922 #[inline(never)]
923 fn run(
924 self,
925 _: diskann_wide::arch::Scalar,
926 x: USlice<'_, $N>,
927 y: USlice<'_, $N>
928 ) -> MathematicalResult<u32> {
929 let len = check_lengths!(x, y)?;
930
931 let mut accum: i32 = 0;
932 for i in 0..len {
933 let ix: i32 = unsafe { x.get_unchecked(i) } as i32;
935 let iy: i32 = unsafe { y.get_unchecked(i) } as i32;
937 let diff = ix - iy;
938 accum += diff * diff;
939 }
940 Ok(MV::new(accum as u32))
941 }
942 }
943 };
944 ($($N:literal),+ $(,)?) => {
945 $(impl_fallback_l2!($N);)+
946 };
947}
948
949impl_fallback_l2!(7, 6, 5, 4, 3, 2);
950
951#[cfg(target_arch = "x86_64")]
952retarget!(diskann_wide::arch::x86_64::V3, SquaredL2, 7, 6, 5, 3);
953
954#[cfg(target_arch = "x86_64")]
955retarget!(diskann_wide::arch::x86_64::V4, SquaredL2, 7, 6, 5, 3, 2);
956
957dispatch_pure!(SquaredL2, 1, 2, 3, 4, 5, 6, 7, 8);
958#[cfg(target_arch = "aarch64")]
959retarget!(
960 diskann_wide::arch::aarch64::Neon,
961 SquaredL2,
962 7,
963 6,
964 5,
965 4,
966 3,
967 2
968);
969
970impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 8>> for InnerProduct
983where
984 A: Architecture,
985 diskann_vector::distance::InnerProduct: for<'a> Target2<A, MV<f32>, &'a [u8], &'a [u8]>,
986{
987 #[inline(always)]
988 fn run(self, arch: A, x: USlice<'_, 8>, y: USlice<'_, 8>) -> MathematicalResult<u32> {
989 check_lengths!(x, y)?;
990 let r: MV<f32> = <_ as Target2<_, _, _, _>>::run(
991 diskann_vector::distance::InnerProduct {},
992 arch,
993 x.as_slice(),
994 y.as_slice(),
995 );
996
997 Ok(MV::new(r.into_inner() as u32))
998 }
999}
1000
1001#[cfg(target_arch = "x86_64")]
1018impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
1019 for InnerProduct
1020{
1021 #[expect(non_camel_case_types)]
1022 #[inline(always)]
1023 fn run(
1024 self,
1025 arch: diskann_wide::arch::x86_64::V4,
1026 x: USlice<'_, 2>,
1027 y: USlice<'_, 2>,
1028 ) -> MathematicalResult<u32> {
1029 let len = check_lengths!(x, y)?;
1030
1031 type i32s = <diskann_wide::arch::x86_64::V4 as Architecture>::i32x16;
1032 type u32s = <diskann_wide::arch::x86_64::V4 as Architecture>::u32x16;
1033 type u8s = <diskann_wide::arch::x86_64::V4 as Architecture>::u8x64;
1034 type i8s = <diskann_wide::arch::x86_64::V4 as Architecture>::i8x64;
1035
1036 let px_u32: *const u32 = x.as_ptr().cast();
1037 let py_u32: *const u32 = y.as_ptr().cast();
1038
1039 let mut i = 0;
1040 let mut s: u32 = 0;
1041
1042 let blocks = len.div_ceil(16);
1044 if i < blocks {
1045 let mut s0 = i32s::default(arch);
1046 let mut s1 = i32s::default(arch);
1047 let mut s2 = i32s::default(arch);
1048 let mut s3 = i32s::default(arch);
1049 let mask = u32s::splat(arch, 0x03030303);
1050 while i + 16 < blocks {
1051 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1056
1057 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1061
1062 let wx: u8s = (vx & mask).reinterpret_simd();
1063 let wy: i8s = (vy & mask).reinterpret_simd();
1064 s0 = s0.dot_simd(wx, wy);
1065
1066 let wx: u8s = ((vx >> 2) & mask).reinterpret_simd();
1067 let wy: i8s = ((vy >> 2) & mask).reinterpret_simd();
1068 s1 = s1.dot_simd(wx, wy);
1069
1070 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1071 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1072 s2 = s2.dot_simd(wx, wy);
1073
1074 let wx: u8s = ((vx >> 6) & mask).reinterpret_simd();
1075 let wy: i8s = ((vy >> 6) & mask).reinterpret_simd();
1076 s3 = s3.dot_simd(wx, wy);
1077
1078 i += 16;
1079 }
1080
1081 let remainder = len / 4 - 4 * i;
1085
1086 let vx = unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
1091 let vx: u32s = vx.reinterpret_simd();
1092
1093 let vy = unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
1097 let vy: u32s = vy.reinterpret_simd();
1098
1099 let wx: u8s = (vx & mask).reinterpret_simd();
1100 let wy: i8s = (vy & mask).reinterpret_simd();
1101 s0 = s0.dot_simd(wx, wy);
1102
1103 let wx: u8s = ((vx >> 2) & mask).reinterpret_simd();
1104 let wy: i8s = ((vy >> 2) & mask).reinterpret_simd();
1105 s1 = s1.dot_simd(wx, wy);
1106
1107 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1108 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1109 s2 = s2.dot_simd(wx, wy);
1110
1111 let wx: u8s = ((vx >> 6) & mask).reinterpret_simd();
1112 let wy: i8s = ((vy >> 6) & mask).reinterpret_simd();
1113 s3 = s3.dot_simd(wx, wy);
1114
1115 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1116 i = (4 * i) + remainder;
1117 }
1118
1119 i *= 4;
1121
1122 debug_assert!(len - i <= 3);
1124 let rest = (len - i).min(3);
1125 if i != len {
1126 for j in 0..rest {
1127 let ix = unsafe { x.get_unchecked(i + j) } as u32;
1129 let iy = unsafe { y.get_unchecked(i + j) } as u32;
1131 s += ix * iy;
1132 }
1133 }
1134
1135 Ok(MV::new(s))
1136 }
1137}
1138
1139#[cfg(target_arch = "x86_64")]
1153impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
1154 for InnerProduct
1155{
1156 #[inline(always)]
1157 fn run(
1158 self,
1159 arch: diskann_wide::arch::x86_64::V3,
1160 x: USlice<'_, 4>,
1161 y: USlice<'_, 4>,
1162 ) -> MathematicalResult<u32> {
1163 let len = check_lengths!(x, y)?;
1164
1165 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1166 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
1167 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1168
1169 let px_u32: *const u32 = x.as_ptr().cast();
1170 let py_u32: *const u32 = y.as_ptr().cast();
1171
1172 let mut i = 0;
1173 let mut s: u32 = 0;
1174
1175 let blocks = len / 8;
1176 if i < blocks {
1177 let mut s0 = i32s::default(arch);
1178 let mut s1 = i32s::default(arch);
1179 let mut s2 = i32s::default(arch);
1180 let mut s3 = i32s::default(arch);
1181 let mask = u32s::splat(arch, 0x000f000f);
1182 while i + 8 < blocks {
1183 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1188
1189 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1193
1194 let wx: i16s = (vx & mask).reinterpret_simd();
1195 let wy: i16s = (vy & mask).reinterpret_simd();
1196 s0 = s0.dot_simd(wx, wy);
1197
1198 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1199 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1200 s1 = s1.dot_simd(wx, wy);
1201
1202 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1203 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1204 s2 = s2.dot_simd(wx, wy);
1205
1206 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1207 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1208 s3 = s3.dot_simd(wx, wy);
1209
1210 i += 8;
1211 }
1212
1213 let remainder = blocks - i;
1214
1215 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1221
1222 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1226
1227 let wx: i16s = (vx & mask).reinterpret_simd();
1228 let wy: i16s = (vy & mask).reinterpret_simd();
1229 s0 = s0.dot_simd(wx, wy);
1230
1231 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1232 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1233 s1 = s1.dot_simd(wx, wy);
1234
1235 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1236 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1237 s2 = s2.dot_simd(wx, wy);
1238
1239 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1240 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1241 s3 = s3.dot_simd(wx, wy);
1242
1243 i += remainder;
1244
1245 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1246 }
1247
1248 i *= 8;
1250
1251 if i != len {
1253 #[inline(never)]
1255 fn fallback(x: USlice<'_, 4>, y: USlice<'_, 4>, from: usize) -> u32 {
1256 let mut s: u32 = 0;
1257 for i in from..x.len() {
1258 let ix = unsafe { x.get_unchecked(i) } as u32;
1260 let iy = unsafe { y.get_unchecked(i) } as u32;
1262 s += ix * iy;
1263 }
1264 s
1265 }
1266 s += fallback(x, y, i);
1267 }
1268
1269 Ok(MV::new(s))
1270 }
1271}
1272
1273#[cfg(target_arch = "x86_64")]
1291impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
1292 for InnerProduct
1293{
1294 #[inline(always)]
1295 fn run(
1296 self,
1297 arch: diskann_wide::arch::x86_64::V4,
1298 x: USlice<'_, 4>,
1299 y: USlice<'_, 4>,
1300 ) -> MathematicalResult<u32> {
1301 let len = check_lengths!(x, y)?;
1302
1303 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V4>::i32x16);
1304 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V4>::u32x16);
1305 diskann_wide::alias!(u8s = <diskann_wide::arch::x86_64::V4>::u8x64);
1306 diskann_wide::alias!(i8s = <diskann_wide::arch::x86_64::V4>::i8x64);
1307
1308 let px_u32: *const u32 = x.as_ptr().cast();
1309 let py_u32: *const u32 = y.as_ptr().cast();
1310
1311 let mut i = 0;
1312 let mut s: u32 = 0;
1313
1314 let blocks = len.div_ceil(8);
1319 if i < blocks {
1320 let mut s0 = i32s::default(arch);
1321 let mut s1 = i32s::default(arch);
1322 let mask = u32s::splat(arch, 0x0f0f0f0f);
1323 while i + 16 < blocks {
1324 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1330
1331 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1335
1336 let wx: u8s = (vx & mask).reinterpret_simd();
1340 let wy: i8s = (vy & mask).reinterpret_simd();
1341 s0 = s0.dot_simd(wx, wy);
1342
1343 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1344 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1345 s1 = s1.dot_simd(wx, wy);
1346
1347 i += 16;
1348 }
1349
1350 let remainder = len / 2 - 4 * i;
1357
1358 if remainder > 0 {
1359 let vx =
1365 unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
1366 let vx: u32s = vx.reinterpret_simd();
1367
1368 let vy =
1372 unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
1373 let vy: u32s = vy.reinterpret_simd();
1374
1375 let wx: u8s = (vx & mask).reinterpret_simd();
1376 let wy: i8s = (vy & mask).reinterpret_simd();
1377 s0 = s0.dot_simd(wx, wy);
1378
1379 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1380 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1381 s1 = s1.dot_simd(wx, wy);
1382 }
1383
1384 s = (s0 + s1).sum_tree() as u32;
1385 i = (4 * i) + remainder;
1386 }
1387
1388 i *= 2;
1390
1391 debug_assert!(len - i <= 1);
1393 if i != len {
1394 let ix = unsafe { x.get_unchecked(i) } as u32;
1396 let iy = unsafe { y.get_unchecked(i) } as u32;
1398 s += ix * iy;
1399 }
1400
1401 Ok(MV::new(s))
1402 }
1403}
1404
1405#[cfg(target_arch = "x86_64")]
1419impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
1420 for InnerProduct
1421{
1422 #[inline(always)]
1423 fn run(
1424 self,
1425 arch: diskann_wide::arch::x86_64::V3,
1426 x: USlice<'_, 2>,
1427 y: USlice<'_, 2>,
1428 ) -> MathematicalResult<u32> {
1429 let len = check_lengths!(x, y)?;
1430
1431 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1432 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
1433 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1434
1435 let px_u32: *const u32 = x.as_ptr().cast();
1436 let py_u32: *const u32 = y.as_ptr().cast();
1437
1438 let mut i = 0;
1439 let mut s: u32 = 0;
1440
1441 let blocks = len / 16;
1443 if i < blocks {
1444 let mut s0 = i32s::default(arch);
1445 let mut s1 = i32s::default(arch);
1446 let mut s2 = i32s::default(arch);
1447 let mut s3 = i32s::default(arch);
1448 let mask = u32s::splat(arch, 0x00030003);
1449 while i + 8 < blocks {
1450 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1455
1456 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1460
1461 let wx: i16s = (vx & mask).reinterpret_simd();
1462 let wy: i16s = (vy & mask).reinterpret_simd();
1463 s0 = s0.dot_simd(wx, wy);
1464
1465 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1466 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1467 s1 = s1.dot_simd(wx, wy);
1468
1469 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1470 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1471 s2 = s2.dot_simd(wx, wy);
1472
1473 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1474 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1475 s3 = s3.dot_simd(wx, wy);
1476
1477 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1478 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1479 s0 = s0.dot_simd(wx, wy);
1480
1481 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1482 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1483 s1 = s1.dot_simd(wx, wy);
1484
1485 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1486 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1487 s2 = s2.dot_simd(wx, wy);
1488
1489 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1490 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1491 s3 = s3.dot_simd(wx, wy);
1492
1493 i += 8;
1494 }
1495
1496 let remainder = blocks - i;
1497
1498 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1504
1505 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1509 let wx: i16s = (vx & mask).reinterpret_simd();
1510 let wy: i16s = (vy & mask).reinterpret_simd();
1511 s0 = s0.dot_simd(wx, wy);
1512
1513 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1514 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1515 s1 = s1.dot_simd(wx, wy);
1516
1517 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1518 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1519 s2 = s2.dot_simd(wx, wy);
1520
1521 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1522 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1523 s3 = s3.dot_simd(wx, wy);
1524
1525 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1526 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1527 s0 = s0.dot_simd(wx, wy);
1528
1529 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1530 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1531 s1 = s1.dot_simd(wx, wy);
1532
1533 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1534 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1535 s2 = s2.dot_simd(wx, wy);
1536
1537 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1538 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1539 s3 = s3.dot_simd(wx, wy);
1540
1541 i += remainder;
1542
1543 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1544 }
1545
1546 i *= 16;
1548
1549 if i != len {
1551 #[inline(never)]
1553 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
1554 let mut s: u32 = 0;
1555 for i in from..x.len() {
1556 let ix = unsafe { x.get_unchecked(i) } as u32;
1558 let iy = unsafe { y.get_unchecked(i) } as u32;
1560 s += ix * iy;
1561 }
1562 s
1563 }
1564 s += fallback(x, y, i);
1565 }
1566
1567 Ok(MV::new(s))
1568 }
1569}
1570
1571#[cfg(target_arch = "aarch64")]
1572impl
1573 Target2<
1574 diskann_wide::arch::aarch64::Neon,
1575 MathematicalResult<u32>,
1576 USlice<'_, 4>,
1577 USlice<'_, 4>,
1578 > for InnerProduct
1579{
1580 #[inline(always)]
1581 fn run(
1582 self,
1583 arch: diskann_wide::arch::aarch64::Neon,
1584 x: USlice<'_, 4>,
1585 y: USlice<'_, 4>,
1586 ) -> MathematicalResult<u32> {
1587 let len = check_lengths!(x, y)?;
1588
1589 diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
1590 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
1591
1592 let px_u8: *const u8 = x.as_ptr().cast();
1593 let py_u8: *const u8 = y.as_ptr().cast();
1594
1595 let mut i = 0;
1596 let mut s: u32 = 0;
1597
1598 let bytes = len / 2;
1600 if i < bytes {
1601 let mut s0 = u32s::default(arch);
1602 let mut s1 = u32s::default(arch);
1603 let mask = u8s::splat(arch, 0x0f);
1604 while i + 16 <= bytes {
1605 let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
1608 let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
1611
1612 let first_x: u8s = x_vec & mask;
1615 let first_y: u8s = y_vec & mask;
1616 s0 = s0.dot_simd(first_x, first_y);
1617
1618 let second_x: u8s = (x_vec >> 4) & mask;
1620 let second_y: u8s = (y_vec >> 4) & mask;
1621 s1 = s1.dot_simd(second_x, second_y);
1622 i += 16;
1624 }
1625
1626 let remaining_bytes = len / 2 - i;
1627
1628 if remaining_bytes > 0 {
1629 let remaining_vec1 = remaining_bytes.min(16);
1630 let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_vec1) };
1634 let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_vec1) };
1637
1638 let first_x: u8s = x_vec & mask;
1639 let first_y: u8s = y_vec & mask;
1640 s0 = s0.dot_simd(first_x, first_y);
1641
1642 let second_x: u8s = (x_vec >> 4) & mask;
1644 let second_y: u8s = (y_vec >> 4) & mask;
1645 s1 = s1.dot_simd(second_x, second_y);
1646 i += remaining_bytes;
1647 }
1648 s = (s0 + s1).sum_tree();
1649 }
1650 i *= 2;
1652
1653 debug_assert!(len - i <= 1);
1655
1656 if i != len {
1657 let ix = unsafe { x.get_unchecked(i) } as i32;
1659 let iy = unsafe { y.get_unchecked(i) } as i32;
1661 s += (ix * iy) as u32;
1662 }
1663
1664 Ok(MV::new(s))
1665 }
1666}
1667
1668impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 1>, USlice<'_, 1>> for InnerProduct
1672where
1673 A: Architecture,
1674{
1675 #[inline(always)]
1676 fn run(self, _: A, x: USlice<'_, 1>, y: USlice<'_, 1>) -> MathematicalResult<u32> {
1677 bitvector_op::<Self, Unsigned>(x, y)
1678 }
1679}
1680
1681#[cfg(target_arch = "aarch64")]
1682impl
1683 Target2<
1684 diskann_wide::arch::aarch64::Neon,
1685 MathematicalResult<u32>,
1686 USlice<'_, 2>,
1687 USlice<'_, 2>,
1688 > for InnerProduct
1689{
1690 #[inline(always)]
1691 fn run(
1692 self,
1693 arch: diskann_wide::arch::aarch64::Neon,
1694 x: USlice<'_, 2>,
1695 y: USlice<'_, 2>,
1696 ) -> MathematicalResult<u32> {
1697 let len = check_lengths!(x, y)?;
1698
1699 diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
1700 diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
1701
1702 let px_u8: *const u8 = x.as_ptr().cast();
1703 let py_u8: *const u8 = y.as_ptr().cast();
1704
1705 let mut i = 0;
1706 let mut s: u32 = 0;
1707
1708 let bytes = len / 4;
1710 if i < bytes {
1711 let mut s0 = u32s::default(arch);
1712 let mut s1 = u32s::default(arch);
1713 let mut s2 = u32s::default(arch);
1714 let mut s3 = u32s::default(arch);
1715 let mask = u8s::splat(arch, 0x03);
1716 while i + 16 <= bytes {
1717 let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
1719 let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
1721
1722 let first_x: u8s = x_vec & mask;
1723 let first_y: u8s = y_vec & mask;
1724 s0 = s0.dot_simd(first_x, first_y);
1725
1726 let second_x: u8s = (x_vec >> 2) & mask;
1727 let second_y: u8s = (y_vec >> 2) & mask;
1728 s1 = s1.dot_simd(second_x, second_y);
1729
1730 let third_x: u8s = (x_vec >> 4) & mask;
1731 let third_y: u8s = (y_vec >> 4) & mask;
1732 s2 = s2.dot_simd(third_x, third_y);
1733
1734 let fourth_x: u8s = (x_vec >> 6) & mask;
1735 let fourth_y: u8s = (y_vec >> 6) & mask;
1736 s3 = s3.dot_simd(fourth_x, fourth_y);
1737 i += 16;
1738 }
1739
1740 let remaining_bytes = bytes - i;
1741 if remaining_bytes > 0 {
1742 let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_bytes) };
1744 let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_bytes) };
1746
1747 let first_x: u8s = x_vec & mask;
1748 let first_y: u8s = y_vec & mask;
1749 s0 = s0.dot_simd(first_x, first_y);
1750
1751 let second_x: u8s = (x_vec >> 2) & mask;
1752 let second_y: u8s = (y_vec >> 2) & mask;
1753 s1 = s1.dot_simd(second_x, second_y);
1754
1755 let third_x: u8s = (x_vec >> 4) & mask;
1756 let third_y: u8s = (y_vec >> 4) & mask;
1757 s2 = s2.dot_simd(third_x, third_y);
1758
1759 let fourth_x: u8s = (x_vec >> 6) & mask;
1760 let fourth_y: u8s = (y_vec >> 6) & mask;
1761 s3 = s3.dot_simd(fourth_x, fourth_y);
1762 i += remaining_bytes;
1763 }
1764
1765 s = ((s0 + s1) + (s2 + s3)).sum_tree();
1766 }
1767
1768 i *= 4;
1770
1771 debug_assert!(len - i <= 3);
1773 if i != len {
1774 #[inline(never)]
1775 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
1776 let mut s: i32 = 0;
1777 for i in from..x.len() {
1778 let ix = unsafe { x.get_unchecked(i) } as i32;
1780 let iy = unsafe { y.get_unchecked(i) } as i32;
1782 s += ix * iy;
1783 }
1784 s as u32
1785 }
1786 s += fallback(x, y, i);
1787 }
1788
1789 Ok(MV::new(s))
1790 }
1791}
1792
1793macro_rules! impl_fallback_ip {
1795 (($N:literal, $M:literal)) => {
1796 impl Target2<diskann_wide::arch::Scalar, MathematicalResult<u32>, USlice<'_, $N>, USlice<'_, $M>> for InnerProduct {
1804 #[inline(never)]
1805 fn run(
1806 self,
1807 _: diskann_wide::arch::Scalar,
1808 x: USlice<'_, $N>,
1809 y: USlice<'_, $M>
1810 ) -> MathematicalResult<u32> {
1811 let len = check_lengths!(x, y)?;
1812
1813 let mut accum: u32 = 0;
1814 for i in 0..len {
1815 let ix = unsafe { x.get_unchecked(i) } as u32;
1817 let iy = unsafe { y.get_unchecked(i) } as u32;
1819 accum += ix * iy;
1820 }
1821 Ok(MV::new(accum))
1822 }
1823 }
1824 };
1825 ($N:literal) => {
1826 impl_fallback_ip!(($N, $N));
1827 };
1828 ($($args:tt),+ $(,)?) => {
1829 $(impl_fallback_ip!($args);)+
1830 };
1831}
1832
1833impl_fallback_ip!(7, 6, 5, 4, 3, 2, (8, 4), (8, 2), (8, 1));
1834
1835#[cfg(target_arch = "x86_64")]
1836retarget!(diskann_wide::arch::x86_64::V3, InnerProduct, 7, 6, 5, 3);
1837
1838#[cfg(target_arch = "x86_64")]
1839retarget!(
1840 diskann_wide::arch::x86_64::V4,
1841 InnerProduct,
1842 7,
1843 6,
1844 5,
1845 3,
1846 (8, 4),
1847 (8, 2),
1848 (8, 1)
1849);
1850
1851dispatch_pure!(
1852 InnerProduct,
1853 1,
1854 2,
1855 3,
1856 4,
1857 5,
1858 6,
1859 7,
1860 (8, 8),
1861 (8, 4),
1862 (8, 2),
1863 (8, 1)
1864);
1865
1866#[cfg(target_arch = "x86_64")]
1870impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 4>>
1871 for InnerProduct
1872{
1873 #[inline(always)]
1885 fn run(
1886 self,
1887 arch: diskann_wide::arch::x86_64::V3,
1888 x: USlice<'_, 8>,
1889 y: USlice<'_, 4>,
1890 ) -> MathematicalResult<u32> {
1891 use std::arch::x86_64::_mm256_maddubs_epi16;
1892
1893 let len = check_lengths!(x, y)?;
1894
1895 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1896 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::x86_64::V3>::u8x16);
1897 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
1898 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1899
1900 let px: *const u8 = x.as_ptr();
1901 let py: *const u8 = y.as_ptr();
1902
1903 let mut i: usize = 0;
1904 let mut s: u32 = 0;
1905
1906 #[inline(always)]
1907 fn unpack_half(input: u8s_16) -> u8s_32 {
1908 let combined = diskann_wide::LoHi::new(input, input >> 4).zip::<u8s_32>();
1909 combined & u8s_32::splat(input.arch(), (1u8 << 4) - 1)
1910 }
1911
1912 let blocks = len / 32;
1914 if blocks > 0 {
1915 let mut acc = i32s::default(arch);
1916
1917 let products = |x: u8s_32, y: u8s_32| -> i16s {
1918 i16s::from_underlying(arch, unsafe {
1920 _mm256_maddubs_epi16(x.to_underlying(), y.to_underlying())
1921 })
1922 };
1923
1924 let ones = i16s::splat(arch, 1);
1925
1926 while i + 4 <= blocks {
1932 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * i)) };
1936 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * i)) };
1938 let m0 = products(vx, unpack_half(vy));
1939
1940 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 1))) };
1943 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 1))) };
1945 let m1 = products(vx, unpack_half(vy));
1946
1947 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 2))) };
1950 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 2))) };
1952 let m2 = products(vx, unpack_half(vy));
1953
1954 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 3))) };
1957 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 3))) };
1959 let m3 = products(vx, unpack_half(vy));
1960
1961 acc = acc.dot_simd(m0 + m1 + m2 + m3, ones);
1962 i += 4;
1963 }
1964
1965 while i < blocks {
1967 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * i)) };
1970 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * i)) };
1972 acc = acc.dot_simd(products(vx, unpack_half(vy)), ones);
1973 i += 1;
1974 }
1975
1976 s = acc.sum_tree() as u32;
1977 }
1978
1979 i *= 32;
1981
1982 if i != len {
1984 #[inline(never)]
1985 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 4>, from: usize) -> u32 {
1986 let mut s: u32 = 0;
1987 for i in from..x.len() {
1988 let ix = unsafe { x.get_unchecked(i) } as u32;
1990 let iy = unsafe { y.get_unchecked(i) } as u32;
1992 s += ix * iy;
1993 }
1994 s
1995 }
1996 s += fallback(x, y, i);
1997 }
1998
1999 Ok(MV::new(s))
2000 }
2001}
2002
2003#[cfg(target_arch = "x86_64")]
2004impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 2>>
2005 for InnerProduct
2006{
2007 #[inline(always)]
2020 fn run(
2021 self,
2022 arch: diskann_wide::arch::x86_64::V3,
2023 x: USlice<'_, 8>,
2024 y: USlice<'_, 2>,
2025 ) -> MathematicalResult<u32> {
2026 use diskann_wide::SplitJoin;
2027 use std::arch::x86_64::_mm256_maddubs_epi16;
2028
2029 let len = check_lengths!(x, y)?;
2030
2031 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
2032 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::x86_64::V3>::u8x16);
2033 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
2034 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
2035
2036 let px: *const u8 = x.as_ptr();
2037 let py: *const u8 = y.as_ptr();
2038
2039 let mut i: usize = 0;
2040 let mut s: u32 = 0;
2041
2042 let blocks = len / 64;
2044 if blocks > 0 {
2045 let mut acc = i32s::default(arch);
2046
2047 let products = |x: u8s_32, y: u8s_32| -> i16s {
2048 i16s::from_underlying(arch, unsafe {
2050 _mm256_maddubs_epi16(x.to_underlying(), y.to_underlying())
2051 })
2052 };
2053
2054 #[inline(always)]
2055 fn unpack_sub<const N: u8>(input: u8s_16) -> u8s_32 {
2056 let combined = diskann_wide::LoHi::new(input, input >> N).zip::<u8s_32>();
2057 combined & u8s_32::splat(input.arch(), (1u8 << N) - 1)
2058 }
2059
2060 let unpack_crumbs = |x: u8s_16| -> (u8s_32, u8s_32) {
2061 let nibbles = unpack_sub::<4>(x);
2063
2064 let diskann_wide::LoHi { lo, hi } = nibbles.split();
2067 let lower = unpack_sub::<2>(lo);
2068 let upper = unpack_sub::<2>(hi);
2069
2070 (lower, upper)
2071 };
2072
2073 let ones = i16s::splat(arch, 1);
2074
2075 while i + 4 <= blocks {
2082 let (vx0, vx1, (vy0, vy1)) = unsafe {
2086 (
2087 u8s_32::load_simd(arch, px.add(64 * i)),
2088 u8s_32::load_simd(arch, px.add(64 * i + 32)),
2089 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * i))),
2090 )
2091 };
2092 let m0a = products(vx0, vy0);
2093 let m0b = products(vx1, vy1);
2094
2095 let (vx0, vx1, (vy0, vy1)) = unsafe {
2098 (
2099 u8s_32::load_simd(arch, px.add(64 * (i + 1))),
2100 u8s_32::load_simd(arch, px.add(64 * (i + 1) + 32)),
2101 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 1)))),
2102 )
2103 };
2104 let m1a = products(vx0, vy0);
2105 let m1b = products(vx1, vy1);
2106
2107 let (vx0, vx1, (vy0, vy1)) = unsafe {
2110 (
2111 u8s_32::load_simd(arch, px.add(64 * (i + 2))),
2112 u8s_32::load_simd(arch, px.add(64 * (i + 2) + 32)),
2113 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 2)))),
2114 )
2115 };
2116 let m2a = products(vx0, vy0);
2117 let m2b = products(vx1, vy1);
2118
2119 let (vx0, vx1, (vy0, vy1)) = unsafe {
2122 (
2123 u8s_32::load_simd(arch, px.add(64 * (i + 3))),
2124 u8s_32::load_simd(arch, px.add(64 * (i + 3) + 32)),
2125 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 3)))),
2126 )
2127 };
2128 let m3a = products(vx0, vy0);
2129 let m3b = products(vx1, vy1);
2130
2131 acc = acc.dot_simd((m0a + m0b + m1a + m1b) + (m2a + m2b + m3a + m3b), ones);
2132 i += 4;
2133 }
2134
2135 while i < blocks {
2137 let (vx0, vx1, (vy0, vy1)) = unsafe {
2140 (
2141 u8s_32::load_simd(arch, px.add(64 * i)),
2142 u8s_32::load_simd(arch, px.add(64 * i + 32)),
2143 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * i))),
2144 )
2145 };
2146 acc = acc.dot_simd(products(vx0, vy0) + products(vx1, vy1), ones);
2147 i += 1;
2148 }
2149
2150 s = acc.sum_tree() as u32;
2151 }
2152
2153 i *= 64;
2155
2156 if i != len {
2158 #[inline(never)]
2159 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 2>, from: usize) -> u32 {
2160 let mut s: u32 = 0;
2161 for i in from..x.len() {
2162 let (ix, iy) =
2164 unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2165 s += ix * iy;
2166 }
2167 s
2168 }
2169 s += fallback(x, y, i);
2170 }
2171
2172 Ok(MV::new(s))
2173 }
2174}
2175
2176#[cfg(target_arch = "x86_64")]
2177impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 1>>
2178 for InnerProduct
2179{
2180 #[inline(always)]
2194 fn run(
2195 self,
2196 arch: diskann_wide::arch::x86_64::V3,
2197 x: USlice<'_, 8>,
2198 y: USlice<'_, 1>,
2199 ) -> MathematicalResult<u32> {
2200 use diskann_wide::{FromInt, SIMDMask};
2201 use std::arch::x86_64::_mm256_sad_epu8;
2202
2203 let len = check_lengths!(x, y)?;
2204
2205 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
2206 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
2207
2208 type Mask32 = diskann_wide::BitMask<32, diskann_wide::arch::x86_64::V3>;
2209 type Mask8x32 = diskann_wide::arch::x86_64::v3::masks::mask8x32;
2210
2211 let px: *const u8 = x.as_ptr();
2212 let py: *const u8 = y.as_ptr();
2213
2214 let mut i: usize = 0;
2215 let mut s: u32 = 0;
2216
2217 let blocks = len / 32;
2219 if blocks > 0 {
2220 let mut acc = i32s::default(arch);
2221 let zero = u8s_32::default(arch);
2222
2223 let masked_sad = |vx: u8s_32, bits: u32| -> i32s {
2229 let byte_mask: Mask8x32 = Mask32::from_int(arch, bits).into();
2231
2232 let masked = vx & u8s_32::from_underlying(arch, byte_mask.to_underlying());
2234
2235 i32s::from_underlying(arch, unsafe {
2238 _mm256_sad_epu8(masked.to_underlying(), zero.to_underlying())
2239 })
2240 };
2241
2242 while i + 4 <= blocks {
2244 let s0 = unsafe {
2247 let vx = u8s_32::load_simd(arch, px.add(32 * i));
2248 let bits = (py.add(4 * i) as *const u32).read_unaligned();
2249 masked_sad(vx, bits)
2250 };
2251
2252 let s1 = unsafe {
2254 let vx = u8s_32::load_simd(arch, px.add(32 * (i + 1)));
2255 let bits = (py.add(4 * (i + 1)) as *const u32).read_unaligned();
2256 masked_sad(vx, bits)
2257 };
2258
2259 let s2 = unsafe {
2261 let vx = u8s_32::load_simd(arch, px.add(32 * (i + 2)));
2262 let bits = (py.add(4 * (i + 2)) as *const u32).read_unaligned();
2263 masked_sad(vx, bits)
2264 };
2265
2266 let s3 = unsafe {
2268 let vx = u8s_32::load_simd(arch, px.add(32 * (i + 3)));
2269 let bits = (py.add(4 * (i + 3)) as *const u32).read_unaligned();
2270 masked_sad(vx, bits)
2271 };
2272
2273 acc = acc + s0 + s1 + s2 + s3;
2274 i += 4;
2275 }
2276
2277 while i < blocks {
2279 let si = unsafe {
2282 let vx = u8s_32::load_simd(arch, px.add(32 * i));
2283 let bits = (py.add(4 * i) as *const u32).read_unaligned();
2284 masked_sad(vx, bits)
2285 };
2286 acc = acc + si;
2287 i += 1;
2288 }
2289
2290 s = acc.sum_tree() as u32;
2291 }
2292
2293 i *= 32;
2295
2296 if i != len {
2298 #[inline(never)]
2299 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 1>, from: usize) -> u32 {
2300 let mut s: u32 = 0;
2301 for i in from..x.len() {
2302 let (ix, iy) =
2304 unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2305 s += ix * iy;
2306 }
2307 s
2308 }
2309 s += fallback(x, y, i);
2310 }
2311
2312 Ok(MV::new(s))
2313 }
2314}
2315
2316#[cfg(target_arch = "aarch64")]
2317retarget!(
2318 diskann_wide::arch::aarch64::Neon,
2319 InnerProduct,
2320 7,
2321 6,
2322 5,
2323 3,
2324 (8, 4),
2325 (8, 2),
2326 (8, 1)
2327);
2328
2329impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 4, BitTranspose>, USlice<'_, 1, Dense>>
2358 for InnerProduct
2359where
2360 A: Architecture,
2361{
2362 #[inline(always)]
2363 fn run(
2364 self,
2365 _: A,
2366 x: USlice<'_, 4, BitTranspose>,
2367 y: USlice<'_, 1, Dense>,
2368 ) -> MathematicalResult<u32> {
2369 let len = check_lengths!(x, y)?;
2370
2371 let px: *const u64 = x.as_ptr().cast();
2378 let py: *const u64 = y.as_ptr().cast();
2379
2380 let mut i = 0;
2381 let mut s: u32 = 0;
2382
2383 let blocks = len / 64;
2384 while i < blocks {
2385 let bits = unsafe { py.add(i).read_unaligned() };
2387
2388 let b0 = unsafe { px.add(4 * i).read_unaligned() };
2394 s += (bits & b0).count_ones();
2395
2396 let b1 = unsafe { px.add(4 * i + 1).read_unaligned() };
2398 s += (bits & b1).count_ones() << 1;
2399
2400 let b2 = unsafe { px.add(4 * i + 2).read_unaligned() };
2402 s += (bits & b2).count_ones() << 2;
2403
2404 let b3 = unsafe { px.add(4 * i + 3).read_unaligned() };
2406 s += (bits & b3).count_ones() << 3;
2407
2408 i += 1;
2409 }
2410
2411 if 64 * i == len {
2413 return Ok(MV::new(s));
2414 }
2415
2416 let k = i * 8;
2418
2419 let py = unsafe { py.cast::<u8>().add(k) };
2424 let bytes_remaining = y.bytes() - k;
2425 let mut bits: u64 = 0;
2426
2427 for j in 0..bytes_remaining.min(8) {
2430 bits += (unsafe { py.add(j).read() } as u64) << (8 * j);
2433 }
2434
2435 bits &= (0x01u64 << (len - (64 * i))) - 1;
2438
2439 let b0 = unsafe { px.add(4 * i).read_unaligned() };
2444 s += (bits & b0).count_ones();
2445
2446 let b1 = unsafe { px.add(4 * i + 1).read_unaligned() };
2448 s += (bits & b1).count_ones() << 1;
2449
2450 let b2 = unsafe { px.add(4 * i + 2).read_unaligned() };
2452 s += (bits & b2).count_ones() << 2;
2453
2454 let b3 = unsafe { px.add(4 * i + 3).read_unaligned() };
2456 s += (bits & b3).count_ones() << 3;
2457
2458 Ok(MV::new(s))
2459 }
2460}
2461
2462impl
2463 PureDistanceFunction<USlice<'_, 4, BitTranspose>, USlice<'_, 1, Dense>, MathematicalResult<u32>>
2464 for InnerProduct
2465{
2466 fn evaluate(
2467 x: USlice<'_, 4, BitTranspose>,
2468 y: USlice<'_, 1, Dense>,
2469 ) -> MathematicalResult<u32> {
2470 (diskann_wide::ARCH).run2(Self, x, y)
2471 }
2472}
2473
2474#[cfg(target_arch = "x86_64")]
2505impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 1>>
2506 for InnerProduct
2507{
2508 #[inline(always)]
2509 fn run(
2510 self,
2511 arch: diskann_wide::arch::x86_64::V3,
2512 x: &[f32],
2513 y: USlice<'_, 1>,
2514 ) -> MathematicalResult<f32> {
2515 let len = check_lengths!(x, y)?;
2516
2517 use std::arch::x86_64::*;
2518
2519 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
2520 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
2521
2522 let values = f32s::from_array(arch, [0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
2525
2526 let variable_shifts = u32s::from_array(arch, [0, 1, 2, 3, 4, 5, 6, 7]);
2528
2529 let px: *const f32 = x.as_ptr();
2530 let py: *const u32 = y.as_ptr().cast();
2531
2532 let mut i = 0;
2533 let mut s = f32s::default(arch);
2534
2535 let prep = |v: u32| -> u32s { u32s::splat(arch, v) >> variable_shifts };
2536 let to_f32 = |v: u32s| -> f32s {
2537 f32s::from_underlying(arch, unsafe {
2540 _mm256_permutevar_ps(values.to_underlying(), v.to_underlying())
2541 })
2542 };
2543
2544 let blocks = len / 32;
2546 if i < blocks {
2547 let mut s0 = f32s::default(arch);
2548 let mut s1 = f32s::default(arch);
2549
2550 while i < blocks {
2551 let iy = prep(unsafe { py.add(i).read_unaligned() });
2553
2554 let ix0 = unsafe { f32s::load_simd(arch, px.add(32 * i)) };
2556 let ix1 = unsafe { f32s::load_simd(arch, px.add(32 * i + 8)) };
2558 let ix2 = unsafe { f32s::load_simd(arch, px.add(32 * i + 16)) };
2560 let ix3 = unsafe { f32s::load_simd(arch, px.add(32 * i + 24)) };
2562
2563 s0 = ix0.mul_add_simd(to_f32(iy), s0);
2564 s1 = ix1.mul_add_simd(to_f32(iy >> 8), s1);
2565 s0 = ix2.mul_add_simd(to_f32(iy >> 16), s0);
2566 s1 = ix3.mul_add_simd(to_f32(iy >> 24), s1);
2567
2568 i += 1;
2569 }
2570 s = s0 + s1;
2571 }
2572
2573 let remainder = len % 32;
2574 if remainder != 0 {
2575 let tail = if len % 8 == 0 { 8 } else { len % 8 };
2576
2577 let py = unsafe { py.add(blocks) };
2580
2581 if remainder <= 8 {
2582 unsafe {
2585 load_one(py, |iy| {
2586 let iy = prep(iy);
2587 let ix = f32s::load_simd_first(arch, px.add(32 * blocks), tail);
2588 s = ix.mul_add_simd(to_f32(iy), s);
2589 })
2590 }
2591 } else if remainder <= 16 {
2592 unsafe {
2595 load_two(py, |iy| {
2596 let iy = prep(iy);
2597 let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
2598 let ix1 = f32s::load_simd_first(arch, px.add(32 * blocks + 8), tail);
2599 s = ix0.mul_add_simd(to_f32(iy), s);
2600 s = ix1.mul_add_simd(to_f32(iy >> 8), s);
2601 })
2602 }
2603 } else if remainder <= 24 {
2604 unsafe {
2607 load_three(py, |iy| {
2608 let iy = prep(iy);
2609
2610 let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
2611 let ix1 = f32s::load_simd(arch, px.add(32 * blocks + 8));
2612 let ix2 = f32s::load_simd_first(arch, px.add(32 * blocks + 16), tail);
2613
2614 s = ix0.mul_add_simd(to_f32(iy), s);
2615 s = ix1.mul_add_simd(to_f32(iy >> 8), s);
2616 s = ix2.mul_add_simd(to_f32(iy >> 16), s);
2617 })
2618 }
2619 } else {
2620 unsafe {
2623 load_four(py, |iy| {
2624 let iy = prep(iy);
2625
2626 let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
2627 let ix1 = f32s::load_simd(arch, px.add(32 * blocks + 8));
2628 let ix2 = f32s::load_simd(arch, px.add(32 * blocks + 16));
2629 let ix3 = f32s::load_simd_first(arch, px.add(32 * blocks + 24), tail);
2630
2631 s = ix0.mul_add_simd(to_f32(iy), s);
2632 s = ix1.mul_add_simd(to_f32(iy >> 8), s);
2633 s = ix2.mul_add_simd(to_f32(iy >> 16), s);
2634 s = ix3.mul_add_simd(to_f32(iy >> 24), s);
2635 })
2636 }
2637 }
2638 }
2639
2640 Ok(MV::new(s.sum_tree()))
2641 }
2642}
2643
2644#[cfg(target_arch = "x86_64")]
2648impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 2>>
2649 for InnerProduct
2650{
2651 #[inline(always)]
2652 fn run(
2653 self,
2654 arch: diskann_wide::arch::x86_64::V3,
2655 x: &[f32],
2656 y: USlice<'_, 2>,
2657 ) -> MathematicalResult<f32> {
2658 let len = check_lengths!(x, y)?;
2659
2660 use std::arch::x86_64::*;
2661
2662 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
2663 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
2664
2665 let values = f32s::from_array(arch, [0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0]);
2669
2670 let variable_shifts = u32s::from_array(arch, [0, 2, 4, 6, 8, 10, 12, 14]);
2672
2673 let px: *const f32 = x.as_ptr();
2674 let py: *const u32 = y.as_ptr().cast();
2675
2676 let mut i = 0;
2677 let mut s = f32s::default(arch);
2678
2679 let prep = |v: u32| -> u32s { u32s::splat(arch, v) >> variable_shifts };
2680 let to_f32 = |v: u32s| -> f32s {
2681 f32s::from_underlying(arch, unsafe {
2684 _mm256_permutevar_ps(values.to_underlying(), v.to_underlying())
2685 })
2686 };
2687
2688 let blocks = len / 16;
2689 if blocks != 0 {
2690 let mut s0 = f32s::default(arch);
2691 let mut s1 = f32s::default(arch);
2692
2693 while i + 2 <= blocks {
2695 let iy = prep(unsafe { py.add(i).read_unaligned() });
2698
2699 let (ix0, ix1) = unsafe {
2702 (
2703 f32s::load_simd(arch, px.add(16 * i)),
2704 f32s::load_simd(arch, px.add(16 * i + 8)),
2705 )
2706 };
2707
2708 s0 = ix0.mul_add_simd(to_f32(iy), s0);
2709 s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
2710
2711 let iy = prep(unsafe { py.add(i + 1).read_unaligned() });
2714
2715 let (ix0, ix1) = unsafe {
2717 (
2718 f32s::load_simd(arch, px.add(16 * (i + 1))),
2719 f32s::load_simd(arch, px.add(16 * (i + 1) + 8)),
2720 )
2721 };
2722
2723 s0 = ix0.mul_add_simd(to_f32(iy), s0);
2724 s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
2725
2726 i += 2;
2727 }
2728
2729 if i < blocks {
2731 let iy = prep(unsafe { py.add(i).read_unaligned() });
2734
2735 let (ix0, ix1) = unsafe {
2737 (
2738 f32s::load_simd(arch, px.add(16 * i)),
2739 f32s::load_simd(arch, px.add(16 * i + 8)),
2740 )
2741 };
2742
2743 s0 = ix0.mul_add_simd(to_f32(iy), s0);
2744 s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
2745 }
2746
2747 s = s0 + s1;
2748 }
2749
2750 let remainder = len % 16;
2751 if remainder != 0 {
2752 let tail = if len % 8 == 0 { 8 } else { len % 8 };
2753 let py = unsafe { py.add(blocks) };
2756
2757 if remainder <= 4 {
2758 unsafe {
2761 load_one(py, |iy| {
2762 let iy = prep(iy);
2763 let ix = f32s::load_simd_first(arch, px.add(16 * blocks), tail);
2764 s = ix.mul_add_simd(to_f32(iy), s);
2765 });
2766 }
2767 } else if remainder <= 8 {
2768 unsafe {
2771 load_two(py, |iy| {
2772 let iy = prep(iy);
2773 let ix = f32s::load_simd_first(arch, px.add(16 * blocks), tail);
2774 s = ix.mul_add_simd(to_f32(iy), s);
2775 });
2776 }
2777 } else if remainder <= 12 {
2778 unsafe {
2781 load_three(py, |iy| {
2782 let iy = prep(iy);
2783 let ix0 = f32s::load_simd(arch, px.add(16 * blocks));
2784 let ix1 = f32s::load_simd_first(arch, px.add(16 * blocks + 8), tail);
2785 s = ix0.mul_add_simd(to_f32(iy), s);
2786 s = ix1.mul_add_simd(to_f32(iy >> 16), s);
2787 });
2788 }
2789 } else {
2790 unsafe {
2793 load_four(py, |iy| {
2794 let iy = prep(iy);
2795 let ix0 = f32s::load_simd(arch, px.add(16 * blocks));
2796 let ix1 = f32s::load_simd_first(arch, px.add(16 * blocks + 8), tail);
2797 s = ix0.mul_add_simd(to_f32(iy), s);
2798 s = ix1.mul_add_simd(to_f32(iy >> 16), s);
2799 });
2800 }
2801 }
2802 }
2803
2804 Ok(MV::new(s.sum_tree()))
2805 }
2806}
2807
2808#[cfg(target_arch = "x86_64")]
2813impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 4>>
2814 for InnerProduct
2815{
2816 #[inline(always)]
2817 fn run(
2818 self,
2819 arch: diskann_wide::arch::x86_64::V3,
2820 x: &[f32],
2821 y: USlice<'_, 4>,
2822 ) -> MathematicalResult<f32> {
2823 let len = check_lengths!(x, y)?;
2824
2825 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
2826 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
2827
2828 let variable_shifts = i32s::from_array(arch, [0, 4, 8, 12, 16, 20, 24, 28]);
2829 let mask = i32s::splat(arch, 0x0f);
2830
2831 let to_f32 = |v: u32| -> f32s {
2832 ((i32s::splat(arch, v as i32) >> variable_shifts) & mask).simd_cast()
2833 };
2834
2835 let px: *const f32 = x.as_ptr();
2836 let py: *const u32 = y.as_ptr().cast();
2837
2838 let mut i = 0;
2839 let mut s = f32s::default(arch);
2840
2841 let blocks = len / 8;
2842 while i < blocks {
2843 let ix = unsafe { f32s::load_simd(arch, px.add(8 * i)) };
2845 let iy = to_f32(unsafe { py.add(i).read_unaligned() });
2847 s = ix.mul_add_simd(iy, s);
2848
2849 i += 1;
2850 }
2851
2852 let remainder = len % 8;
2853 if remainder != 0 {
2854 let f = |iy| {
2855 let ix = unsafe { f32s::load_simd_first(arch, px.add(8 * blocks), remainder) };
2859 s = ix.mul_add_simd(to_f32(iy), s);
2860 };
2861
2862 let py = unsafe { py.add(blocks) };
2865
2866 if remainder <= 2 {
2867 unsafe { load_one(py, f) };
2869 } else if remainder <= 4 {
2870 unsafe { load_two(py, f) };
2872 } else if remainder <= 6 {
2873 unsafe { load_three(py, f) };
2875 } else {
2876 unsafe { load_four(py, f) };
2878 }
2879 }
2880
2881 Ok(MV::new(s.sum_tree()))
2882 }
2883}
2884
2885impl<const N: usize>
2886 Target2<diskann_wide::arch::Scalar, MathematicalResult<f32>, &[f32], USlice<'_, N>>
2887 for InnerProduct
2888where
2889 Unsigned: Representation<N>,
2890{
2891 #[inline(always)]
2894 fn run(
2895 self,
2896 _: diskann_wide::arch::Scalar,
2897 x: &[f32],
2898 y: USlice<'_, N>,
2899 ) -> MathematicalResult<f32> {
2900 check_lengths!(x, y)?;
2901
2902 let mut s = 0.0;
2903 for (i, x) in x.iter().enumerate() {
2904 let y = unsafe { y.get_unchecked(i) } as f32;
2907 s += x * y;
2908 }
2909
2910 Ok(MV::new(s))
2911 }
2912}
2913
2914macro_rules! ip_retarget {
2916 ($arch:path, $N:literal) => {
2917 impl Target2<$arch, MathematicalResult<f32>, &[f32], USlice<'_, $N>>
2918 for InnerProduct
2919 {
2920 #[inline(always)]
2921 fn run(
2922 self,
2923 arch: $arch,
2924 x: &[f32],
2925 y: USlice<'_, $N>,
2926 ) -> MathematicalResult<f32> {
2927 self.run(arch.retarget(), x, y)
2928 }
2929 }
2930 };
2931 ($arch:path, $($Ns:literal),*) => {
2932 $(ip_retarget!($arch, $Ns);)*
2933 }
2934}
2935
2936#[cfg(target_arch = "x86_64")]
2937ip_retarget!(diskann_wide::arch::x86_64::V3, 3, 5, 6, 7, 8);
2938
2939#[cfg(target_arch = "x86_64")]
2940ip_retarget!(diskann_wide::arch::x86_64::V4, 1, 2, 3, 4, 5, 6, 7, 8);
2941
2942#[cfg(target_arch = "aarch64")]
2943ip_retarget!(diskann_wide::arch::aarch64::Neon, 1, 2, 3, 4, 5, 6, 7, 8);
2944
2945macro_rules! dispatch_full_ip {
2948 ($N:literal) => {
2949 impl PureDistanceFunction<&[f32], USlice<'_, $N>, MathematicalResult<f32>>
2953 for InnerProduct
2954 {
2955 fn evaluate(x: &[f32], y: USlice<'_, $N>) -> MathematicalResult<f32> {
2956 Self.run(ARCH, x, y)
2957 }
2958 }
2959 };
2960 ($($Ns:literal),*) => {
2961 $(dispatch_full_ip!($Ns);)*
2962 }
2963}
2964
2965dispatch_full_ip!(1, 2, 3, 4, 5, 6, 7, 8);
2966
2967#[cfg(test)]
2972mod tests {
2973 use std::{collections::HashMap, fmt::Display, sync::LazyLock};
2974
2975 use diskann_utils::{Reborrow, lazy_format};
2976 use rand::{
2977 Rng, SeedableRng,
2978 distr::{Distribution, Uniform},
2979 rngs::StdRng,
2980 seq::IndexedRandom,
2981 };
2982
2983 use super::*;
2984 use crate::bits::{BoxedBitSlice, Representation, Unsigned};
2985
2986 type MR = MathematicalResult<u32>;
2987
2988 #[inline(always)]
2989 fn should_check_this_dimension(dim: usize) -> bool {
2990 if cfg!(miri) {
2991 return dim.is_power_of_two()
2992 || (dim > 1 && (dim - 1).is_power_of_two())
2993 || (dim < 64 && (dim % 8 == 7));
2994 }
2995
2996 true
2997 }
2998
2999 fn test_bitslice_distances<const NBITS: usize, R>(
3009 dim_max: usize,
3010 trials_per_dim: usize,
3011 evaluate_l2: &dyn Fn(USlice<'_, NBITS>, USlice<'_, NBITS>) -> MR,
3012 evaluate_ip: &dyn Fn(USlice<'_, NBITS>, USlice<'_, NBITS>) -> MR,
3013 context: &str,
3014 rng: &mut R,
3015 ) where
3016 Unsigned: Representation<NBITS>,
3017 R: Rng,
3018 {
3019 let domain = Unsigned::domain_const::<NBITS>();
3020 let min: i64 = *domain.start();
3021 let max: i64 = *domain.end();
3022
3023 let dist = Uniform::new_inclusive(min, max).unwrap();
3024
3025 for dim in 0..dim_max {
3026 if !should_check_this_dimension(dim) {
3027 continue;
3028 }
3029
3030 let mut x_reference: Vec<u8> = vec![0; dim];
3031 let mut y_reference: Vec<u8> = vec![0; dim];
3032
3033 let mut x = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(dim);
3034 let mut y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(dim);
3035
3036 for trial in 0..trials_per_dim {
3037 x_reference
3038 .iter_mut()
3039 .for_each(|i| *i = dist.sample(rng).try_into().unwrap());
3040 y_reference
3041 .iter_mut()
3042 .for_each(|i| *i = dist.sample(rng).try_into().unwrap());
3043
3044 x.as_mut_slice().fill(u8::MAX);
3047 y.as_mut_slice().fill(u8::MAX);
3048
3049 for i in 0..dim {
3050 x.set(i, x_reference[i].into()).unwrap();
3051 y.set(i, y_reference[i].into()).unwrap();
3052 }
3053
3054 let expected: MV<f32> =
3056 diskann_vector::distance::SquaredL2::evaluate(&*x_reference, &*y_reference);
3057
3058 let got = evaluate_l2(x.reborrow(), y.reborrow()).unwrap();
3059
3060 assert_eq!(
3062 expected.into_inner(),
3063 got.into_inner() as f32,
3064 "failed SquaredL2 for NBITS = {}, dim = {}, trial = {} -- context {}",
3065 NBITS,
3066 dim,
3067 trial,
3068 context,
3069 );
3070
3071 let expected: MV<f32> =
3073 diskann_vector::distance::InnerProduct::evaluate(&*x_reference, &*y_reference);
3074
3075 let got = evaluate_ip(x.reborrow(), y.reborrow()).unwrap();
3076
3077 assert_eq!(
3079 expected.into_inner(),
3080 got.into_inner() as f32,
3081 "faild InnerProduct for NBITS = {}, dim = {}, trial = {} -- context {}",
3082 NBITS,
3083 dim,
3084 trial,
3085 context,
3086 );
3087 }
3088 }
3089
3090 let x = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(10);
3092 let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(11);
3093
3094 assert!(
3095 evaluate_l2(x.reborrow(), y.reborrow()).is_err(),
3096 "context: {}",
3097 context
3098 );
3099 assert!(
3100 evaluate_l2(y.reborrow(), x.reborrow()).is_err(),
3101 "context: {}",
3102 context
3103 );
3104
3105 assert!(
3106 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
3107 "context: {}",
3108 context
3109 );
3110 assert!(
3111 evaluate_ip(y.reborrow(), x.reborrow()).is_err(),
3112 "context: {}",
3113 context
3114 );
3115 }
3116
3117 cfg_if::cfg_if! {
3118 if #[cfg(miri)] {
3119 const MAX_DIM: usize = 132;
3120 const TRIALS_PER_DIM: usize = 1;
3121 } else {
3122 const MAX_DIM: usize = 256;
3123 const TRIALS_PER_DIM: usize = 20;
3124 }
3125 }
3126
3127 static BITSLICE_TEST_BOUNDS: LazyLock<HashMap<Key, Bounds>> = LazyLock::new(|| {
3137 use ArchKey::{Neon, Scalar, X86_64_V3, X86_64_V4};
3138 [
3139 (Key::new(1, Scalar), Bounds::new(64, 64)),
3140 (Key::new(1, X86_64_V3), Bounds::new(256, 256)),
3141 (Key::new(1, X86_64_V4), Bounds::new(256, 256)),
3142 (Key::new(1, Neon), Bounds::new(64, 64)),
3143 (Key::new(2, Scalar), Bounds::new(64, 64)),
3144 (Key::new(2, X86_64_V3), Bounds::new(512, 300)),
3146 (Key::new(2, X86_64_V4), Bounds::new(768, 600)), (Key::new(2, Neon), Bounds::new(256, 256)),
3148 (Key::new(3, Scalar), Bounds::new(64, 64)),
3149 (Key::new(3, X86_64_V3), Bounds::new(256, 96)),
3150 (Key::new(3, X86_64_V4), Bounds::new(256, 96)),
3151 (Key::new(3, Neon), Bounds::new(64, 64)),
3152 (Key::new(4, Scalar), Bounds::new(64, 64)),
3153 (Key::new(4, X86_64_V3), Bounds::new(256, 150)),
3155 (Key::new(4, X86_64_V4), Bounds::new(512, 300)),
3156 (Key::new(4, Neon), Bounds::new(128, 128)),
3157 (Key::new(5, Scalar), Bounds::new(64, 64)),
3158 (Key::new(5, X86_64_V3), Bounds::new(256, 96)),
3159 (Key::new(5, X86_64_V4), Bounds::new(256, 96)),
3160 (Key::new(5, Neon), Bounds::new(64, 64)),
3161 (Key::new(6, Scalar), Bounds::new(64, 64)),
3162 (Key::new(6, X86_64_V3), Bounds::new(256, 96)),
3163 (Key::new(6, X86_64_V4), Bounds::new(256, 96)),
3164 (Key::new(6, Neon), Bounds::new(64, 64)),
3165 (Key::new(7, Scalar), Bounds::new(64, 64)),
3166 (Key::new(7, X86_64_V3), Bounds::new(256, 96)),
3167 (Key::new(7, X86_64_V4), Bounds::new(256, 96)),
3168 (Key::new(7, Neon), Bounds::new(64, 64)),
3169 (Key::new(8, Scalar), Bounds::new(64, 64)),
3170 (Key::new(8, X86_64_V3), Bounds::new(256, 96)),
3171 (Key::new(8, X86_64_V4), Bounds::new(256, 96)),
3172 (Key::new(8, Neon), Bounds::new(64, 64)),
3173 ]
3174 .into_iter()
3175 .collect()
3176 });
3177
3178 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
3179 enum ArchKey {
3180 Scalar,
3181 #[expect(non_camel_case_types)]
3182 X86_64_V3,
3183 #[expect(non_camel_case_types)]
3184 X86_64_V4,
3185 Neon,
3186 }
3187
3188 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
3189 struct Key {
3190 nbits: usize,
3191 arch: ArchKey,
3192 }
3193
3194 impl Key {
3195 fn new(nbits: usize, arch: ArchKey) -> Self {
3196 Self { nbits, arch }
3197 }
3198 }
3199
3200 #[derive(Debug, Clone, Copy)]
3201 struct Bounds {
3202 standard: usize,
3203 miri: usize,
3204 }
3205
3206 impl Bounds {
3207 fn new(standard: usize, miri: usize) -> Self {
3208 Self { standard, miri }
3209 }
3210
3211 fn get(&self) -> usize {
3212 if cfg!(miri) { self.miri } else { self.standard }
3213 }
3214 }
3215
3216 macro_rules! test_bitslice {
3217 ($name:ident, $nbits:literal, $seed:literal) => {
3218 #[test]
3219 fn $name() {
3220 let mut rng = StdRng::seed_from_u64($seed);
3221
3222 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::Scalar)].get();
3223
3224 test_bitslice_distances::<$nbits, _>(
3225 max_dim,
3226 TRIALS_PER_DIM,
3227 &|x, y| SquaredL2::evaluate(x, y),
3228 &|x, y| InnerProduct::evaluate(x, y),
3229 "pure distance function",
3230 &mut rng,
3231 );
3232
3233 test_bitslice_distances::<$nbits, _>(
3234 max_dim,
3235 TRIALS_PER_DIM,
3236 &|x, y| diskann_wide::arch::Scalar::new().run2(SquaredL2, x, y),
3237 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
3238 "scalar arch",
3239 &mut rng,
3240 );
3241
3242 #[cfg(target_arch = "x86_64")]
3244 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
3245 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::X86_64_V3)].get();
3246 test_bitslice_distances::<$nbits, _>(
3247 max_dim,
3248 TRIALS_PER_DIM,
3249 &|x, y| arch.run2(SquaredL2, x, y),
3250 &|x, y| arch.run2(InnerProduct, x, y),
3251 "x86-64-v3",
3252 &mut rng,
3253 );
3254 }
3255
3256 #[cfg(target_arch = "x86_64")]
3257 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
3258 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::X86_64_V4)].get();
3259 test_bitslice_distances::<$nbits, _>(
3260 max_dim,
3261 TRIALS_PER_DIM,
3262 &|x, y| arch.run2(SquaredL2, x, y),
3263 &|x, y| arch.run2(InnerProduct, x, y),
3264 "x86-64-v4",
3265 &mut rng,
3266 );
3267 }
3268
3269 #[cfg(target_arch = "aarch64")]
3270 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
3271 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::Neon)].get();
3272 test_bitslice_distances::<$nbits, _>(
3273 max_dim,
3274 TRIALS_PER_DIM,
3275 &|x, y| arch.run2(SquaredL2, x, y),
3276 &|x, y| arch.run2(InnerProduct, x, y),
3277 "neon",
3278 &mut rng,
3279 );
3280 }
3281 }
3282 };
3283 }
3284
3285 test_bitslice!(test_bitslice_distances_8bit, 8, 0xf0330c6d880e08ff);
3286 test_bitslice!(test_bitslice_distances_7bit, 7, 0x98aa7f2d4c83844f);
3287 test_bitslice!(test_bitslice_distances_6bit, 6, 0xf2f7ad7a37764b4c);
3288 test_bitslice!(test_bitslice_distances_5bit, 5, 0xae878d14973fb43f);
3289 test_bitslice!(test_bitslice_distances_4bit, 4, 0x8d6dbb8a6b19a4f8);
3290 test_bitslice!(test_bitslice_distances_3bit, 3, 0x8f56767236e58da2);
3291 test_bitslice!(test_bitslice_distances_2bit, 2, 0xb04f741a257b61af);
3292 test_bitslice!(test_bitslice_distances_1bit, 1, 0x820ea031c379eab5);
3293
3294 fn test_hamming_distances<R>(dim_max: usize, trials_per_dim: usize, rng: &mut R)
3299 where
3300 R: Rng,
3301 {
3302 let dist: [i8; 2] = [-1, 1];
3303
3304 for dim in 0..dim_max {
3305 if !should_check_this_dimension(dim) {
3306 continue;
3307 }
3308
3309 let mut x_reference: Vec<i8> = vec![1; dim];
3310 let mut y_reference: Vec<i8> = vec![1; dim];
3311
3312 let mut x = BoxedBitSlice::<1, Binary>::new_boxed(dim);
3313 let mut y = BoxedBitSlice::<1, Binary>::new_boxed(dim);
3314
3315 for _ in 0..trials_per_dim {
3316 x_reference
3317 .iter_mut()
3318 .for_each(|i| *i = *dist.choose(rng).unwrap());
3319 y_reference
3320 .iter_mut()
3321 .for_each(|i| *i = *dist.choose(rng).unwrap());
3322
3323 x.as_mut_slice().fill(u8::MAX);
3326 y.as_mut_slice().fill(u8::MAX);
3327
3328 for i in 0..dim {
3329 x.set(i, x_reference[i].into()).unwrap();
3330 y.set(i, y_reference[i].into()).unwrap();
3331 }
3332
3333 let expected: MV<f32> =
3339 diskann_vector::distance::SquaredL2::evaluate(&*x_reference, &*y_reference);
3340 let got: MV<u32> = Hamming::evaluate(x.reborrow(), y.reborrow()).unwrap();
3341 assert_eq!(4.0 * (got.into_inner() as f32), expected.into_inner());
3342 }
3343 }
3344
3345 let x = BoxedBitSlice::<1, Binary>::new_boxed(10);
3346 let y = BoxedBitSlice::<1, Binary>::new_boxed(11);
3347 assert!(Hamming::evaluate(x.reborrow(), y.reborrow()).is_err());
3348 assert!(Hamming::evaluate(y.reborrow(), x.reborrow()).is_err());
3349 }
3350
3351 #[test]
3352 fn test_hamming_distance() {
3353 let mut rng = StdRng::seed_from_u64(0x2160419161246d97);
3354 test_hamming_distances(MAX_DIM, TRIALS_PER_DIM, &mut rng);
3355 }
3356
3357 fn test_bit_transpose_distances<R>(
3362 dim_max: usize,
3363 trials_per_dim: usize,
3364 evaluate_ip: &dyn Fn(USlice<'_, 4, BitTranspose>, USlice<'_, 1>) -> MR,
3365 context: &str,
3366 rng: &mut R,
3367 ) where
3368 R: Rng,
3369 {
3370 let dist_4bit = {
3371 let domain = Unsigned::domain_const::<4>();
3372 Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
3373 };
3374
3375 let dist_1bit = {
3376 let domain = Unsigned::domain_const::<1>();
3377 Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
3378 };
3379
3380 for dim in 0..dim_max {
3381 if !should_check_this_dimension(dim) {
3382 continue;
3383 }
3384
3385 let mut x_reference: Vec<u8> = vec![0; dim];
3386 let mut y_reference: Vec<u8> = vec![0; dim];
3387
3388 let mut x = BoxedBitSlice::<4, Unsigned, BitTranspose>::new_boxed(dim);
3389 let mut y = BoxedBitSlice::<1, Unsigned, Dense>::new_boxed(dim);
3390
3391 for trial in 0..trials_per_dim {
3392 x_reference
3393 .iter_mut()
3394 .for_each(|i| *i = dist_4bit.sample(rng).try_into().unwrap());
3395 y_reference
3396 .iter_mut()
3397 .for_each(|i| *i = dist_1bit.sample(rng).try_into().unwrap());
3398
3399 x.as_mut_slice().fill(u8::MAX);
3401 y.as_mut_slice().fill(u8::MAX);
3402
3403 for i in 0..dim {
3404 x.set(i, x_reference[i].into()).unwrap();
3405 y.set(i, y_reference[i].into()).unwrap();
3406 }
3407
3408 let expected: MV<f32> =
3410 diskann_vector::distance::InnerProduct::evaluate(&*x_reference, &*y_reference);
3411
3412 let got = evaluate_ip(x.reborrow(), y.reborrow());
3413
3414 assert_eq!(
3416 expected.into_inner(),
3417 got.unwrap().into_inner() as f32,
3418 "faild InnerProduct for dim = {}, trial = {} -- context {}",
3419 dim,
3420 trial,
3421 context,
3422 );
3423 }
3424 }
3425
3426 let x = BoxedBitSlice::<4, Unsigned, BitTranspose>::new_boxed(10);
3427 let y = BoxedBitSlice::<1, Unsigned>::new_boxed(11);
3428 assert!(
3429 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
3430 "context: {}",
3431 context
3432 );
3433
3434 let y = BoxedBitSlice::<1, Unsigned>::new_boxed(9);
3435 assert!(
3436 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
3437 "context: {}",
3438 context
3439 );
3440 }
3441
3442 #[test]
3443 fn test_bit_transpose_distance() {
3444 let mut rng = StdRng::seed_from_u64(0xe20e26e926d4b853);
3445
3446 test_bit_transpose_distances(
3447 MAX_DIM,
3448 TRIALS_PER_DIM,
3449 &|x, y| InnerProduct::evaluate(x, y),
3450 "pure distance function",
3451 &mut rng,
3452 );
3453
3454 test_bit_transpose_distances(
3455 MAX_DIM,
3456 TRIALS_PER_DIM,
3457 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
3458 "scalar",
3459 &mut rng,
3460 );
3461
3462 #[cfg(target_arch = "x86_64")]
3464 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
3465 test_bit_transpose_distances(
3466 MAX_DIM,
3467 TRIALS_PER_DIM,
3468 &|x, y| arch.run2(InnerProduct, x, y),
3469 "x86-64-v3",
3470 &mut rng,
3471 );
3472 }
3473
3474 #[cfg(target_arch = "x86_64")]
3476 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
3477 test_bit_transpose_distances(
3478 MAX_DIM,
3479 TRIALS_PER_DIM,
3480 &|x, y| arch.run2(InnerProduct, x, y),
3481 "x86-64-v4",
3482 &mut rng,
3483 );
3484 }
3485
3486 #[cfg(target_arch = "aarch64")]
3488 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
3489 test_bit_transpose_distances(
3490 MAX_DIM,
3491 TRIALS_PER_DIM,
3492 &|x, y| arch.run2(InnerProduct, x, y),
3493 "neon",
3494 &mut rng,
3495 );
3496 }
3497 }
3498
3499 fn test_full_distances<const NBITS: usize>(
3504 dim_max: usize,
3505 trials_per_dim: usize,
3506 evaluate_ip: &dyn Fn(&[f32], USlice<'_, NBITS>) -> MathematicalResult<f32>,
3507 context: &str,
3508 rng: &mut impl Rng,
3509 ) where
3510 Unsigned: Representation<NBITS>,
3511 {
3512 let dist_float = [-2.0, -1.0, 0.0, 1.0, 2.0];
3514 let dist_bit = {
3515 let domain = Unsigned::domain_const::<NBITS>();
3516 Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
3517 };
3518
3519 for dim in 0..dim_max {
3520 if !should_check_this_dimension(dim) {
3521 continue;
3522 }
3523
3524 let mut x: Vec<f32> = vec![0.0; dim];
3525
3526 let mut y_reference: Vec<u8> = vec![0; dim];
3527 let mut y = BoxedBitSlice::<NBITS, Unsigned, Dense>::new_boxed(dim);
3528
3529 for trial in 0..trials_per_dim {
3530 x.iter_mut()
3531 .for_each(|i| *i = *dist_float.choose(rng).unwrap());
3532 y_reference
3533 .iter_mut()
3534 .for_each(|i| *i = dist_bit.sample(rng).try_into().unwrap());
3535
3536 y.as_mut_slice().fill(u8::MAX);
3538
3539 let mut expected = 0.0;
3540 for i in 0..dim {
3541 y.set(i, y_reference[i].into()).unwrap();
3542 expected += y_reference[i] as f32 * x[i];
3543 }
3544
3545 let got = evaluate_ip(&x, y.reborrow()).unwrap();
3547
3548 assert_eq!(
3550 expected,
3551 got.into_inner(),
3552 "faild InnerProduct for dim = {}, trial = {} -- context {}",
3553 dim,
3554 trial,
3555 context,
3556 );
3557
3558 let scalar: MV<f32> = InnerProduct
3561 .run(diskann_wide::arch::Scalar, x.as_slice(), y.reborrow())
3562 .unwrap();
3563 assert_eq!(got.into_inner(), scalar.into_inner());
3564 }
3565 }
3566
3567 let x = vec![0.0; 10];
3569 let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(11);
3570 assert!(
3571 evaluate_ip(x.as_slice(), y.reborrow()).is_err(),
3572 "context: {}",
3573 context
3574 );
3575
3576 let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(9);
3577 assert!(
3578 evaluate_ip(x.as_slice(), y.reborrow()).is_err(),
3579 "context: {}",
3580 context
3581 );
3582 }
3583
3584 macro_rules! test_full {
3585 ($name:ident, $nbits:literal, $seed:literal) => {
3586 #[test]
3587 fn $name() {
3588 let mut rng = StdRng::seed_from_u64($seed);
3589
3590 test_full_distances::<$nbits>(
3591 MAX_DIM,
3592 TRIALS_PER_DIM,
3593 &|x, y| InnerProduct::evaluate(x, y),
3594 "pure distance function",
3595 &mut rng,
3596 );
3597
3598 test_full_distances::<$nbits>(
3599 MAX_DIM,
3600 TRIALS_PER_DIM,
3601 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
3602 "scalar",
3603 &mut rng,
3604 );
3605
3606 #[cfg(target_arch = "x86_64")]
3608 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
3609 test_full_distances::<$nbits>(
3610 MAX_DIM,
3611 TRIALS_PER_DIM,
3612 &|x, y| arch.run2(InnerProduct, x, y),
3613 "x86-64-v3",
3614 &mut rng,
3615 );
3616 }
3617
3618 #[cfg(target_arch = "x86_64")]
3619 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
3620 test_full_distances::<$nbits>(
3621 MAX_DIM,
3622 TRIALS_PER_DIM,
3623 &|x, y| arch.run2(InnerProduct, x, y),
3624 "x86-64-v4",
3625 &mut rng,
3626 );
3627 }
3628
3629 #[cfg(target_arch = "aarch64")]
3630 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
3631 test_full_distances::<$nbits>(
3632 MAX_DIM,
3633 TRIALS_PER_DIM,
3634 &|x, y| arch.run2(InnerProduct, x, y),
3635 "neon",
3636 &mut rng,
3637 );
3638 }
3639 }
3640 };
3641 }
3642
3643 test_full!(test_full_distance_1bit, 1, 0xe20e26e926d4b853);
3644 test_full!(test_full_distance_2bit, 2, 0xae9542700aecbf68);
3645 test_full!(test_full_distance_3bit, 3, 0xfffd04b26bb6068c);
3646 test_full!(test_full_distance_4bit, 4, 0x86db49fd1a1704ba);
3647 test_full!(test_full_distance_5bit, 5, 0x3a35dc7fa7931c41);
3648 test_full!(test_full_distance_6bit, 6, 0x1f69de79e418d336);
3649 test_full!(test_full_distance_7bit, 7, 0x3fcf17b82dadc5ab);
3650 test_full!(test_full_distance_8bit, 8, 0x85dcaf48b1399db2);
3651
3652 struct HetCase<const M: usize> {
3659 x_vals: Vec<i64>,
3660 y_vals: Vec<i64>,
3661 }
3662
3663 impl<const M: usize> HetCase<M>
3664 where
3665 Unsigned: Representation<M>,
3666 {
3667 fn new(dim: usize, fill: impl FnMut(usize) -> (i64, i64)) -> Self {
3668 let (x_vals, y_vals) = (0..dim).map(fill).unzip();
3669 Self { x_vals, y_vals }
3670 }
3671
3672 fn check_with(
3673 &self,
3674 label: impl Display,
3675 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
3676 ) {
3677 let dim = self.x_vals.len();
3678 let mut x = BoxedBitSlice::<8, Unsigned>::new_boxed(dim);
3679 let mut y = BoxedBitSlice::<M, Unsigned>::new_boxed(dim);
3680 x.as_mut_slice().fill(u8::MAX);
3682 y.as_mut_slice().fill(u8::MAX);
3683 for (i, (&xv, &yv)) in self.x_vals.iter().zip(&self.y_vals).enumerate() {
3684 x.set(i, xv).unwrap();
3685 y.set(i, yv).unwrap();
3686 }
3687 let expected: u32 = self
3688 .x_vals
3689 .iter()
3690 .zip(&self.y_vals)
3691 .map(|(&a, &b)| a as u32 * b as u32)
3692 .sum();
3693 let got = evaluate(x.reborrow(), y.reborrow()).unwrap().into_inner();
3694 assert_eq!(expected, got, "{} failed for dim = {}", label, dim);
3695 }
3696 }
3697
3698 fn fuzz_heterogeneous_ip<const M: usize>(
3700 dim_max: usize,
3701 trials_per_dim: usize,
3702 max_val: i64,
3703 evaluate_ip: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
3704 context: &str,
3705 rng: &mut impl Rng,
3706 ) where
3707 Unsigned: Representation<M>,
3708 {
3709 let dist_8bit = Uniform::new_inclusive(0i64, 255i64).unwrap();
3710 let dist_mbit = Uniform::new_inclusive(0i64, max_val).unwrap();
3711
3712 for dim in 0..dim_max {
3713 for trial in 0..trials_per_dim {
3714 HetCase::<M>::new(dim, |_| {
3715 (dist_8bit.sample(&mut *rng), dist_mbit.sample(&mut *rng))
3716 })
3717 .check_with(
3718 lazy_format!("IP(8,{}) dim={dim}, trial={trial} -- {context}", M),
3719 evaluate_ip,
3720 );
3721 }
3722
3723 let x = BoxedBitSlice::<8, Unsigned>::new_boxed(dim);
3725 let y = BoxedBitSlice::<M, Unsigned>::new_boxed(dim + 1);
3726 assert!(
3727 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
3728 "context: {}",
3729 context,
3730 );
3731 }
3732 }
3733
3734 fn het_test_max_values<const M: usize>(
3737 max_val: i64,
3738 context: &str,
3739 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
3740 ) where
3741 Unsigned: Representation<M>,
3742 {
3743 let dims = [127, 128, 129, 255, 256, 512, 768, 896, 3072];
3744 for &dim in &dims {
3745 let case = HetCase::<M>::new(dim, |_| (255, max_val));
3746 case.check_with(lazy_format!("max-value {context} dim={dim}"), evaluate);
3747 }
3748 }
3749
3750 fn het_test_known_answers<const M: usize>(
3752 max_val: i64,
3753 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
3754 ) where
3755 Unsigned: Representation<M>,
3756 {
3757 HetCase::<M>::new(64, |_| (200, max_val)).check_with("vpmaddubsw operand-order", evaluate);
3760
3761 let y_val = (max_val / 2).max(1);
3763 HetCase::<M>::new(128, |i| ((i % 256) as i64, y_val))
3764 .check_with("ascending-x constant-y", evaluate);
3765
3766 HetCase::<M>::new(1, |_| (200, max_val)).check_with("single element", evaluate);
3768 }
3769
3770 fn het_test_edge_cases<const M: usize>(
3772 max_val: i64,
3773 block_size: usize,
3774 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
3775 ) where
3776 Unsigned: Representation<M>,
3777 {
3778 let y_half = (max_val / 2).max(1);
3779
3780 HetCase::<M>::new(64, |_| (0, max_val)).check_with("x-zero y-nonzero", evaluate);
3782 HetCase::<M>::new(64, |_| (255, 0)).check_with("y-zero x-nonzero", evaluate);
3783
3784 for dim in 0..=(block_size + 1) {
3786 HetCase::<M>::new(dim, |_| (3, y_half)).check_with("uniform fill", evaluate);
3787 }
3788
3789 for &dim in &[block_size, 2 * block_size, 4 * block_size, 8 * block_size] {
3791 HetCase::<M>::new(dim, |_| (100, max_val)).check_with("exact block boundary", evaluate);
3792 }
3793
3794 HetCase::<M>::new(300, |i| ((i % 256) as i64, 1))
3796 .check_with("x-varies y-constant", evaluate);
3797
3798 HetCase::<M>::new(300, |i| (1, (i as i64) % (max_val + 1)))
3800 .check_with("x-constant y-varies", evaluate);
3801
3802 HetCase::<M>::new(128, |i| if i % 2 == 0 { (255, max_val) } else { (0, 0) })
3804 .check_with("alternating pattern", evaluate);
3805
3806 HetCase::<M>::new(128, |i| if i % 2 == 0 { (0, 0) } else { (255, max_val) })
3808 .check_with("opposite alternating", evaluate);
3809
3810 HetCase::<M>::new(1024, |_| (255, max_val)).check_with("large accumulation", evaluate);
3812
3813 for x_val in [128i64, 170, 200, 240, 255] {
3815 HetCase::<M>::new(block_size, move |_| (x_val, y_half))
3816 .check_with(lazy_format!("x > 127 (x_val={x_val})"), evaluate);
3817 }
3818
3819 HetCase::<M>::new(block_size - 1, |i| {
3821 (
3822 ((i * 7 + 3) % 256) as i64,
3823 ((i * 11 + 5) as i64) % (max_val + 1),
3824 )
3825 })
3826 .check_with("dim=block_size-1 (all scalar)", evaluate);
3827
3828 let unroll4 = 4 * block_size;
3830 for &dim in &[
3831 unroll4,
3832 unroll4 + 1,
3833 unroll4 + block_size,
3834 unroll4 + block_size + 1,
3835 ] {
3836 HetCase::<M>::new(dim, |i| {
3837 (((i + 1) % 256) as i64, ((i + 1) as i64) % (max_val + 1))
3838 })
3839 .check_with("unroll boundary", evaluate);
3840 }
3841 }
3842
3843 macro_rules! heterogeneous_ip_tests_8xM {
3844 (
3845 mod_name: $mod:ident,
3846 M: $M:literal,
3847 max_val: $max_val:literal,
3848 block_size: $block_size:literal,
3849 seed_fuzz: $seed_fuzz:literal,
3850 ) => {
3851 mod $mod {
3852 use super::*;
3853
3854 #[test]
3855 fn all_ip_dispatches() {
3856 let mut rng = StdRng::seed_from_u64($seed_fuzz);
3857
3858 fuzz_heterogeneous_ip::<$M>(
3859 MAX_DIM,
3860 TRIALS_PER_DIM,
3861 $max_val,
3862 &|x, y| InnerProduct::evaluate(x, y),
3863 "pure distance function",
3864 &mut rng,
3865 );
3866 fuzz_heterogeneous_ip::<$M>(
3867 MAX_DIM,
3868 TRIALS_PER_DIM,
3869 $max_val,
3870 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
3871 "scalar arch",
3872 &mut rng,
3873 );
3874 #[cfg(target_arch = "x86_64")]
3875 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
3876 fuzz_heterogeneous_ip::<$M>(
3877 MAX_DIM,
3878 TRIALS_PER_DIM,
3879 $max_val,
3880 &|x, y| arch.run2(InnerProduct, x, y),
3881 "x86-64-v3",
3882 &mut rng,
3883 );
3884 }
3885 #[cfg(target_arch = "x86_64")]
3886 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
3887 fuzz_heterogeneous_ip::<$M>(
3888 MAX_DIM,
3889 TRIALS_PER_DIM,
3890 $max_val,
3891 &|x, y| arch.run2(InnerProduct, x, y),
3892 "x86-64-v4",
3893 &mut rng,
3894 );
3895 }
3896 }
3897
3898 #[test]
3899 fn max_values() {
3900 het_test_max_values::<$M>($max_val, "dispatch", &|x, y| {
3901 InnerProduct::evaluate(x, y)
3902 });
3903 #[cfg(target_arch = "x86_64")]
3904 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
3905 het_test_max_values::<$M>($max_val, "V3", &|x, y| {
3906 arch.run2(InnerProduct, x, y)
3907 });
3908 }
3909 }
3910
3911 #[test]
3912 fn known_answers() {
3913 het_test_known_answers::<$M>($max_val, &|x, y| InnerProduct::evaluate(x, y));
3914 }
3915
3916 #[test]
3917 fn edge_cases() {
3918 het_test_edge_cases::<$M>($max_val, $block_size, &|x, y| {
3919 InnerProduct::evaluate(x, y)
3920 });
3921 }
3922 }
3923 };
3924 }
3925
3926 heterogeneous_ip_tests_8xM! {
3927 mod_name: heterogeneous_ip_8x4,
3928 M: 4,
3929 max_val: 15,
3930 block_size: 32,
3931 seed_fuzz: 0xd3a7f1c09b2e4856,
3932 }
3933
3934 heterogeneous_ip_tests_8xM! {
3935 mod_name: heterogeneous_ip_8x2,
3936 M: 2,
3937 max_val: 3,
3938 block_size: 64,
3939 seed_fuzz: 0x82c4a6e809f1d3b5,
3940 }
3941
3942 heterogeneous_ip_tests_8xM! {
3943 mod_name: heterogeneous_ip_8x1,
3944 M: 1,
3945 max_val: 1,
3946 block_size: 32,
3947 seed_fuzz: 0x1b17_a5e7c2d0f839,
3948 }
3949}