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
117use super::{Binary, BitSlice, BitTranspose, Dense, Representation, Unsigned};
118use crate::distances::{Hamming, InnerProduct, MV, MathematicalResult, SquaredL2, check_lengths};
119
120type USlice<'a, const N: usize, Perm = Dense> = BitSlice<'a, N, Unsigned, Perm>;
122
123macro_rules! retarget {
129 ($arch:path, $op:ty, ($N:literal, $M:literal)) => {
130 impl Target2<
131 $arch,
132 MathematicalResult<u32>,
133 USlice<'_, $N>,
134 USlice<'_, $M>,
135 > for $op {
136 #[inline(always)]
137 fn run(
138 self,
139 arch: $arch,
140 x: USlice<'_, $N>,
141 y: USlice<'_, $M>
142 ) -> MathematicalResult<u32> {
143 self.run(arch.retarget(), x, y)
144 }
145 }
146 };
147 ($arch:path, $op:ty, $N:literal) => {
148 retarget!($arch, $op, ($N, $N));
149 };
150 ($arch:path, $op:ty, $($args:tt),+ $(,)?) => {
151 $(retarget!($arch, $op, $args);)+
152 };
153}
154
155macro_rules! dispatch_pure {
157 ($op:ty, ($N:literal, $M:literal)) => {
158 impl PureDistanceFunction<USlice<'_, $N>, USlice<'_, $M>, MathematicalResult<u32>> for $op {
159 #[inline(always)]
160 fn evaluate(x: USlice<'_, $N>, y: USlice<'_, $M>) -> MathematicalResult<u32> {
161 (diskann_wide::ARCH).run2(Self, x, y)
162 }
163 }
164 };
165 ($op:ty, $N:literal) => {
166 dispatch_pure!($op, ($N, $N));
167 };
168 ($op:ty, $($args:tt),+ $(,)?) => {
169 $(dispatch_pure!($op, $args);)+
170 }
171}
172
173#[cfg(target_arch = "x86_64")]
180unsafe fn load_one<F, R>(ptr: *const u32, mut f: F) -> R
181where
182 F: FnMut(u32) -> R,
183{
184 f(unsafe { ptr.cast::<u8>().read_unaligned() }.into())
186}
187
188#[cfg(target_arch = "x86_64")]
195unsafe fn load_two<F, R>(ptr: *const u32, mut f: F) -> R
196where
197 F: FnMut(u32) -> R,
198{
199 f(unsafe { ptr.cast::<u16>().read_unaligned() }.into())
201}
202
203#[cfg(target_arch = "x86_64")]
210unsafe fn load_three<F, R>(ptr: *const u32, mut f: F) -> R
211where
212 F: FnMut(u32) -> R,
213{
214 let lo: u32 = unsafe { ptr.cast::<u16>().read_unaligned() }.into();
216 let hi: u32 = unsafe { ptr.cast::<u8>().add(2).read_unaligned() }.into();
218 f(lo | hi << 16)
219}
220
221#[cfg(target_arch = "x86_64")]
228unsafe fn load_four<F, R>(ptr: *const u32, mut f: F) -> R
229where
230 F: FnMut(u32) -> R,
231{
232 f(unsafe { ptr.read_unaligned() })
234}
235
236trait BitVectorOp<Repr>
248where
249 Repr: Representation<1>,
250{
251 fn on_u64(x: u64, y: u64) -> u32;
253
254 fn on_u8(x: u8, y: u8) -> u32;
259}
260
261impl BitVectorOp<Unsigned> for SquaredL2 {
263 #[inline(always)]
264 fn on_u64(x: u64, y: u64) -> u32 {
265 (x ^ y).count_ones()
266 }
267 #[inline(always)]
268 fn on_u8(x: u8, y: u8) -> u32 {
269 (x ^ y).count_ones()
270 }
271}
272
273impl BitVectorOp<Binary> for Hamming {
275 #[inline(always)]
276 fn on_u64(x: u64, y: u64) -> u32 {
277 (x ^ y).count_ones()
278 }
279 #[inline(always)]
280 fn on_u8(x: u8, y: u8) -> u32 {
281 (x ^ y).count_ones()
282 }
283}
284
285impl BitVectorOp<Unsigned> for InnerProduct {
293 #[inline(always)]
294 fn on_u64(x: u64, y: u64) -> u32 {
295 (x & y).count_ones()
296 }
297 #[inline(always)]
298 fn on_u8(x: u8, y: u8) -> u32 {
299 (x & y).count_ones()
300 }
301}
302
303#[inline(always)]
308fn bitvector_op<Op, Repr>(
309 x: BitSlice<'_, 1, Repr>,
310 y: BitSlice<'_, 1, Repr>,
311) -> MathematicalResult<u32>
312where
313 Repr: Representation<1>,
314 Op: BitVectorOp<Repr>,
315{
316 let len = check_lengths!(x, y)?;
317
318 let px: *const u64 = x.as_ptr().cast();
319 let py: *const u64 = y.as_ptr().cast();
320
321 let mut i = 0;
322 let mut s: u32 = 0;
323
324 let blocks = len / 64;
326 while i < blocks {
327 let vx = unsafe { px.add(i).read_unaligned() };
331
332 let vy = unsafe { py.add(i).read_unaligned() };
336
337 s += Op::on_u64(vx, vy);
338 i += 1;
339 }
340
341 i *= 8;
343 let px: *const u8 = x.as_ptr();
344 let py: *const u8 = y.as_ptr();
345
346 let blocks = len / 8;
347 while i < blocks {
348 let vx = unsafe { px.add(i).read_unaligned() };
351
352 let vy = unsafe { py.add(i).read_unaligned() };
356 s += Op::on_u8(vx, vy);
357 i += 1;
358 }
359
360 if i * 8 != len {
361 let vx = unsafe { px.add(i).read_unaligned() };
364
365 let vy = unsafe { py.add(i).read_unaligned() };
367 let m = (0x01u8 << (len - 8 * i)) - 1;
368
369 s += Op::on_u8(vx & m, vy & m)
370 }
371 Ok(MV::new(s))
372}
373
374impl PureDistanceFunction<BitSlice<'_, 1, Binary>, BitSlice<'_, 1, Binary>, MathematicalResult<u32>>
378 for Hamming
379{
380 fn evaluate(x: BitSlice<'_, 1, Binary>, y: BitSlice<'_, 1, Binary>) -> MathematicalResult<u32> {
381 bitvector_op::<Hamming, Binary>(x, y)
382 }
383}
384
385impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 8>> for SquaredL2
398where
399 A: Architecture,
400 diskann_vector::distance::SquaredL2: for<'a> Target2<A, MV<f32>, &'a [u8], &'a [u8]>,
401{
402 #[inline(always)]
403 fn run(self, arch: A, x: USlice<'_, 8>, y: USlice<'_, 8>) -> MathematicalResult<u32> {
404 check_lengths!(x, y)?;
405
406 let r: MV<f32> = <_ as Target2<_, _, _, _>>::run(
407 diskann_vector::distance::SquaredL2 {},
408 arch,
409 x.as_slice(),
410 y.as_slice(),
411 );
412
413 Ok(MV::new(r.into_inner() as u32))
414 }
415}
416
417#[cfg(target_arch = "x86_64")]
431impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
432 for SquaredL2
433{
434 #[inline(always)]
435 fn run(
436 self,
437 arch: diskann_wide::arch::x86_64::V3,
438 x: USlice<'_, 4>,
439 y: USlice<'_, 4>,
440 ) -> MathematicalResult<u32> {
441 let len = check_lengths!(x, y)?;
442
443 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
444 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
445 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
446
447 let px_u32: *const u32 = x.as_ptr().cast();
448 let py_u32: *const u32 = y.as_ptr().cast();
449
450 let mut i = 0;
451 let mut s: u32 = 0;
452
453 let blocks = len / 8;
455 if i < blocks {
456 let mut s0 = i32s::default(arch);
457 let mut s1 = i32s::default(arch);
458 let mut s2 = i32s::default(arch);
459 let mut s3 = i32s::default(arch);
460 let mask = u32s::splat(arch, 0x000f000f);
461 while i + 8 < blocks {
462 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
467
468 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
472
473 let wx: i16s = (vx & mask).reinterpret_simd();
474 let wy: i16s = (vy & mask).reinterpret_simd();
475 let d = wx - wy;
476 s0 = s0.dot_simd(d, d);
477
478 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
479 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
480 let d = wx - wy;
481 s1 = s1.dot_simd(d, d);
482
483 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
484 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
485 let d = wx - wy;
486 s2 = s2.dot_simd(d, d);
487
488 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
489 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
490 let d = wx - wy;
491 s3 = s3.dot_simd(d, d);
492
493 i += 8;
494 }
495
496 let remainder = blocks - i;
497
498 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
504
505 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
509
510 let wx: i16s = (vx & mask).reinterpret_simd();
511 let wy: i16s = (vy & mask).reinterpret_simd();
512 let d = wx - wy;
513 s0 = s0.dot_simd(d, d);
514
515 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
516 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
517 let d = wx - wy;
518 s1 = s1.dot_simd(d, d);
519
520 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
521 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
522 let d = wx - wy;
523 s2 = s2.dot_simd(d, d);
524
525 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
526 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
527 let d = wx - wy;
528 s3 = s3.dot_simd(d, d);
529
530 i += remainder;
531
532 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
533 }
534
535 i *= 8;
537
538 if i != len {
540 #[inline(never)]
542 fn fallback(x: USlice<'_, 4>, y: USlice<'_, 4>, from: usize) -> u32 {
543 let mut s: i32 = 0;
544 for i in from..x.len() {
545 let ix = unsafe { x.get_unchecked(i) } as i32;
547 let iy = unsafe { y.get_unchecked(i) } as i32;
549 let d = ix - iy;
550 s += d * d;
551 }
552 s as u32
553 }
554 s += fallback(x, y, i);
555 }
556
557 Ok(MV::new(s))
558 }
559}
560
561#[cfg(target_arch = "x86_64")]
575impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
576 for SquaredL2
577{
578 #[inline(always)]
579 fn run(
580 self,
581 arch: diskann_wide::arch::x86_64::V4,
582 x: USlice<'_, 4>,
583 y: USlice<'_, 4>,
584 ) -> MathematicalResult<u32> {
585 let len = check_lengths!(x, y)?;
586
587 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V4>::i32x16);
588 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V4>::u32x16);
589 diskann_wide::alias!(u8s = <diskann_wide::arch::x86_64::V4>::u8x64);
590 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V4>::i16x32);
591
592 let px_u32: *const u32 = x.as_ptr().cast();
593 let py_u32: *const u32 = y.as_ptr().cast();
594
595 let mut i = 0;
596 let mut s: u32 = 0;
597
598 let blocks = len.div_ceil(8);
603 if i < blocks {
604 let mut s0 = i32s::default(arch);
605 let mut s1 = i32s::default(arch);
606 let mut s2 = i32s::default(arch);
607 let mut s3 = i32s::default(arch);
608 let mask = u32s::splat(arch, 0x000f000f);
609 while i + 16 < blocks {
610 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
616
617 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
621
622 let wx: i16s = (vx & mask).reinterpret_simd();
623 let wy: i16s = (vy & mask).reinterpret_simd();
624 let d = wx - wy;
625 s0 = s0.dot_simd(d, d);
626
627 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
628 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
629 let d = wx - wy;
630 s1 = s1.dot_simd(d, d);
631
632 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
633 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
634 let d = wx - wy;
635 s2 = s2.dot_simd(d, d);
636
637 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
638 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
639 let d = wx - wy;
640 s3 = s3.dot_simd(d, d);
641
642 i += 16;
643 }
644
645 let remainder = len / 2 - 4 * i;
652
653 if remainder > 0 {
654 let vx =
660 unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
661 let vx: u32s = vx.reinterpret_simd();
662
663 let vy =
667 unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
668 let vy: u32s = vy.reinterpret_simd();
669
670 let wx: i16s = (vx & mask).reinterpret_simd();
671 let wy: i16s = (vy & mask).reinterpret_simd();
672 let d = wx - wy;
673 s0 = s0.dot_simd(d, d);
674
675 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
676 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
677 let d = wx - wy;
678 s1 = s1.dot_simd(d, d);
679
680 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
681 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
682 let d = wx - wy;
683 s2 = s2.dot_simd(d, d);
684
685 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
686 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
687 let d = wx - wy;
688 s3 = s3.dot_simd(d, d);
689 }
690
691 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
692 i = (4 * i) + remainder;
693 }
694
695 i *= 2;
697
698 debug_assert!(len - i <= 1);
700 if i != len {
701 let ix = unsafe { x.get_unchecked(i) } as i32;
703 let iy = unsafe { y.get_unchecked(i) } as i32;
705 let d = ix - iy;
706 s += (d * d) as u32;
707 }
708
709 Ok(MV::new(s))
710 }
711}
712
713#[cfg(target_arch = "x86_64")]
727impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
728 for SquaredL2
729{
730 #[inline(always)]
731 fn run(
732 self,
733 arch: diskann_wide::arch::x86_64::V3,
734 x: USlice<'_, 2>,
735 y: USlice<'_, 2>,
736 ) -> MathematicalResult<u32> {
737 let len = check_lengths!(x, y)?;
738
739 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
740 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
741 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
742
743 let px_u32: *const u32 = x.as_ptr().cast();
744 let py_u32: *const u32 = y.as_ptr().cast();
745
746 let mut i = 0;
747 let mut s: u32 = 0;
748
749 let blocks = len / 16;
751 if i < blocks {
752 let mut s0 = i32s::default(arch);
753 let mut s1 = i32s::default(arch);
754 let mut s2 = i32s::default(arch);
755 let mut s3 = i32s::default(arch);
756 let mask = u32s::splat(arch, 0x00030003);
757 while i + 8 < blocks {
758 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
763
764 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
768
769 let wx: i16s = (vx & mask).reinterpret_simd();
770 let wy: i16s = (vy & mask).reinterpret_simd();
771 let d = wx - wy;
772 s0 = s0.dot_simd(d, d);
773
774 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
775 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
776 let d = wx - wy;
777 s1 = s1.dot_simd(d, d);
778
779 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
780 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
781 let d = wx - wy;
782 s2 = s2.dot_simd(d, d);
783
784 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
785 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
786 let d = wx - wy;
787 s3 = s3.dot_simd(d, d);
788
789 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
790 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
791 let d = wx - wy;
792 s0 = s0.dot_simd(d, d);
793
794 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
795 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
796 let d = wx - wy;
797 s1 = s1.dot_simd(d, d);
798
799 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
800 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
801 let d = wx - wy;
802 s2 = s2.dot_simd(d, d);
803
804 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
805 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
806 let d = wx - wy;
807 s3 = s3.dot_simd(d, d);
808
809 i += 8;
810 }
811
812 let remainder = blocks - i;
813
814 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
820
821 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
825 let wx: i16s = (vx & mask).reinterpret_simd();
826 let wy: i16s = (vy & mask).reinterpret_simd();
827 let d = wx - wy;
828 s0 = s0.dot_simd(d, d);
829
830 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
831 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
832 let d = wx - wy;
833 s1 = s1.dot_simd(d, d);
834
835 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
836 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
837 let d = wx - wy;
838 s2 = s2.dot_simd(d, d);
839
840 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
841 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
842 let d = wx - wy;
843 s3 = s3.dot_simd(d, d);
844
845 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
846 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
847 let d = wx - wy;
848 s0 = s0.dot_simd(d, d);
849
850 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
851 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
852 let d = wx - wy;
853 s1 = s1.dot_simd(d, d);
854
855 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
856 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
857 let d = wx - wy;
858 s2 = s2.dot_simd(d, d);
859
860 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
861 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
862 let d = wx - wy;
863 s3 = s3.dot_simd(d, d);
864
865 i += remainder;
866
867 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
868 }
869
870 i *= 16;
872
873 if i != len {
875 #[inline(never)]
877 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
878 let mut s: i32 = 0;
879 for i in from..x.len() {
880 let ix = unsafe { x.get_unchecked(i) } as i32;
882 let iy = unsafe { y.get_unchecked(i) } as i32;
884 let d = ix - iy;
885 s += d * d;
886 }
887 s as u32
888 }
889 s += fallback(x, y, i);
890 }
891
892 Ok(MV::new(s))
893 }
894}
895
896impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 1>, USlice<'_, 1>> for SquaredL2
900where
901 A: Architecture,
902{
903 fn run(self, _: A, x: USlice<'_, 1>, y: USlice<'_, 1>) -> MathematicalResult<u32> {
904 bitvector_op::<Self, Unsigned>(x, y)
905 }
906}
907
908macro_rules! impl_fallback_l2 {
910 ($N:literal) => {
911 impl Target2<diskann_wide::arch::Scalar, MathematicalResult<u32>, USlice<'_, $N>, USlice<'_, $N>> for SquaredL2 {
919 #[inline(never)]
920 fn run(
921 self,
922 _: diskann_wide::arch::Scalar,
923 x: USlice<'_, $N>,
924 y: USlice<'_, $N>
925 ) -> MathematicalResult<u32> {
926 let len = check_lengths!(x, y)?;
927
928 let mut accum: i32 = 0;
929 for i in 0..len {
930 let ix: i32 = unsafe { x.get_unchecked(i) } as i32;
932 let iy: i32 = unsafe { y.get_unchecked(i) } as i32;
934 let diff = ix - iy;
935 accum += diff * diff;
936 }
937 Ok(MV::new(accum as u32))
938 }
939 }
940 };
941 ($($N:literal),+ $(,)?) => {
942 $(impl_fallback_l2!($N);)+
943 };
944}
945
946impl_fallback_l2!(7, 6, 5, 4, 3, 2);
947
948#[cfg(target_arch = "x86_64")]
949retarget!(diskann_wide::arch::x86_64::V3, SquaredL2, 7, 6, 5, 3);
950
951#[cfg(target_arch = "x86_64")]
952retarget!(diskann_wide::arch::x86_64::V4, SquaredL2, 7, 6, 5, 3, 2);
953
954dispatch_pure!(SquaredL2, 1, 2, 3, 4, 5, 6, 7, 8);
955#[cfg(target_arch = "aarch64")]
956retarget!(
957 diskann_wide::arch::aarch64::Neon,
958 SquaredL2,
959 7,
960 6,
961 5,
962 4,
963 3,
964 2
965);
966
967impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 8>> for InnerProduct
980where
981 A: Architecture,
982 diskann_vector::distance::InnerProduct: for<'a> Target2<A, MV<f32>, &'a [u8], &'a [u8]>,
983{
984 #[inline(always)]
985 fn run(self, arch: A, x: USlice<'_, 8>, y: USlice<'_, 8>) -> MathematicalResult<u32> {
986 check_lengths!(x, y)?;
987 let r: MV<f32> = <_ as Target2<_, _, _, _>>::run(
988 diskann_vector::distance::InnerProduct {},
989 arch,
990 x.as_slice(),
991 y.as_slice(),
992 );
993
994 Ok(MV::new(r.into_inner() as u32))
995 }
996}
997
998#[cfg(target_arch = "x86_64")]
1015impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
1016 for InnerProduct
1017{
1018 #[expect(non_camel_case_types)]
1019 #[inline(always)]
1020 fn run(
1021 self,
1022 arch: diskann_wide::arch::x86_64::V4,
1023 x: USlice<'_, 2>,
1024 y: USlice<'_, 2>,
1025 ) -> MathematicalResult<u32> {
1026 let len = check_lengths!(x, y)?;
1027
1028 type i32s = <diskann_wide::arch::x86_64::V4 as Architecture>::i32x16;
1029 type u32s = <diskann_wide::arch::x86_64::V4 as Architecture>::u32x16;
1030 type u8s = <diskann_wide::arch::x86_64::V4 as Architecture>::u8x64;
1031 type i8s = <diskann_wide::arch::x86_64::V4 as Architecture>::i8x64;
1032
1033 let px_u32: *const u32 = x.as_ptr().cast();
1034 let py_u32: *const u32 = y.as_ptr().cast();
1035
1036 let mut i = 0;
1037 let mut s: u32 = 0;
1038
1039 let blocks = len.div_ceil(16);
1041 if i < blocks {
1042 let mut s0 = i32s::default(arch);
1043 let mut s1 = i32s::default(arch);
1044 let mut s2 = i32s::default(arch);
1045 let mut s3 = i32s::default(arch);
1046 let mask = u32s::splat(arch, 0x03030303);
1047 while i + 16 < blocks {
1048 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1053
1054 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1058
1059 let wx: u8s = (vx & mask).reinterpret_simd();
1060 let wy: i8s = (vy & mask).reinterpret_simd();
1061 s0 = s0.dot_simd(wx, wy);
1062
1063 let wx: u8s = ((vx >> 2) & mask).reinterpret_simd();
1064 let wy: i8s = ((vy >> 2) & mask).reinterpret_simd();
1065 s1 = s1.dot_simd(wx, wy);
1066
1067 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1068 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1069 s2 = s2.dot_simd(wx, wy);
1070
1071 let wx: u8s = ((vx >> 6) & mask).reinterpret_simd();
1072 let wy: i8s = ((vy >> 6) & mask).reinterpret_simd();
1073 s3 = s3.dot_simd(wx, wy);
1074
1075 i += 16;
1076 }
1077
1078 let remainder = len / 4 - 4 * i;
1082
1083 let vx = unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
1088 let vx: u32s = vx.reinterpret_simd();
1089
1090 let vy = unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
1094 let vy: u32s = vy.reinterpret_simd();
1095
1096 let wx: u8s = (vx & mask).reinterpret_simd();
1097 let wy: i8s = (vy & mask).reinterpret_simd();
1098 s0 = s0.dot_simd(wx, wy);
1099
1100 let wx: u8s = ((vx >> 2) & mask).reinterpret_simd();
1101 let wy: i8s = ((vy >> 2) & mask).reinterpret_simd();
1102 s1 = s1.dot_simd(wx, wy);
1103
1104 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1105 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1106 s2 = s2.dot_simd(wx, wy);
1107
1108 let wx: u8s = ((vx >> 6) & mask).reinterpret_simd();
1109 let wy: i8s = ((vy >> 6) & mask).reinterpret_simd();
1110 s3 = s3.dot_simd(wx, wy);
1111
1112 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1113 i = (4 * i) + remainder;
1114 }
1115
1116 i *= 4;
1118
1119 debug_assert!(len - i <= 3);
1121 let rest = (len - i).min(3);
1122 if i != len {
1123 for j in 0..rest {
1124 let ix = unsafe { x.get_unchecked(i + j) } as u32;
1126 let iy = unsafe { y.get_unchecked(i + j) } as u32;
1128 s += ix * iy;
1129 }
1130 }
1131
1132 Ok(MV::new(s))
1133 }
1134}
1135
1136#[cfg(target_arch = "x86_64")]
1150impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
1151 for InnerProduct
1152{
1153 #[inline(always)]
1154 fn run(
1155 self,
1156 arch: diskann_wide::arch::x86_64::V3,
1157 x: USlice<'_, 4>,
1158 y: USlice<'_, 4>,
1159 ) -> MathematicalResult<u32> {
1160 let len = check_lengths!(x, y)?;
1161
1162 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1163 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
1164 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1165
1166 let px_u32: *const u32 = x.as_ptr().cast();
1167 let py_u32: *const u32 = y.as_ptr().cast();
1168
1169 let mut i = 0;
1170 let mut s: u32 = 0;
1171
1172 let blocks = len / 8;
1173 if i < blocks {
1174 let mut s0 = i32s::default(arch);
1175 let mut s1 = i32s::default(arch);
1176 let mut s2 = i32s::default(arch);
1177 let mut s3 = i32s::default(arch);
1178 let mask = u32s::splat(arch, 0x000f000f);
1179 while i + 8 < blocks {
1180 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1185
1186 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1190
1191 let wx: i16s = (vx & mask).reinterpret_simd();
1192 let wy: i16s = (vy & mask).reinterpret_simd();
1193 s0 = s0.dot_simd(wx, wy);
1194
1195 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1196 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1197 s1 = s1.dot_simd(wx, wy);
1198
1199 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1200 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1201 s2 = s2.dot_simd(wx, wy);
1202
1203 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1204 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1205 s3 = s3.dot_simd(wx, wy);
1206
1207 i += 8;
1208 }
1209
1210 let remainder = blocks - i;
1211
1212 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1218
1219 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1223
1224 let wx: i16s = (vx & mask).reinterpret_simd();
1225 let wy: i16s = (vy & mask).reinterpret_simd();
1226 s0 = s0.dot_simd(wx, wy);
1227
1228 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1229 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1230 s1 = s1.dot_simd(wx, wy);
1231
1232 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1233 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1234 s2 = s2.dot_simd(wx, wy);
1235
1236 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1237 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1238 s3 = s3.dot_simd(wx, wy);
1239
1240 i += remainder;
1241
1242 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1243 }
1244
1245 i *= 8;
1247
1248 if i != len {
1250 #[inline(never)]
1252 fn fallback(x: USlice<'_, 4>, y: USlice<'_, 4>, from: usize) -> u32 {
1253 let mut s: u32 = 0;
1254 for i in from..x.len() {
1255 let ix = unsafe { x.get_unchecked(i) } as u32;
1257 let iy = unsafe { y.get_unchecked(i) } as u32;
1259 s += ix * iy;
1260 }
1261 s
1262 }
1263 s += fallback(x, y, i);
1264 }
1265
1266 Ok(MV::new(s))
1267 }
1268}
1269
1270#[cfg(target_arch = "x86_64")]
1288impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
1289 for InnerProduct
1290{
1291 #[inline(always)]
1292 fn run(
1293 self,
1294 arch: diskann_wide::arch::x86_64::V4,
1295 x: USlice<'_, 4>,
1296 y: USlice<'_, 4>,
1297 ) -> MathematicalResult<u32> {
1298 let len = check_lengths!(x, y)?;
1299
1300 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V4>::i32x16);
1301 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V4>::u32x16);
1302 diskann_wide::alias!(u8s = <diskann_wide::arch::x86_64::V4>::u8x64);
1303 diskann_wide::alias!(i8s = <diskann_wide::arch::x86_64::V4>::i8x64);
1304
1305 let px_u32: *const u32 = x.as_ptr().cast();
1306 let py_u32: *const u32 = y.as_ptr().cast();
1307
1308 let mut i = 0;
1309 let mut s: u32 = 0;
1310
1311 let blocks = len.div_ceil(8);
1316 if i < blocks {
1317 let mut s0 = i32s::default(arch);
1318 let mut s1 = i32s::default(arch);
1319 let mask = u32s::splat(arch, 0x0f0f0f0f);
1320 while i + 16 < blocks {
1321 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1327
1328 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1332
1333 let wx: u8s = (vx & mask).reinterpret_simd();
1337 let wy: i8s = (vy & mask).reinterpret_simd();
1338 s0 = s0.dot_simd(wx, wy);
1339
1340 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1341 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1342 s1 = s1.dot_simd(wx, wy);
1343
1344 i += 16;
1345 }
1346
1347 let remainder = len / 2 - 4 * i;
1354
1355 if remainder > 0 {
1356 let vx =
1362 unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
1363 let vx: u32s = vx.reinterpret_simd();
1364
1365 let vy =
1369 unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
1370 let vy: u32s = vy.reinterpret_simd();
1371
1372 let wx: u8s = (vx & mask).reinterpret_simd();
1373 let wy: i8s = (vy & mask).reinterpret_simd();
1374 s0 = s0.dot_simd(wx, wy);
1375
1376 let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1377 let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1378 s1 = s1.dot_simd(wx, wy);
1379 }
1380
1381 s = (s0 + s1).sum_tree() as u32;
1382 i = (4 * i) + remainder;
1383 }
1384
1385 i *= 2;
1387
1388 debug_assert!(len - i <= 1);
1390 if i != len {
1391 let ix = unsafe { x.get_unchecked(i) } as u32;
1393 let iy = unsafe { y.get_unchecked(i) } as u32;
1395 s += ix * iy;
1396 }
1397
1398 Ok(MV::new(s))
1399 }
1400}
1401
1402#[cfg(target_arch = "x86_64")]
1416impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
1417 for InnerProduct
1418{
1419 #[inline(always)]
1420 fn run(
1421 self,
1422 arch: diskann_wide::arch::x86_64::V3,
1423 x: USlice<'_, 2>,
1424 y: USlice<'_, 2>,
1425 ) -> MathematicalResult<u32> {
1426 let len = check_lengths!(x, y)?;
1427
1428 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1429 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
1430 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1431
1432 let px_u32: *const u32 = x.as_ptr().cast();
1433 let py_u32: *const u32 = y.as_ptr().cast();
1434
1435 let mut i = 0;
1436 let mut s: u32 = 0;
1437
1438 let blocks = len / 16;
1440 if i < blocks {
1441 let mut s0 = i32s::default(arch);
1442 let mut s1 = i32s::default(arch);
1443 let mut s2 = i32s::default(arch);
1444 let mut s3 = i32s::default(arch);
1445 let mask = u32s::splat(arch, 0x00030003);
1446 while i + 8 < blocks {
1447 let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1452
1453 let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1457
1458 let wx: i16s = (vx & mask).reinterpret_simd();
1459 let wy: i16s = (vy & mask).reinterpret_simd();
1460 s0 = s0.dot_simd(wx, wy);
1461
1462 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1463 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1464 s1 = s1.dot_simd(wx, wy);
1465
1466 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1467 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1468 s2 = s2.dot_simd(wx, wy);
1469
1470 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1471 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1472 s3 = s3.dot_simd(wx, wy);
1473
1474 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1475 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1476 s0 = s0.dot_simd(wx, wy);
1477
1478 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1479 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1480 s1 = s1.dot_simd(wx, wy);
1481
1482 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1483 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1484 s2 = s2.dot_simd(wx, wy);
1485
1486 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1487 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1488 s3 = s3.dot_simd(wx, wy);
1489
1490 i += 8;
1491 }
1492
1493 let remainder = blocks - i;
1494
1495 let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1501
1502 let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1506 let wx: i16s = (vx & mask).reinterpret_simd();
1507 let wy: i16s = (vy & mask).reinterpret_simd();
1508 s0 = s0.dot_simd(wx, wy);
1509
1510 let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1511 let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1512 s1 = s1.dot_simd(wx, wy);
1513
1514 let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1515 let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1516 s2 = s2.dot_simd(wx, wy);
1517
1518 let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1519 let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1520 s3 = s3.dot_simd(wx, wy);
1521
1522 let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1523 let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1524 s0 = s0.dot_simd(wx, wy);
1525
1526 let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1527 let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1528 s1 = s1.dot_simd(wx, wy);
1529
1530 let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1531 let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1532 s2 = s2.dot_simd(wx, wy);
1533
1534 let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1535 let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1536 s3 = s3.dot_simd(wx, wy);
1537
1538 i += remainder;
1539
1540 s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1541 }
1542
1543 i *= 16;
1545
1546 if i != len {
1548 #[inline(never)]
1550 fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
1551 let mut s: u32 = 0;
1552 for i in from..x.len() {
1553 let ix = unsafe { x.get_unchecked(i) } as u32;
1555 let iy = unsafe { y.get_unchecked(i) } as u32;
1557 s += ix * iy;
1558 }
1559 s
1560 }
1561 s += fallback(x, y, i);
1562 }
1563
1564 Ok(MV::new(s))
1565 }
1566}
1567
1568impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 1>, USlice<'_, 1>> for InnerProduct
1572where
1573 A: Architecture,
1574{
1575 #[inline(always)]
1576 fn run(self, _: A, x: USlice<'_, 1>, y: USlice<'_, 1>) -> MathematicalResult<u32> {
1577 bitvector_op::<Self, Unsigned>(x, y)
1578 }
1579}
1580
1581macro_rules! impl_fallback_ip {
1583 (($N:literal, $M:literal)) => {
1584 impl Target2<diskann_wide::arch::Scalar, MathematicalResult<u32>, USlice<'_, $N>, USlice<'_, $M>> for InnerProduct {
1592 #[inline(never)]
1593 fn run(
1594 self,
1595 _: diskann_wide::arch::Scalar,
1596 x: USlice<'_, $N>,
1597 y: USlice<'_, $M>
1598 ) -> MathematicalResult<u32> {
1599 let len = check_lengths!(x, y)?;
1600
1601 let mut accum: u32 = 0;
1602 for i in 0..len {
1603 let ix = unsafe { x.get_unchecked(i) } as u32;
1605 let iy = unsafe { y.get_unchecked(i) } as u32;
1607 accum += ix * iy;
1608 }
1609 Ok(MV::new(accum))
1610 }
1611 }
1612 };
1613 ($N:literal) => {
1614 impl_fallback_ip!(($N, $N));
1615 };
1616 ($($args:tt),+ $(,)?) => {
1617 $(impl_fallback_ip!($args);)+
1618 };
1619}
1620
1621impl_fallback_ip!(7, 6, 5, 4, 3, 2, (8, 4), (8, 2), (8, 1));
1622
1623#[cfg(target_arch = "x86_64")]
1624retarget!(diskann_wide::arch::x86_64::V3, InnerProduct, 7, 6, 5, 3);
1625
1626#[cfg(target_arch = "x86_64")]
1627retarget!(
1628 diskann_wide::arch::x86_64::V4,
1629 InnerProduct,
1630 7,
1631 6,
1632 5,
1633 3,
1634 (8, 4),
1635 (8, 2),
1636 (8, 1)
1637);
1638
1639dispatch_pure!(
1640 InnerProduct,
1641 1,
1642 2,
1643 3,
1644 4,
1645 5,
1646 6,
1647 7,
1648 (8, 8),
1649 (8, 4),
1650 (8, 2),
1651 (8, 1)
1652);
1653
1654#[cfg(target_arch = "x86_64")]
1658impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 4>>
1659 for InnerProduct
1660{
1661 #[inline(always)]
1673 fn run(
1674 self,
1675 arch: diskann_wide::arch::x86_64::V3,
1676 x: USlice<'_, 8>,
1677 y: USlice<'_, 4>,
1678 ) -> MathematicalResult<u32> {
1679 use std::arch::x86_64::_mm256_maddubs_epi16;
1680
1681 let len = check_lengths!(x, y)?;
1682
1683 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1684 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::x86_64::V3>::u8x16);
1685 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
1686 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1687
1688 let px: *const u8 = x.as_ptr();
1689 let py: *const u8 = y.as_ptr();
1690
1691 let mut i: usize = 0;
1692 let mut s: u32 = 0;
1693
1694 #[inline(always)]
1695 fn unpack_half(input: u8s_16) -> u8s_32 {
1696 let combined = diskann_wide::LoHi::new(input, input >> 4).zip::<u8s_32>();
1697 combined & u8s_32::splat(input.arch(), (1u8 << 4) - 1)
1698 }
1699
1700 let blocks = len / 32;
1702 if blocks > 0 {
1703 let mut acc = i32s::default(arch);
1704
1705 let products = |x: u8s_32, y: u8s_32| -> i16s {
1706 i16s::from_underlying(arch, unsafe {
1708 _mm256_maddubs_epi16(x.to_underlying(), y.to_underlying())
1709 })
1710 };
1711
1712 let ones = i16s::splat(arch, 1);
1713
1714 while i + 4 <= blocks {
1720 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * i)) };
1724 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * i)) };
1726 let m0 = products(vx, unpack_half(vy));
1727
1728 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 1))) };
1731 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 1))) };
1733 let m1 = products(vx, unpack_half(vy));
1734
1735 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 2))) };
1738 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 2))) };
1740 let m2 = products(vx, unpack_half(vy));
1741
1742 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 3))) };
1745 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 3))) };
1747 let m3 = products(vx, unpack_half(vy));
1748
1749 acc = acc.dot_simd(m0 + m1 + m2 + m3, ones);
1750 i += 4;
1751 }
1752
1753 while i < blocks {
1755 let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * i)) };
1758 let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * i)) };
1760 acc = acc.dot_simd(products(vx, unpack_half(vy)), ones);
1761 i += 1;
1762 }
1763
1764 s = acc.sum_tree() as u32;
1765 }
1766
1767 i *= 32;
1769
1770 if i != len {
1772 #[inline(never)]
1773 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 4>, from: usize) -> u32 {
1774 let mut s: u32 = 0;
1775 for i in from..x.len() {
1776 let ix = unsafe { x.get_unchecked(i) } as u32;
1778 let iy = unsafe { y.get_unchecked(i) } as u32;
1780 s += ix * iy;
1781 }
1782 s
1783 }
1784 s += fallback(x, y, i);
1785 }
1786
1787 Ok(MV::new(s))
1788 }
1789}
1790
1791#[cfg(target_arch = "x86_64")]
1792impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 2>>
1793 for InnerProduct
1794{
1795 #[inline(always)]
1808 fn run(
1809 self,
1810 arch: diskann_wide::arch::x86_64::V3,
1811 x: USlice<'_, 8>,
1812 y: USlice<'_, 2>,
1813 ) -> MathematicalResult<u32> {
1814 use diskann_wide::SplitJoin;
1815 use std::arch::x86_64::_mm256_maddubs_epi16;
1816
1817 let len = check_lengths!(x, y)?;
1818
1819 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1820 diskann_wide::alias!(u8s_16 = <diskann_wide::arch::x86_64::V3>::u8x16);
1821 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
1822 diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1823
1824 let px: *const u8 = x.as_ptr();
1825 let py: *const u8 = y.as_ptr();
1826
1827 let mut i: usize = 0;
1828 let mut s: u32 = 0;
1829
1830 let blocks = len / 64;
1832 if blocks > 0 {
1833 let mut acc = i32s::default(arch);
1834
1835 let products = |x: u8s_32, y: u8s_32| -> i16s {
1836 i16s::from_underlying(arch, unsafe {
1838 _mm256_maddubs_epi16(x.to_underlying(), y.to_underlying())
1839 })
1840 };
1841
1842 #[inline(always)]
1843 fn unpack_sub<const N: u8>(input: u8s_16) -> u8s_32 {
1844 let combined = diskann_wide::LoHi::new(input, input >> N).zip::<u8s_32>();
1845 combined & u8s_32::splat(input.arch(), (1u8 << N) - 1)
1846 }
1847
1848 let unpack_crumbs = |x: u8s_16| -> (u8s_32, u8s_32) {
1849 let nibbles = unpack_sub::<4>(x);
1851
1852 let diskann_wide::LoHi { lo, hi } = nibbles.split();
1855 let lower = unpack_sub::<2>(lo);
1856 let upper = unpack_sub::<2>(hi);
1857
1858 (lower, upper)
1859 };
1860
1861 let ones = i16s::splat(arch, 1);
1862
1863 while i + 4 <= blocks {
1870 let (vx0, vx1, (vy0, vy1)) = unsafe {
1874 (
1875 u8s_32::load_simd(arch, px.add(64 * i)),
1876 u8s_32::load_simd(arch, px.add(64 * i + 32)),
1877 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * i))),
1878 )
1879 };
1880 let m0a = products(vx0, vy0);
1881 let m0b = products(vx1, vy1);
1882
1883 let (vx0, vx1, (vy0, vy1)) = unsafe {
1886 (
1887 u8s_32::load_simd(arch, px.add(64 * (i + 1))),
1888 u8s_32::load_simd(arch, px.add(64 * (i + 1) + 32)),
1889 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 1)))),
1890 )
1891 };
1892 let m1a = products(vx0, vy0);
1893 let m1b = products(vx1, vy1);
1894
1895 let (vx0, vx1, (vy0, vy1)) = unsafe {
1898 (
1899 u8s_32::load_simd(arch, px.add(64 * (i + 2))),
1900 u8s_32::load_simd(arch, px.add(64 * (i + 2) + 32)),
1901 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 2)))),
1902 )
1903 };
1904 let m2a = products(vx0, vy0);
1905 let m2b = products(vx1, vy1);
1906
1907 let (vx0, vx1, (vy0, vy1)) = unsafe {
1910 (
1911 u8s_32::load_simd(arch, px.add(64 * (i + 3))),
1912 u8s_32::load_simd(arch, px.add(64 * (i + 3) + 32)),
1913 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 3)))),
1914 )
1915 };
1916 let m3a = products(vx0, vy0);
1917 let m3b = products(vx1, vy1);
1918
1919 acc = acc.dot_simd((m0a + m0b + m1a + m1b) + (m2a + m2b + m3a + m3b), ones);
1920 i += 4;
1921 }
1922
1923 while i < blocks {
1925 let (vx0, vx1, (vy0, vy1)) = unsafe {
1928 (
1929 u8s_32::load_simd(arch, px.add(64 * i)),
1930 u8s_32::load_simd(arch, px.add(64 * i + 32)),
1931 unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * i))),
1932 )
1933 };
1934 acc = acc.dot_simd(products(vx0, vy0) + products(vx1, vy1), ones);
1935 i += 1;
1936 }
1937
1938 s = acc.sum_tree() as u32;
1939 }
1940
1941 i *= 64;
1943
1944 if i != len {
1946 #[inline(never)]
1947 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 2>, from: usize) -> u32 {
1948 let mut s: u32 = 0;
1949 for i in from..x.len() {
1950 let (ix, iy) =
1952 unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
1953 s += ix * iy;
1954 }
1955 s
1956 }
1957 s += fallback(x, y, i);
1958 }
1959
1960 Ok(MV::new(s))
1961 }
1962}
1963
1964#[cfg(target_arch = "x86_64")]
1965impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 1>>
1966 for InnerProduct
1967{
1968 #[inline(always)]
1982 fn run(
1983 self,
1984 arch: diskann_wide::arch::x86_64::V3,
1985 x: USlice<'_, 8>,
1986 y: USlice<'_, 1>,
1987 ) -> MathematicalResult<u32> {
1988 use diskann_wide::{FromInt, SIMDMask};
1989 use std::arch::x86_64::_mm256_sad_epu8;
1990
1991 let len = check_lengths!(x, y)?;
1992
1993 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1994 diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
1995
1996 type Mask32 = diskann_wide::BitMask<32, diskann_wide::arch::x86_64::V3>;
1997 type Mask8x32 = diskann_wide::arch::x86_64::v3::masks::mask8x32;
1998
1999 let px: *const u8 = x.as_ptr();
2000 let py: *const u8 = y.as_ptr();
2001
2002 let mut i: usize = 0;
2003 let mut s: u32 = 0;
2004
2005 let blocks = len / 32;
2007 if blocks > 0 {
2008 let mut acc = i32s::default(arch);
2009 let zero = u8s_32::default(arch);
2010
2011 let masked_sad = |vx: u8s_32, bits: u32| -> i32s {
2017 let byte_mask: Mask8x32 = Mask32::from_int(arch, bits).into();
2019
2020 let masked = vx & u8s_32::from_underlying(arch, byte_mask.to_underlying());
2022
2023 i32s::from_underlying(arch, unsafe {
2026 _mm256_sad_epu8(masked.to_underlying(), zero.to_underlying())
2027 })
2028 };
2029
2030 while i + 4 <= blocks {
2032 let s0 = unsafe {
2035 let vx = u8s_32::load_simd(arch, px.add(32 * i));
2036 let bits = (py.add(4 * i) as *const u32).read_unaligned();
2037 masked_sad(vx, bits)
2038 };
2039
2040 let s1 = unsafe {
2042 let vx = u8s_32::load_simd(arch, px.add(32 * (i + 1)));
2043 let bits = (py.add(4 * (i + 1)) as *const u32).read_unaligned();
2044 masked_sad(vx, bits)
2045 };
2046
2047 let s2 = unsafe {
2049 let vx = u8s_32::load_simd(arch, px.add(32 * (i + 2)));
2050 let bits = (py.add(4 * (i + 2)) as *const u32).read_unaligned();
2051 masked_sad(vx, bits)
2052 };
2053
2054 let s3 = unsafe {
2056 let vx = u8s_32::load_simd(arch, px.add(32 * (i + 3)));
2057 let bits = (py.add(4 * (i + 3)) as *const u32).read_unaligned();
2058 masked_sad(vx, bits)
2059 };
2060
2061 acc = acc + s0 + s1 + s2 + s3;
2062 i += 4;
2063 }
2064
2065 while i < blocks {
2067 let si = unsafe {
2070 let vx = u8s_32::load_simd(arch, px.add(32 * i));
2071 let bits = (py.add(4 * i) as *const u32).read_unaligned();
2072 masked_sad(vx, bits)
2073 };
2074 acc = acc + si;
2075 i += 1;
2076 }
2077
2078 s = acc.sum_tree() as u32;
2079 }
2080
2081 i *= 32;
2083
2084 if i != len {
2086 #[inline(never)]
2087 fn fallback(x: USlice<'_, 8>, y: USlice<'_, 1>, from: usize) -> u32 {
2088 let mut s: u32 = 0;
2089 for i in from..x.len() {
2090 let (ix, iy) =
2092 unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2093 s += ix * iy;
2094 }
2095 s
2096 }
2097 s += fallback(x, y, i);
2098 }
2099
2100 Ok(MV::new(s))
2101 }
2102}
2103
2104#[cfg(target_arch = "aarch64")]
2105retarget!(
2106 diskann_wide::arch::aarch64::Neon,
2107 InnerProduct,
2108 7,
2109 6,
2110 5,
2111 4,
2112 3,
2113 2,
2114 (8, 4),
2115 (8, 2),
2116 (8, 1)
2117);
2118
2119impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 4, BitTranspose>, USlice<'_, 1, Dense>>
2148 for InnerProduct
2149where
2150 A: Architecture,
2151{
2152 #[inline(always)]
2153 fn run(
2154 self,
2155 _: A,
2156 x: USlice<'_, 4, BitTranspose>,
2157 y: USlice<'_, 1, Dense>,
2158 ) -> MathematicalResult<u32> {
2159 let len = check_lengths!(x, y)?;
2160
2161 let px: *const u64 = x.as_ptr().cast();
2168 let py: *const u64 = y.as_ptr().cast();
2169
2170 let mut i = 0;
2171 let mut s: u32 = 0;
2172
2173 let blocks = len / 64;
2174 while i < blocks {
2175 let bits = unsafe { py.add(i).read_unaligned() };
2177
2178 let b0 = unsafe { px.add(4 * i).read_unaligned() };
2184 s += (bits & b0).count_ones();
2185
2186 let b1 = unsafe { px.add(4 * i + 1).read_unaligned() };
2188 s += (bits & b1).count_ones() << 1;
2189
2190 let b2 = unsafe { px.add(4 * i + 2).read_unaligned() };
2192 s += (bits & b2).count_ones() << 2;
2193
2194 let b3 = unsafe { px.add(4 * i + 3).read_unaligned() };
2196 s += (bits & b3).count_ones() << 3;
2197
2198 i += 1;
2199 }
2200
2201 if 64 * i == len {
2203 return Ok(MV::new(s));
2204 }
2205
2206 let k = i * 8;
2208
2209 let py = unsafe { py.cast::<u8>().add(k) };
2214 let bytes_remaining = y.bytes() - k;
2215 let mut bits: u64 = 0;
2216
2217 for j in 0..bytes_remaining.min(8) {
2220 bits += (unsafe { py.add(j).read() } as u64) << (8 * j);
2223 }
2224
2225 bits &= (0x01u64 << (len - (64 * i))) - 1;
2228
2229 let b0 = unsafe { px.add(4 * i).read_unaligned() };
2234 s += (bits & b0).count_ones();
2235
2236 let b1 = unsafe { px.add(4 * i + 1).read_unaligned() };
2238 s += (bits & b1).count_ones() << 1;
2239
2240 let b2 = unsafe { px.add(4 * i + 2).read_unaligned() };
2242 s += (bits & b2).count_ones() << 2;
2243
2244 let b3 = unsafe { px.add(4 * i + 3).read_unaligned() };
2246 s += (bits & b3).count_ones() << 3;
2247
2248 Ok(MV::new(s))
2249 }
2250}
2251
2252impl
2253 PureDistanceFunction<USlice<'_, 4, BitTranspose>, USlice<'_, 1, Dense>, MathematicalResult<u32>>
2254 for InnerProduct
2255{
2256 fn evaluate(
2257 x: USlice<'_, 4, BitTranspose>,
2258 y: USlice<'_, 1, Dense>,
2259 ) -> MathematicalResult<u32> {
2260 (diskann_wide::ARCH).run2(Self, x, y)
2261 }
2262}
2263
2264#[cfg(target_arch = "x86_64")]
2295impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 1>>
2296 for InnerProduct
2297{
2298 #[inline(always)]
2299 fn run(
2300 self,
2301 arch: diskann_wide::arch::x86_64::V3,
2302 x: &[f32],
2303 y: USlice<'_, 1>,
2304 ) -> MathematicalResult<f32> {
2305 let len = check_lengths!(x, y)?;
2306
2307 use std::arch::x86_64::*;
2308
2309 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
2310 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
2311
2312 let values = f32s::from_array(arch, [0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
2315
2316 let variable_shifts = u32s::from_array(arch, [0, 1, 2, 3, 4, 5, 6, 7]);
2318
2319 let px: *const f32 = x.as_ptr();
2320 let py: *const u32 = y.as_ptr().cast();
2321
2322 let mut i = 0;
2323 let mut s = f32s::default(arch);
2324
2325 let prep = |v: u32| -> u32s { u32s::splat(arch, v) >> variable_shifts };
2326 let to_f32 = |v: u32s| -> f32s {
2327 f32s::from_underlying(arch, unsafe {
2330 _mm256_permutevar_ps(values.to_underlying(), v.to_underlying())
2331 })
2332 };
2333
2334 let blocks = len / 32;
2336 if i < blocks {
2337 let mut s0 = f32s::default(arch);
2338 let mut s1 = f32s::default(arch);
2339
2340 while i < blocks {
2341 let iy = prep(unsafe { py.add(i).read_unaligned() });
2343
2344 let ix0 = unsafe { f32s::load_simd(arch, px.add(32 * i)) };
2346 let ix1 = unsafe { f32s::load_simd(arch, px.add(32 * i + 8)) };
2348 let ix2 = unsafe { f32s::load_simd(arch, px.add(32 * i + 16)) };
2350 let ix3 = unsafe { f32s::load_simd(arch, px.add(32 * i + 24)) };
2352
2353 s0 = ix0.mul_add_simd(to_f32(iy), s0);
2354 s1 = ix1.mul_add_simd(to_f32(iy >> 8), s1);
2355 s0 = ix2.mul_add_simd(to_f32(iy >> 16), s0);
2356 s1 = ix3.mul_add_simd(to_f32(iy >> 24), s1);
2357
2358 i += 1;
2359 }
2360 s = s0 + s1;
2361 }
2362
2363 let remainder = len % 32;
2364 if remainder != 0 {
2365 let tail = if len % 8 == 0 { 8 } else { len % 8 };
2366
2367 let py = unsafe { py.add(blocks) };
2370
2371 if remainder <= 8 {
2372 unsafe {
2375 load_one(py, |iy| {
2376 let iy = prep(iy);
2377 let ix = f32s::load_simd_first(arch, px.add(32 * blocks), tail);
2378 s = ix.mul_add_simd(to_f32(iy), s);
2379 })
2380 }
2381 } else if remainder <= 16 {
2382 unsafe {
2385 load_two(py, |iy| {
2386 let iy = prep(iy);
2387 let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
2388 let ix1 = f32s::load_simd_first(arch, px.add(32 * blocks + 8), tail);
2389 s = ix0.mul_add_simd(to_f32(iy), s);
2390 s = ix1.mul_add_simd(to_f32(iy >> 8), s);
2391 })
2392 }
2393 } else if remainder <= 24 {
2394 unsafe {
2397 load_three(py, |iy| {
2398 let iy = prep(iy);
2399
2400 let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
2401 let ix1 = f32s::load_simd(arch, px.add(32 * blocks + 8));
2402 let ix2 = f32s::load_simd_first(arch, px.add(32 * blocks + 16), tail);
2403
2404 s = ix0.mul_add_simd(to_f32(iy), s);
2405 s = ix1.mul_add_simd(to_f32(iy >> 8), s);
2406 s = ix2.mul_add_simd(to_f32(iy >> 16), s);
2407 })
2408 }
2409 } else {
2410 unsafe {
2413 load_four(py, |iy| {
2414 let iy = prep(iy);
2415
2416 let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
2417 let ix1 = f32s::load_simd(arch, px.add(32 * blocks + 8));
2418 let ix2 = f32s::load_simd(arch, px.add(32 * blocks + 16));
2419 let ix3 = f32s::load_simd_first(arch, px.add(32 * blocks + 24), tail);
2420
2421 s = ix0.mul_add_simd(to_f32(iy), s);
2422 s = ix1.mul_add_simd(to_f32(iy >> 8), s);
2423 s = ix2.mul_add_simd(to_f32(iy >> 16), s);
2424 s = ix3.mul_add_simd(to_f32(iy >> 24), s);
2425 })
2426 }
2427 }
2428 }
2429
2430 Ok(MV::new(s.sum_tree()))
2431 }
2432}
2433
2434#[cfg(target_arch = "x86_64")]
2438impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 2>>
2439 for InnerProduct
2440{
2441 #[inline(always)]
2442 fn run(
2443 self,
2444 arch: diskann_wide::arch::x86_64::V3,
2445 x: &[f32],
2446 y: USlice<'_, 2>,
2447 ) -> MathematicalResult<f32> {
2448 let len = check_lengths!(x, y)?;
2449
2450 use std::arch::x86_64::*;
2451
2452 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
2453 diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
2454
2455 let values = f32s::from_array(arch, [0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0]);
2459
2460 let variable_shifts = u32s::from_array(arch, [0, 2, 4, 6, 8, 10, 12, 14]);
2462
2463 let px: *const f32 = x.as_ptr();
2464 let py: *const u32 = y.as_ptr().cast();
2465
2466 let mut i = 0;
2467 let mut s = f32s::default(arch);
2468
2469 let prep = |v: u32| -> u32s { u32s::splat(arch, v) >> variable_shifts };
2470 let to_f32 = |v: u32s| -> f32s {
2471 f32s::from_underlying(arch, unsafe {
2474 _mm256_permutevar_ps(values.to_underlying(), v.to_underlying())
2475 })
2476 };
2477
2478 let blocks = len / 16;
2479 if blocks != 0 {
2480 let mut s0 = f32s::default(arch);
2481 let mut s1 = f32s::default(arch);
2482
2483 while i + 2 <= blocks {
2485 let iy = prep(unsafe { py.add(i).read_unaligned() });
2488
2489 let (ix0, ix1) = unsafe {
2492 (
2493 f32s::load_simd(arch, px.add(16 * i)),
2494 f32s::load_simd(arch, px.add(16 * i + 8)),
2495 )
2496 };
2497
2498 s0 = ix0.mul_add_simd(to_f32(iy), s0);
2499 s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
2500
2501 let iy = prep(unsafe { py.add(i + 1).read_unaligned() });
2504
2505 let (ix0, ix1) = unsafe {
2507 (
2508 f32s::load_simd(arch, px.add(16 * (i + 1))),
2509 f32s::load_simd(arch, px.add(16 * (i + 1) + 8)),
2510 )
2511 };
2512
2513 s0 = ix0.mul_add_simd(to_f32(iy), s0);
2514 s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
2515
2516 i += 2;
2517 }
2518
2519 if i < blocks {
2521 let iy = prep(unsafe { py.add(i).read_unaligned() });
2524
2525 let (ix0, ix1) = unsafe {
2527 (
2528 f32s::load_simd(arch, px.add(16 * i)),
2529 f32s::load_simd(arch, px.add(16 * i + 8)),
2530 )
2531 };
2532
2533 s0 = ix0.mul_add_simd(to_f32(iy), s0);
2534 s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
2535 }
2536
2537 s = s0 + s1;
2538 }
2539
2540 let remainder = len % 16;
2541 if remainder != 0 {
2542 let tail = if len % 8 == 0 { 8 } else { len % 8 };
2543 let py = unsafe { py.add(blocks) };
2546
2547 if remainder <= 4 {
2548 unsafe {
2551 load_one(py, |iy| {
2552 let iy = prep(iy);
2553 let ix = f32s::load_simd_first(arch, px.add(16 * blocks), tail);
2554 s = ix.mul_add_simd(to_f32(iy), s);
2555 });
2556 }
2557 } else if remainder <= 8 {
2558 unsafe {
2561 load_two(py, |iy| {
2562 let iy = prep(iy);
2563 let ix = f32s::load_simd_first(arch, px.add(16 * blocks), tail);
2564 s = ix.mul_add_simd(to_f32(iy), s);
2565 });
2566 }
2567 } else if remainder <= 12 {
2568 unsafe {
2571 load_three(py, |iy| {
2572 let iy = prep(iy);
2573 let ix0 = f32s::load_simd(arch, px.add(16 * blocks));
2574 let ix1 = f32s::load_simd_first(arch, px.add(16 * blocks + 8), tail);
2575 s = ix0.mul_add_simd(to_f32(iy), s);
2576 s = ix1.mul_add_simd(to_f32(iy >> 16), s);
2577 });
2578 }
2579 } else {
2580 unsafe {
2583 load_four(py, |iy| {
2584 let iy = prep(iy);
2585 let ix0 = f32s::load_simd(arch, px.add(16 * blocks));
2586 let ix1 = f32s::load_simd_first(arch, px.add(16 * blocks + 8), tail);
2587 s = ix0.mul_add_simd(to_f32(iy), s);
2588 s = ix1.mul_add_simd(to_f32(iy >> 16), s);
2589 });
2590 }
2591 }
2592 }
2593
2594 Ok(MV::new(s.sum_tree()))
2595 }
2596}
2597
2598#[cfg(target_arch = "x86_64")]
2603impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 4>>
2604 for InnerProduct
2605{
2606 #[inline(always)]
2607 fn run(
2608 self,
2609 arch: diskann_wide::arch::x86_64::V3,
2610 x: &[f32],
2611 y: USlice<'_, 4>,
2612 ) -> MathematicalResult<f32> {
2613 let len = check_lengths!(x, y)?;
2614
2615 diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
2616 diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
2617
2618 let variable_shifts = i32s::from_array(arch, [0, 4, 8, 12, 16, 20, 24, 28]);
2619 let mask = i32s::splat(arch, 0x0f);
2620
2621 let to_f32 = |v: u32| -> f32s {
2622 ((i32s::splat(arch, v as i32) >> variable_shifts) & mask).simd_cast()
2623 };
2624
2625 let px: *const f32 = x.as_ptr();
2626 let py: *const u32 = y.as_ptr().cast();
2627
2628 let mut i = 0;
2629 let mut s = f32s::default(arch);
2630
2631 let blocks = len / 8;
2632 while i < blocks {
2633 let ix = unsafe { f32s::load_simd(arch, px.add(8 * i)) };
2635 let iy = to_f32(unsafe { py.add(i).read_unaligned() });
2637 s = ix.mul_add_simd(iy, s);
2638
2639 i += 1;
2640 }
2641
2642 let remainder = len % 8;
2643 if remainder != 0 {
2644 let f = |iy| {
2645 let ix = unsafe { f32s::load_simd_first(arch, px.add(8 * blocks), remainder) };
2649 s = ix.mul_add_simd(to_f32(iy), s);
2650 };
2651
2652 let py = unsafe { py.add(blocks) };
2655
2656 if remainder <= 2 {
2657 unsafe { load_one(py, f) };
2659 } else if remainder <= 4 {
2660 unsafe { load_two(py, f) };
2662 } else if remainder <= 6 {
2663 unsafe { load_three(py, f) };
2665 } else {
2666 unsafe { load_four(py, f) };
2668 }
2669 }
2670
2671 Ok(MV::new(s.sum_tree()))
2672 }
2673}
2674
2675impl<const N: usize>
2676 Target2<diskann_wide::arch::Scalar, MathematicalResult<f32>, &[f32], USlice<'_, N>>
2677 for InnerProduct
2678where
2679 Unsigned: Representation<N>,
2680{
2681 #[inline(always)]
2684 fn run(
2685 self,
2686 _: diskann_wide::arch::Scalar,
2687 x: &[f32],
2688 y: USlice<'_, N>,
2689 ) -> MathematicalResult<f32> {
2690 check_lengths!(x, y)?;
2691
2692 let mut s = 0.0;
2693 for (i, x) in x.iter().enumerate() {
2694 let y = unsafe { y.get_unchecked(i) } as f32;
2697 s += x * y;
2698 }
2699
2700 Ok(MV::new(s))
2701 }
2702}
2703
2704macro_rules! ip_retarget {
2706 ($arch:path, $N:literal) => {
2707 impl Target2<$arch, MathematicalResult<f32>, &[f32], USlice<'_, $N>>
2708 for InnerProduct
2709 {
2710 #[inline(always)]
2711 fn run(
2712 self,
2713 arch: $arch,
2714 x: &[f32],
2715 y: USlice<'_, $N>,
2716 ) -> MathematicalResult<f32> {
2717 self.run(arch.retarget(), x, y)
2718 }
2719 }
2720 };
2721 ($arch:path, $($Ns:literal),*) => {
2722 $(ip_retarget!($arch, $Ns);)*
2723 }
2724}
2725
2726#[cfg(target_arch = "x86_64")]
2727ip_retarget!(diskann_wide::arch::x86_64::V3, 3, 5, 6, 7, 8);
2728
2729#[cfg(target_arch = "x86_64")]
2730ip_retarget!(diskann_wide::arch::x86_64::V4, 1, 2, 3, 4, 5, 6, 7, 8);
2731
2732#[cfg(target_arch = "aarch64")]
2733ip_retarget!(diskann_wide::arch::aarch64::Neon, 1, 2, 3, 4, 5, 6, 7, 8);
2734
2735macro_rules! dispatch_full_ip {
2738 ($N:literal) => {
2739 impl PureDistanceFunction<&[f32], USlice<'_, $N>, MathematicalResult<f32>>
2743 for InnerProduct
2744 {
2745 fn evaluate(x: &[f32], y: USlice<'_, $N>) -> MathematicalResult<f32> {
2746 Self.run(ARCH, x, y)
2747 }
2748 }
2749 };
2750 ($($Ns:literal),*) => {
2751 $(dispatch_full_ip!($Ns);)*
2752 }
2753}
2754
2755dispatch_full_ip!(1, 2, 3, 4, 5, 6, 7, 8);
2756
2757#[cfg(test)]
2762mod tests {
2763 use std::{collections::HashMap, fmt::Display, sync::LazyLock};
2764
2765 use diskann_utils::{Reborrow, lazy_format};
2766 use rand::{
2767 Rng, SeedableRng,
2768 distr::{Distribution, Uniform},
2769 rngs::StdRng,
2770 seq::IndexedRandom,
2771 };
2772
2773 use super::*;
2774 use crate::bits::{BoxedBitSlice, Representation, Unsigned};
2775
2776 type MR = MathematicalResult<u32>;
2777
2778 #[inline(always)]
2779 fn should_check_this_dimension(dim: usize) -> bool {
2780 if cfg!(miri) {
2781 return dim.is_power_of_two()
2782 || (dim > 1 && (dim - 1).is_power_of_two())
2783 || (dim < 64 && (dim % 8 == 7));
2784 }
2785
2786 true
2787 }
2788
2789 fn test_bitslice_distances<const NBITS: usize, R>(
2799 dim_max: usize,
2800 trials_per_dim: usize,
2801 evaluate_l2: &dyn Fn(USlice<'_, NBITS>, USlice<'_, NBITS>) -> MR,
2802 evaluate_ip: &dyn Fn(USlice<'_, NBITS>, USlice<'_, NBITS>) -> MR,
2803 context: &str,
2804 rng: &mut R,
2805 ) where
2806 Unsigned: Representation<NBITS>,
2807 R: Rng,
2808 {
2809 let domain = Unsigned::domain_const::<NBITS>();
2810 let min: i64 = *domain.start();
2811 let max: i64 = *domain.end();
2812
2813 let dist = Uniform::new_inclusive(min, max).unwrap();
2814
2815 for dim in 0..dim_max {
2816 if !should_check_this_dimension(dim) {
2817 continue;
2818 }
2819
2820 let mut x_reference: Vec<u8> = vec![0; dim];
2821 let mut y_reference: Vec<u8> = vec![0; dim];
2822
2823 let mut x = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(dim);
2824 let mut y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(dim);
2825
2826 for trial in 0..trials_per_dim {
2827 x_reference
2828 .iter_mut()
2829 .for_each(|i| *i = dist.sample(rng).try_into().unwrap());
2830 y_reference
2831 .iter_mut()
2832 .for_each(|i| *i = dist.sample(rng).try_into().unwrap());
2833
2834 x.as_mut_slice().fill(u8::MAX);
2837 y.as_mut_slice().fill(u8::MAX);
2838
2839 for i in 0..dim {
2840 x.set(i, x_reference[i].into()).unwrap();
2841 y.set(i, y_reference[i].into()).unwrap();
2842 }
2843
2844 let expected: MV<f32> =
2846 diskann_vector::distance::SquaredL2::evaluate(&*x_reference, &*y_reference);
2847
2848 let got = evaluate_l2(x.reborrow(), y.reborrow()).unwrap();
2849
2850 assert_eq!(
2852 expected.into_inner(),
2853 got.into_inner() as f32,
2854 "failed SquaredL2 for NBITS = {}, dim = {}, trial = {} -- context {}",
2855 NBITS,
2856 dim,
2857 trial,
2858 context,
2859 );
2860
2861 let expected: MV<f32> =
2863 diskann_vector::distance::InnerProduct::evaluate(&*x_reference, &*y_reference);
2864
2865 let got = evaluate_ip(x.reborrow(), y.reborrow()).unwrap();
2866
2867 assert_eq!(
2869 expected.into_inner(),
2870 got.into_inner() as f32,
2871 "faild InnerProduct for NBITS = {}, dim = {}, trial = {} -- context {}",
2872 NBITS,
2873 dim,
2874 trial,
2875 context,
2876 );
2877 }
2878 }
2879
2880 let x = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(10);
2882 let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(11);
2883
2884 assert!(
2885 evaluate_l2(x.reborrow(), y.reborrow()).is_err(),
2886 "context: {}",
2887 context
2888 );
2889 assert!(
2890 evaluate_l2(y.reborrow(), x.reborrow()).is_err(),
2891 "context: {}",
2892 context
2893 );
2894
2895 assert!(
2896 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
2897 "context: {}",
2898 context
2899 );
2900 assert!(
2901 evaluate_ip(y.reborrow(), x.reborrow()).is_err(),
2902 "context: {}",
2903 context
2904 );
2905 }
2906
2907 cfg_if::cfg_if! {
2908 if #[cfg(miri)] {
2909 const MAX_DIM: usize = 132;
2910 const TRIALS_PER_DIM: usize = 1;
2911 } else {
2912 const MAX_DIM: usize = 256;
2913 const TRIALS_PER_DIM: usize = 20;
2914 }
2915 }
2916
2917 static BITSLICE_TEST_BOUNDS: LazyLock<HashMap<Key, Bounds>> = LazyLock::new(|| {
2927 use ArchKey::{Neon, Scalar, X86_64_V3, X86_64_V4};
2928 [
2929 (Key::new(1, Scalar), Bounds::new(64, 64)),
2930 (Key::new(1, X86_64_V3), Bounds::new(256, 256)),
2931 (Key::new(1, X86_64_V4), Bounds::new(256, 256)),
2932 (Key::new(1, Neon), Bounds::new(64, 64)),
2933 (Key::new(2, Scalar), Bounds::new(64, 64)),
2934 (Key::new(2, X86_64_V3), Bounds::new(512, 300)),
2936 (Key::new(2, X86_64_V4), Bounds::new(768, 600)), (Key::new(2, Neon), Bounds::new(64, 64)),
2938 (Key::new(3, Scalar), Bounds::new(64, 64)),
2939 (Key::new(3, X86_64_V3), Bounds::new(256, 96)),
2940 (Key::new(3, X86_64_V4), Bounds::new(256, 96)),
2941 (Key::new(3, Neon), Bounds::new(64, 64)),
2942 (Key::new(4, Scalar), Bounds::new(64, 64)),
2943 (Key::new(4, X86_64_V3), Bounds::new(256, 150)),
2945 (Key::new(4, X86_64_V4), Bounds::new(512, 300)),
2946 (Key::new(4, Neon), Bounds::new(64, 64)),
2947 (Key::new(5, Scalar), Bounds::new(64, 64)),
2948 (Key::new(5, X86_64_V3), Bounds::new(256, 96)),
2949 (Key::new(5, X86_64_V4), Bounds::new(256, 96)),
2950 (Key::new(5, Neon), Bounds::new(64, 64)),
2951 (Key::new(6, Scalar), Bounds::new(64, 64)),
2952 (Key::new(6, X86_64_V3), Bounds::new(256, 96)),
2953 (Key::new(6, X86_64_V4), Bounds::new(256, 96)),
2954 (Key::new(6, Neon), Bounds::new(64, 64)),
2955 (Key::new(7, Scalar), Bounds::new(64, 64)),
2956 (Key::new(7, X86_64_V3), Bounds::new(256, 96)),
2957 (Key::new(7, X86_64_V4), Bounds::new(256, 96)),
2958 (Key::new(7, Neon), Bounds::new(64, 64)),
2959 (Key::new(8, Scalar), Bounds::new(64, 64)),
2960 (Key::new(8, X86_64_V3), Bounds::new(256, 96)),
2961 (Key::new(8, X86_64_V4), Bounds::new(256, 96)),
2962 (Key::new(8, Neon), Bounds::new(64, 64)),
2963 ]
2964 .into_iter()
2965 .collect()
2966 });
2967
2968 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
2969 enum ArchKey {
2970 Scalar,
2971 #[expect(non_camel_case_types)]
2972 X86_64_V3,
2973 #[expect(non_camel_case_types)]
2974 X86_64_V4,
2975 Neon,
2976 }
2977
2978 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
2979 struct Key {
2980 nbits: usize,
2981 arch: ArchKey,
2982 }
2983
2984 impl Key {
2985 fn new(nbits: usize, arch: ArchKey) -> Self {
2986 Self { nbits, arch }
2987 }
2988 }
2989
2990 #[derive(Debug, Clone, Copy)]
2991 struct Bounds {
2992 standard: usize,
2993 miri: usize,
2994 }
2995
2996 impl Bounds {
2997 fn new(standard: usize, miri: usize) -> Self {
2998 Self { standard, miri }
2999 }
3000
3001 fn get(&self) -> usize {
3002 if cfg!(miri) { self.miri } else { self.standard }
3003 }
3004 }
3005
3006 macro_rules! test_bitslice {
3007 ($name:ident, $nbits:literal, $seed:literal) => {
3008 #[test]
3009 fn $name() {
3010 let mut rng = StdRng::seed_from_u64($seed);
3011
3012 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::Scalar)].get();
3013
3014 test_bitslice_distances::<$nbits, _>(
3015 max_dim,
3016 TRIALS_PER_DIM,
3017 &|x, y| SquaredL2::evaluate(x, y),
3018 &|x, y| InnerProduct::evaluate(x, y),
3019 "pure distance function",
3020 &mut rng,
3021 );
3022
3023 test_bitslice_distances::<$nbits, _>(
3024 max_dim,
3025 TRIALS_PER_DIM,
3026 &|x, y| diskann_wide::arch::Scalar::new().run2(SquaredL2, x, y),
3027 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
3028 "scalar arch",
3029 &mut rng,
3030 );
3031
3032 #[cfg(target_arch = "x86_64")]
3034 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
3035 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::X86_64_V3)].get();
3036 test_bitslice_distances::<$nbits, _>(
3037 max_dim,
3038 TRIALS_PER_DIM,
3039 &|x, y| arch.run2(SquaredL2, x, y),
3040 &|x, y| arch.run2(InnerProduct, x, y),
3041 "x86-64-v3",
3042 &mut rng,
3043 );
3044 }
3045
3046 #[cfg(target_arch = "x86_64")]
3047 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
3048 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::X86_64_V4)].get();
3049 test_bitslice_distances::<$nbits, _>(
3050 max_dim,
3051 TRIALS_PER_DIM,
3052 &|x, y| arch.run2(SquaredL2, x, y),
3053 &|x, y| arch.run2(InnerProduct, x, y),
3054 "x86-64-v4",
3055 &mut rng,
3056 );
3057 }
3058
3059 #[cfg(target_arch = "aarch64")]
3060 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
3061 let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::Neon)].get();
3062 test_bitslice_distances::<$nbits, _>(
3063 max_dim,
3064 TRIALS_PER_DIM,
3065 &|x, y| arch.run2(SquaredL2, x, y),
3066 &|x, y| arch.run2(InnerProduct, x, y),
3067 "neon",
3068 &mut rng,
3069 );
3070 }
3071 }
3072 };
3073 }
3074
3075 test_bitslice!(test_bitslice_distances_8bit, 8, 0xf0330c6d880e08ff);
3076 test_bitslice!(test_bitslice_distances_7bit, 7, 0x98aa7f2d4c83844f);
3077 test_bitslice!(test_bitslice_distances_6bit, 6, 0xf2f7ad7a37764b4c);
3078 test_bitslice!(test_bitslice_distances_5bit, 5, 0xae878d14973fb43f);
3079 test_bitslice!(test_bitslice_distances_4bit, 4, 0x8d6dbb8a6b19a4f8);
3080 test_bitslice!(test_bitslice_distances_3bit, 3, 0x8f56767236e58da2);
3081 test_bitslice!(test_bitslice_distances_2bit, 2, 0xb04f741a257b61af);
3082 test_bitslice!(test_bitslice_distances_1bit, 1, 0x820ea031c379eab5);
3083
3084 fn test_hamming_distances<R>(dim_max: usize, trials_per_dim: usize, rng: &mut R)
3089 where
3090 R: Rng,
3091 {
3092 let dist: [i8; 2] = [-1, 1];
3093
3094 for dim in 0..dim_max {
3095 if !should_check_this_dimension(dim) {
3096 continue;
3097 }
3098
3099 let mut x_reference: Vec<i8> = vec![1; dim];
3100 let mut y_reference: Vec<i8> = vec![1; dim];
3101
3102 let mut x = BoxedBitSlice::<1, Binary>::new_boxed(dim);
3103 let mut y = BoxedBitSlice::<1, Binary>::new_boxed(dim);
3104
3105 for _ in 0..trials_per_dim {
3106 x_reference
3107 .iter_mut()
3108 .for_each(|i| *i = *dist.choose(rng).unwrap());
3109 y_reference
3110 .iter_mut()
3111 .for_each(|i| *i = *dist.choose(rng).unwrap());
3112
3113 x.as_mut_slice().fill(u8::MAX);
3116 y.as_mut_slice().fill(u8::MAX);
3117
3118 for i in 0..dim {
3119 x.set(i, x_reference[i].into()).unwrap();
3120 y.set(i, y_reference[i].into()).unwrap();
3121 }
3122
3123 let expected: MV<f32> =
3129 diskann_vector::distance::SquaredL2::evaluate(&*x_reference, &*y_reference);
3130 let got: MV<u32> = Hamming::evaluate(x.reborrow(), y.reborrow()).unwrap();
3131 assert_eq!(4.0 * (got.into_inner() as f32), expected.into_inner());
3132 }
3133 }
3134
3135 let x = BoxedBitSlice::<1, Binary>::new_boxed(10);
3136 let y = BoxedBitSlice::<1, Binary>::new_boxed(11);
3137 assert!(Hamming::evaluate(x.reborrow(), y.reborrow()).is_err());
3138 assert!(Hamming::evaluate(y.reborrow(), x.reborrow()).is_err());
3139 }
3140
3141 #[test]
3142 fn test_hamming_distance() {
3143 let mut rng = StdRng::seed_from_u64(0x2160419161246d97);
3144 test_hamming_distances(MAX_DIM, TRIALS_PER_DIM, &mut rng);
3145 }
3146
3147 fn test_bit_transpose_distances<R>(
3152 dim_max: usize,
3153 trials_per_dim: usize,
3154 evaluate_ip: &dyn Fn(USlice<'_, 4, BitTranspose>, USlice<'_, 1>) -> MR,
3155 context: &str,
3156 rng: &mut R,
3157 ) where
3158 R: Rng,
3159 {
3160 let dist_4bit = {
3161 let domain = Unsigned::domain_const::<4>();
3162 Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
3163 };
3164
3165 let dist_1bit = {
3166 let domain = Unsigned::domain_const::<1>();
3167 Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
3168 };
3169
3170 for dim in 0..dim_max {
3171 if !should_check_this_dimension(dim) {
3172 continue;
3173 }
3174
3175 let mut x_reference: Vec<u8> = vec![0; dim];
3176 let mut y_reference: Vec<u8> = vec![0; dim];
3177
3178 let mut x = BoxedBitSlice::<4, Unsigned, BitTranspose>::new_boxed(dim);
3179 let mut y = BoxedBitSlice::<1, Unsigned, Dense>::new_boxed(dim);
3180
3181 for trial in 0..trials_per_dim {
3182 x_reference
3183 .iter_mut()
3184 .for_each(|i| *i = dist_4bit.sample(rng).try_into().unwrap());
3185 y_reference
3186 .iter_mut()
3187 .for_each(|i| *i = dist_1bit.sample(rng).try_into().unwrap());
3188
3189 x.as_mut_slice().fill(u8::MAX);
3191 y.as_mut_slice().fill(u8::MAX);
3192
3193 for i in 0..dim {
3194 x.set(i, x_reference[i].into()).unwrap();
3195 y.set(i, y_reference[i].into()).unwrap();
3196 }
3197
3198 let expected: MV<f32> =
3200 diskann_vector::distance::InnerProduct::evaluate(&*x_reference, &*y_reference);
3201
3202 let got = evaluate_ip(x.reborrow(), y.reborrow());
3203
3204 assert_eq!(
3206 expected.into_inner(),
3207 got.unwrap().into_inner() as f32,
3208 "faild InnerProduct for dim = {}, trial = {} -- context {}",
3209 dim,
3210 trial,
3211 context,
3212 );
3213 }
3214 }
3215
3216 let x = BoxedBitSlice::<4, Unsigned, BitTranspose>::new_boxed(10);
3217 let y = BoxedBitSlice::<1, Unsigned>::new_boxed(11);
3218 assert!(
3219 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
3220 "context: {}",
3221 context
3222 );
3223
3224 let y = BoxedBitSlice::<1, Unsigned>::new_boxed(9);
3225 assert!(
3226 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
3227 "context: {}",
3228 context
3229 );
3230 }
3231
3232 #[test]
3233 fn test_bit_transpose_distance() {
3234 let mut rng = StdRng::seed_from_u64(0xe20e26e926d4b853);
3235
3236 test_bit_transpose_distances(
3237 MAX_DIM,
3238 TRIALS_PER_DIM,
3239 &|x, y| InnerProduct::evaluate(x, y),
3240 "pure distance function",
3241 &mut rng,
3242 );
3243
3244 test_bit_transpose_distances(
3245 MAX_DIM,
3246 TRIALS_PER_DIM,
3247 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
3248 "scalar",
3249 &mut rng,
3250 );
3251
3252 #[cfg(target_arch = "x86_64")]
3254 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
3255 test_bit_transpose_distances(
3256 MAX_DIM,
3257 TRIALS_PER_DIM,
3258 &|x, y| arch.run2(InnerProduct, x, y),
3259 "x86-64-v3",
3260 &mut rng,
3261 );
3262 }
3263
3264 #[cfg(target_arch = "x86_64")]
3266 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
3267 test_bit_transpose_distances(
3268 MAX_DIM,
3269 TRIALS_PER_DIM,
3270 &|x, y| arch.run2(InnerProduct, x, y),
3271 "x86-64-v4",
3272 &mut rng,
3273 );
3274 }
3275
3276 #[cfg(target_arch = "aarch64")]
3278 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
3279 test_bit_transpose_distances(
3280 MAX_DIM,
3281 TRIALS_PER_DIM,
3282 &|x, y| arch.run2(InnerProduct, x, y),
3283 "neon",
3284 &mut rng,
3285 );
3286 }
3287 }
3288
3289 fn test_full_distances<const NBITS: usize>(
3294 dim_max: usize,
3295 trials_per_dim: usize,
3296 evaluate_ip: &dyn Fn(&[f32], USlice<'_, NBITS>) -> MathematicalResult<f32>,
3297 context: &str,
3298 rng: &mut impl Rng,
3299 ) where
3300 Unsigned: Representation<NBITS>,
3301 {
3302 let dist_float = [-2.0, -1.0, 0.0, 1.0, 2.0];
3304 let dist_bit = {
3305 let domain = Unsigned::domain_const::<NBITS>();
3306 Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
3307 };
3308
3309 for dim in 0..dim_max {
3310 if !should_check_this_dimension(dim) {
3311 continue;
3312 }
3313
3314 let mut x: Vec<f32> = vec![0.0; dim];
3315
3316 let mut y_reference: Vec<u8> = vec![0; dim];
3317 let mut y = BoxedBitSlice::<NBITS, Unsigned, Dense>::new_boxed(dim);
3318
3319 for trial in 0..trials_per_dim {
3320 x.iter_mut()
3321 .for_each(|i| *i = *dist_float.choose(rng).unwrap());
3322 y_reference
3323 .iter_mut()
3324 .for_each(|i| *i = dist_bit.sample(rng).try_into().unwrap());
3325
3326 y.as_mut_slice().fill(u8::MAX);
3328
3329 let mut expected = 0.0;
3330 for i in 0..dim {
3331 y.set(i, y_reference[i].into()).unwrap();
3332 expected += y_reference[i] as f32 * x[i];
3333 }
3334
3335 let got = evaluate_ip(&x, y.reborrow()).unwrap();
3337
3338 assert_eq!(
3340 expected,
3341 got.into_inner(),
3342 "faild InnerProduct for dim = {}, trial = {} -- context {}",
3343 dim,
3344 trial,
3345 context,
3346 );
3347
3348 let scalar: MV<f32> = InnerProduct
3351 .run(diskann_wide::arch::Scalar, x.as_slice(), y.reborrow())
3352 .unwrap();
3353 assert_eq!(got.into_inner(), scalar.into_inner());
3354 }
3355 }
3356
3357 let x = vec![0.0; 10];
3359 let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(11);
3360 assert!(
3361 evaluate_ip(x.as_slice(), y.reborrow()).is_err(),
3362 "context: {}",
3363 context
3364 );
3365
3366 let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(9);
3367 assert!(
3368 evaluate_ip(x.as_slice(), y.reborrow()).is_err(),
3369 "context: {}",
3370 context
3371 );
3372 }
3373
3374 macro_rules! test_full {
3375 ($name:ident, $nbits:literal, $seed:literal) => {
3376 #[test]
3377 fn $name() {
3378 let mut rng = StdRng::seed_from_u64($seed);
3379
3380 test_full_distances::<$nbits>(
3381 MAX_DIM,
3382 TRIALS_PER_DIM,
3383 &|x, y| InnerProduct::evaluate(x, y),
3384 "pure distance function",
3385 &mut rng,
3386 );
3387
3388 test_full_distances::<$nbits>(
3389 MAX_DIM,
3390 TRIALS_PER_DIM,
3391 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
3392 "scalar",
3393 &mut rng,
3394 );
3395
3396 #[cfg(target_arch = "x86_64")]
3398 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
3399 test_full_distances::<$nbits>(
3400 MAX_DIM,
3401 TRIALS_PER_DIM,
3402 &|x, y| arch.run2(InnerProduct, x, y),
3403 "x86-64-v3",
3404 &mut rng,
3405 );
3406 }
3407
3408 #[cfg(target_arch = "x86_64")]
3409 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
3410 test_full_distances::<$nbits>(
3411 MAX_DIM,
3412 TRIALS_PER_DIM,
3413 &|x, y| arch.run2(InnerProduct, x, y),
3414 "x86-64-v4",
3415 &mut rng,
3416 );
3417 }
3418
3419 #[cfg(target_arch = "aarch64")]
3420 if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
3421 test_full_distances::<$nbits>(
3422 MAX_DIM,
3423 TRIALS_PER_DIM,
3424 &|x, y| arch.run2(InnerProduct, x, y),
3425 "neon",
3426 &mut rng,
3427 );
3428 }
3429 }
3430 };
3431 }
3432
3433 test_full!(test_full_distance_1bit, 1, 0xe20e26e926d4b853);
3434 test_full!(test_full_distance_2bit, 2, 0xae9542700aecbf68);
3435 test_full!(test_full_distance_3bit, 3, 0xfffd04b26bb6068c);
3436 test_full!(test_full_distance_4bit, 4, 0x86db49fd1a1704ba);
3437 test_full!(test_full_distance_5bit, 5, 0x3a35dc7fa7931c41);
3438 test_full!(test_full_distance_6bit, 6, 0x1f69de79e418d336);
3439 test_full!(test_full_distance_7bit, 7, 0x3fcf17b82dadc5ab);
3440 test_full!(test_full_distance_8bit, 8, 0x85dcaf48b1399db2);
3441
3442 struct HetCase<const M: usize> {
3449 x_vals: Vec<i64>,
3450 y_vals: Vec<i64>,
3451 }
3452
3453 impl<const M: usize> HetCase<M>
3454 where
3455 Unsigned: Representation<M>,
3456 {
3457 fn new(dim: usize, fill: impl FnMut(usize) -> (i64, i64)) -> Self {
3458 let (x_vals, y_vals) = (0..dim).map(fill).unzip();
3459 Self { x_vals, y_vals }
3460 }
3461
3462 fn check_with(
3463 &self,
3464 label: impl Display,
3465 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
3466 ) {
3467 let dim = self.x_vals.len();
3468 let mut x = BoxedBitSlice::<8, Unsigned>::new_boxed(dim);
3469 let mut y = BoxedBitSlice::<M, Unsigned>::new_boxed(dim);
3470 x.as_mut_slice().fill(u8::MAX);
3472 y.as_mut_slice().fill(u8::MAX);
3473 for (i, (&xv, &yv)) in self.x_vals.iter().zip(&self.y_vals).enumerate() {
3474 x.set(i, xv).unwrap();
3475 y.set(i, yv).unwrap();
3476 }
3477 let expected: u32 = self
3478 .x_vals
3479 .iter()
3480 .zip(&self.y_vals)
3481 .map(|(&a, &b)| a as u32 * b as u32)
3482 .sum();
3483 let got = evaluate(x.reborrow(), y.reborrow()).unwrap().into_inner();
3484 assert_eq!(expected, got, "{} failed for dim = {}", label, dim);
3485 }
3486 }
3487
3488 fn fuzz_heterogeneous_ip<const M: usize>(
3490 dim_max: usize,
3491 trials_per_dim: usize,
3492 max_val: i64,
3493 evaluate_ip: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
3494 context: &str,
3495 rng: &mut impl Rng,
3496 ) where
3497 Unsigned: Representation<M>,
3498 {
3499 let dist_8bit = Uniform::new_inclusive(0i64, 255i64).unwrap();
3500 let dist_mbit = Uniform::new_inclusive(0i64, max_val).unwrap();
3501
3502 for dim in 0..dim_max {
3503 for trial in 0..trials_per_dim {
3504 HetCase::<M>::new(dim, |_| {
3505 (dist_8bit.sample(&mut *rng), dist_mbit.sample(&mut *rng))
3506 })
3507 .check_with(
3508 lazy_format!("IP(8,{}) dim={dim}, trial={trial} -- {context}", M),
3509 evaluate_ip,
3510 );
3511 }
3512
3513 let x = BoxedBitSlice::<8, Unsigned>::new_boxed(dim);
3515 let y = BoxedBitSlice::<M, Unsigned>::new_boxed(dim + 1);
3516 assert!(
3517 evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
3518 "context: {}",
3519 context,
3520 );
3521 }
3522 }
3523
3524 fn het_test_max_values<const M: usize>(
3527 max_val: i64,
3528 context: &str,
3529 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
3530 ) where
3531 Unsigned: Representation<M>,
3532 {
3533 let dims = [127, 128, 129, 255, 256, 512, 768, 896, 3072];
3534 for &dim in &dims {
3535 let case = HetCase::<M>::new(dim, |_| (255, max_val));
3536 case.check_with(lazy_format!("max-value {context} dim={dim}"), evaluate);
3537 }
3538 }
3539
3540 fn het_test_known_answers<const M: usize>(
3542 max_val: i64,
3543 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
3544 ) where
3545 Unsigned: Representation<M>,
3546 {
3547 HetCase::<M>::new(64, |_| (200, max_val)).check_with("vpmaddubsw operand-order", evaluate);
3550
3551 let y_val = (max_val / 2).max(1);
3553 HetCase::<M>::new(128, |i| ((i % 256) as i64, y_val))
3554 .check_with("ascending-x constant-y", evaluate);
3555
3556 HetCase::<M>::new(1, |_| (200, max_val)).check_with("single element", evaluate);
3558 }
3559
3560 fn het_test_edge_cases<const M: usize>(
3562 max_val: i64,
3563 block_size: usize,
3564 evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
3565 ) where
3566 Unsigned: Representation<M>,
3567 {
3568 let y_half = (max_val / 2).max(1);
3569
3570 HetCase::<M>::new(64, |_| (0, max_val)).check_with("x-zero y-nonzero", evaluate);
3572 HetCase::<M>::new(64, |_| (255, 0)).check_with("y-zero x-nonzero", evaluate);
3573
3574 for dim in 0..=(block_size + 1) {
3576 HetCase::<M>::new(dim, |_| (3, y_half)).check_with("uniform fill", evaluate);
3577 }
3578
3579 for &dim in &[block_size, 2 * block_size, 4 * block_size, 8 * block_size] {
3581 HetCase::<M>::new(dim, |_| (100, max_val)).check_with("exact block boundary", evaluate);
3582 }
3583
3584 HetCase::<M>::new(300, |i| ((i % 256) as i64, 1))
3586 .check_with("x-varies y-constant", evaluate);
3587
3588 HetCase::<M>::new(300, |i| (1, (i as i64) % (max_val + 1)))
3590 .check_with("x-constant y-varies", evaluate);
3591
3592 HetCase::<M>::new(128, |i| if i % 2 == 0 { (255, max_val) } else { (0, 0) })
3594 .check_with("alternating pattern", evaluate);
3595
3596 HetCase::<M>::new(128, |i| if i % 2 == 0 { (0, 0) } else { (255, max_val) })
3598 .check_with("opposite alternating", evaluate);
3599
3600 HetCase::<M>::new(1024, |_| (255, max_val)).check_with("large accumulation", evaluate);
3602
3603 for x_val in [128i64, 170, 200, 240, 255] {
3605 HetCase::<M>::new(block_size, move |_| (x_val, y_half))
3606 .check_with(lazy_format!("x > 127 (x_val={x_val})"), evaluate);
3607 }
3608
3609 HetCase::<M>::new(block_size - 1, |i| {
3611 (
3612 ((i * 7 + 3) % 256) as i64,
3613 ((i * 11 + 5) as i64) % (max_val + 1),
3614 )
3615 })
3616 .check_with("dim=block_size-1 (all scalar)", evaluate);
3617
3618 let unroll4 = 4 * block_size;
3620 for &dim in &[
3621 unroll4,
3622 unroll4 + 1,
3623 unroll4 + block_size,
3624 unroll4 + block_size + 1,
3625 ] {
3626 HetCase::<M>::new(dim, |i| {
3627 (((i + 1) % 256) as i64, ((i + 1) as i64) % (max_val + 1))
3628 })
3629 .check_with("unroll boundary", evaluate);
3630 }
3631 }
3632
3633 macro_rules! heterogeneous_ip_tests_8xM {
3634 (
3635 mod_name: $mod:ident,
3636 M: $M:literal,
3637 max_val: $max_val:literal,
3638 block_size: $block_size:literal,
3639 seed_fuzz: $seed_fuzz:literal,
3640 ) => {
3641 mod $mod {
3642 use super::*;
3643
3644 #[test]
3645 fn all_ip_dispatches() {
3646 let mut rng = StdRng::seed_from_u64($seed_fuzz);
3647
3648 fuzz_heterogeneous_ip::<$M>(
3649 MAX_DIM,
3650 TRIALS_PER_DIM,
3651 $max_val,
3652 &|x, y| InnerProduct::evaluate(x, y),
3653 "pure distance function",
3654 &mut rng,
3655 );
3656 fuzz_heterogeneous_ip::<$M>(
3657 MAX_DIM,
3658 TRIALS_PER_DIM,
3659 $max_val,
3660 &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
3661 "scalar arch",
3662 &mut rng,
3663 );
3664 #[cfg(target_arch = "x86_64")]
3665 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
3666 fuzz_heterogeneous_ip::<$M>(
3667 MAX_DIM,
3668 TRIALS_PER_DIM,
3669 $max_val,
3670 &|x, y| arch.run2(InnerProduct, x, y),
3671 "x86-64-v3",
3672 &mut rng,
3673 );
3674 }
3675 #[cfg(target_arch = "x86_64")]
3676 if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
3677 fuzz_heterogeneous_ip::<$M>(
3678 MAX_DIM,
3679 TRIALS_PER_DIM,
3680 $max_val,
3681 &|x, y| arch.run2(InnerProduct, x, y),
3682 "x86-64-v4",
3683 &mut rng,
3684 );
3685 }
3686 }
3687
3688 #[test]
3689 fn max_values() {
3690 het_test_max_values::<$M>($max_val, "dispatch", &|x, y| {
3691 InnerProduct::evaluate(x, y)
3692 });
3693 #[cfg(target_arch = "x86_64")]
3694 if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
3695 het_test_max_values::<$M>($max_val, "V3", &|x, y| {
3696 arch.run2(InnerProduct, x, y)
3697 });
3698 }
3699 }
3700
3701 #[test]
3702 fn known_answers() {
3703 het_test_known_answers::<$M>($max_val, &|x, y| InnerProduct::evaluate(x, y));
3704 }
3705
3706 #[test]
3707 fn edge_cases() {
3708 het_test_edge_cases::<$M>($max_val, $block_size, &|x, y| {
3709 InnerProduct::evaluate(x, y)
3710 });
3711 }
3712 }
3713 };
3714 }
3715
3716 heterogeneous_ip_tests_8xM! {
3717 mod_name: heterogeneous_ip_8x4,
3718 M: 4,
3719 max_val: 15,
3720 block_size: 32,
3721 seed_fuzz: 0xd3a7f1c09b2e4856,
3722 }
3723
3724 heterogeneous_ip_tests_8xM! {
3725 mod_name: heterogeneous_ip_8x2,
3726 M: 2,
3727 max_val: 3,
3728 block_size: 64,
3729 seed_fuzz: 0x82c4a6e809f1d3b5,
3730 }
3731
3732 heterogeneous_ip_tests_8xM! {
3733 mod_name: heterogeneous_ip_8x1,
3734 M: 1,
3735 max_val: 1,
3736 block_size: 32,
3737 seed_fuzz: 0x1b17_a5e7c2d0f839,
3738 }
3739}