Skip to main content

diskann_quantization/bits/
distances.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6//! # Low-level functions
7//!
8//! The methods here are meant to be primitives used by the distance functions for the
9//! various scalar-quantized-like quantizers.
10//!
11//! As such, they typically return integer distance results since they largely operate over
12//! raw bit-slices.
13//!
14//! ## Micro-architecture Mapping
15//!
16//! There are two interfaces for interacting with the distance primitives:
17//!
18//! * [`diskann_wide::arch::Target2`]: A micro-architecture aware interface where the target
19//!   micro-architecture is provided as an explicit argument.
20//!
21//!   This can be used in conjunction with [`diskann_wide::Architecture::run2`] to apply the
22//!   necessary target-features to opt-into newer architecture code generation when
23//!   compiling the whole binary for an older architecture.
24//!
25//!   This interface is also composable with micro-architecture dispatching done higher in
26//!   the callstack, and so should be preferred when incorporating into quantizer distance
27//!   computations.
28//!
29//! * [`diskann_vector::PureDistanceFunction`]: If micro-architecture awareness is not needed,
30//!   this provides a simple interface targeting [`diskann_wide::ARCH`] (the current compilation
31//!   architecture).
32//!
33//!   This interface will always yield a binary compatible with the compilation architecture
34//!   target, but will not enable faster code-paths when compiling for older architectures.
35//!
36//! The following table summarizes the implementation status of kernels. All kernels have
37//! `diskann_wide::arch::Scalar` implementation fallbacks.
38//!
39//! Implementation Kind:
40//!
41//! * "Fallback": A fallback implementation using scalar indexing.
42//!
43//! * "Optimized": A better implementation than "fallback" that does not contain
44//!   target-depeendent code, instead relying on compiler optimizations.
45//!
46//!   Micro-architecture dispatch is still relevant as it allows the compiler to generate
47//!   better code for newer machines.
48//!
49//! * "Yes": Architecture specific SIMD implementation exists.
50//!
51//! * "No": Architecture specific implementation does not exist - the next most-specific
52//!   implementation is used. For example, if a `x86-64-v3` implementation does not exist,
53//!   then the "scalar" implementation will be used instead.
54//!
55//! Type Aliases
56//!
57//! * `USlice<N>`: `BitSlice<N, Unsigned, Dense>`
58//! * `TSlice<N>`: `BitSlice<N, Unsigned, BitTranspose>`
59//! * `BSlice`: `BitSlice<1, Binary, Dense>`
60//!
61//! * `MV<T>`: [`diskann_vector::MathematicalValue<T>`]
62//!
63//! ### Inner Product
64//!
65//! | LHS           | RHS           | Result    | Scalar    | x86-64-v3     | x86-64-v4 | Neon      |
66//! |---------------|---------------|-----------|-----------|---------------|-----------|-----------|
67//! | `USlice<1>`   | `USlice<1>`   | `MV<u32>` | Optimized | Optimized     | Uses V3   | Optimized |
68//! | `USlice<2>`   | `USlice<2>`   | `MV<u32>` | Fallback  | Yes           | Yes       | Optimized |
69//! | `USlice<3>`   | `USlice<3>`   | `MV<u32>` | Fallback  | No            | Uses V3   | Fallback  |
70//! | `USlice<4>`   | `USlice<4>`   | `MV<u32>` | Fallback  | Yes           | Uses V3   | Optimized |
71//! | `USlice<5>`   | `USlice<5>`   | `MV<u32>` | Fallback  | No            | Uses V3   | Fallback  |
72//! | `USlice<6>`   | `USlice<6>`   | `MV<u32>` | Fallback  | No            | Uses V3   | Fallback  |
73//! | `USlice<7>`   | `USlice<7>`   | `MV<u32>` | Fallback  | No            | Uses V3   | Fallback  |
74//! | `USlice<8>`   | `USlice<8>`   | `MV<u32>` | Yes       | Yes           | Yes       | Fallback  |
75//! |               |               |           |           |               |           |           |
76//! | `USlice<8>`   | `USlice<4>`   | `MV<u32>` | Fallback  | Yes           | Uses V3   | Optimized |
77//! | `USlice<8>`   | `USlice<2>`   | `MV<u32>` | Fallback  | Yes           | Uses V3   | Optimized |
78//! | `USlice<8>`   | `USlice<1>`   | `MV<u32>` | Fallback  | Yes           | Uses V3   | Optimized |
79//! |               |               | `       ` |           |               |           |           |
80//! | `TSlice<4>`   | `USlice<1>`   | `MV<u32>` | Optimized | Optimized     | Optimized | Optimized |
81//! |               |               | `       ` |           |               |           |           |
82//! | `&[f32]`      | `USlice<1>`   | `MV<f32>` | Fallback  | Yes           | Uses V3   | Optimized |
83//! | `&[f32]`      | `USlice<2>`   | `MV<f32>` | Fallback  | Yes           | Uses V3   | Optimized |
84//! | `&[f32]`      | `USlice<3>`   | `MV<f32>` | Fallback  | Yes           | Uses V3   | Fallback  |
85//! | `&[f32]`      | `USlice<4>`   | `MV<f32>` | Fallback  | Yes           | Uses V3   | Optimized |
86//! | `&[f32]`      | `USlice<5>`   | `MV<f32>` | Fallback  | No            | Uses V3   | Fallback  |
87//! | `&[f32]`      | `USlice<6>`   | `MV<f32>` | Fallback  | No            | Uses V3   | Fallback  |
88//! | `&[f32]`      | `USlice<7>`   | `MV<f32>` | Fallback  | No            | Uses V3   | Fallback  |
89//! | `&[f32]`      | `USlice<8>`   | `MV<f32>` | Fallback  | No            | Uses V3   | Fallback  |
90//!
91//! ### Squared L2
92//!
93//! | LHS           | RHS           | Result    | Scalar    | x86-64-v3     | x86-64-v4 | Neon      |
94//! |---------------|---------------|-----------|-----------|---------------|-----------|-----------|
95//! | `USlice<1>`   | `USlice<1>`   | `MV<u32>` | Optimized | Optimized     | Uses V3   | Optimized |
96//! | `USlice<2>`   | `USlice<2>`   | `MV<u32>` | Fallback  | Yes           | Uses V3   | Optimized |
97//! | `USlice<3>`   | `USlice<3>`   | `MV<u32>` | Fallback  | No            | Uses V3   | Fallback  |
98//! | `USlice<4>`   | `USlice<4>`   | `MV<u32>` | Fallback  | Yes           | Uses V3   | Optimized |
99//! | `USlice<5>`   | `USlice<5>`   | `MV<u32>` | Fallback  | No            | Uses V3   | Fallback  |
100//! | `USlice<6>`   | `USlice<6>`   | `MV<u32>` | Fallback  | No            | Uses V3   | Fallback  |
101//! | `USlice<7>`   | `USlice<7>`   | `MV<u32>` | Fallback  | No            | Uses V3   | Fallback  |
102//! | `USlice<8>`   | `USlice<8>`   | `MV<u32>` | Yes       | Yes           | Yes       | Fallback  |
103//!
104//! ### Hamming
105//!
106//! | LHS           | RHS           | Result    | Scalar    | x86-64-v3     | x86-64-v4 | Neon      |
107//! |---------------|---------------|-----------|-----------|---------------|-----------|-----------|
108//! | `BSlice`      | `BSlice`      | `MV<u32>` | Optimized | Optimized     | Uses V3   | Optimized |
109
110use diskann_vector::PureDistanceFunction;
111use diskann_wide::{ARCH, Architecture, arch::Target2};
112#[cfg(target_arch = "x86_64")]
113use diskann_wide::{
114    SIMDCast, SIMDDotProduct, SIMDMulAdd, SIMDReinterpret, SIMDSumTree, SIMDVector,
115};
116
117#[cfg(target_arch = "aarch64")]
118use diskann_wide::{
119    SIMDDotProduct, SIMDMulAdd, SIMDPartialEq, SIMDSelect, SIMDSumTree, SIMDVector,
120};
121
122use super::{Binary, BitSlice, BitTranspose, Dense, Representation, Unsigned};
123use crate::distances::{Hamming, InnerProduct, MV, MathematicalResult, SquaredL2, check_lengths};
124
125// Convenience alias.
126type USlice<'a, const N: usize, Perm = Dense> = BitSlice<'a, N, Unsigned, Perm>;
127
128/// Retarget the architectures via the `retarget` inherent method.
129///
130/// * `diskann_wide::arch::x86_64::V3` -> [`diskann_wide::arch::Scalar`]
131/// * `diskann_wide::arch::x86_64::V4` -> `diskann_wide::arch::x86_64::V3`
132/// * `diskann_wide::arch::aarch64::Neon` -> [`diskann_wide::arch::Scalar`]
133macro_rules! retarget {
134    ($arch:path, $op:ty, ($N:literal, $M:literal)) => {
135        impl Target2<
136            $arch,
137            MathematicalResult<u32>,
138            USlice<'_, $N>,
139            USlice<'_, $M>,
140        > for $op {
141            #[inline(always)]
142            fn run(
143                self,
144                arch: $arch,
145                x: USlice<'_, $N>,
146                y: USlice<'_, $M>
147            ) -> MathematicalResult<u32> {
148                self.run(arch.retarget(), x, y)
149            }
150        }
151    };
152    ($arch:path, $op:ty, $N:literal) => {
153        retarget!($arch, $op, ($N, $N));
154    };
155    ($arch:path, $op:ty, $($args:tt),+ $(,)?) => {
156        $(retarget!($arch, $op, $args);)+
157    };
158}
159
160/// Impledment [`diskann_vector::PureDistanceFunction`] using the current compilation architecture
161macro_rules! dispatch_pure {
162    ($op:ty, ($N:literal, $M:literal)) => {
163        impl PureDistanceFunction<USlice<'_, $N>, USlice<'_, $M>, MathematicalResult<u32>> for $op {
164            #[inline(always)]
165            fn evaluate(x: USlice<'_, $N>, y: USlice<'_, $M>) -> MathematicalResult<u32> {
166                (diskann_wide::ARCH).run2(Self, x, y)
167            }
168        }
169    };
170    ($op:ty, $N:literal) => {
171        dispatch_pure!($op, ($N, $N));
172    };
173    ($op:ty, $($args:tt),+ $(,)?) => {
174        $(dispatch_pure!($op, $args);)+
175    }
176}
177
178/// Load 1 byte beginning at `ptr` and invoke `f` with that byte.
179///
180/// # Safety
181///
182/// * The memory range `[ptr, ptr + 1)` (in bytes) must be dereferencable.
183/// * `ptr` does not need to be aligned.
184#[cfg(target_arch = "x86_64")]
185unsafe fn load_one<F, R>(ptr: *const u32, mut f: F) -> R
186where
187    F: FnMut(u32) -> R,
188{
189    // SAFETY: Caller asserts that one byte is readable.
190    f(unsafe { ptr.cast::<u8>().read_unaligned() }.into())
191}
192
193/// Load 2 bytes beginning at `ptr` and invoke `f` with the value.
194///
195/// # Safety
196///
197/// * The memory range `[ptr, ptr + 2)` (in bytes) must be dereferencable.
198/// * `ptr` does not need to be aligned.
199#[cfg(target_arch = "x86_64")]
200unsafe fn load_two<F, R>(ptr: *const u32, mut f: F) -> R
201where
202    F: FnMut(u32) -> R,
203{
204    // SAFETY: Caller asserts that two bytes are readable.
205    f(unsafe { ptr.cast::<u16>().read_unaligned() }.into())
206}
207
208/// Load 3 bytes beginning at `ptr` and invoke `f` with the value.
209///
210/// # Safety
211///
212/// * The memory range `[ptr, ptr + 3)` (in bytes) must be dereferencable.
213/// * `ptr` does not need to be aligned.
214#[cfg(target_arch = "x86_64")]
215unsafe fn load_three<F, R>(ptr: *const u32, mut f: F) -> R
216where
217    F: FnMut(u32) -> R,
218{
219    // SAFETY: Caller asserts that three bytes are readable. This loads the first two.
220    let lo: u32 = unsafe { ptr.cast::<u16>().read_unaligned() }.into();
221    // SAFETY: Caller asserts that three bytes are readable. This loads the third.
222    let hi: u32 = unsafe { ptr.cast::<u8>().add(2).read_unaligned() }.into();
223    f(lo | hi << 16)
224}
225
226/// Load 4 bytes beginning at `ptr` and invoke `f` with the value.
227///
228/// # Safety
229///
230/// * The memory range `[ptr, ptr + 4)` (in bytes) must be dereferencable.
231/// * `ptr` does not need to be aligned.
232#[cfg(target_arch = "x86_64")]
233unsafe fn load_four<F, R>(ptr: *const u32, mut f: F) -> R
234where
235    F: FnMut(u32) -> R,
236{
237    // SAFETY: Caller asserts that four bytes are readable.
238    f(unsafe { ptr.read_unaligned() })
239}
240
241////////////////////////////
242// Distances on BitSlices //
243////////////////////////////
244
245/// Operations to apply to 1-bit encodings.
246///
247/// The general structure of 1-bit vector operations is the same, but the element wise
248/// operator is different. This trait encapsulates the differences in behavior required
249/// for different distance function.
250///
251/// The exact operations to apply depending on the representation of the bit encoding.
252trait BitVectorOp<Repr>
253where
254    Repr: Representation<1>,
255{
256    /// Apply the op to all bits in the 64-bit arguments.
257    fn on_u64(x: u64, y: u64) -> u32;
258
259    /// Apply the op to all bits in the 8-bit arguments.
260    ///
261    /// NOTE: Implementations must have the correct behavior when the upper bits of `x`
262    /// and `y` are set to 0 when handling epilogues.
263    fn on_u8(x: u8, y: u8) -> u32;
264}
265
266/// Computing Squared-L2 amounts to evaluating the pop-count of a bitwise `xor`.
267impl BitVectorOp<Unsigned> for SquaredL2 {
268    #[inline(always)]
269    fn on_u64(x: u64, y: u64) -> u32 {
270        (x ^ y).count_ones()
271    }
272    #[inline(always)]
273    fn on_u8(x: u8, y: u8) -> u32 {
274        (x ^ y).count_ones()
275    }
276}
277
278/// Computing Squared-L2 amounts to evaluating the pop-count of a bitwise `xor`.
279impl BitVectorOp<Binary> for Hamming {
280    #[inline(always)]
281    fn on_u64(x: u64, y: u64) -> u32 {
282        (x ^ y).count_ones()
283    }
284    #[inline(always)]
285    fn on_u8(x: u8, y: u8) -> u32 {
286        (x ^ y).count_ones()
287    }
288}
289
290/// The implementation as `and` is not straight-forward.
291///
292/// Recall that scalar quantization encodings are unsigned, so "0" is zero and "1" is some
293/// non-zero value.
294///
295/// When computing the inner product, `0 * x == 0` for all `x` and only `x * x` has a
296/// non-zero value. Therefore, the elementwise op is an `and` and not `xnor`.
297impl BitVectorOp<Unsigned> for InnerProduct {
298    #[inline(always)]
299    fn on_u64(x: u64, y: u64) -> u32 {
300        (x & y).count_ones()
301    }
302    #[inline(always)]
303    fn on_u8(x: u8, y: u8) -> u32 {
304        (x & y).count_ones()
305    }
306}
307
308/// A general algorithm for applying a bitwise operand to two dense bit vectors of equal
309/// but arbitrary length.
310///
311/// NOTE: The `inline(always)` attribute is required to inheret the caller's target-features.
312#[inline(always)]
313fn bitvector_op<Op, Repr>(
314    x: BitSlice<'_, 1, Repr>,
315    y: BitSlice<'_, 1, Repr>,
316) -> MathematicalResult<u32>
317where
318    Repr: Representation<1>,
319    Op: BitVectorOp<Repr>,
320{
321    let len = check_lengths!(x, y)?;
322
323    let px: *const u64 = x.as_ptr().cast();
324    let py: *const u64 = y.as_ptr().cast();
325
326    let mut i = 0;
327    let mut s: u32 = 0;
328
329    // Work in groups of 64
330    let blocks = len / 64;
331    while i < blocks {
332        // SAFETY: We know at least 64-bits (8-bytes) are valid from this offset (by
333        // guarantee of the `BitSlice`). All bit-patterns of a `u64` are valid, `u64: Copy`,
334        // and an `unaligned` read is used.
335        let vx = unsafe { px.add(i).read_unaligned() };
336
337        // SAFETY: The same logic applies to `y` because:
338        // 1. It has the same type as `x`.
339        // 2. We've verified that it has the same length as `x`.
340        let vy = unsafe { py.add(i).read_unaligned() };
341
342        s += Op::on_u64(vx, vy);
343        i += 1;
344    }
345
346    // Work in groups of 8
347    i *= 8;
348    let px: *const u8 = x.as_ptr();
349    let py: *const u8 = y.as_ptr();
350
351    let blocks = len / 8;
352    while i < blocks {
353        // SAFETY: The underlying pointer is a `*const u8` and we have checked that this
354        // offset is within the bounds of the slice underlying the bitslice.
355        let vx = unsafe { px.add(i).read_unaligned() };
356
357        // SAFETY: The same logic applies to `y` because:
358        // 1. It has the same type as `x`.
359        // 2. We've verified that it has the same length as `x`.
360        let vy = unsafe { py.add(i).read_unaligned() };
361        s += Op::on_u8(vx, vy);
362        i += 1;
363    }
364
365    if i * 8 != len {
366        // SAFETY: The underlying slice is readable in the range
367        // `[px, px + floor(len / 8) + 1)`. This accesses `px + floor(len / 8)`.
368        let vx = unsafe { px.add(i).read_unaligned() };
369
370        // SAFETY: Same as above.
371        let vy = unsafe { py.add(i).read_unaligned() };
372        let m = (0x01u8 << (len - 8 * i)) - 1;
373
374        s += Op::on_u8(vx & m, vy & m)
375    }
376    Ok(MV::new(s))
377}
378
379/// Compute the hamming distance between `x` and `y`.
380///
381/// Returns an error if the arguments have different lengths.
382impl PureDistanceFunction<BitSlice<'_, 1, Binary>, BitSlice<'_, 1, Binary>, MathematicalResult<u32>>
383    for Hamming
384{
385    fn evaluate(x: BitSlice<'_, 1, Binary>, y: BitSlice<'_, 1, Binary>) -> MathematicalResult<u32> {
386        bitvector_op::<Hamming, Binary>(x, y)
387    }
388}
389
390///////////////
391// SquaredL2 //
392///////////////
393
394/// Compute the squared L2 distance between `x` and `y`.
395///
396/// Returns an error if the arguments have different lengths.
397///
398/// # Implementation Notes
399///
400/// This can directly invoke the methods implemented in `vector` because
401/// `BitSlice<'_, 8, Unsigned>` is isomorphic to `&[u8]`.
402impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 8>> for SquaredL2
403where
404    A: Architecture,
405    diskann_vector::distance::SquaredL2: for<'a> Target2<A, MV<f32>, &'a [u8], &'a [u8]>,
406{
407    #[inline(always)]
408    fn run(self, arch: A, x: USlice<'_, 8>, y: USlice<'_, 8>) -> MathematicalResult<u32> {
409        check_lengths!(x, y)?;
410
411        let r: MV<f32> = <_ as Target2<_, _, _, _>>::run(
412            diskann_vector::distance::SquaredL2 {},
413            arch,
414            x.as_slice(),
415            y.as_slice(),
416        );
417
418        Ok(MV::new(r.into_inner() as u32))
419    }
420}
421
422/// Compute the squared L2 distance between `x` and `y`.
423///
424/// Returns an error if the arguments have different lengths.
425///
426/// # Implementation Notes
427///
428/// This implementation is optimized around x86 with the AVX2 vector extension.
429/// Specifically, we try to hit `Wide::<i32, 8> as SIMDDotProduct<Wide<i16, 8>>` so we can
430/// hit the `_mm256_madd_epi16` intrinsic.
431///
432/// Also note that AVX2 does not have 16-bit integer bit-shift instructions. Instead, we
433/// have to use 32-bit integer shifts and then bit-cast to 16-bit intrinsics.
434/// This works because we need to apply the same shift to all lanes.
435#[cfg(target_arch = "x86_64")]
436impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
437    for SquaredL2
438{
439    #[inline(always)]
440    fn run(
441        self,
442        arch: diskann_wide::arch::x86_64::V3,
443        x: USlice<'_, 4>,
444        y: USlice<'_, 4>,
445    ) -> MathematicalResult<u32> {
446        let len = check_lengths!(x, y)?;
447
448        diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
449        diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
450        diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
451
452        let px_u32: *const u32 = x.as_ptr().cast();
453        let py_u32: *const u32 = y.as_ptr().cast();
454
455        let mut i = 0;
456        let mut s: u32 = 0;
457
458        // The number of 32-bit blocks over the underlying slice.
459        let blocks = len / 8;
460        if i < blocks {
461            let mut s0 = i32s::default(arch);
462            let mut s1 = i32s::default(arch);
463            let mut s2 = i32s::default(arch);
464            let mut s3 = i32s::default(arch);
465            let mask = u32s::splat(arch, 0x000f000f);
466            while i + 8 < blocks {
467                // SAFETY: We have checked that `i + 8 < blocks` which means the address
468                // range `[px_u32 + i, px_u32 + i + 8 * std::mem::size_of::<u32>())` is valid.
469                //
470                // The load has no alignment requirements.
471                let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
472
473                // SAFETY: The same logic applies to `y` because:
474                // 1. It has the same type as `x`.
475                // 2. We've verified that it has the same length as `x`.
476                let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
477
478                let wx: i16s = (vx & mask).reinterpret_simd();
479                let wy: i16s = (vy & mask).reinterpret_simd();
480                let d = wx - wy;
481                s0 = s0.dot_simd(d, d);
482
483                let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
484                let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
485                let d = wx - wy;
486                s1 = s1.dot_simd(d, d);
487
488                let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
489                let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
490                let d = wx - wy;
491                s2 = s2.dot_simd(d, d);
492
493                let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
494                let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
495                let d = wx - wy;
496                s3 = s3.dot_simd(d, d);
497
498                i += 8;
499            }
500
501            let remainder = blocks - i;
502
503            // SAFETY: At least one value of type `u32` is valid for an unaligned starting
504            // at offset `i`. The exact number is computed as `remainder`.
505            //
506            // The predicated load is guaranteed not to access memory after `remainder` and
507            // has no alignment requirements.
508            let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
509
510            // SAFETY: The same logic applies to `y` because:
511            // 1. It has the same type as `x`.
512            // 2. We've verified that it has the same length as `x`.
513            let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
514
515            let wx: i16s = (vx & mask).reinterpret_simd();
516            let wy: i16s = (vy & mask).reinterpret_simd();
517            let d = wx - wy;
518            s0 = s0.dot_simd(d, d);
519
520            let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
521            let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
522            let d = wx - wy;
523            s1 = s1.dot_simd(d, d);
524
525            let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
526            let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
527            let d = wx - wy;
528            s2 = s2.dot_simd(d, d);
529
530            let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
531            let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
532            let d = wx - wy;
533            s3 = s3.dot_simd(d, d);
534
535            i += remainder;
536
537            s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
538        }
539
540        // Convert blocks to indexes.
541        i *= 8;
542
543        // Deal with the remainder the slow way.
544        if i != len {
545            // Outline the fallback routine to keep code-generation at this level cleaner.
546            #[inline(never)]
547            fn fallback(x: USlice<'_, 4>, y: USlice<'_, 4>, from: usize) -> u32 {
548                let mut s: i32 = 0;
549                for i in from..x.len() {
550                    // SAFETY: `i` is guaranteed to be less than `x.len()`.
551                    let ix = unsafe { x.get_unchecked(i) } as i32;
552                    // SAFETY: `i` is guaranteed to be less than `y.len()`.
553                    let iy = unsafe { y.get_unchecked(i) } as i32;
554                    let d = ix - iy;
555                    s += d * d;
556                }
557                s as u32
558            }
559            s += fallback(x, y, i);
560        }
561
562        Ok(MV::new(s))
563    }
564}
565
566/// Compute the squared L2 distance between `x` and `y`.
567///
568/// Returns an error if the arguments have different lengths.
569///
570/// # Implementation Notes
571///
572/// This implementation is optimized for x86 with the AVX-512 vector extension.
573///
574/// We load data as `u32x16`, shift and mask to extract 4-bit nibbles at 16-bit granularity
575/// (`0x000f000f` mask), reinterpret as `i16x32`, compute differences, and use
576/// `_mm512_madd_epi16` via `dot_simd` to accumulate squared differences into 4 independent
577/// `i32x16` accumulators. Reinterpreting after a shift is well-defined because the same
578/// shift amount is applied uniformly to all lanes.
579#[cfg(target_arch = "x86_64")]
580impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
581    for SquaredL2
582{
583    #[inline(always)]
584    fn run(
585        self,
586        arch: diskann_wide::arch::x86_64::V4,
587        x: USlice<'_, 4>,
588        y: USlice<'_, 4>,
589    ) -> MathematicalResult<u32> {
590        let len = check_lengths!(x, y)?;
591
592        diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V4>::i32x16);
593        diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V4>::u32x16);
594        diskann_wide::alias!(u8s = <diskann_wide::arch::x86_64::V4>::u8x64);
595        diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V4>::i16x32);
596
597        let px_u32: *const u32 = x.as_ptr().cast();
598        let py_u32: *const u32 = y.as_ptr().cast();
599
600        let mut i = 0;
601        let mut s: u32 = 0;
602
603        // Number of u32 blocks (rounded up). Each u32 holds 8 nibbles = 8 four-bit values.
604        // We use `div_ceil` so a partial trailing byte (1..=7 stray nibbles) is still
605        // covered by the predicated `u8` load below; the scalar fallback only ever needs to
606        // handle at most a single dangling nibble.
607        let blocks = len.div_ceil(8);
608        if i < blocks {
609            let mut s0 = i32s::default(arch);
610            let mut s1 = i32s::default(arch);
611            let mut s2 = i32s::default(arch);
612            let mut s3 = i32s::default(arch);
613            let mask = u32s::splat(arch, 0x000f000f);
614            while i + 16 < blocks {
615                // SAFETY: We have checked that `i + 16 < blocks` which means the
616                // 16-element range `px_u32.add(i)..px_u32.add(i + 16)` (in `u32` units)
617                // is dereferenceable.
618                //
619                // The load has no alignment requirements.
620                let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
621
622                // SAFETY: The same logic applies to `y` because:
623                // 1. It has the same type as `x`.
624                // 2. We've verified that it has the same length as `x`.
625                let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
626
627                let wx: i16s = (vx & mask).reinterpret_simd();
628                let wy: i16s = (vy & mask).reinterpret_simd();
629                let d = wx - wy;
630                s0 = s0.dot_simd(d, d);
631
632                let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
633                let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
634                let d = wx - wy;
635                s1 = s1.dot_simd(d, d);
636
637                let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
638                let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
639                let d = wx - wy;
640                s2 = s2.dot_simd(d, d);
641
642                let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
643                let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
644                let d = wx - wy;
645                s3 = s3.dot_simd(d, d);
646
647                i += 16;
648            }
649
650            // Compute the number of bytes still to process:
651            //   * `len / 2`  — total full bytes in the input (2 nibbles/byte).
652            //   * `4 * i`    — bytes already consumed (4 bytes per u32 × `i` u32s).
653            //
654            // The loop invariant `i + 16 < blocks` (with `blocks = len.div_ceil(8)`)
655            // guarantees `4 * i < len / 2 + small`, so this subtraction is safe.
656            let remainder = len / 2 - 4 * i;
657
658            if remainder > 0 {
659                // SAFETY: `remainder` bytes are dereferenceable starting at
660                // `px_u32.add(i).cast::<u8>()` (i.e. byte offset `4 * i` from `px_u32`).
661                //
662                // The predicated load is guaranteed not to access memory beyond the
663                // first `remainder` bytes and has no alignment requirements.
664                let vx =
665                    unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
666                let vx: u32s = vx.reinterpret_simd();
667
668                // SAFETY: The same logic applies to `y` because:
669                // 1. It has the same type as `x`.
670                // 2. We've verified that it has the same length as `x`.
671                let vy =
672                    unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
673                let vy: u32s = vy.reinterpret_simd();
674
675                let wx: i16s = (vx & mask).reinterpret_simd();
676                let wy: i16s = (vy & mask).reinterpret_simd();
677                let d = wx - wy;
678                s0 = s0.dot_simd(d, d);
679
680                let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
681                let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
682                let d = wx - wy;
683                s1 = s1.dot_simd(d, d);
684
685                let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
686                let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
687                let d = wx - wy;
688                s2 = s2.dot_simd(d, d);
689
690                let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
691                let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
692                let d = wx - wy;
693                s3 = s3.dot_simd(d, d);
694            }
695
696            s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
697            i = (4 * i) + remainder;
698        }
699
700        // Convert bytes to nibble indexes.
701        i *= 2;
702
703        // Deal with the remainder the slow way (at most 1 element).
704        debug_assert!(len - i <= 1);
705        if i != len {
706            // SAFETY: `i` is guaranteed to be less than `x.len()`.
707            let ix = unsafe { x.get_unchecked(i) } as i32;
708            // SAFETY: `i` is guaranteed to be less than `y.len()`.
709            let iy = unsafe { y.get_unchecked(i) } as i32;
710            let d = ix - iy;
711            s += (d * d) as u32;
712        }
713
714        Ok(MV::new(s))
715    }
716}
717
718#[cfg(target_arch = "aarch64")]
719impl
720    Target2<
721        diskann_wide::arch::aarch64::Neon,
722        MathematicalResult<u32>,
723        USlice<'_, 4>,
724        USlice<'_, 4>,
725    > for SquaredL2
726{
727    #[inline(always)]
728    fn run(
729        self,
730        arch: diskann_wide::arch::aarch64::Neon,
731        x: USlice<'_, 4>,
732        y: USlice<'_, 4>,
733    ) -> MathematicalResult<u32> {
734        // returns number of quantized vectors
735        let len = check_lengths!(x, y)?;
736
737        diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
738        diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
739
740        let px_u8: *const u8 = x.as_ptr().cast();
741        let py_u8: *const u8 = y.as_ptr().cast();
742
743        let mut i = 0;
744        let mut s: u32 = 0;
745
746        // number of bytes over the underlying slice
747        let bytes = len / 2;
748        if i < bytes {
749            let mut s0 = u32s::default(arch);
750            let mut s1 = u32s::default(arch);
751            let mask = u8s::splat(arch, 0x0f);
752            while i + 16 <= bytes {
753                // SAFETY: both operations are safe since we verified that
754                // both pointers (of equal length) have >= 16 bytes available
755                // from the offset `i`
756                let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
757                // SAFETY: y_vec meets the same condition as x_vec
758                // due to equal length and same offset
759                let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
760
761                // compute abs diff then dot product for lower 4 bits, result is stored as 32x4
762                let lower_x: u8s = x_vec & mask;
763                let lower_y: u8s = y_vec & mask;
764                let d = arch.vabdq_u8(lower_x, lower_y);
765                s0 = s0.dot_simd(d, d);
766
767                // compute abs diff then dot product for upper 4 bits, result is stored as 32x4
768                let upper_x: u8s = (x_vec >> 4) & mask;
769                let upper_y: u8s = (y_vec >> 4) & mask;
770                let d = arch.vabdq_u8(upper_x, upper_y);
771                s1 = s1.dot_simd(d, d);
772
773                // repeat for next 16 bytes block
774                i += 16;
775            }
776
777            let remaining_bytes = len / 2 - i;
778
779            if remaining_bytes > 0 {
780                // SAFETY: `remaining_bytes` is in `1..16`, and at least that many bytes
781                // are readable from offset `i`. `load_simd_first` reads only those bytes
782                // and zero-fills the remaining lanes.
783                let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_bytes) };
784                // SAFETY: the same condition that holds for `x_vec` holds for `y_vec`
785                let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_bytes) };
786
787                // compute abs diff then dot product for lower 4 bits, result is stored as 32x4
788                let lower_x: u8s = x_vec & mask;
789                let lower_y: u8s = y_vec & mask;
790                let d = arch.vabdq_u8(lower_x, lower_y);
791                s0 = s0.dot_simd(d, d);
792
793                // compute abs diff then dot product for upper 4 bits, result is stored as 32x4
794                let upper_x: u8s = (x_vec >> 4) & mask;
795                let upper_y: u8s = (y_vec >> 4) & mask;
796                let d = arch.vabdq_u8(upper_x, upper_y);
797                s1 = s1.dot_simd(d, d);
798
799                i += remaining_bytes;
800            }
801            s = (s0 + s1).sum_tree();
802        }
803
804        // Convert bytes to nibble indexes.
805        i *= 2;
806
807        // Deal with the remainder the slow way (at most 1 element).
808        debug_assert!(len - i <= 1);
809        if i != len {
810            // SAFETY: `i` is guaranteed to be less than `x.len()`.
811            let ix = unsafe { x.get_unchecked(i) } as i32;
812            // SAFETY: `i` is guaranteed to be less than `y.len()`.
813            let iy = unsafe { y.get_unchecked(i) } as i32;
814            let d = ix - iy;
815            s += (d * d) as u32;
816        }
817
818        Ok(MV::new(s))
819    }
820}
821
822#[cfg(target_arch = "aarch64")]
823impl
824    Target2<
825        diskann_wide::arch::aarch64::Neon,
826        MathematicalResult<u32>,
827        USlice<'_, 2>,
828        USlice<'_, 2>,
829    > for SquaredL2
830{
831    #[inline(always)]
832    fn run(
833        self,
834        arch: diskann_wide::arch::aarch64::Neon,
835        x: USlice<'_, 2>,
836        y: USlice<'_, 2>,
837    ) -> MathematicalResult<u32> {
838        // returns number of quantized vectors
839        let len = check_lengths!(x, y)?;
840
841        diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
842        diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
843
844        let px_u8: *const u8 = x.as_ptr().cast();
845        let py_u8: *const u8 = y.as_ptr().cast();
846
847        let mut i = 0;
848        let mut s: u32 = 0;
849
850        // number of bytes over the underlying slice
851        let bytes = len / 4;
852        if i < bytes {
853            let mut s0 = u32s::default(arch);
854            let mut s1 = u32s::default(arch);
855            let mut s2 = u32s::default(arch);
856            let mut s3 = u32s::default(arch);
857            let mask = u8s::splat(arch, 0x03);
858            while i + 16 <= bytes {
859                // SAFETY: both operations are safe since we verified that
860                // both pointers (of equal length) have >= 16 bytes available
861                // from the offset `i`
862                let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
863                // SAFETY: y_vec meets the same condition as x_vec
864                // due to equal length and same offset
865                let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
866
867                // compute abs diff then dot product for lower 2 bits, result is stored as 32x4
868                let first_x: u8s = x_vec & mask;
869                let first_y: u8s = y_vec & mask;
870                let d = arch.vabdq_u8(first_x, first_y);
871                s0 = s0.dot_simd(d, d);
872
873                // compute abs diff then dot product for next 2 bits, result is stored as 32x4
874                let second_x: u8s = (x_vec >> 2) & mask;
875                let second_y: u8s = (y_vec >> 2) & mask;
876                let d = arch.vabdq_u8(second_x, second_y);
877                s1 = s1.dot_simd(d, d);
878
879                // compute abs diff then dot product for next 2 bits, result is stored as 32x4
880                let third_x: u8s = (x_vec >> 4) & mask;
881                let third_y: u8s = (y_vec >> 4) & mask;
882                let d = arch.vabdq_u8(third_x, third_y);
883                s2 = s2.dot_simd(d, d);
884
885                // compute abs diff then dot product for last 2 bits, result is stored as 32x4
886                let fourth_x: u8s = (x_vec >> 6) & mask;
887                let fourth_y: u8s = (y_vec >> 6) & mask;
888                let d = arch.vabdq_u8(fourth_x, fourth_y);
889                s3 = s3.dot_simd(d, d);
890                // repeat for next block
891                i += 16;
892            }
893
894            let remaining_bytes = len / 4 - i;
895
896            if remaining_bytes > 0 {
897                // SAFETY: `remaining_bytes` is in `1..16`, and at least that many bytes
898                // are readable from offset `i`. `load_simd_first` reads only those bytes
899                // and zero-fills the remaining lanes.
900                let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_bytes) };
901                // SAFETY: the same condition that holds for `x_vec` holds for `y_vec`
902                let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_bytes) };
903
904                // compute abs diff then dot product for first 2 bits, result is stored as 32x4
905                let first_x: u8s = x_vec & mask;
906                let first_y: u8s = y_vec & mask;
907                let d = arch.vabdq_u8(first_x, first_y);
908                s0 = s0.dot_simd(d, d);
909
910                // compute abs diff then dot product for next 2 bits, result is stored as 32x4
911                let second_x: u8s = (x_vec >> 2) & mask;
912                let second_y: u8s = (y_vec >> 2) & mask;
913                let d = arch.vabdq_u8(second_x, second_y);
914                s1 = s1.dot_simd(d, d);
915
916                // compute abs diff then dot product for next 2 bits, result is stored as 32x4
917                let third_x: u8s = (x_vec >> 4) & mask;
918                let third_y: u8s = (y_vec >> 4) & mask;
919                let d = arch.vabdq_u8(third_x, third_y);
920                s2 = s2.dot_simd(d, d);
921
922                // compute abs diff then dot product for last 2 bits, result is stored as 32x4
923                let fourth_x: u8s = (x_vec >> 6) & mask;
924                let fourth_y: u8s = (y_vec >> 6) & mask;
925                let d = arch.vabdq_u8(fourth_x, fourth_y);
926                s3 = s3.dot_simd(d, d);
927                i += remaining_bytes;
928            }
929            s = ((s0 + s1) + (s2 + s3)).sum_tree();
930        }
931
932        // Convert bytes to quantized vector indexes
933        i *= 4;
934
935        // Deal with the remainder the slow way (at most 3 elements).
936        debug_assert!(len - i <= 3);
937        if i != len {
938            #[inline(never)]
939            fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
940                let mut s: i32 = 0;
941                for i in from..x.len() {
942                    // SAFETY: `i` is guaranteed to be less than `x.len()`.
943                    let ix = unsafe { x.get_unchecked(i) } as i32;
944                    // SAFETY: `i` is guaranteed to be less than `y.len()`.
945                    let iy = unsafe { y.get_unchecked(i) } as i32;
946                    let d = ix - iy;
947                    s += d * d;
948                }
949                s as u32
950            }
951            s += fallback(x, y, i);
952        }
953
954        Ok(MV::new(s))
955    }
956}
957
958/// Compute the squared L2 distance between `x` and `y`.
959///
960/// Returns an error if the arguments have different lengths.
961///
962/// # Implementation Notes
963///
964/// This implementation is optimized around x86 with the AVX2 vector extension.
965/// Specifically, we try to hit `Wide::<i32, 8> as SIMDDotProduct<Wide<i16, 8>>` so we can
966/// hit the `_mm256_madd_epi16` intrinsic.
967///
968/// Also note that AVX2 does not have 16-bit integer bit-shift instructions. Instead, we
969/// have to use 32-bit integer shifts and then bit-cast to 16-bit intrinsics.
970/// This works because we need to apply the same shift to all lanes.
971#[cfg(target_arch = "x86_64")]
972impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
973    for SquaredL2
974{
975    #[inline(always)]
976    fn run(
977        self,
978        arch: diskann_wide::arch::x86_64::V3,
979        x: USlice<'_, 2>,
980        y: USlice<'_, 2>,
981    ) -> MathematicalResult<u32> {
982        let len = check_lengths!(x, y)?;
983
984        diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
985        diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
986        diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
987
988        let px_u32: *const u32 = x.as_ptr().cast();
989        let py_u32: *const u32 = y.as_ptr().cast();
990
991        let mut i = 0;
992        let mut s: u32 = 0;
993
994        // The number of 32-bit blocks over the underlying slice.
995        let blocks = len / 16;
996        if i < blocks {
997            let mut s0 = i32s::default(arch);
998            let mut s1 = i32s::default(arch);
999            let mut s2 = i32s::default(arch);
1000            let mut s3 = i32s::default(arch);
1001            let mask = u32s::splat(arch, 0x00030003);
1002            while i + 8 < blocks {
1003                // SAFETY: We have checked that `i + 8 < blocks` which means the address
1004                // range `[px_u32 + i, px_u32 + i + 8 * std::mem::size_of::<u32>())` is valid.
1005                //
1006                // The load has no alignment requirements.
1007                let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1008
1009                // SAFETY: The same logic applies to `y` because:
1010                // 1. It has the same type as `x`.
1011                // 2. We've verified that it has the same length as `x`.
1012                let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1013
1014                let wx: i16s = (vx & mask).reinterpret_simd();
1015                let wy: i16s = (vy & mask).reinterpret_simd();
1016                let d = wx - wy;
1017                s0 = s0.dot_simd(d, d);
1018
1019                let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1020                let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1021                let d = wx - wy;
1022                s1 = s1.dot_simd(d, d);
1023
1024                let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1025                let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1026                let d = wx - wy;
1027                s2 = s2.dot_simd(d, d);
1028
1029                let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1030                let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1031                let d = wx - wy;
1032                s3 = s3.dot_simd(d, d);
1033
1034                let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1035                let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1036                let d = wx - wy;
1037                s0 = s0.dot_simd(d, d);
1038
1039                let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1040                let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1041                let d = wx - wy;
1042                s1 = s1.dot_simd(d, d);
1043
1044                let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1045                let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1046                let d = wx - wy;
1047                s2 = s2.dot_simd(d, d);
1048
1049                let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1050                let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1051                let d = wx - wy;
1052                s3 = s3.dot_simd(d, d);
1053
1054                i += 8;
1055            }
1056
1057            let remainder = blocks - i;
1058
1059            // SAFETY: At least one value of type `u32` is valid for an unaligned starting
1060            // at offset `i`. The exact number is computed as `remainder`.
1061            //
1062            // The predicated load is guaranteed not to access memory after `remainder` and
1063            // has no alignment requirements.
1064            let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1065
1066            // SAFETY: The same logic applies to `y` because:
1067            // 1. It has the same type as `x`.
1068            // 2. We've verified that it has the same length as `x`.
1069            let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1070            let wx: i16s = (vx & mask).reinterpret_simd();
1071            let wy: i16s = (vy & mask).reinterpret_simd();
1072            let d = wx - wy;
1073            s0 = s0.dot_simd(d, d);
1074
1075            let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1076            let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1077            let d = wx - wy;
1078            s1 = s1.dot_simd(d, d);
1079
1080            let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1081            let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1082            let d = wx - wy;
1083            s2 = s2.dot_simd(d, d);
1084
1085            let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1086            let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1087            let d = wx - wy;
1088            s3 = s3.dot_simd(d, d);
1089
1090            let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1091            let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1092            let d = wx - wy;
1093            s0 = s0.dot_simd(d, d);
1094
1095            let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1096            let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1097            let d = wx - wy;
1098            s1 = s1.dot_simd(d, d);
1099
1100            let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1101            let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1102            let d = wx - wy;
1103            s2 = s2.dot_simd(d, d);
1104
1105            let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1106            let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1107            let d = wx - wy;
1108            s3 = s3.dot_simd(d, d);
1109
1110            i += remainder;
1111
1112            s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1113        }
1114
1115        // Convert blocks to indexes.
1116        i *= 16;
1117
1118        // Deal with the remainder the slow way.
1119        if i != len {
1120            // Outline the fallback routine to keep code-generation at this level cleaner.
1121            #[inline(never)]
1122            fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
1123                let mut s: i32 = 0;
1124                for i in from..x.len() {
1125                    // SAFETY: `i` is guaranteed to be less than `x.len()`.
1126                    let ix = unsafe { x.get_unchecked(i) } as i32;
1127                    // SAFETY: `i` is guaranteed to be less than `y.len()`.
1128                    let iy = unsafe { y.get_unchecked(i) } as i32;
1129                    let d = ix - iy;
1130                    s += d * d;
1131                }
1132                s as u32
1133            }
1134            s += fallback(x, y, i);
1135        }
1136
1137        Ok(MV::new(s))
1138    }
1139}
1140
1141/// Compute the squared L2 distance between bitvectors `x` and `y`.
1142///
1143/// Returns an error if the arguments have different lengths.
1144impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 1>, USlice<'_, 1>> for SquaredL2
1145where
1146    A: Architecture,
1147{
1148    fn run(self, _: A, x: USlice<'_, 1>, y: USlice<'_, 1>) -> MathematicalResult<u32> {
1149        bitvector_op::<Self, Unsigned>(x, y)
1150    }
1151}
1152
1153/// An implementation for L2 distance that uses scalar indexing for the implementation.
1154macro_rules! impl_fallback_l2 {
1155    ($N:literal) => {
1156        /// Compute the squared L2 distance between `x` and `y`.
1157        ///
1158        /// Returns an error if the arguments have different lengths.
1159        ///
1160        /// # Performance
1161        ///
1162        /// This function uses a generic implementation and therefore is not very fast.
1163        impl Target2<diskann_wide::arch::Scalar, MathematicalResult<u32>, USlice<'_, $N>, USlice<'_, $N>> for SquaredL2 {
1164            #[inline(never)]
1165            fn run(
1166                self,
1167                _: diskann_wide::arch::Scalar,
1168                x: USlice<'_, $N>,
1169                y: USlice<'_, $N>
1170            ) -> MathematicalResult<u32> {
1171                let len = check_lengths!(x, y)?;
1172
1173                let mut accum: i32 = 0;
1174                for i in 0..len {
1175                    // SAFETY: `i` is guaranteed to be less than `x.len()`.
1176                    let ix: i32 = unsafe { x.get_unchecked(i) } as i32;
1177                    // SAFETY: `i` is guaranteed to be less than `y.len()`.
1178                    let iy: i32 = unsafe { y.get_unchecked(i) } as i32;
1179                    let diff = ix - iy;
1180                    accum += diff * diff;
1181                }
1182                Ok(MV::new(accum as u32))
1183            }
1184        }
1185    };
1186    ($($N:literal),+ $(,)?) => {
1187        $(impl_fallback_l2!($N);)+
1188    };
1189}
1190
1191impl_fallback_l2!(7, 6, 5, 4, 3, 2);
1192
1193#[cfg(target_arch = "x86_64")]
1194retarget!(diskann_wide::arch::x86_64::V3, SquaredL2, 7, 6, 5, 3);
1195
1196#[cfg(target_arch = "x86_64")]
1197retarget!(diskann_wide::arch::x86_64::V4, SquaredL2, 7, 6, 5, 3, 2);
1198
1199dispatch_pure!(SquaredL2, 1, 2, 3, 4, 5, 6, 7, 8);
1200#[cfg(target_arch = "aarch64")]
1201retarget!(diskann_wide::arch::aarch64::Neon, SquaredL2, 7, 6, 5, 3,);
1202
1203///////////////////
1204// Inner Product //
1205///////////////////
1206
1207/// Compute the inner product between `x` and `y`.
1208///
1209/// Returns an error if the arguments have different lengths.
1210///
1211/// # Implementation Notes
1212///
1213/// This can directly invoke the methods implemented in `vector` because
1214/// `BitSlice<'_, 8, Unsigned>` is isomorphic to `&[u8]`.
1215impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 8>> for InnerProduct
1216where
1217    A: Architecture,
1218    diskann_vector::distance::InnerProduct: for<'a> Target2<A, MV<f32>, &'a [u8], &'a [u8]>,
1219{
1220    #[inline(always)]
1221    fn run(self, arch: A, x: USlice<'_, 8>, y: USlice<'_, 8>) -> MathematicalResult<u32> {
1222        check_lengths!(x, y)?;
1223        let r: MV<f32> = <_ as Target2<_, _, _, _>>::run(
1224            diskann_vector::distance::InnerProduct {},
1225            arch,
1226            x.as_slice(),
1227            y.as_slice(),
1228        );
1229
1230        Ok(MV::new(r.into_inner() as u32))
1231    }
1232}
1233
1234/// Compute the inner product between `x` and `y`.
1235///
1236/// Returns an error if the arguments have different lengths.
1237///
1238/// # Implementation Notes
1239///
1240/// This is optimized around the `__mm512_dpbusd_epi32` VNNI instruction, which computes the
1241/// pairwise dot product between vectors of 8-bit integers and accumulates groups of 4 with
1242/// an `i32` accumulation vector.
1243///
1244/// One quirk of this instruction is that one argument must be unsigned and the other must
1245/// be signed. Since thie kernsl works on 2-bit integers, this is not a limitation. Just
1246/// something to be aware of.
1247///
1248/// Since AVX512 does not have an 8-bit shift instruction, we generally load data as
1249/// `u32x16` (which has a native shift) and bit-cast it to `u8x64` as needed.
1250#[cfg(target_arch = "x86_64")]
1251impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
1252    for InnerProduct
1253{
1254    #[expect(non_camel_case_types)]
1255    #[inline(always)]
1256    fn run(
1257        self,
1258        arch: diskann_wide::arch::x86_64::V4,
1259        x: USlice<'_, 2>,
1260        y: USlice<'_, 2>,
1261    ) -> MathematicalResult<u32> {
1262        let len = check_lengths!(x, y)?;
1263
1264        type i32s = <diskann_wide::arch::x86_64::V4 as Architecture>::i32x16;
1265        type u32s = <diskann_wide::arch::x86_64::V4 as Architecture>::u32x16;
1266        type u8s = <diskann_wide::arch::x86_64::V4 as Architecture>::u8x64;
1267        type i8s = <diskann_wide::arch::x86_64::V4 as Architecture>::i8x64;
1268
1269        let px_u32: *const u32 = x.as_ptr().cast();
1270        let py_u32: *const u32 = y.as_ptr().cast();
1271
1272        let mut i = 0;
1273        let mut s: u32 = 0;
1274
1275        // The number of 32-bit blocks over the underlying slice.
1276        let blocks = len.div_ceil(16);
1277        if i < blocks {
1278            let mut s0 = i32s::default(arch);
1279            let mut s1 = i32s::default(arch);
1280            let mut s2 = i32s::default(arch);
1281            let mut s3 = i32s::default(arch);
1282            let mask = u32s::splat(arch, 0x03030303);
1283            while i + 16 < blocks {
1284                // SAFETY: We have checked that `i + 16 < blocks` which means the address
1285                // range `[px_u32 + i, px_u32 + i + 16 * std::mem::size_of::<u32>())` is valid.
1286                //
1287                // The load has no alignment requirements.
1288                let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1289
1290                // SAFETY: The same logic applies to `y` because:
1291                // 1. It has the same type as `x`.
1292                // 2. We've verified that it has the same length as `x`.
1293                let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1294
1295                let wx: u8s = (vx & mask).reinterpret_simd();
1296                let wy: i8s = (vy & mask).reinterpret_simd();
1297                s0 = s0.dot_simd(wx, wy);
1298
1299                let wx: u8s = ((vx >> 2) & mask).reinterpret_simd();
1300                let wy: i8s = ((vy >> 2) & mask).reinterpret_simd();
1301                s1 = s1.dot_simd(wx, wy);
1302
1303                let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1304                let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1305                s2 = s2.dot_simd(wx, wy);
1306
1307                let wx: u8s = ((vx >> 6) & mask).reinterpret_simd();
1308                let wy: i8s = ((vy >> 6) & mask).reinterpret_simd();
1309                s3 = s3.dot_simd(wx, wy);
1310
1311                i += 16;
1312            }
1313
1314            // Here
1315            // * `len / 4` gives the number of full bytes
1316            // * `4 * i` gives the number of bytes processed.
1317            let remainder = len / 4 - 4 * i;
1318
1319            // SAFETY: At least `remainder` bytes are valid starting at an offset of `i`.
1320            //
1321            // The predicated load is guaranteed not to access memory after `remainder` and
1322            // has no alignment requirements.
1323            let vx = unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
1324            let vx: u32s = vx.reinterpret_simd();
1325
1326            // SAFETY: The same logic applies to `y` because:
1327            // 1. It has the same type as `x`.
1328            // 2. We've verified that it has the same length as `x`.
1329            let vy = unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
1330            let vy: u32s = vy.reinterpret_simd();
1331
1332            let wx: u8s = (vx & mask).reinterpret_simd();
1333            let wy: i8s = (vy & mask).reinterpret_simd();
1334            s0 = s0.dot_simd(wx, wy);
1335
1336            let wx: u8s = ((vx >> 2) & mask).reinterpret_simd();
1337            let wy: i8s = ((vy >> 2) & mask).reinterpret_simd();
1338            s1 = s1.dot_simd(wx, wy);
1339
1340            let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1341            let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1342            s2 = s2.dot_simd(wx, wy);
1343
1344            let wx: u8s = ((vx >> 6) & mask).reinterpret_simd();
1345            let wy: i8s = ((vy >> 6) & mask).reinterpret_simd();
1346            s3 = s3.dot_simd(wx, wy);
1347
1348            s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1349            i = (4 * i) + remainder;
1350        }
1351
1352        // Convert blocks to indexes.
1353        i *= 4;
1354
1355        // Deal with the remainder the slow way.
1356        debug_assert!(len - i <= 3);
1357        let rest = (len - i).min(3);
1358        if i != len {
1359            for j in 0..rest {
1360                // SAFETY: `i` is guaranteed to be less than `x.len()`.
1361                let ix = unsafe { x.get_unchecked(i + j) } as u32;
1362                // SAFETY: `i` is guaranteed to be less than `y.len()`.
1363                let iy = unsafe { y.get_unchecked(i + j) } as u32;
1364                s += ix * iy;
1365            }
1366        }
1367
1368        Ok(MV::new(s))
1369    }
1370}
1371
1372/// Compute the inner product between `x` and `y`.
1373///
1374/// Returns an error if the arguments have different lengths.
1375///
1376/// # Implementation Notes
1377///
1378/// This implementation is optimized around x86 with the AVX2 vector extension.
1379/// Specifically, we try to hit `Wide::<i32, 8> as SIMDDotProduct<Wide<i16, 8>>` so we can
1380/// hit the `_mm256_madd_epi16` intrinsic.
1381///
1382/// Also note that AVX2 does not have 16-bit integer bit-shift instructions. Instead, we
1383/// have to use 32-bit integer shifts and then bit-cast to 16-bit intrinsics.
1384/// This works because we need to apply the same shift to all lanes.
1385#[cfg(target_arch = "x86_64")]
1386impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
1387    for InnerProduct
1388{
1389    #[inline(always)]
1390    fn run(
1391        self,
1392        arch: diskann_wide::arch::x86_64::V3,
1393        x: USlice<'_, 4>,
1394        y: USlice<'_, 4>,
1395    ) -> MathematicalResult<u32> {
1396        let len = check_lengths!(x, y)?;
1397
1398        diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1399        diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
1400        diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1401
1402        let px_u32: *const u32 = x.as_ptr().cast();
1403        let py_u32: *const u32 = y.as_ptr().cast();
1404
1405        let mut i = 0;
1406        let mut s: u32 = 0;
1407
1408        let blocks = len / 8;
1409        if i < blocks {
1410            let mut s0 = i32s::default(arch);
1411            let mut s1 = i32s::default(arch);
1412            let mut s2 = i32s::default(arch);
1413            let mut s3 = i32s::default(arch);
1414            let mask = u32s::splat(arch, 0x000f000f);
1415            while i + 8 < blocks {
1416                // SAFETY: We have checked that `i + 8 < blocks` which means the address
1417                // range `[px_u32 + i, px_u32 + i + 8 * std::mem::size_of::<u32>())` is valid.
1418                //
1419                // The load has no alignment requirements.
1420                let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1421
1422                // SAFETY: The same logic applies to `y` because:
1423                // 1. It has the same type as `x`.
1424                // 2. We've verified that it has the same length as `x`.
1425                let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1426
1427                let wx: i16s = (vx & mask).reinterpret_simd();
1428                let wy: i16s = (vy & mask).reinterpret_simd();
1429                s0 = s0.dot_simd(wx, wy);
1430
1431                let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1432                let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1433                s1 = s1.dot_simd(wx, wy);
1434
1435                let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1436                let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1437                s2 = s2.dot_simd(wx, wy);
1438
1439                let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1440                let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1441                s3 = s3.dot_simd(wx, wy);
1442
1443                i += 8;
1444            }
1445
1446            let remainder = blocks - i;
1447
1448            // SAFETY: At least one value of type `u32` is valid for an unaligned starting
1449            // at offset `i`. The exact number is computed as `remainder`.
1450            //
1451            // The predicated load is guaranteed not to access memory after `remainder` and
1452            // has no alignment requirements.
1453            let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1454
1455            // SAFETY: The same logic applies to `y` because:
1456            // 1. It has the same type as `x`.
1457            // 2. We've verified that it has the same length as `x`.
1458            let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1459
1460            let wx: i16s = (vx & mask).reinterpret_simd();
1461            let wy: i16s = (vy & mask).reinterpret_simd();
1462            s0 = s0.dot_simd(wx, wy);
1463
1464            let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1465            let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1466            s1 = s1.dot_simd(wx, wy);
1467
1468            let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1469            let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1470            s2 = s2.dot_simd(wx, wy);
1471
1472            let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1473            let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1474            s3 = s3.dot_simd(wx, wy);
1475
1476            i += remainder;
1477
1478            s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1479        }
1480
1481        // Convert blocks to indexes.
1482        i *= 8;
1483
1484        // Deal with the remainder the slow way.
1485        if i != len {
1486            // Outline the fallback routine to keep code-generation at this level cleaner.
1487            #[inline(never)]
1488            fn fallback(x: USlice<'_, 4>, y: USlice<'_, 4>, from: usize) -> u32 {
1489                let mut s: u32 = 0;
1490                for i in from..x.len() {
1491                    // SAFETY: `i` is guaranteed to be less than `x.len()`.
1492                    let ix = unsafe { x.get_unchecked(i) } as u32;
1493                    // SAFETY: `i` is guaranteed to be less than `y.len()`.
1494                    let iy = unsafe { y.get_unchecked(i) } as u32;
1495                    s += ix * iy;
1496                }
1497                s
1498            }
1499            s += fallback(x, y, i);
1500        }
1501
1502        Ok(MV::new(s))
1503    }
1504}
1505
1506/// Compute the inner product between `x` and `y`.
1507///
1508/// Returns an error if the arguments have different lengths.
1509///
1510/// # Implementation Notes
1511///
1512/// This implementation is optimized around the AVX-512 VNNI `_mm512_dpbusd_epi32`
1513/// instruction, which computes the pairwise dot product between vectors of 8-bit integers
1514/// and accumulates groups of 4 into an `i32` accumulator.
1515///
1516/// For 4-bit values, each byte holds 2 nibbles. We load data as `u32x16`, mask with
1517/// `0x0f0f0f0f` to extract the low nibbles as bytes, and shift right by 4 then mask to
1518/// extract the high nibbles. This yields `u8x64` / `i8x64` operands for VNNI, requiring
1519/// only 2 shift positions.
1520///
1521/// Since AVX-512 does not have an 8-bit shift instruction, we load data as `u32x16`
1522/// (which has a native shift) and bit-cast to `u8x64` as needed.
1523#[cfg(target_arch = "x86_64")]
1524impl Target2<diskann_wide::arch::x86_64::V4, MathematicalResult<u32>, USlice<'_, 4>, USlice<'_, 4>>
1525    for InnerProduct
1526{
1527    #[inline(always)]
1528    fn run(
1529        self,
1530        arch: diskann_wide::arch::x86_64::V4,
1531        x: USlice<'_, 4>,
1532        y: USlice<'_, 4>,
1533    ) -> MathematicalResult<u32> {
1534        let len = check_lengths!(x, y)?;
1535
1536        diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V4>::i32x16);
1537        diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V4>::u32x16);
1538        diskann_wide::alias!(u8s = <diskann_wide::arch::x86_64::V4>::u8x64);
1539        diskann_wide::alias!(i8s = <diskann_wide::arch::x86_64::V4>::i8x64);
1540
1541        let px_u32: *const u32 = x.as_ptr().cast();
1542        let py_u32: *const u32 = y.as_ptr().cast();
1543
1544        let mut i = 0;
1545        let mut s: u32 = 0;
1546
1547        // Number of u32 blocks (rounded up). Each u32 holds 8 nibbles = 8 four-bit values.
1548        // We use `div_ceil` so a partial trailing byte (1..=7 stray nibbles) is still
1549        // covered by the predicated `u8` load below; the scalar fallback only ever needs to
1550        // handle at most a single dangling nibble.
1551        let blocks = len.div_ceil(8);
1552        if i < blocks {
1553            let mut s0 = i32s::default(arch);
1554            let mut s1 = i32s::default(arch);
1555            let mask = u32s::splat(arch, 0x0f0f0f0f);
1556            while i + 16 < blocks {
1557                // SAFETY: We have checked that `i + 16 < blocks` which means the
1558                // 16-element range `px_u32.add(i)..px_u32.add(i + 16)` (in `u32` units)
1559                // is dereferenceable.
1560                //
1561                // The load has no alignment requirements.
1562                let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1563
1564                // SAFETY: The same logic applies to `y` because:
1565                // 1. It has the same type as `x`.
1566                // 2. We've verified that it has the same length as `x`.
1567                let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1568
1569                // VNNI `vpdpbusd` requires (unsigned, signed) operands in this order;
1570                // `dot_simd(u8s, i8s)` lowers to that intrinsic on V4. 4-bit values fit in
1571                // the positive half of `i8`, so the unsigned->signed reinterpret is safe.
1572                let wx: u8s = (vx & mask).reinterpret_simd();
1573                let wy: i8s = (vy & mask).reinterpret_simd();
1574                s0 = s0.dot_simd(wx, wy);
1575
1576                let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1577                let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1578                s1 = s1.dot_simd(wx, wy);
1579
1580                i += 16;
1581            }
1582
1583            // Compute the number of bytes still to process:
1584            //   * `len / 2`  — total full bytes in the input (2 nibbles/byte).
1585            //   * `4 * i`    — bytes already consumed (4 bytes per u32 × `i` u32s).
1586            //
1587            // The loop invariant `i + 16 < blocks` (with `blocks = len.div_ceil(8)`)
1588            // guarantees `4 * i < len / 2 + small`, so this subtraction is safe.
1589            let remainder = len / 2 - 4 * i;
1590
1591            if remainder > 0 {
1592                // SAFETY: `remainder` bytes are dereferenceable starting at
1593                // `px_u32.add(i).cast::<u8>()` (i.e. byte offset `4 * i` from `px_u32`).
1594                //
1595                // The predicated load is guaranteed not to access memory beyond the
1596                // first `remainder` bytes and has no alignment requirements.
1597                let vx =
1598                    unsafe { u8s::load_simd_first(arch, px_u32.add(i).cast::<u8>(), remainder) };
1599                let vx: u32s = vx.reinterpret_simd();
1600
1601                // SAFETY: The same logic applies to `y` because:
1602                // 1. It has the same type as `x`.
1603                // 2. We've verified that it has the same length as `x`.
1604                let vy =
1605                    unsafe { u8s::load_simd_first(arch, py_u32.add(i).cast::<u8>(), remainder) };
1606                let vy: u32s = vy.reinterpret_simd();
1607
1608                let wx: u8s = (vx & mask).reinterpret_simd();
1609                let wy: i8s = (vy & mask).reinterpret_simd();
1610                s0 = s0.dot_simd(wx, wy);
1611
1612                let wx: u8s = ((vx >> 4) & mask).reinterpret_simd();
1613                let wy: i8s = ((vy >> 4) & mask).reinterpret_simd();
1614                s1 = s1.dot_simd(wx, wy);
1615            }
1616
1617            s = (s0 + s1).sum_tree() as u32;
1618            i = (4 * i) + remainder;
1619        }
1620
1621        // Convert bytes to nibble indexes.
1622        i *= 2;
1623
1624        // Deal with the remainder the slow way (at most 1 element).
1625        debug_assert!(len - i <= 1);
1626        if i != len {
1627            // SAFETY: `i` is guaranteed to be less than `x.len()`.
1628            let ix = unsafe { x.get_unchecked(i) } as u32;
1629            // SAFETY: `i` is guaranteed to be less than `y.len()`.
1630            let iy = unsafe { y.get_unchecked(i) } as u32;
1631            s += ix * iy;
1632        }
1633
1634        Ok(MV::new(s))
1635    }
1636}
1637
1638/// Compute the inner product between `x` and `y`.
1639///
1640/// Returns an error if the arguments have different lengths.
1641///
1642/// # Implementation Notes
1643///
1644/// This implementation is optimized around x86 with the AVX2 vector extension.
1645/// Specifically, we try to hit `Wide::<i32, 8> as SIMDDotProduct<Wide<i16, 8>>` so we can
1646/// hit the `_mm256_madd_epi16` intrinsic.
1647///
1648/// Also note that AVX2 does not have 16-bit integer bit-shift instructions. Instead, we
1649/// have to use 32-bit integer shifts and then bit-cast to 16-bit intrinsics.
1650/// This works because we need to apply the same shift to all lanes.
1651#[cfg(target_arch = "x86_64")]
1652impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 2>, USlice<'_, 2>>
1653    for InnerProduct
1654{
1655    #[inline(always)]
1656    fn run(
1657        self,
1658        arch: diskann_wide::arch::x86_64::V3,
1659        x: USlice<'_, 2>,
1660        y: USlice<'_, 2>,
1661    ) -> MathematicalResult<u32> {
1662        let len = check_lengths!(x, y)?;
1663
1664        diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
1665        diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
1666        diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
1667
1668        let px_u32: *const u32 = x.as_ptr().cast();
1669        let py_u32: *const u32 = y.as_ptr().cast();
1670
1671        let mut i = 0;
1672        let mut s: u32 = 0;
1673
1674        // The number of 32-bit blocks over the underlying slice.
1675        let blocks = len / 16;
1676        if i < blocks {
1677            let mut s0 = i32s::default(arch);
1678            let mut s1 = i32s::default(arch);
1679            let mut s2 = i32s::default(arch);
1680            let mut s3 = i32s::default(arch);
1681            let mask = u32s::splat(arch, 0x00030003);
1682            while i + 8 < blocks {
1683                // SAFETY: We have checked that `i + 8 < blocks` which means the address
1684                // range `[px_u32 + i, px_u32 + i + 8 * std::mem::size_of::<u32>())` is valid.
1685                //
1686                // The load has no alignment requirements.
1687                let vx = unsafe { u32s::load_simd(arch, px_u32.add(i)) };
1688
1689                // SAFETY: The same logic applies to `y` because:
1690                // 1. It has the same type as `x`.
1691                // 2. We've verified that it has the same length as `x`.
1692                let vy = unsafe { u32s::load_simd(arch, py_u32.add(i)) };
1693
1694                let wx: i16s = (vx & mask).reinterpret_simd();
1695                let wy: i16s = (vy & mask).reinterpret_simd();
1696                s0 = s0.dot_simd(wx, wy);
1697
1698                let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1699                let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1700                s1 = s1.dot_simd(wx, wy);
1701
1702                let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1703                let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1704                s2 = s2.dot_simd(wx, wy);
1705
1706                let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1707                let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1708                s3 = s3.dot_simd(wx, wy);
1709
1710                let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1711                let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1712                s0 = s0.dot_simd(wx, wy);
1713
1714                let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1715                let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1716                s1 = s1.dot_simd(wx, wy);
1717
1718                let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1719                let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1720                s2 = s2.dot_simd(wx, wy);
1721
1722                let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1723                let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1724                s3 = s3.dot_simd(wx, wy);
1725
1726                i += 8;
1727            }
1728
1729            let remainder = blocks - i;
1730
1731            // SAFETY: At least one value of type `u32` is valid for an unaligned starting
1732            // at offset `i`. The exact number is computed as `remainder`.
1733            //
1734            // The predicated load is guaranteed not to access memory after `remainder` and
1735            // has no alignment requirements.
1736            let vx = unsafe { u32s::load_simd_first(arch, px_u32.add(i), remainder) };
1737
1738            // SAFETY: The same logic applies to `y` because:
1739            // 1. It has the same type as `x`.
1740            // 2. We've verified that it has the same length as `x`.
1741            let vy = unsafe { u32s::load_simd_first(arch, py_u32.add(i), remainder) };
1742            let wx: i16s = (vx & mask).reinterpret_simd();
1743            let wy: i16s = (vy & mask).reinterpret_simd();
1744            s0 = s0.dot_simd(wx, wy);
1745
1746            let wx: i16s = (vx >> 2 & mask).reinterpret_simd();
1747            let wy: i16s = (vy >> 2 & mask).reinterpret_simd();
1748            s1 = s1.dot_simd(wx, wy);
1749
1750            let wx: i16s = (vx >> 4 & mask).reinterpret_simd();
1751            let wy: i16s = (vy >> 4 & mask).reinterpret_simd();
1752            s2 = s2.dot_simd(wx, wy);
1753
1754            let wx: i16s = (vx >> 6 & mask).reinterpret_simd();
1755            let wy: i16s = (vy >> 6 & mask).reinterpret_simd();
1756            s3 = s3.dot_simd(wx, wy);
1757
1758            let wx: i16s = (vx >> 8 & mask).reinterpret_simd();
1759            let wy: i16s = (vy >> 8 & mask).reinterpret_simd();
1760            s0 = s0.dot_simd(wx, wy);
1761
1762            let wx: i16s = (vx >> 10 & mask).reinterpret_simd();
1763            let wy: i16s = (vy >> 10 & mask).reinterpret_simd();
1764            s1 = s1.dot_simd(wx, wy);
1765
1766            let wx: i16s = (vx >> 12 & mask).reinterpret_simd();
1767            let wy: i16s = (vy >> 12 & mask).reinterpret_simd();
1768            s2 = s2.dot_simd(wx, wy);
1769
1770            let wx: i16s = (vx >> 14 & mask).reinterpret_simd();
1771            let wy: i16s = (vy >> 14 & mask).reinterpret_simd();
1772            s3 = s3.dot_simd(wx, wy);
1773
1774            i += remainder;
1775
1776            s = ((s0 + s1) + (s2 + s3)).sum_tree() as u32;
1777        }
1778
1779        // Convert blocks to indexes.
1780        i *= 16;
1781
1782        // Deal with the remainder the slow way.
1783        if i != len {
1784            // Outline the fallback routine to keep code-generation at this level cleaner.
1785            #[inline(never)]
1786            fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
1787                let mut s: u32 = 0;
1788                for i in from..x.len() {
1789                    // SAFETY: `i` is guaranteed to be less than `x.len()`.
1790                    let ix = unsafe { x.get_unchecked(i) } as u32;
1791                    // SAFETY: `i` is guaranteed to be less than `y.len()`.
1792                    let iy = unsafe { y.get_unchecked(i) } as u32;
1793                    s += ix * iy;
1794                }
1795                s
1796            }
1797            s += fallback(x, y, i);
1798        }
1799
1800        Ok(MV::new(s))
1801    }
1802}
1803
1804#[cfg(target_arch = "aarch64")]
1805impl
1806    Target2<
1807        diskann_wide::arch::aarch64::Neon,
1808        MathematicalResult<u32>,
1809        USlice<'_, 4>,
1810        USlice<'_, 4>,
1811    > for InnerProduct
1812{
1813    #[inline(always)]
1814    fn run(
1815        self,
1816        arch: diskann_wide::arch::aarch64::Neon,
1817        x: USlice<'_, 4>,
1818        y: USlice<'_, 4>,
1819    ) -> MathematicalResult<u32> {
1820        let len = check_lengths!(x, y)?;
1821
1822        diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
1823        diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
1824
1825        let px_u8: *const u8 = x.as_ptr().cast();
1826        let py_u8: *const u8 = y.as_ptr().cast();
1827
1828        let mut i = 0;
1829        let mut s: u32 = 0;
1830
1831        // number of bytes over the underlying slice
1832        let bytes = len / 2;
1833        if i < bytes {
1834            let mut s0 = u32s::default(arch);
1835            let mut s1 = u32s::default(arch);
1836            let mask = u8s::splat(arch, 0x0f);
1837            while i + 16 <= bytes {
1838                // SAFETY: load simd loads 16 bytes from offset `i`
1839                // we have already verified that i + 16 <= bytes
1840                let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
1841                // SAFETY: same logic applies for y and same conditions hold
1842                // since the lengths are element sizes of x and y are equal.
1843                let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
1844
1845                // compute dot product for lower 4 bits
1846                // each set of 4 results is reduced to one lane
1847                let first_x: u8s = x_vec & mask;
1848                let first_y: u8s = y_vec & mask;
1849                s0 = s0.dot_simd(first_x, first_y);
1850
1851                // repeat for upper 4 bits
1852                let second_x: u8s = (x_vec >> 4) & mask;
1853                let second_y: u8s = (y_vec >> 4) & mask;
1854                s1 = s1.dot_simd(second_x, second_y);
1855                // repeat for next block
1856                i += 16;
1857            }
1858
1859            let remaining_bytes = len / 2 - i;
1860
1861            if remaining_bytes > 0 {
1862                let remaining_vec1 = remaining_bytes.min(16);
1863                // SAFETY: up to `remaining_bytes` can be loaded from the offset `i`
1864                // since the floor division ensures that we only read bytes that are fully
1865                // packed with elements from the slice.
1866                let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_vec1) };
1867                // SAFETY: same logic applies for y and same conditions hold
1868                // since the lengths are element sizes of x and y are equal.
1869                let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_vec1) };
1870
1871                let first_x: u8s = x_vec & mask;
1872                let first_y: u8s = y_vec & mask;
1873                s0 = s0.dot_simd(first_x, first_y);
1874
1875                // compute dot product for upper 4 bits, result is stored as 32x4
1876                let second_x: u8s = (x_vec >> 4) & mask;
1877                let second_y: u8s = (y_vec >> 4) & mask;
1878                s1 = s1.dot_simd(second_x, second_y);
1879                i += remaining_bytes;
1880            }
1881            s = (s0 + s1).sum_tree();
1882        }
1883        // Convert bytes to nibble indexes.
1884        i *= 2;
1885
1886        // Deal with the remainder the slow way (at most 1 element).
1887        debug_assert!(len - i <= 1);
1888
1889        if i != len {
1890            // SAFETY: `i` is guaranteed to be less than `x.len()`.
1891            let ix = unsafe { x.get_unchecked(i) } as i32;
1892            // SAFETY: `i` is guaranteed to be less than `y.len()`.
1893            let iy = unsafe { y.get_unchecked(i) } as i32;
1894            s += (ix * iy) as u32;
1895        }
1896
1897        Ok(MV::new(s))
1898    }
1899}
1900
1901/// Compute the inner product between bitvectors `x` and `y`.
1902///
1903/// Returns an error if the arguments have different lengths.
1904impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 1>, USlice<'_, 1>> for InnerProduct
1905where
1906    A: Architecture,
1907{
1908    #[inline(always)]
1909    fn run(self, _: A, x: USlice<'_, 1>, y: USlice<'_, 1>) -> MathematicalResult<u32> {
1910        bitvector_op::<Self, Unsigned>(x, y)
1911    }
1912}
1913
1914#[cfg(target_arch = "aarch64")]
1915impl
1916    Target2<
1917        diskann_wide::arch::aarch64::Neon,
1918        MathematicalResult<u32>,
1919        USlice<'_, 2>,
1920        USlice<'_, 2>,
1921    > for InnerProduct
1922{
1923    #[inline(always)]
1924    fn run(
1925        self,
1926        arch: diskann_wide::arch::aarch64::Neon,
1927        x: USlice<'_, 2>,
1928        y: USlice<'_, 2>,
1929    ) -> MathematicalResult<u32> {
1930        let len = check_lengths!(x, y)?;
1931
1932        diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16);
1933        diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
1934
1935        let px_u8: *const u8 = x.as_ptr().cast();
1936        let py_u8: *const u8 = y.as_ptr().cast();
1937
1938        let mut i = 0;
1939        let mut s: u32 = 0;
1940
1941        // number of bytes over the underlying slice
1942        let bytes = len / 4;
1943        if i < bytes {
1944            let mut s0 = u32s::default(arch);
1945            let mut s1 = u32s::default(arch);
1946            let mut s2 = u32s::default(arch);
1947            let mut s3 = u32s::default(arch);
1948            let mask = u8s::splat(arch, 0x03);
1949            while i + 16 <= bytes {
1950                // SAFETY: `i + 16 <= bytes` guarantees 16 bytes are readable.
1951                let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) };
1952                // SAFETY: `y` has the same validated length as `x`.
1953                let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) };
1954
1955                let first_x: u8s = x_vec & mask;
1956                let first_y: u8s = y_vec & mask;
1957                s0 = s0.dot_simd(first_x, first_y);
1958
1959                let second_x: u8s = (x_vec >> 2) & mask;
1960                let second_y: u8s = (y_vec >> 2) & mask;
1961                s1 = s1.dot_simd(second_x, second_y);
1962
1963                let third_x: u8s = (x_vec >> 4) & mask;
1964                let third_y: u8s = (y_vec >> 4) & mask;
1965                s2 = s2.dot_simd(third_x, third_y);
1966
1967                let fourth_x: u8s = (x_vec >> 6) & mask;
1968                let fourth_y: u8s = (y_vec >> 6) & mask;
1969                s3 = s3.dot_simd(fourth_x, fourth_y);
1970                i += 16;
1971            }
1972
1973            let remaining_bytes = bytes - i;
1974            if remaining_bytes > 0 {
1975                // SAFETY: Predicated load reads exactly `remaining_bytes`.
1976                let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_bytes) };
1977                // SAFETY: `y` has the same validated length as `x`.
1978                let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_bytes) };
1979
1980                let first_x: u8s = x_vec & mask;
1981                let first_y: u8s = y_vec & mask;
1982                s0 = s0.dot_simd(first_x, first_y);
1983
1984                let second_x: u8s = (x_vec >> 2) & mask;
1985                let second_y: u8s = (y_vec >> 2) & mask;
1986                s1 = s1.dot_simd(second_x, second_y);
1987
1988                let third_x: u8s = (x_vec >> 4) & mask;
1989                let third_y: u8s = (y_vec >> 4) & mask;
1990                s2 = s2.dot_simd(third_x, third_y);
1991
1992                let fourth_x: u8s = (x_vec >> 6) & mask;
1993                let fourth_y: u8s = (y_vec >> 6) & mask;
1994                s3 = s3.dot_simd(fourth_x, fourth_y);
1995                i += remaining_bytes;
1996            }
1997
1998            s = ((s0 + s1) + (s2 + s3)).sum_tree();
1999        }
2000
2001        // Convert bytes to quantized vector indexes.
2002        i *= 4;
2003
2004        // Deal with the remainder the slow way (at most 3 elements).
2005        debug_assert!(len - i <= 3);
2006        if i != len {
2007            #[inline(never)]
2008            fn fallback(x: USlice<'_, 2>, y: USlice<'_, 2>, from: usize) -> u32 {
2009                let mut s: i32 = 0;
2010                for i in from..x.len() {
2011                    // SAFETY: `i` is guaranteed to be less than `x.len()`.
2012                    let ix = unsafe { x.get_unchecked(i) } as i32;
2013                    // SAFETY: `i` is guaranteed to be less than `y.len()`.
2014                    let iy = unsafe { y.get_unchecked(i) } as i32;
2015                    s += ix * iy;
2016                }
2017                s as u32
2018            }
2019            s += fallback(x, y, i);
2020        }
2021
2022        Ok(MV::new(s))
2023    }
2024}
2025
2026/// An implementation for inner products that uses scalar indexing for the implementation.
2027macro_rules! impl_fallback_ip {
2028    (($N:literal, $M:literal)) => {
2029        /// Compute the inner product between `x` and `y`.
2030        ///
2031        /// Returns an error if the arguments have different lengths.
2032        ///
2033        /// # Performance
2034        ///
2035        /// This function uses a generic implementation and therefore is not very fast.
2036        impl Target2<diskann_wide::arch::Scalar, MathematicalResult<u32>, USlice<'_, $N>, USlice<'_, $M>> for InnerProduct {
2037            #[inline(never)]
2038            fn run(
2039                self,
2040                _: diskann_wide::arch::Scalar,
2041                x: USlice<'_, $N>,
2042                y: USlice<'_, $M>
2043            ) -> MathematicalResult<u32> {
2044                let len = check_lengths!(x, y)?;
2045
2046                let mut accum: u32 = 0;
2047                for i in 0..len {
2048                    // SAFETY: `i` is guaranteed to be less than `x.len()`.
2049                    let ix = unsafe { x.get_unchecked(i) } as u32;
2050                    // SAFETY: `i` is guaranteed to be less than `y.len()`.
2051                    let iy = unsafe { y.get_unchecked(i) } as u32;
2052                    accum += ix * iy;
2053                }
2054                Ok(MV::new(accum))
2055            }
2056        }
2057    };
2058    ($N:literal) => {
2059        impl_fallback_ip!(($N, $N));
2060    };
2061    ($($args:tt),+ $(,)?) => {
2062        $(impl_fallback_ip!($args);)+
2063    };
2064}
2065
2066impl_fallback_ip!(7, 6, 5, 4, 3, 2, (8, 4), (8, 2), (8, 1));
2067
2068#[cfg(target_arch = "x86_64")]
2069retarget!(diskann_wide::arch::x86_64::V3, InnerProduct, 7, 6, 5, 3);
2070
2071#[cfg(target_arch = "x86_64")]
2072retarget!(
2073    diskann_wide::arch::x86_64::V4,
2074    InnerProduct,
2075    7,
2076    6,
2077    5,
2078    3,
2079    (8, 4),
2080    (8, 2),
2081    (8, 1)
2082);
2083
2084dispatch_pure!(
2085    InnerProduct,
2086    1,
2087    2,
2088    3,
2089    4,
2090    5,
2091    6,
2092    7,
2093    (8, 8),
2094    (8, 4),
2095    (8, 2),
2096    (8, 1)
2097);
2098
2099//////////////////////////////////////////
2100// Heterogeneous USlice<8> × USlice<M> //
2101/////////////////////////////////////////
2102#[cfg(target_arch = "x86_64")]
2103impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 4>>
2104    for InnerProduct
2105{
2106    /// Computes the inner product of 8-bit unsigned × 4-bit unsigned vectors using V3 intrinsics.
2107    ///
2108    /// # Strategy
2109    ///
2110    /// Unpack each 16-byte chunk of `y` into 32 nibble values via `unpack_half`,
2111    /// then compute saturated pairwise products through wide's V3 intrinsic wrapper.
2112    /// Each sum is at most 7_650, so saturation does not occur for these inputs.
2113    ///
2114    /// The main loop is 4× unrolled: four i16 products are summed in i16 before a single
2115    /// `_mm256_madd_epi16(…, 1)` widens to i32. This is safe because
2116    /// 4 × (255 × 15 × 2) = 30_600 < i16::MAX.
2117    #[inline(always)]
2118    fn run(
2119        self,
2120        arch: diskann_wide::arch::x86_64::V3,
2121        x: USlice<'_, 8>,
2122        y: USlice<'_, 4>,
2123    ) -> MathematicalResult<u32> {
2124        let len = check_lengths!(x, y)?;
2125
2126        diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
2127        diskann_wide::alias!(u8s_16 = <diskann_wide::arch::x86_64::V3>::u8x16);
2128        diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
2129        diskann_wide::alias!(i8s_32 = <diskann_wide::arch::x86_64::V3>::i8x32);
2130        diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
2131
2132        let px: *const u8 = x.as_ptr();
2133        let py: *const u8 = y.as_ptr();
2134
2135        let mut i: usize = 0;
2136        let mut s: u32 = 0;
2137
2138        #[inline(always)]
2139        fn unpack_half(input: u8s_16) -> u8s_32 {
2140            let combined = diskann_wide::LoHi::new(input, input >> 4).zip::<u8s_32>();
2141            combined & u8s_32::splat(input.arch(), (1u8 << 4) - 1)
2142        }
2143
2144        // Each block processes 32 elements: 32 bytes from x, 16 packed bytes from y.
2145        let blocks = len / 32;
2146        if blocks > 0 {
2147            let mut acc = i32s::default(arch);
2148
2149            let products = |x: u8s_32, y: u8s_32| -> i16s {
2150                let signed_y = i8s_32::from_underlying(arch, y.to_underlying());
2151                arch.mm256_maddubs_epi16(x, signed_y)
2152            };
2153
2154            let ones = i16s::splat(arch, 1);
2155
2156            // Main loop: 4× unrolled, processing 128 elements per iteration.
2157            //
2158            // `products` returns i16 lanes, each at most 255 × 15 × 2 = 7_650.
2159            // We sum 4 such values in i16 before widening to i32:
2160            //   4 × 7_650 = 30_600 < i16::MAX (32_767)
2161            while i + 4 <= blocks {
2162                // Block 0
2163                // SAFETY: `i + 4 <= blocks` guarantees at least 4 full blocks remain.
2164                // Each block needs 32 bytes from `px` and 16 bytes from `py`.
2165                let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * i)) };
2166                // SAFETY: `i + 4 <= blocks` guarantees 16 bytes readable at `py.add(16 * i)`.
2167                let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * i)) };
2168                let m0 = products(vx, unpack_half(vy));
2169
2170                // Block 1
2171                // SAFETY: `i + 1 < i + 4 <= blocks`.
2172                let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 1))) };
2173                // SAFETY: same bound; 16 bytes readable at `py.add(16 * (i + 1))`.
2174                let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 1))) };
2175                let m1 = products(vx, unpack_half(vy));
2176
2177                // Block 2
2178                // SAFETY: `i + 2 < i + 4 <= blocks`.
2179                let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 2))) };
2180                // SAFETY: same bound; 16 bytes readable at `py.add(16 * (i + 2))`.
2181                let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 2))) };
2182                let m2 = products(vx, unpack_half(vy));
2183
2184                // Block 3
2185                // SAFETY: `i + 3 < i + 4 <= blocks`.
2186                let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * (i + 3))) };
2187                // SAFETY: same bound; 16 bytes readable at `py.add(16 * (i + 3))`.
2188                let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * (i + 3))) };
2189                let m3 = products(vx, unpack_half(vy));
2190
2191                acc = acc.dot_simd(m0 + m1 + m2 + m3, ones);
2192                i += 4;
2193            }
2194
2195            // Drain remaining full 32-element blocks (0..3 iterations).
2196            while i < blocks {
2197                // SAFETY: `i < blocks` guarantees 32 bytes from `px` and 16 bytes
2198                // from `py` are readable at this offset.
2199                let vx = unsafe { u8s_32::load_simd(arch, px.add(32 * i)) };
2200                // SAFETY: `i < blocks` guarantees 16 bytes readable at `py.add(16 * i)`.
2201                let vy = unsafe { u8s_16::load_simd(arch, py.add(16 * i)) };
2202                acc = acc.dot_simd(products(vx, unpack_half(vy)), ones);
2203                i += 1;
2204            }
2205
2206            s = acc.sum_tree() as u32;
2207        }
2208
2209        // Convert block count to element index.
2210        i *= 32;
2211
2212        // Scalar fallback for the remaining < 32 elements.
2213        if i != len {
2214            #[inline(never)]
2215            fn fallback(x: USlice<'_, 8>, y: USlice<'_, 4>, from: usize) -> u32 {
2216                let mut s: u32 = 0;
2217                for i in from..x.len() {
2218                    // SAFETY: `i` is bounded by `x.len()`.
2219                    let ix = unsafe { x.get_unchecked(i) } as u32;
2220                    // SAFETY: `i` is bounded by `x.len()` which equals `y.len()`.
2221                    let iy = unsafe { y.get_unchecked(i) } as u32;
2222                    s += ix * iy;
2223                }
2224                s
2225            }
2226            s += fallback(x, y, i);
2227        }
2228
2229        Ok(MV::new(s))
2230    }
2231}
2232
2233#[cfg(target_arch = "x86_64")]
2234impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 2>>
2235    for InnerProduct
2236{
2237    /// Computes the inner product of 8-bit unsigned × 2-bit unsigned vectors using AVX2.
2238    ///
2239    /// # Strategy
2240    ///
2241    /// Unpack each 16-byte chunk of `y` into 64 crumb values via a two-level cascade:
2242    /// first `unpack_sub` splits bytes into nibbles, then a second pass splits
2243    /// nibbles into crumbs (masked with `0x03`). Each unpacked half is paired with 32
2244    /// bytes of `x` using saturated pairwise products through wide's V3 intrinsic wrapper.
2245    /// Each sum is at most 1_530, so saturation does not occur for these inputs.
2246    ///
2247    /// The main loop is 4× unrolled: eight i16 products (4 blocks × 2 halves) are summed
2248    /// in i16 before a single `_mm256_madd_epi16(…, 1)` widens to i32. This is safe
2249    /// because 8 × (255 × 3 × 2) = 12_240 < i16::MAX.
2250    #[inline(always)]
2251    fn run(
2252        self,
2253        arch: diskann_wide::arch::x86_64::V3,
2254        x: USlice<'_, 8>,
2255        y: USlice<'_, 2>,
2256    ) -> MathematicalResult<u32> {
2257        use diskann_wide::SplitJoin;
2258
2259        let len = check_lengths!(x, y)?;
2260
2261        diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
2262        diskann_wide::alias!(u8s_16 = <diskann_wide::arch::x86_64::V3>::u8x16);
2263        diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
2264        diskann_wide::alias!(i8s_32 = <diskann_wide::arch::x86_64::V3>::i8x32);
2265        diskann_wide::alias!(i16s = <diskann_wide::arch::x86_64::V3>::i16x16);
2266
2267        let px: *const u8 = x.as_ptr();
2268        let py: *const u8 = y.as_ptr();
2269
2270        let mut i: usize = 0;
2271        let mut s: u32 = 0;
2272
2273        // Each block processes 64 elements: 64 bytes from x, 16 packed bytes from y.
2274        let blocks = len / 64;
2275        if blocks > 0 {
2276            let mut acc = i32s::default(arch);
2277
2278            let products = |x: u8s_32, y: u8s_32| -> i16s {
2279                let signed_y = i8s_32::from_underlying(arch, y.to_underlying());
2280                arch.mm256_maddubs_epi16(x, signed_y)
2281            };
2282
2283            #[inline(always)]
2284            fn unpack_sub<const N: u8>(input: u8s_16) -> u8s_32 {
2285                let combined = diskann_wide::LoHi::new(input, input >> N).zip::<u8s_32>();
2286                combined & u8s_32::splat(input.arch(), (1u8 << N) - 1)
2287            }
2288
2289            let unpack_crumbs = |x: u8s_16| -> (u8s_32, u8s_32) {
2290                // Level 1: nibble split → 32 × 4-bit values in a u8x32.
2291                let nibbles = unpack_sub::<4>(x);
2292
2293                // Split the u8x32 into two u8x16 halves (lo = elements 0..15,
2294                // hi = elements 16..31), then apply Level 2 crumb split to each.
2295                let diskann_wide::LoHi { lo, hi } = nibbles.split();
2296                let lower = unpack_sub::<2>(lo);
2297                let upper = unpack_sub::<2>(hi);
2298
2299                (lower, upper)
2300            };
2301
2302            let ones = i16s::splat(arch, 1);
2303
2304            // Main loop: 4× unrolled, processing 256 elements per iteration.
2305            //
2306            // `products` returns i16 lanes, each at most 255 × 3 × 2 = 1_530.
2307            // Each iteration produces 4 blocks × 2 products = 8 values.
2308            // We sum all 8 in i16 before widening to i32:
2309            //   8 × 1_530 = 12_240 < i16::MAX (32_767)
2310            while i + 4 <= blocks {
2311                // Block 0
2312                // SAFETY: `i + 4 <= blocks` guarantees at least 4 full blocks remain.
2313                // Each block needs 64 bytes from `px` and 16 bytes from `py`.
2314                let (vx0, vx1, (vy0, vy1)) = unsafe {
2315                    (
2316                        u8s_32::load_simd(arch, px.add(64 * i)),
2317                        u8s_32::load_simd(arch, px.add(64 * i + 32)),
2318                        unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * i))),
2319                    )
2320                };
2321                let m0a = products(vx0, vy0);
2322                let m0b = products(vx1, vy1);
2323
2324                // Block 1
2325                // SAFETY: `i + 1 < i + 4 <= blocks`.
2326                let (vx0, vx1, (vy0, vy1)) = unsafe {
2327                    (
2328                        u8s_32::load_simd(arch, px.add(64 * (i + 1))),
2329                        u8s_32::load_simd(arch, px.add(64 * (i + 1) + 32)),
2330                        unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 1)))),
2331                    )
2332                };
2333                let m1a = products(vx0, vy0);
2334                let m1b = products(vx1, vy1);
2335
2336                // Block 2
2337                // SAFETY: `i + 2 < i + 4 <= blocks`.
2338                let (vx0, vx1, (vy0, vy1)) = unsafe {
2339                    (
2340                        u8s_32::load_simd(arch, px.add(64 * (i + 2))),
2341                        u8s_32::load_simd(arch, px.add(64 * (i + 2) + 32)),
2342                        unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 2)))),
2343                    )
2344                };
2345                let m2a = products(vx0, vy0);
2346                let m2b = products(vx1, vy1);
2347
2348                // Block 3
2349                // SAFETY: `i + 3 < i + 4 <= blocks`.
2350                let (vx0, vx1, (vy0, vy1)) = unsafe {
2351                    (
2352                        u8s_32::load_simd(arch, px.add(64 * (i + 3))),
2353                        u8s_32::load_simd(arch, px.add(64 * (i + 3) + 32)),
2354                        unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * (i + 3)))),
2355                    )
2356                };
2357                let m3a = products(vx0, vy0);
2358                let m3b = products(vx1, vy1);
2359
2360                acc = acc.dot_simd((m0a + m0b + m1a + m1b) + (m2a + m2b + m3a + m3b), ones);
2361                i += 4;
2362            }
2363
2364            // Drain remaining full 64-element blocks (0..3 iterations).
2365            while i < blocks {
2366                // SAFETY: `i < blocks` guarantees 64 bytes from `px` and 16 bytes
2367                // from `py` are readable at this offset.
2368                let (vx0, vx1, (vy0, vy1)) = unsafe {
2369                    (
2370                        u8s_32::load_simd(arch, px.add(64 * i)),
2371                        u8s_32::load_simd(arch, px.add(64 * i + 32)),
2372                        unpack_crumbs(u8s_16::load_simd(arch, py.add(16 * i))),
2373                    )
2374                };
2375                acc = acc.dot_simd(products(vx0, vy0) + products(vx1, vy1), ones);
2376                i += 1;
2377            }
2378
2379            s = acc.sum_tree() as u32;
2380        }
2381
2382        // Convert block count to element index.
2383        i *= 64;
2384
2385        // Scalar fallback for the remaining < 64 elements.
2386        if i != len {
2387            #[inline(never)]
2388            fn fallback(x: USlice<'_, 8>, y: USlice<'_, 2>, from: usize) -> u32 {
2389                let mut s: u32 = 0;
2390                for i in from..x.len() {
2391                    // SAFETY: `i` is in `from..x.len()`, which equals `y.len()`.
2392                    let (ix, iy) =
2393                        unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2394                    s += ix * iy;
2395                }
2396                s
2397            }
2398            s += fallback(x, y, i);
2399        }
2400
2401        Ok(MV::new(s))
2402    }
2403}
2404
2405#[cfg(target_arch = "x86_64")]
2406impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, 8>, USlice<'_, 1>>
2407    for InnerProduct
2408{
2409    /// Computes the inner product of 8-bit unsigned × 1-bit unsigned vectors using V3 intrinsics.
2410    ///
2411    /// For each 32-element block we load 32 bytes from `x` and 4 bytes (32 bits) from `y`.
2412    /// ANDing the data with the mask created from 4 bytes from `y` zeroes unselected lanes.
2413    /// Finally, `_mm256_sad_epu8` horizontally sums the masked bytes in groups of 8.
2414    ///
2415    /// The main loop is 4× unrolled, processing 128 elements per iteration.
2416    ///
2417    /// ## Overflow
2418    ///
2419    /// Each `sad` output lane holds at most `8 × 255 = 2_040`. Accumulated across `d/32`
2420    /// blocks, the per-lane max is `(d/32) × 2_040`. At dim = 3072: `96 × 2_040 = 195_840`,
2421    /// well within i32 range.
2422    #[inline(always)]
2423    fn run(
2424        self,
2425        arch: diskann_wide::arch::x86_64::V3,
2426        x: USlice<'_, 8>,
2427        y: USlice<'_, 1>,
2428    ) -> MathematicalResult<u32> {
2429        use diskann_wide::{FromInt, SIMDMask};
2430        use std::arch::x86_64::_mm256_sad_epu8;
2431
2432        let len = check_lengths!(x, y)?;
2433
2434        diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
2435        diskann_wide::alias!(u8s_32 = <diskann_wide::arch::x86_64::V3>::u8x32);
2436
2437        type Mask32 = diskann_wide::BitMask<32, diskann_wide::arch::x86_64::V3>;
2438        type Mask8x32 = diskann_wide::arch::x86_64::v3::masks::mask8x32;
2439
2440        let px: *const u8 = x.as_ptr();
2441        let py: *const u8 = y.as_ptr();
2442
2443        let mut i: usize = 0;
2444        let mut s: u32 = 0;
2445
2446        // Each block processes 32 elements: 32 bytes from x, 4 bytes (32 bits) from y.
2447        let blocks = len / 32;
2448        if blocks > 0 {
2449            let mut acc = i32s::default(arch);
2450            let zero = u8s_32::default(arch);
2451
2452            // Expand 32 bits of `y` to a byte mask, AND with `x`, and horizontally sum
2453            // the masked bytes via `_mm256_sad_epu8`.
2454            //
2455            // Returns an `i32x8` where lanes 0, 2, 4, 6 hold group sums (each ≤ 2040)
2456            // and lanes 1, 3, 5, 7 are zero.
2457            let masked_sad = |vx: u8s_32, bits: u32| -> i32s {
2458                // Expand 32-bit mask → 32 bytes of 0xFF/0x00.
2459                let byte_mask: Mask8x32 = Mask32::from_int(arch, bits).into();
2460
2461                // AND data with mask: zeroes lanes where the bit is 0.
2462                let masked = vx & u8s_32::from_underlying(arch, byte_mask.to_underlying());
2463
2464                // Horizontal byte sum in groups of 8 → 4 × u64 partial sums.
2465                // SAFETY: `arch` is V3 (AVX2), `_mm256_sad_epu8` is available.
2466                i32s::from_underlying(arch, unsafe {
2467                    _mm256_sad_epu8(masked.to_underlying(), zero.to_underlying())
2468                })
2469            };
2470
2471            // Main loop: 4× unrolled, processing 128 elements per iteration.
2472            while i + 4 <= blocks {
2473                // SAFETY: `i + 4 <= blocks` guarantees at least 4 full blocks remain.
2474                // Each block needs 32 bytes from `px` and 4 bytes from `py`.
2475                let s0 = unsafe {
2476                    let vx = u8s_32::load_simd(arch, px.add(32 * i));
2477                    let bits = (py.add(4 * i) as *const u32).read_unaligned();
2478                    masked_sad(vx, bits)
2479                };
2480
2481                // SAFETY: `i + 1 < i + 4 <= blocks`.
2482                let s1 = unsafe {
2483                    let vx = u8s_32::load_simd(arch, px.add(32 * (i + 1)));
2484                    let bits = (py.add(4 * (i + 1)) as *const u32).read_unaligned();
2485                    masked_sad(vx, bits)
2486                };
2487
2488                // SAFETY: `i + 2 < i + 4 <= blocks`.
2489                let s2 = unsafe {
2490                    let vx = u8s_32::load_simd(arch, px.add(32 * (i + 2)));
2491                    let bits = (py.add(4 * (i + 2)) as *const u32).read_unaligned();
2492                    masked_sad(vx, bits)
2493                };
2494
2495                // SAFETY: `i + 3 < i + 4 <= blocks`.
2496                let s3 = unsafe {
2497                    let vx = u8s_32::load_simd(arch, px.add(32 * (i + 3)));
2498                    let bits = (py.add(4 * (i + 3)) as *const u32).read_unaligned();
2499                    masked_sad(vx, bits)
2500                };
2501
2502                acc = acc + s0 + s1 + s2 + s3;
2503                i += 4;
2504            }
2505
2506            // Drain remaining full 32-element blocks (0..3 iterations).
2507            while i < blocks {
2508                // SAFETY: `i < blocks` guarantees 32 bytes from `px` and 4 bytes from `py`
2509                // are readable at this offset.
2510                let si = unsafe {
2511                    let vx = u8s_32::load_simd(arch, px.add(32 * i));
2512                    let bits = (py.add(4 * i) as *const u32).read_unaligned();
2513                    masked_sad(vx, bits)
2514                };
2515                acc = acc + si;
2516                i += 1;
2517            }
2518
2519            s = acc.sum_tree() as u32;
2520        }
2521
2522        // Convert block count to element index.
2523        i *= 32;
2524
2525        // Scalar fallback for the remaining < 32 elements.
2526        if i != len {
2527            #[inline(never)]
2528            fn fallback(x: USlice<'_, 8>, y: USlice<'_, 1>, from: usize) -> u32 {
2529                let mut s: u32 = 0;
2530                for i in from..x.len() {
2531                    // SAFETY: `i` is in `from..x.len()`, which equals `y.len()`.
2532                    let (ix, iy) =
2533                        unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2534                    s += ix * iy;
2535                }
2536                s
2537            }
2538            s += fallback(x, y, i);
2539        }
2540
2541        Ok(MV::new(s))
2542    }
2543}
2544
2545#[cfg(target_arch = "aarch64")]
2546impl
2547    Target2<
2548        diskann_wide::arch::aarch64::Neon,
2549        MathematicalResult<u32>,
2550        USlice<'_, 8>,
2551        USlice<'_, 4>,
2552    > for InnerProduct
2553{
2554    #[inline(always)]
2555    fn run(
2556        self,
2557        arch: diskann_wide::arch::aarch64::Neon,
2558        x: USlice<'_, 8>,
2559        y: USlice<'_, 4>,
2560    ) -> MathematicalResult<u32> {
2561        // returns number of quantized vectors
2562        let len = check_lengths!(x, y)?;
2563
2564        diskann_wide::alias!(u8s_16 = <diskann_wide::arch::aarch64::Neon>::u8x16);
2565        #[expect(non_camel_case_types)]
2566        type u8s_8 = diskann_wide::arch::aarch64::u8x8;
2567        diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
2568
2569        let px_u8: *const u8 = x.as_ptr().cast();
2570        let py_u8: *const u8 = y.as_ptr().cast();
2571
2572        let mut i = 0;
2573        let mut s: u32 = 0;
2574
2575        // number of y bytes over the underlying slice
2576        let y_bytes = len / 2;
2577        if i < y_bytes {
2578            let mut s0 = u32s::default(arch);
2579
2580            #[inline(always)]
2581            fn split_and_zip(input: u8s_8, arch: diskann_wide::arch::aarch64::Neon) -> u8s_16 {
2582                use diskann_wide::{LoHi, ZipUnzip};
2583                let lo = input;
2584                let hi = input >> 4;
2585                let halves = LoHi::new(lo, hi);
2586                let zipped_halves = u8s_16::zip(halves);
2587                zipped_halves & u8s_16::splat(arch, 0x0f)
2588            }
2589
2590            while i + 8 <= y_bytes {
2591                // SAFETY: `i + 8 <= y_bytes` guarantees 8 readable bytes at `py_u8.add(i)`.
2592                let y_vec = split_and_zip(unsafe { u8s_8::load_simd(arch, py_u8.add(i)) }, arch);
2593                // SAFETY: `2 * (i + 8) <= 2 * y_bytes == x.bytes()` guarantees a 16-byte load
2594                // from `px_u8.add(2 * i)`.
2595                let x_vec = unsafe { u8s_16::load_simd(arch, px_u8.add(i * 2)) };
2596
2597                // compute dot product for lower 4 bits, result is stored as 32x4
2598                s0 = s0.dot_simd(x_vec, y_vec);
2599
2600                // repeat for next 8 byte block of y and 16 byte block of x
2601                i += 8;
2602            }
2603
2604            let remaining_bytes = len / 2 - i;
2605
2606            if remaining_bytes > 0 {
2607                // SAFETY: less than 8 bytes remain of y, we infer that less than 16 remain of x.
2608                let x_vec =
2609                    unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 2), remaining_bytes * 2) };
2610
2611                // SAFETY: `remaining_bytes` is exactly the readable tail length from `py_u8.add(i)`.
2612                let y_vec = split_and_zip(
2613                    unsafe { u8s_8::load_simd_first(arch, py_u8.add(i), remaining_bytes) },
2614                    arch,
2615                );
2616
2617                s0 = s0.dot_simd(x_vec, y_vec);
2618                i += remaining_bytes;
2619            }
2620            s = s0.sum_tree();
2621        }
2622
2623        // Convert bytes to nibble indexes.
2624        i *= 2;
2625
2626        // Deal with the remainder the slow way (at most 1 element).
2627        debug_assert!(len - i <= 1);
2628        if i != len {
2629            // SAFETY: `i` is guaranteed to be less than `x.len()`.
2630            let ix = unsafe { x.get_unchecked(i) } as u32;
2631            // SAFETY: `i` is guaranteed to be less than `y.len()`.
2632            let iy = unsafe { y.get_unchecked(i) } as u32;
2633            s += ix * iy;
2634        }
2635
2636        Ok(MV::new(s))
2637    }
2638}
2639
2640#[cfg(target_arch = "aarch64")]
2641impl
2642    Target2<
2643        diskann_wide::arch::aarch64::Neon,
2644        MathematicalResult<u32>,
2645        USlice<'_, 8>,
2646        USlice<'_, 2>,
2647    > for InnerProduct
2648{
2649    #[inline(always)]
2650    fn run(
2651        self,
2652        arch: diskann_wide::arch::aarch64::Neon,
2653        x: USlice<'_, 8>,
2654        y: USlice<'_, 2>,
2655    ) -> MathematicalResult<u32> {
2656        let len = check_lengths!(x, y)?;
2657
2658        #[expect(non_camel_case_types)]
2659        type u8s_8 = diskann_wide::arch::aarch64::u8x8;
2660        diskann_wide::alias!(u8s_16 = <diskann_wide::arch::aarch64::Neon>::u8x16);
2661        diskann_wide::alias!(u8s_32 = <diskann_wide::arch::aarch64::Neon>::u8x32);
2662        diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
2663
2664        let px_u8: *const u8 = x.as_ptr().cast();
2665        let py_u8: *const u8 = y.as_ptr().cast();
2666
2667        let mut i = 0;
2668        let mut s: u32 = 0;
2669
2670        #[inline(always)]
2671        fn split_and_zip_four_bit(input: u8s_8, arch: diskann_wide::arch::aarch64::Neon) -> u8s_16 {
2672            use diskann_wide::{LoHi, ZipUnzip};
2673            let lo = input;
2674            let hi = input >> 4;
2675            let halves = LoHi::new(lo, hi);
2676            let zipped_halves = u8s_16::zip(halves);
2677            zipped_halves & u8s_16::splat(arch, 0x0f)
2678        }
2679
2680        #[inline(always)]
2681        fn split_and_zip_two_bits(
2682            input: u8s_8,
2683            arch: diskann_wide::arch::aarch64::Neon,
2684        ) -> (u8s_16, u8s_16) {
2685            use diskann_wide::{LoHi, SplitJoin, ZipUnzip};
2686            let four_bit_split = split_and_zip_four_bit(input, arch);
2687            let lo = four_bit_split;
2688            let hi = four_bit_split >> 2;
2689            let zipped = u8s_32::zip(LoHi::new(lo, hi));
2690            let LoHi { lo, hi } = zipped.split();
2691            let mask = u8s_16::splat(arch, 0x03);
2692            (lo & mask, hi & mask)
2693        }
2694
2695        let y_bytes = len / 4; // number of y bytes over the underlying slice
2696        if i < y_bytes {
2697            let mut s0 = u32s::default(arch);
2698            let mut s1 = u32s::default(arch);
2699
2700            while i + 8 <= y_bytes {
2701                // SAFETY: `i + 8 <= y_bytes` guarantees that 8 bytes from `py_u8` are readable at offset `i`.
2702                let (y_vec1, y_vec2) =
2703                    split_and_zip_two_bits(unsafe { u8s_8::load_simd(arch, py_u8.add(i)) }, arch);
2704
2705                // SAFETY: `4 * (i + 8) <= 4 * y_bytes == x.bytes()` guarantees the 16-byte
2706                // loads at `4 * i` and `4 * i + 16` are in bounds.
2707                let x_vec1 = unsafe { u8s_16::load_simd(arch, px_u8.add(4 * i)) };
2708                // SAFETY: same bound as above; this is the second 16-byte chunk in the same valid 32-byte block.
2709                let x_vec2 = unsafe { u8s_16::load_simd(arch, px_u8.add(4 * i + 16)) };
2710
2711                // compute dot product for lower 4 bits, result is stored as 32x4
2712                s0 = s0.dot_simd(x_vec1, y_vec1);
2713
2714                // compute dot product for upper 4 bits, result is stored as 32x4
2715                s1 = s1.dot_simd(x_vec2, y_vec2);
2716
2717                // repeat for next block
2718                i += 8;
2719            }
2720
2721            let remaining_y_bytes = len / 4 - i;
2722            let remaining_x_bytes = remaining_y_bytes * 4;
2723
2724            if remaining_y_bytes > 0 {
2725                // SAFETY: `remaining_y_bytes` is the exact readable tail length at `py_u8.add(i)`.
2726                let (y_vec1, y_vec2) = split_and_zip_two_bits(
2727                    unsafe { u8s_8::load_simd_first(arch, py_u8.add(i), remaining_y_bytes) },
2728                    arch,
2729                );
2730
2731                // SAFETY: `remaining_x_bytes == 4 * remaining_y_bytes` is exactly the
2732                // in-bounds tail length from `px_u8.add(4 * i)`.
2733                let x_first = remaining_x_bytes.min(16);
2734                let x_second = remaining_x_bytes.saturating_sub(16);
2735
2736                // SAFETY: `x_first <= remaining_x_bytes` and `px_u8.add(4 * i)` points at the tail start.
2737                let x_vec1 = unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 4), x_first) };
2738                s0 = s0.dot_simd(x_vec1, y_vec1);
2739
2740                if x_second > 0 {
2741                    // SAFETY: `x_second > 0` means bytes remain after the first 16-byte chunk.
2742                    let x_vec2 =
2743                        unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 4 + 16), x_second) };
2744                    s1 = s1.dot_simd(x_vec2, y_vec2);
2745                }
2746                i += remaining_y_bytes;
2747            }
2748            s = (s0 + s1).sum_tree();
2749        }
2750
2751        // converting from y bytes to logical elements.
2752        i *= 4;
2753
2754        // Deal with the remainder the slow way (at most 3 element).
2755        debug_assert!(len - i <= 3);
2756        if i != len {
2757            #[inline(never)]
2758            fn fallback(x: USlice<'_, 8>, y: USlice<'_, 2>, from: usize) -> u32 {
2759                let mut s: u32 = 0;
2760                for i in from..x.len() {
2761                    // SAFETY: `i` is in `from..x.len()`, which equals `y.len()`.
2762                    let (ix, iy) =
2763                        unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2764                    s += ix * iy;
2765                }
2766                s
2767            }
2768            s += fallback(x, y, i)
2769        }
2770
2771        Ok(MV::new(s))
2772    }
2773}
2774
2775#[cfg(target_arch = "aarch64")]
2776impl
2777    Target2<
2778        diskann_wide::arch::aarch64::Neon,
2779        MathematicalResult<u32>,
2780        USlice<'_, 8>,
2781        USlice<'_, 1>,
2782    > for InnerProduct
2783{
2784    #[inline(always)]
2785    fn run(
2786        self,
2787        arch: diskann_wide::arch::aarch64::Neon,
2788        x: USlice<'_, 8>,
2789        y: USlice<'_, 1>,
2790    ) -> MathematicalResult<u32> {
2791        // returns number of quantized vectors
2792        let len = check_lengths!(x, y)?;
2793
2794        #[expect(non_camel_case_types)]
2795        type u8s_8 = diskann_wide::arch::aarch64::u8x8;
2796        diskann_wide::alias!(u8s_16 = <diskann_wide::arch::aarch64::Neon>::u8x16);
2797        diskann_wide::alias!(u8s_32 = <diskann_wide::arch::aarch64::Neon>::u8x32);
2798        diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4);
2799
2800        let px_u8: *const u8 = x.as_ptr().cast();
2801        let py_u8: *const u8 = y.as_ptr().cast();
2802
2803        let mut i = 0;
2804        let mut s: u32 = 0;
2805
2806        #[inline(always)]
2807        fn split_and_zip_four_bits(
2808            input: u8s_8,
2809            arch: diskann_wide::arch::aarch64::Neon,
2810        ) -> u8s_16 {
2811            use diskann_wide::{LoHi, ZipUnzip};
2812            let lo = input;
2813            let hi = input >> 4;
2814            let halves = LoHi::new(lo, hi);
2815            let zipped_halves = u8s_16::zip(halves);
2816            zipped_halves & u8s_16::splat(arch, 0x0f)
2817        }
2818
2819        #[inline(always)]
2820        fn split_and_zip_two_bits(
2821            input: u8s_8,
2822            arch: diskann_wide::arch::aarch64::Neon,
2823        ) -> (u8s_16, u8s_16) {
2824            use diskann_wide::{LoHi, SplitJoin, ZipUnzip};
2825            let four_bit_split = split_and_zip_four_bits(input, arch);
2826            let lo = four_bit_split;
2827            let hi = four_bit_split >> 2;
2828            let zipped = u8s_32::zip(LoHi::new(lo, hi));
2829            let LoHi { lo, hi } = zipped.split();
2830            let mask = u8s_16::splat(arch, 0x03);
2831            (lo & mask, hi & mask)
2832        }
2833
2834        #[inline(always)]
2835        fn split_and_zip_one_bit(
2836            input: u8s_8,
2837            arch: diskann_wide::arch::aarch64::Neon,
2838        ) -> (u8s_16, u8s_16, u8s_16, u8s_16) {
2839            use diskann_wide::{LoHi, SplitJoin, ZipUnzip};
2840            let (lower_two_bits, upper_two_bits) = split_and_zip_two_bits(input, arch);
2841            let vec1_lo = lower_two_bits;
2842            let vec1_hi = lower_two_bits >> 1;
2843            let vec2_lo = upper_two_bits;
2844            let vec2_hi = upper_two_bits >> 1;
2845            let vec1_zipped = u8s_32::zip(LoHi::new(vec1_lo, vec1_hi));
2846            let vec2_zipped = u8s_32::zip(LoHi::new(vec2_lo, vec2_hi));
2847            let LoHi {
2848                lo: vec1_lo,
2849                hi: vec1_hi,
2850            } = vec1_zipped.split();
2851            let LoHi {
2852                lo: vec2_lo,
2853                hi: vec2_hi,
2854            } = vec2_zipped.split();
2855            let mask = u8s_16::splat(arch, 0x01);
2856            (
2857                vec1_lo & mask,
2858                vec1_hi & mask,
2859                vec2_lo & mask,
2860                vec2_hi & mask,
2861            )
2862        }
2863
2864        let y_bytes = len / 8; // number of y bytes over the underlying slice
2865        if i < y_bytes {
2866            let mut s0 = u32s::default(arch);
2867            let mut s1 = u32s::default(arch);
2868            let mut s2 = u32s::default(arch);
2869            let mut s3 = u32s::default(arch);
2870
2871            while i + 8 <= y_bytes {
2872                // SAFETY: `i + 8 <= y_bytes` guarantees that 8 bytes from `py_u8` are readable at offset `i`.
2873                let (y_vec1, y_vec2, y_vec3, y_vec4) =
2874                    split_and_zip_one_bit(unsafe { u8s_8::load_simd(arch, py_u8.add(i)) }, arch);
2875
2876                // SAFETY: `8 * (i + 8) <= 8 * y_bytes == x.bytes()` guarantees four 16-byte
2877                // loads at offsets `8 * i + {0,16,32,48}` are in bounds.
2878                let x_vec1 = unsafe { u8s_16::load_simd(arch, px_u8.add(8 * i)) };
2879                // SAFETY: same bound as above; this is the second 16-byte chunk in the same valid 64-byte block.
2880                let x_vec2 = unsafe { u8s_16::load_simd(arch, px_u8.add(8 * i + 16)) };
2881                // SAFETY: same bound as above; this is the third 16-byte chunk in the same valid 64-byte block.
2882                let x_vec3 = unsafe { u8s_16::load_simd(arch, px_u8.add(8 * i + 32)) };
2883                // SAFETY: same bound as above; this is the fourth 16-byte chunk in the same valid 64-byte block.
2884                let x_vec4 = unsafe { u8s_16::load_simd(arch, px_u8.add(8 * i + 48)) };
2885
2886                // compute dot product for first 16 logical elements
2887                s0 = s0.dot_simd(x_vec1, y_vec1);
2888
2889                // compute dot product for next 16 logical elements
2890                s1 = s1.dot_simd(x_vec2, y_vec2);
2891
2892                // compute dot product for next 16 logical elements
2893                s2 = s2.dot_simd(x_vec3, y_vec3);
2894
2895                // compute dot product for last 16 logical elements
2896                s3 = s3.dot_simd(x_vec4, y_vec4);
2897
2898                // repeat for next block
2899                i += 8;
2900            }
2901
2902            let remaining_y_bytes = len / 8 - i;
2903            let remaining_x_bytes = remaining_y_bytes * 8;
2904
2905            if remaining_y_bytes > 0 {
2906                // SAFETY: `remaining_y_bytes` is the exact readable tail length at `py_u8.add(i)`.
2907                let (y_vec1, y_vec2, y_vec3, y_vec4) = split_and_zip_one_bit(
2908                    unsafe { u8s_8::load_simd_first(arch, py_u8.add(i), remaining_y_bytes) },
2909                    arch,
2910                );
2911
2912                // SAFETY: `remaining_x_bytes == 8 * remaining_y_bytes` is exactly the
2913                // in-bounds tail length from `px_u8.add(8 * i)`.
2914                let x_first = remaining_x_bytes.min(16);
2915                let x_second = remaining_x_bytes.saturating_sub(16).min(16);
2916                let x_third = remaining_x_bytes.saturating_sub(32).min(16);
2917                let x_fourth = remaining_x_bytes.saturating_sub(48).min(16);
2918
2919                // SAFETY: `x_first <= remaining_x_bytes` and `px_u8.add(8 * i)` points at the tail start.
2920                let x_vec1 = unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 8), x_first) };
2921                s0 = s0.dot_simd(x_vec1, y_vec1);
2922
2923                if x_second > 0 {
2924                    // SAFETY: `x_second > 0` means bytes remain after the first 16-byte chunk.
2925                    let x_vec2 =
2926                        unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 8 + 16), x_second) };
2927                    s1 = s1.dot_simd(x_vec2, y_vec2);
2928                }
2929
2930                if x_third > 0 {
2931                    // SAFETY: `x_third > 0` means bytes remain after the first two 16-byte chunks.
2932                    let x_vec3 =
2933                        unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 8 + 32), x_third) };
2934                    s2 = s2.dot_simd(x_vec3, y_vec3);
2935                }
2936
2937                if x_fourth > 0 {
2938                    // SAFETY: `x_fourth > 0` means bytes remain after the first three 16-byte chunks.
2939                    let x_vec4 =
2940                        unsafe { u8s_16::load_simd_first(arch, px_u8.add(i * 8 + 48), x_fourth) };
2941                    s3 = s3.dot_simd(x_vec4, y_vec4);
2942                }
2943
2944                i += remaining_y_bytes;
2945            }
2946            s = ((s0 + s1) + (s2 + s3)).sum_tree();
2947        }
2948
2949        // converting from y bytes to logical elements.
2950        i *= 8;
2951
2952        // Deal with the remainder the slow way (at most 7 element).
2953        debug_assert!(len - i <= 7);
2954        if i != len {
2955            #[inline(never)]
2956            fn fallback(x: USlice<'_, 8>, y: USlice<'_, 1>, from: usize) -> u32 {
2957                let mut s: u32 = 0;
2958                for i in from..x.len() {
2959                    // SAFETY: `i` is in `from..x.len()`, which equals `y.len()`.
2960                    let (ix, iy) =
2961                        unsafe { (x.get_unchecked(i) as u32, y.get_unchecked(i) as u32) };
2962                    s += ix * iy;
2963                }
2964                s
2965            }
2966            s += fallback(x, y, i)
2967        }
2968
2969        Ok(MV::new(s))
2970    }
2971}
2972
2973#[cfg(target_arch = "aarch64")]
2974retarget!(diskann_wide::arch::aarch64::Neon, InnerProduct, 7, 6, 5, 3);
2975
2976//////////////////
2977// BitTranspose //
2978//////////////////
2979
2980/// The strategy is to compute the inner product `<x, y>` by decomposing the problem into
2981/// groups of 64-dimensions.
2982///
2983/// For each group, we load the 64-bits of `y` into a word `bits`. And the four 64-bit words
2984/// of the group in `x` in `b0`, `b1`, `b2`, and `b3`.
2985///
2986/// Note that bit `i` in `b0` is bit-0 of the `i`-th value in ths group. Likewise, bit `i`
2987/// in `b1` is bit-1 of the same word.
2988///
2989/// This means that we can compute the partial inner product for this group as
2990/// ```math
2991/// (bits & b0).count_ones()                // Contribution of bit 0
2992///     + 2 * (bits & b1).count_ones()      // Contribution of bit 1
2993///     + 4 * (bits & b2).count_ones()      // Contribution of bit 2
2994///     + 8 * (bits & b3).count_ones()      // Contribution of bit 3
2995/// ```
2996/// We process as many full groups as we can.
2997///
2998/// To handle the remainder, we need to be careful about acessing `y` because `BitSlice`
2999/// only guarantees the validity of reads at the byte level. That is - we cannot assume that
3000/// a full 64-bit read is valid.
3001///
3002/// The bit-tranposed `x`, on the other hand, guarantees allocations in blocks of
3003/// 4 * 64-bits, so it can be treated as normal.
3004impl<A> Target2<A, MathematicalResult<u32>, USlice<'_, 4, BitTranspose>, USlice<'_, 1, Dense>>
3005    for InnerProduct
3006where
3007    A: Architecture,
3008{
3009    #[inline(always)]
3010    fn run(
3011        self,
3012        _: A,
3013        x: USlice<'_, 4, BitTranspose>,
3014        y: USlice<'_, 1, Dense>,
3015    ) -> MathematicalResult<u32> {
3016        let len = check_lengths!(x, y)?;
3017
3018        // We work in blocks of 64 element.
3019        //
3020        // The `BitTranspose` guarantees read are valid in blocks of 64 elements (32 byte).
3021        // However, the `Dense` representation only pads to bytes.
3022        // Our strategy for dealing with fewer than 64 remaining elements is to reconstruct
3023        // a 64-bit integer from bytes.
3024        let px: *const u64 = x.as_ptr().cast();
3025        let py: *const u64 = y.as_ptr().cast();
3026
3027        let mut i = 0;
3028        let mut s: u32 = 0;
3029
3030        let blocks = len / 64;
3031        while i < blocks {
3032            // SAFETY: `y` is valid for at least `blocks` 64-bit reads and `i < blocks`.
3033            let bits = unsafe { py.add(i).read_unaligned() };
3034
3035            // SAFETY: The layout for `x` is grouped into 32-byte blocks. We've ensured that
3036            // the lengths of the two vectors are the same, so we know that `x` has at least
3037            // `blocks` such regions.
3038            //
3039            // This loads the first 64-bits of block `i` where `i < blocks`.
3040            let b0 = unsafe { px.add(4 * i).read_unaligned() };
3041            s += (bits & b0).count_ones();
3042
3043            // SAFETY: This loads the second 64-bit word of block `i`.
3044            let b1 = unsafe { px.add(4 * i + 1).read_unaligned() };
3045            s += (bits & b1).count_ones() << 1;
3046
3047            // SAFETY: This loads the third 64-bit word of block `i`.
3048            let b2 = unsafe { px.add(4 * i + 2).read_unaligned() };
3049            s += (bits & b2).count_ones() << 2;
3050
3051            // SAFETY: This loads the fourth 64-bit word of block `i`.
3052            let b3 = unsafe { px.add(4 * i + 3).read_unaligned() };
3053            s += (bits & b3).count_ones() << 3;
3054
3055            i += 1;
3056        }
3057
3058        // If the input length is a multiple of 64 - then we're done.
3059        if 64 * i == len {
3060            return Ok(MV::new(s));
3061        }
3062
3063        // Convert blocks to bytes.
3064        let k = i * 8;
3065
3066        // Unpack the last elements from the bit-vector.
3067        //
3068        // SAFETY: The length of the 1-bit BitSlice is `ceil(len / 8)`. This computation
3069        // effectively computes `ceil((64 * floor(len / 64)) / 8)`, which is less.
3070        let py = unsafe { py.cast::<u8>().add(k) };
3071        let bytes_remaining = y.bytes() - k;
3072        let mut bits: u64 = 0;
3073
3074        // Code - generation: Applying `min(8)` gives a constant upper-bound to the
3075        // compiler, allowing better code-generation.
3076        for j in 0..bytes_remaining.min(8) {
3077            // SAFETY: Starting at `py`, there are `bytes_remaining` valid bytes. This
3078            // accesses all of them.
3079            bits += (unsafe { py.add(j).read() } as u64) << (8 * j);
3080        }
3081
3082        // Because the upper-bits of the last loaded byte can contain indeterminate bits,
3083        // we must mask out all out-of-bounds bits.
3084        bits &= (0x01u64 << (len - (64 * i))) - 1;
3085
3086        // Combine with the remainders.
3087        //
3088        // SAFETY: The `BitTranspose` permutation always allocates in granularies of blocks.
3089        // This loads the first 64-bit word of the last block.
3090        let b0 = unsafe { px.add(4 * i).read_unaligned() };
3091        s += (bits & b0).count_ones();
3092
3093        // SAFETY: This loads the second 64-bit word of the last block.
3094        let b1 = unsafe { px.add(4 * i + 1).read_unaligned() };
3095        s += (bits & b1).count_ones() << 1;
3096
3097        // SAFETY: This loads the third 64-bit word of the last block.
3098        let b2 = unsafe { px.add(4 * i + 2).read_unaligned() };
3099        s += (bits & b2).count_ones() << 2;
3100
3101        // SAFETY: This loads the fourth 64-bit word of the last block.
3102        let b3 = unsafe { px.add(4 * i + 3).read_unaligned() };
3103        s += (bits & b3).count_ones() << 3;
3104
3105        Ok(MV::new(s))
3106    }
3107}
3108
3109impl
3110    PureDistanceFunction<USlice<'_, 4, BitTranspose>, USlice<'_, 1, Dense>, MathematicalResult<u32>>
3111    for InnerProduct
3112{
3113    fn evaluate(
3114        x: USlice<'_, 4, BitTranspose>,
3115        y: USlice<'_, 1, Dense>,
3116    ) -> MathematicalResult<u32> {
3117        (diskann_wide::ARCH).run2(Self, x, y)
3118    }
3119}
3120
3121////////////////////
3122// Full Precision //
3123////////////////////
3124
3125/// The main trick here is avoiding explicit conversion from 1 bit integers to 32-bit
3126/// floating-point numbers by using `_mm256_permutevar_ps`, which performs a shuffle on two
3127/// independent 128-bit lanes of `f32` values in a register `A` using the lower 2-bits of
3128/// each 32-bit integer in a register `B`.
3129///
3130/// Importantly, this instruction only takes a single cycle and we can avoid any kind of
3131/// masking. Going the route of conversion would require and `AND` operation to isolate
3132/// bottom bits and a somewhat lengthy 32-bit integer to `f32` conversion instruction.
3133///
3134/// The overall strategy broadcasts a 32-bit integer (consisting of 32, 1-bit values) across
3135/// 8 lanes into a register `A`.
3136///
3137/// Each lane is then shifted by a different amount so:
3138///
3139/// * Lane 0 has value 0 as its least significant bit (LSB)
3140/// * Lane 1 has value 1 as its LSB.
3141/// * Lane 2 has value 2 as its LSB.
3142/// * etc.
3143///
3144/// These LSB's are used to power the shuffle function to convert to `f32` values (either
3145/// 0.0 or 1.0) and we can FMA as needed.
3146///
3147/// To process the next group of 8 values, we shift all lanes in `A` by 8-bits so lane 0
3148/// has value 8 as its LSB, lane 1 has value 9 etc.
3149///
3150/// A total of three shifts are applied to extract all 32 1-bit value as `f32` in order.
3151#[cfg(target_arch = "x86_64")]
3152impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 1>>
3153    for InnerProduct
3154{
3155    #[inline(always)]
3156    fn run(
3157        self,
3158        arch: diskann_wide::arch::x86_64::V3,
3159        x: &[f32],
3160        y: USlice<'_, 1>,
3161    ) -> MathematicalResult<f32> {
3162        let len = check_lengths!(x, y)?;
3163
3164        use std::arch::x86_64::*;
3165
3166        diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
3167        diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
3168
3169        // Replicate 0s and 1s so we effectively get a shuffle that only depends on the
3170        // bottom bit (instead of the lowest 2).
3171        let values = f32s::from_array(arch, [0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
3172
3173        // Shifts required to offset each lane.
3174        let variable_shifts = u32s::from_array(arch, [0, 1, 2, 3, 4, 5, 6, 7]);
3175
3176        let px: *const f32 = x.as_ptr();
3177        let py: *const u32 = y.as_ptr().cast();
3178
3179        let mut i = 0;
3180        let mut s = f32s::default(arch);
3181
3182        let prep = |v: u32| -> u32s { u32s::splat(arch, v) >> variable_shifts };
3183        let to_f32 = |v: u32s| -> f32s {
3184            // SAFETY: The `_mm256_permutevar_ps` instruction requires the AVX extension,
3185            // which the presence of the `x86_64::V3` architecture guarantees is available.
3186            f32s::from_underlying(arch, unsafe {
3187                _mm256_permutevar_ps(values.to_underlying(), v.to_underlying())
3188            })
3189        };
3190
3191        // Data is processed in groups of 32 elements.
3192        let blocks = len / 32;
3193        if i < blocks {
3194            let mut s0 = f32s::default(arch);
3195            let mut s1 = f32s::default(arch);
3196
3197            while i < blocks {
3198                // SAFETY: `i < blocks` implies 32-bits are readable from this offset.
3199                let iy = prep(unsafe { py.add(i).read_unaligned() });
3200
3201                // SAFETY: `i < blocks` implies 32 f32 values are readable beginning at `32*i`.
3202                let ix0 = unsafe { f32s::load_simd(arch, px.add(32 * i)) };
3203                // SAFETY: See above.
3204                let ix1 = unsafe { f32s::load_simd(arch, px.add(32 * i + 8)) };
3205                // SAFETY: See above.
3206                let ix2 = unsafe { f32s::load_simd(arch, px.add(32 * i + 16)) };
3207                // SAFETY: See above.
3208                let ix3 = unsafe { f32s::load_simd(arch, px.add(32 * i + 24)) };
3209
3210                s0 = ix0.mul_add_simd(to_f32(iy), s0);
3211                s1 = ix1.mul_add_simd(to_f32(iy >> 8), s1);
3212                s0 = ix2.mul_add_simd(to_f32(iy >> 16), s0);
3213                s1 = ix3.mul_add_simd(to_f32(iy >> 24), s1);
3214
3215                i += 1;
3216            }
3217            s = s0 + s1;
3218        }
3219
3220        let remainder = len % 32;
3221        if remainder != 0 {
3222            let tail = if len % 8 == 0 { 8 } else { len % 8 };
3223
3224            // SAFETY: Because `remainder != 0`, there is valid memory beginning at the
3225            // offset `blocks`, so this addition remains within an allocated object.
3226            let py = unsafe { py.add(blocks) };
3227
3228            if remainder <= 8 {
3229                // SAFETY: Non-zero remainder implies at least one byte is readable for `py`.
3230                // The same logic applies to the SIMD loads.
3231                unsafe {
3232                    load_one(py, |iy| {
3233                        let iy = prep(iy);
3234                        let ix = f32s::load_simd_first(arch, px.add(32 * blocks), tail);
3235                        s = ix.mul_add_simd(to_f32(iy), s);
3236                    })
3237                }
3238            } else if remainder <= 16 {
3239                // SAFETY: At least two bytes are readable for `py`.
3240                // The same logic applies to the SIMD loads.
3241                unsafe {
3242                    load_two(py, |iy| {
3243                        let iy = prep(iy);
3244                        let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
3245                        let ix1 = f32s::load_simd_first(arch, px.add(32 * blocks + 8), tail);
3246                        s = ix0.mul_add_simd(to_f32(iy), s);
3247                        s = ix1.mul_add_simd(to_f32(iy >> 8), s);
3248                    })
3249                }
3250            } else if remainder <= 24 {
3251                // SAFETY: At least three bytes are readable for `py`.
3252                // The same logic applies to the SIMD loads.
3253                unsafe {
3254                    load_three(py, |iy| {
3255                        let iy = prep(iy);
3256
3257                        let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
3258                        let ix1 = f32s::load_simd(arch, px.add(32 * blocks + 8));
3259                        let ix2 = f32s::load_simd_first(arch, px.add(32 * blocks + 16), tail);
3260
3261                        s = ix0.mul_add_simd(to_f32(iy), s);
3262                        s = ix1.mul_add_simd(to_f32(iy >> 8), s);
3263                        s = ix2.mul_add_simd(to_f32(iy >> 16), s);
3264                    })
3265                }
3266            } else {
3267                // SAFETY: At least four bytes are readable for `py`.
3268                // The same logic applies to the SIMD loads.
3269                unsafe {
3270                    load_four(py, |iy| {
3271                        let iy = prep(iy);
3272
3273                        let ix0 = f32s::load_simd(arch, px.add(32 * blocks));
3274                        let ix1 = f32s::load_simd(arch, px.add(32 * blocks + 8));
3275                        let ix2 = f32s::load_simd(arch, px.add(32 * blocks + 16));
3276                        let ix3 = f32s::load_simd_first(arch, px.add(32 * blocks + 24), tail);
3277
3278                        s = ix0.mul_add_simd(to_f32(iy), s);
3279                        s = ix1.mul_add_simd(to_f32(iy >> 8), s);
3280                        s = ix2.mul_add_simd(to_f32(iy >> 16), s);
3281                        s = ix3.mul_add_simd(to_f32(iy >> 24), s);
3282                    })
3283                }
3284            }
3285        }
3286
3287        Ok(MV::new(s.sum_tree()))
3288    }
3289}
3290
3291/// The strategy used here is almost identical to that used for 1-bit distances. The main
3292/// difference is that now we use the full 2-bit shuffle capabilities of `_mm256_permutevar_ps`
3293/// and ths relatives sizes of the shifts are slightly different.
3294#[cfg(target_arch = "x86_64")]
3295impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 2>>
3296    for InnerProduct
3297{
3298    #[inline(always)]
3299    fn run(
3300        self,
3301        arch: diskann_wide::arch::x86_64::V3,
3302        x: &[f32],
3303        y: USlice<'_, 2>,
3304    ) -> MathematicalResult<f32> {
3305        let len = check_lengths!(x, y)?;
3306
3307        use std::arch::x86_64::*;
3308
3309        diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
3310        diskann_wide::alias!(u32s = <diskann_wide::arch::x86_64::V3>::u32x8);
3311
3312        // This is the lookup table mapping 2-bit patterns to their equivalent `f32`
3313        // representation. The AVX2 shuffle only applies within each 128-bit group of the
3314        // full 256-bit register, so we replicate the contents.
3315        let values = f32s::from_array(arch, [0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0]);
3316
3317        // Shifts required to get logical dimensions shifted to the lower 2-bits of each lane.
3318        let variable_shifts = u32s::from_array(arch, [0, 2, 4, 6, 8, 10, 12, 14]);
3319
3320        let px: *const f32 = x.as_ptr();
3321        let py: *const u32 = y.as_ptr().cast();
3322
3323        let mut i = 0;
3324        let mut s = f32s::default(arch);
3325
3326        let prep = |v: u32| -> u32s { u32s::splat(arch, v) >> variable_shifts };
3327        let to_f32 = |v: u32s| -> f32s {
3328            // SAFETY: The `_mm256_permutevar_ps` instruction requires the AVX extension,
3329            // which the presense of the `x86_64::V3` architecture guarantees is available.
3330            f32s::from_underlying(arch, unsafe {
3331                _mm256_permutevar_ps(values.to_underlying(), v.to_underlying())
3332            })
3333        };
3334
3335        let blocks = len / 16;
3336        if blocks != 0 {
3337            let mut s0 = f32s::default(arch);
3338            let mut s1 = f32s::default(arch);
3339
3340            // Process 32 elements.
3341            while i + 2 <= blocks {
3342                // SAFETY: `i + 2 <= blocks` implies `py.add(i)` is in-bounds and readable
3343                // for 4 unaligned bytes.
3344                let iy = prep(unsafe { py.add(i).read_unaligned() });
3345
3346                // SAFETY: Same logic as above, just applied to `f32` values instead of
3347                // packed bits.
3348                let (ix0, ix1) = unsafe {
3349                    (
3350                        f32s::load_simd(arch, px.add(16 * i)),
3351                        f32s::load_simd(arch, px.add(16 * i + 8)),
3352                    )
3353                };
3354
3355                s0 = ix0.mul_add_simd(to_f32(iy), s0);
3356                s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
3357
3358                // SAFETY: `i + 2 <= blocks` implies `py.add(i + 1)` is in-bounds and readable
3359                // for 4 unaligned bytes.
3360                let iy = prep(unsafe { py.add(i + 1).read_unaligned() });
3361
3362                // SAFETY: Same logic as above.
3363                let (ix0, ix1) = unsafe {
3364                    (
3365                        f32s::load_simd(arch, px.add(16 * (i + 1))),
3366                        f32s::load_simd(arch, px.add(16 * (i + 1) + 8)),
3367                    )
3368                };
3369
3370                s0 = ix0.mul_add_simd(to_f32(iy), s0);
3371                s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
3372
3373                i += 2;
3374            }
3375
3376            // Process 16 elements
3377            if i < blocks {
3378                // SAFETY: `i < blocks` implies `py.add(i)` is in-bounds and readable for
3379                // 4 unaligned bytes.
3380                let iy = prep(unsafe { py.add(i).read_unaligned() });
3381
3382                // SAFETY: Same logic as above.
3383                let (ix0, ix1) = unsafe {
3384                    (
3385                        f32s::load_simd(arch, px.add(16 * i)),
3386                        f32s::load_simd(arch, px.add(16 * i + 8)),
3387                    )
3388                };
3389
3390                s0 = ix0.mul_add_simd(to_f32(iy), s0);
3391                s1 = ix1.mul_add_simd(to_f32(iy >> 16), s1);
3392            }
3393
3394            s = s0 + s1;
3395        }
3396
3397        let remainder = len % 16;
3398        if remainder != 0 {
3399            let tail = if len % 8 == 0 { 8 } else { len % 8 };
3400            // SAFETY: Non-zero remainder implies there are readable bytes after the offset
3401            // `blocks`, so the addition is valid.
3402            let py = unsafe { py.add(blocks) };
3403
3404            if remainder <= 4 {
3405                // SAFETY: Non-zero remainder implies at least one byte is readable for `py`.
3406                // The same logic applies to the SIMD loads.
3407                unsafe {
3408                    load_one(py, |iy| {
3409                        let iy = prep(iy);
3410                        let ix = f32s::load_simd_first(arch, px.add(16 * blocks), tail);
3411                        s = ix.mul_add_simd(to_f32(iy), s);
3412                    });
3413                }
3414            } else if remainder <= 8 {
3415                // SAFETY: At least two bytes are readable for `py`.
3416                // The same logic applies to the SIMD loads.
3417                unsafe {
3418                    load_two(py, |iy| {
3419                        let iy = prep(iy);
3420                        let ix = f32s::load_simd_first(arch, px.add(16 * blocks), tail);
3421                        s = ix.mul_add_simd(to_f32(iy), s);
3422                    });
3423                }
3424            } else if remainder <= 12 {
3425                // SAFETY: At least three bytes are readable for `py`.
3426                // The same logic applies to the SIMD loads.
3427                unsafe {
3428                    load_three(py, |iy| {
3429                        let iy = prep(iy);
3430                        let ix0 = f32s::load_simd(arch, px.add(16 * blocks));
3431                        let ix1 = f32s::load_simd_first(arch, px.add(16 * blocks + 8), tail);
3432                        s = ix0.mul_add_simd(to_f32(iy), s);
3433                        s = ix1.mul_add_simd(to_f32(iy >> 16), s);
3434                    });
3435                }
3436            } else {
3437                // SAFETY: At least four bytes are readable for `py`.
3438                // The same logic applies to the SIMD loads.
3439                unsafe {
3440                    load_four(py, |iy| {
3441                        let iy = prep(iy);
3442                        let ix0 = f32s::load_simd(arch, px.add(16 * blocks));
3443                        let ix1 = f32s::load_simd_first(arch, px.add(16 * blocks + 8), tail);
3444                        s = ix0.mul_add_simd(to_f32(iy), s);
3445                        s = ix1.mul_add_simd(to_f32(iy >> 16), s);
3446                    });
3447                }
3448            }
3449        }
3450
3451        Ok(MV::new(s.sum_tree()))
3452    }
3453}
3454
3455/// Decode eight consecutive codes from each 24-bit group. Interior loads overlap by one
3456/// byte; the final group is loaded separately so no padding or readable metadata is needed.
3457#[cfg(target_arch = "x86_64")]
3458impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 3>>
3459    for InnerProduct
3460{
3461    #[inline(always)]
3462    fn run(
3463        self,
3464        arch: diskann_wide::arch::x86_64::V3,
3465        x: &[f32],
3466        y: USlice<'_, 3>,
3467    ) -> MathematicalResult<f32> {
3468        let len = check_lengths!(x, y)?;
3469        diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
3470        diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
3471
3472        let shifts = i32s::from_array(arch, [0, 3, 6, 9, 12, 15, 18, 21]);
3473        let mask = i32s::splat(arch, 7);
3474        let decode =
3475            |word: u32| -> f32s { ((i32s::splat(arch, word as i32) >> shifts) & mask).simd_cast() };
3476        let px = x.as_ptr();
3477        let py = y.as_ptr();
3478        let mut s0 = f32s::default(arch);
3479        let mut s1 = f32s::default(arch);
3480        let mut s2 = f32s::default(arch);
3481        let mut s3 = f32s::default(arch);
3482        let mut i = 0;
3483
3484        while len - i > 32 {
3485            // SAFETY: At least 33 dimensions remain: 32 query values and 13 packed
3486            // bytes are readable. The last window covers bytes [9, 13).
3487            unsafe {
3488                let packed = py.add(3 * (i / 8));
3489                let c0 = decode(packed.cast::<u32>().read_unaligned());
3490                let c1 = decode(packed.add(3).cast::<u32>().read_unaligned());
3491                let c2 = decode(packed.add(6).cast::<u32>().read_unaligned());
3492                let c3 = decode(packed.add(9).cast::<u32>().read_unaligned());
3493                s0 = f32s::load_simd(arch, px.add(i)).mul_add_simd(c0, s0);
3494                s1 = f32s::load_simd(arch, px.add(i + 8)).mul_add_simd(c1, s1);
3495                s2 = f32s::load_simd(arch, px.add(i + 16)).mul_add_simd(c2, s2);
3496                s3 = f32s::load_simd(arch, px.add(i + 24)).mul_add_simd(c3, s3);
3497            }
3498            i += 32;
3499        }
3500        // Leave at least one dimension after each pair: its byte makes the second
3501        // overlapping u32 load valid, even when the packed slice has no trailing storage.
3502        if len - i > 16 {
3503            // SAFETY: At least 17 dimensions remain: 16 query values and 7 packed bytes
3504            // are readable. The two windows cover bytes [0, 4) and [3, 7).
3505            unsafe {
3506                let packed = py.add(3 * (i / 8));
3507                let c0 = decode(packed.cast::<u32>().read_unaligned());
3508                let c1 = decode(packed.add(3).cast::<u32>().read_unaligned());
3509                s0 = f32s::load_simd(arch, px.add(i)).mul_add_simd(c0, s0);
3510                s1 = f32s::load_simd(arch, px.add(i + 8)).mul_add_simd(c1, s1);
3511            }
3512            i += 16;
3513        }
3514        if len - i > 8 {
3515            // SAFETY: At least 9 dimensions remain, so 8 query values and 4 packed
3516            // bytes are readable at these offsets.
3517            unsafe {
3518                let codes = decode(py.add(3 * (i / 8)).cast::<u32>().read_unaligned());
3519                s0 = f32s::load_simd(arch, px.add(i)).mul_add_simd(codes, s0);
3520            }
3521            i += 8;
3522        }
3523        let tail = len - i;
3524        if tail != 0 {
3525            let finish = |word| {
3526                // SAFETY: There are `tail` query values remaining; inactive lanes are zero.
3527                let query = unsafe { f32s::load_simd_first(arch, px.add(i), tail) };
3528                s0 = query.mul_add_simd(decode(word), s0);
3529            };
3530            // SAFETY: The remaining 1..=8 codes occupy ceil(3 * tail / 8) bytes.
3531            // Each helper reads precisely that many bytes, with no alignment requirement.
3532            unsafe {
3533                let packed = py.add(3 * (i / 8)).cast::<u32>();
3534                match tail {
3535                    1..=2 => load_one(packed, finish),
3536                    3..=5 => load_two(packed, finish),
3537                    _ => load_three(packed, finish),
3538                }
3539            }
3540        }
3541        let sum = (s0 + s1) + (s2 + s3);
3542        Ok(MV::new(sum.sum_tree()))
3543    }
3544}
3545
3546/// The strategy here is similar to the 1 and 2-bit strategies. However, instead of using
3547/// `_mm256_permutevar_ps`, we now go directly for 32-bit integer to 32-bit floating point.
3548///
3549/// This is because the shuffle intrinsic only supports 2-bit shuffles.
3550#[cfg(target_arch = "x86_64")]
3551impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<f32>, &[f32], USlice<'_, 4>>
3552    for InnerProduct
3553{
3554    #[inline(always)]
3555    fn run(
3556        self,
3557        arch: diskann_wide::arch::x86_64::V3,
3558        x: &[f32],
3559        y: USlice<'_, 4>,
3560    ) -> MathematicalResult<f32> {
3561        let len = check_lengths!(x, y)?;
3562
3563        diskann_wide::alias!(f32s = <diskann_wide::arch::x86_64::V3>::f32x8);
3564        diskann_wide::alias!(i32s = <diskann_wide::arch::x86_64::V3>::i32x8);
3565
3566        let variable_shifts = i32s::from_array(arch, [0, 4, 8, 12, 16, 20, 24, 28]);
3567        let mask = i32s::splat(arch, 0x0f);
3568
3569        let to_f32 = |v: u32| -> f32s {
3570            ((i32s::splat(arch, v as i32) >> variable_shifts) & mask).simd_cast()
3571        };
3572
3573        let px: *const f32 = x.as_ptr();
3574        let py: *const u32 = y.as_ptr().cast();
3575
3576        let mut i = 0;
3577        let mut s = f32s::default(arch);
3578
3579        let blocks = len / 8;
3580        while i < blocks {
3581            // SAFETY: `i < blocks` implies that 8 `f32` values are readable from `8*i`.
3582            let ix = unsafe { f32s::load_simd(arch, px.add(8 * i)) };
3583            // SAFETY: Same logic as above - but applied to the packed bits.
3584            let iy = to_f32(unsafe { py.add(i).read_unaligned() });
3585            s = ix.mul_add_simd(iy, s);
3586
3587            i += 1;
3588        }
3589
3590        let remainder = len % 8;
3591        if remainder != 0 {
3592            let f = |iy| {
3593                // SAFETY: The epilogue handles at most 8 values. Since the remainder is
3594                // non-zero, the pointer arithmetic is in-bounds and `load_simd_first` will
3595                // avoid accessing the out-of-bounds elements.
3596                let ix = unsafe { f32s::load_simd_first(arch, px.add(8 * blocks), remainder) };
3597                s = ix.mul_add_simd(to_f32(iy), s);
3598            };
3599
3600            // SAFETY: Non-zero remainder means there are readable bytes from the offset
3601            // `blocks`.
3602            let py = unsafe { py.add(blocks) };
3603
3604            if remainder <= 2 {
3605                // SAFETY: Non-zero remainder less than 2 implies that one byte is readable.
3606                unsafe { load_one(py, f) };
3607            } else if remainder <= 4 {
3608                // SAFETY: At least two bytes are readable from `py`.
3609                unsafe { load_two(py, f) };
3610            } else if remainder <= 6 {
3611                // SAFETY: At least three bytes are readable from `py`.
3612                unsafe { load_three(py, f) };
3613            } else {
3614                // SAFETY: At least four bytes are readable from `py`.
3615                unsafe { load_four(py, f) };
3616            }
3617        }
3618
3619        Ok(MV::new(s.sum_tree()))
3620    }
3621}
3622
3623#[cfg(target_arch = "aarch64")]
3624impl Target2<diskann_wide::arch::aarch64::Neon, MathematicalResult<f32>, &[f32], USlice<'_, 1>>
3625    for InnerProduct
3626{
3627    #[inline(always)]
3628    fn run(
3629        self,
3630        arch: diskann_wide::arch::aarch64::Neon,
3631        x: &[f32],
3632        y: USlice<'_, 1>,
3633    ) -> MathematicalResult<f32> {
3634        // returns number of quantized vectors
3635        let len = check_lengths!(x, y)?;
3636
3637        diskann_wide::alias!(f32s_4 = <diskann_wide::arch::aarch64::Neon>::f32x4);
3638        diskann_wide::alias!(u32s_4 = <diskann_wide::arch::aarch64::Neon>::u32x4);
3639
3640        let px_f32: *const f32 = x.as_ptr();
3641        let py_u8: *const u8 = y.as_ptr().cast();
3642
3643        let mut i = 0;
3644        let mut s: f32 = 0.0;
3645
3646        let y_bytes = len / 8; // number of y bytes over the underlying slice
3647        if i < y_bytes {
3648            let mut s0 = f32s_4::default(arch);
3649            let mut s1 = f32s_4::default(arch);
3650            let mut s2 = f32s_4::default(arch);
3651            let mut s3 = f32s_4::default(arch);
3652            let bits0 = u32s_4::from_array(arch, [1, 2, 4, 8]);
3653            let bits1 = u32s_4::from_array(arch, [16, 32, 64, 128]);
3654            let bits2 = u32s_4::from_array(arch, [256, 512, 1024, 2048]);
3655            let bits3 = u32s_4::from_array(arch, [4096, 8192, 16384, 32768]);
3656            let bits4 = u32s_4::from_array(arch, [65536, 131072, 262144, 524288]);
3657            let bits5 = u32s_4::from_array(arch, [1048576, 2097152, 4194304, 8388608]);
3658            let bits6 = u32s_4::from_array(arch, [16777216, 33554432, 67108864, 134217728]);
3659            let bits7 = u32s_4::from_array(arch, [268435456, 536870912, 1073741824, 2147483648]);
3660            let zero_u32 = u32s_4::splat(arch, 0);
3661            let zero_f32 = f32s_4::splat(arch, 0.0);
3662
3663            while i + 4 <= y_bytes {
3664                // SAFETY: `i + 4 <= y_bytes` guarantees that four packed bytes are readable.
3665                let y_word = unsafe { py_u8.add(i).cast::<u32>().read_unaligned() };
3666                let y_words = u32s_4::splat(arch, y_word);
3667
3668                // SAFETY: `i + 4 <= y_bytes` guarantees 32 logical elements are readable.
3669                let x_ptr = unsafe { px_f32.add(8 * i) };
3670                // SAFETY: `x_ptr` is valid for 32 consecutive `f32` values.
3671                let x0 = unsafe { f32s_4::load_simd(arch, x_ptr) };
3672                // SAFETY: `x_ptr` is valid for 32 consecutive `f32` values.
3673                let x1 = unsafe { f32s_4::load_simd(arch, x_ptr.add(4)) };
3674                // SAFETY: `x_ptr` is valid for 32 consecutive `f32` values.
3675                let x2 = unsafe { f32s_4::load_simd(arch, x_ptr.add(8)) };
3676                // SAFETY: `x_ptr` is valid for 32 consecutive `f32` values.
3677                let x3 = unsafe { f32s_4::load_simd(arch, x_ptr.add(12)) };
3678                // SAFETY: `x_ptr` is valid for 32 consecutive `f32` values.
3679                let x4 = unsafe { f32s_4::load_simd(arch, x_ptr.add(16)) };
3680                // SAFETY: `x_ptr` is valid for 32 consecutive `f32` values.
3681                let x5 = unsafe { f32s_4::load_simd(arch, x_ptr.add(20)) };
3682                // SAFETY: `x_ptr` is valid for 32 consecutive `f32` values.
3683                let x6 = unsafe { f32s_4::load_simd(arch, x_ptr.add(24)) };
3684                // SAFETY: `x_ptr` is valid for 32 consecutive `f32` values.
3685                let x7 = unsafe { f32s_4::load_simd(arch, x_ptr.add(28)) };
3686
3687                s0 = s0 + (y_words & bits0).eq_simd(zero_u32).select(zero_f32, x0);
3688                s1 = s1 + (y_words & bits1).eq_simd(zero_u32).select(zero_f32, x1);
3689                s2 = s2 + (y_words & bits2).eq_simd(zero_u32).select(zero_f32, x2);
3690                s3 = s3 + (y_words & bits3).eq_simd(zero_u32).select(zero_f32, x3);
3691                s0 = s0 + (y_words & bits4).eq_simd(zero_u32).select(zero_f32, x4);
3692                s1 = s1 + (y_words & bits5).eq_simd(zero_u32).select(zero_f32, x5);
3693                s2 = s2 + (y_words & bits6).eq_simd(zero_u32).select(zero_f32, x6);
3694                s3 = s3 + (y_words & bits7).eq_simd(zero_u32).select(zero_f32, x7);
3695
3696                i += 4;
3697            }
3698
3699            while i < y_bytes {
3700                // SAFETY: The loop condition guarantees that one byte is readable.
3701                let y_word = unsafe { *py_u8.add(i) };
3702                let y_words = u32s_4::splat(arch, y_word as u32);
3703
3704                // SAFETY: The loop condition guarantees that 8 logical elements can be read.
3705                let x_ptr = unsafe { px_f32.add(8 * i) };
3706                // SAFETY: `x_ptr` is valid for 8 consecutive `f32` values.
3707                let x0 = unsafe { f32s_4::load_simd(arch, x_ptr) };
3708                // SAFETY: `x_ptr` is valid for 8 consecutive `f32` values.
3709                let x1 = unsafe { f32s_4::load_simd(arch, x_ptr.add(4)) };
3710
3711                s0 = s0 + (y_words & bits0).eq_simd(zero_u32).select(zero_f32, x0);
3712                s1 = s1 + (y_words & bits1).eq_simd(zero_u32).select(zero_f32, x1);
3713                i += 1;
3714            }
3715            s = ((s0 + s1) + (s2 + s3)).sum_tree();
3716        }
3717
3718        // converting from y bytes to logical elements.
3719        i *= 8;
3720
3721        // Deal with the remainder the slow way (at most 7 elements).
3722        debug_assert!(len - i <= 7);
3723        if i != len {
3724            #[inline(never)]
3725            fn fallback(x: &[f32], y: USlice<'_, 1>, from: usize) -> f32 {
3726                let mut s: f32 = 0.0;
3727                for i in from..x.len() {
3728                    // SAFETY: `i` is in `from..x.len()`, which equals `y.len()`.
3729                    let (ix, iy) = unsafe { (*x.get_unchecked(i), y.get_unchecked(i) as f32) };
3730                    s += ix * iy;
3731                }
3732                s
3733            }
3734            s += fallback(x, y, i)
3735        }
3736
3737        Ok(MV::new(s))
3738    }
3739}
3740
3741#[cfg(target_arch = "aarch64")]
3742impl Target2<diskann_wide::arch::aarch64::Neon, MathematicalResult<f32>, &[f32], USlice<'_, 2>>
3743    for InnerProduct
3744{
3745    #[inline(always)]
3746    fn run(
3747        self,
3748        arch: diskann_wide::arch::aarch64::Neon,
3749        x: &[f32],
3750        y: USlice<'_, 2>,
3751    ) -> MathematicalResult<f32> {
3752        // returns number of quantized vectors
3753        let len = check_lengths!(x, y)?;
3754
3755        diskann_wide::alias!(u8s_8 = type diskann_wide::arch::aarch64::u8x8);
3756        diskann_wide::alias!(u16s_8 = type diskann_wide::arch::aarch64::u16x8);
3757        diskann_wide::alias!(f32s_8 = <diskann_wide::arch::aarch64::Neon>::f32x8);
3758
3759        let px_f32: *const f32 = x.as_ptr();
3760        let py_u8: *const u8 = y.as_ptr();
3761
3762        let mut i = 0;
3763        let mut s: f32 = 0.0;
3764
3765        let y_bytes = len / 4; // number of y blocks over the underlying slice
3766        if i < y_bytes {
3767            let mut s0 = f32s_8::default(arch);
3768            let mut s1 = f32s_8::default(arch);
3769            let mut s2 = f32s_8::default(arch);
3770            let mut s3 = f32s_8::default(arch);
3771
3772            let mask = u8s_8::splat(arch, 0x03);
3773
3774            while i + 8 <= y_bytes {
3775                // SAFETY: The loop condition guarantees that eight packed bytes are readable.
3776                let y_vec = unsafe { u8s_8::load_simd(arch, py_u8.add(i)) };
3777
3778                // SAFETY: Eight packed bytes represent 32 logical values, and the loop
3779                // condition therefore establishes that `4 * i < x.len()`.
3780                let x_base = unsafe { px_f32.add(4 * i) };
3781                // SAFETY: The loop condition guarantees 32 readable `f32` values at
3782                // `x_base`; each structured load consumes 16 values.
3783                let x_lo = unsafe { arch.vld4q_f32(x_base) };
3784                // SAFETY: Same bound as above; offsetting by 16 stays within the 32 readable
3785                // values guaranteed by the loop condition.
3786                let x_hi_base = unsafe { x_base.add(16) };
3787                // SAFETY: Same bound as above; offsetting by 16 stays within the 32 readable
3788                // values guaranteed by the loop condition.
3789                let x_hi = unsafe { arch.vld4q_f32(x_hi_base) };
3790
3791                let x_vec1 = f32s_8::new(x_lo[0], x_hi[0]);
3792                let y_vec1: f32s_8 = (y_vec & mask).into();
3793                s0 = x_vec1.mul_add_simd(y_vec1, s0);
3794
3795                let x_vec2 = f32s_8::new(x_lo[1], x_hi[1]);
3796                let y_vec2: f32s_8 = ((y_vec >> 2) & mask).into();
3797                s1 = x_vec2.mul_add_simd(y_vec2, s1);
3798
3799                let x_vec3 = f32s_8::new(x_lo[2], x_hi[2]);
3800                let y_vec3: f32s_8 = ((y_vec >> 4) & mask).into();
3801                s2 = x_vec3.mul_add_simd(y_vec3, s2);
3802
3803                let x_vec4 = f32s_8::new(x_lo[3], x_hi[3]);
3804                let y_vec4: f32s_8 = ((y_vec >> 6) & mask).into();
3805                s3 = x_vec4.mul_add_simd(y_vec4, s3);
3806
3807                i += 8;
3808            }
3809
3810            // remainder uses a  different approach since diskann wide
3811            // does not provide a method for variable length de-interleaved loads
3812            while i + 1 < y_bytes {
3813                let mask_u16 = u16s_8::splat(arch, 0x0003);
3814                // SAFETY: `i + 1 < y_bytes` implies at least two packed bytes are readable at
3815                // offset `i`, allowing an unaligned `u16` read.
3816                let y_u16 = unsafe { py_u8.add(i).cast::<u16>().read_unaligned() };
3817                let y_raw = u16s_8::splat(arch, y_u16);
3818                let shifts = u16s_8::from_array(arch, [0, 2, 4, 6, 8, 10, 12, 14]);
3819                let y_vec: f32s_8 = ((y_raw >> shifts) & mask_u16).into();
3820                // SAFETY: `i + 1 < y_bytes` means at least 8 logical elements remain, so
3821                // loading 8 `f32` values from `px_f32.add(4 * i)` is in-bounds.
3822                let x_vec = unsafe { f32s_8::load_simd(arch, px_f32.add(4 * i)) };
3823                s0 = x_vec.mul_add_simd(y_vec, s0);
3824                i += 2;
3825            }
3826
3827            s = ((s0 + s1) + (s2 + s3)).sum_tree();
3828        }
3829
3830        // converting from y blocks to logical elements.
3831        i *= 4;
3832
3833        // Deal with the remainder the slow way (at most 7 elements).
3834        debug_assert!(len - i <= 7);
3835        if i != len {
3836            #[inline(never)]
3837            fn fallback(x: &[f32], y: USlice<'_, 2>, from: usize) -> f32 {
3838                let mut s: f32 = 0.0;
3839                for i in from..x.len() {
3840                    // SAFETY: `i` is in `from..x.len()`, which equals `y.len()`.
3841                    let (ix, iy) = unsafe { (*x.get_unchecked(i), y.get_unchecked(i) as f32) };
3842                    s += ix * iy;
3843                }
3844                s
3845            }
3846            s += fallback(x, y, i);
3847        }
3848
3849        Ok(MV::new(s))
3850    }
3851}
3852
3853#[cfg(target_arch = "aarch64")]
3854impl Target2<diskann_wide::arch::aarch64::Neon, MathematicalResult<f32>, &[f32], USlice<'_, 4>>
3855    for InnerProduct
3856{
3857    #[inline(always)]
3858    fn run(
3859        self,
3860        arch: diskann_wide::arch::aarch64::Neon,
3861        x: &[f32],
3862        y: USlice<'_, 4>,
3863    ) -> MathematicalResult<f32> {
3864        // returns number of quantized vectors
3865        let len = check_lengths!(x, y)?;
3866
3867        diskann_wide::alias!(u8s_8 = type diskann_wide::arch::aarch64::u8x8);
3868        diskann_wide::alias!(u8s_16 = <diskann_wide::arch::aarch64::Neon>::u8x16);
3869        diskann_wide::alias!(f32s_8 = <diskann_wide::arch::aarch64::Neon>::f32x8);
3870        diskann_wide::alias!(f32s_16 = <diskann_wide::arch::aarch64::Neon>::f32x16);
3871
3872        let px_f32: *const f32 = x.as_ptr();
3873        let py_u8: *const u8 = y.as_ptr().cast();
3874
3875        let mut i = 0;
3876        let mut s: f32 = 0.0;
3877
3878        let y_bytes = len / 2; // number of y blocks over the underlying slice
3879        if i < y_bytes {
3880            let mut s0 = f32s_8::default(arch);
3881            let mut s1 = f32s_8::default(arch);
3882            let mask = u8s_8::splat(arch, 0x0f);
3883
3884            while i + 8 <= y_bytes {
3885                // SAFETY: The loop condition guarantees eight readable packed bytes.
3886                let y_vec = unsafe { u8s_8::load_simd(arch, py_u8.add(i)) };
3887                // SAFETY: Eight packed bytes represent 16 logical values, so the loop
3888                // condition guarantees 16 readable `f32` values at offset `2 * i`.
3889                let x_lo_base = unsafe { px_f32.add(2 * i) };
3890                // SAFETY: The loop condition guarantees 16 readable `f32` values at `2 * i`,
3891                // so the first deinterleaved load (8 values) is in-bounds.
3892                let [x_vec_1, x_vec_2] = unsafe { arch.vld2q_f32(x_lo_base) };
3893                // SAFETY: Eight packed bytes represent 16 logical values, so the loop
3894                // condition guarantees 16 readable `f32` values at offset `2 * i`.
3895                let x_hi_base = unsafe { px_f32.add(2 * i + 8) };
3896                // SAFETY: Same loop bound as above; offsetting by 8 stays within the same
3897                // 16-value readable window.
3898                let [x_vec_3, x_vec_4] = unsafe { arch.vld2q_f32(x_hi_base) };
3899
3900                let x_vec_even = f32s_8::new(x_vec_1, x_vec_3);
3901                let x_vec_odd = f32s_8::new(x_vec_2, x_vec_4);
3902
3903                let y_vec_even: f32s_8 = (y_vec & mask).into();
3904                let y_vec_odd: f32s_8 = (y_vec >> 4).into();
3905
3906                s0 = x_vec_even.mul_add_simd(y_vec_even, s0);
3907                s1 = x_vec_odd.mul_add_simd(y_vec_odd, s1);
3908
3909                i += 8;
3910            }
3911
3912            #[inline(always)]
3913            fn split_and_zip(input: u8s_8) -> u8s_16 {
3914                use diskann_wide::{LoHi, ZipUnzip};
3915                let zipped = u8s_16::zip(LoHi::new(input, input >> 4));
3916                zipped & u8s_16::splat(input.arch(), 0x0f)
3917            }
3918
3919            let remaining_y_bytes = len / 2 - i;
3920
3921            let mut s_grouped = f32s_16::new(s0, s1);
3922
3923            // remainder uses a  different approach since diskann wide
3924            // does not provide a method for variable length de-interleaved loads
3925            if remaining_y_bytes > 0 {
3926                // SAFETY: `remaining_y_bytes` is in `1..8`, and that many packed bytes
3927                // remain readable at offset `i`.
3928                let y_vec_u8s_8 =
3929                    unsafe { u8s_8::load_simd_first(arch, py_u8.add(i), remaining_y_bytes) };
3930                let y_vec_u8s_16 = split_and_zip(y_vec_u8s_8);
3931                let y_vec: f32s_16 = y_vec_u8s_16.into();
3932
3933                // SAFETY: Every remaining packed byte represents two logical values, so
3934                // `remaining_y_bytes * 2` values remain readable at offset `2 * i`.
3935                let x_vec = unsafe {
3936                    f32s_16::load_simd_first(arch, px_f32.add(2 * i), remaining_y_bytes * 2)
3937                };
3938
3939                s_grouped = x_vec.mul_add_simd(y_vec, s_grouped);
3940
3941                i += remaining_y_bytes;
3942            }
3943            s = s_grouped.sum_tree();
3944        }
3945
3946        // converting from y bytes to logical elements.
3947        i *= 2;
3948
3949        // Deal with the remainder the slow way (at most 1 element).
3950        debug_assert!(len - i <= 1);
3951        if i != len {
3952            #[inline(never)]
3953            fn fallback(x: &[f32], y: USlice<'_, 4>, from: usize) -> f32 {
3954                let mut s: f32 = 0.0;
3955                for i in from..x.len() {
3956                    // SAFETY: `i` is in `from..x.len()`, which equals `y.len()`.
3957                    let (ix, iy) = unsafe { (*x.get_unchecked(i), y.get_unchecked(i) as f32) };
3958                    s += ix * iy;
3959                }
3960                s
3961            }
3962            s += fallback(x, y, i);
3963        }
3964
3965        Ok(MV::new(s))
3966    }
3967}
3968
3969impl<const N: usize>
3970    Target2<diskann_wide::arch::Scalar, MathematicalResult<f32>, &[f32], USlice<'_, N>>
3971    for InnerProduct
3972where
3973    Unsigned: Representation<N>,
3974{
3975    /// A fallback implementation that uses scaler indexing to retrieve values from
3976    /// the corresponding `BitSlice`.
3977    #[inline(always)]
3978    fn run(
3979        self,
3980        _: diskann_wide::arch::Scalar,
3981        x: &[f32],
3982        y: USlice<'_, N>,
3983    ) -> MathematicalResult<f32> {
3984        check_lengths!(x, y)?;
3985
3986        let mut s = 0.0;
3987        for (i, x) in x.iter().enumerate() {
3988            // SAFETY: We've ensured that `x.len() == y.len()`, so this access is
3989            // always inbounds.
3990            let y = unsafe { y.get_unchecked(i) } as f32;
3991            s += x * y;
3992        }
3993
3994        Ok(MV::new(s))
3995    }
3996}
3997
3998/// Implement `Target2` for higher architecture in terms of the scalar fallback.
3999macro_rules! ip_retarget {
4000    ($arch:path, $N:literal) => {
4001        impl Target2<$arch, MathematicalResult<f32>, &[f32], USlice<'_, $N>>
4002            for InnerProduct
4003        {
4004            #[inline(always)]
4005            fn run(
4006                self,
4007                arch: $arch,
4008                x: &[f32],
4009                y: USlice<'_, $N>,
4010            ) -> MathematicalResult<f32> {
4011                self.run(arch.retarget(), x, y)
4012            }
4013        }
4014    };
4015    ($arch:path, $($Ns:literal),*) => {
4016        $(ip_retarget!($arch, $Ns);)*
4017    }
4018}
4019
4020#[cfg(target_arch = "x86_64")]
4021ip_retarget!(diskann_wide::arch::x86_64::V3, 5, 6, 7, 8);
4022
4023#[cfg(target_arch = "x86_64")]
4024ip_retarget!(diskann_wide::arch::x86_64::V4, 1, 2, 3, 4, 5, 6, 7, 8);
4025
4026#[cfg(target_arch = "aarch64")]
4027ip_retarget!(diskann_wide::arch::aarch64::Neon, 3, 5, 6, 7, 8);
4028
4029/// Delegate the implementation of `PureDistanceFunction` to `diskann_wide::arch::Target2`
4030/// with the current architectures.
4031macro_rules! dispatch_full_ip {
4032    ($N:literal) => {
4033        /// Compute the inner product between `x` and `y`.
4034        ///
4035        /// Returns an error if the arguments have different lengths.
4036        impl PureDistanceFunction<&[f32], USlice<'_, $N>, MathematicalResult<f32>>
4037            for InnerProduct
4038        {
4039            fn evaluate(x: &[f32], y: USlice<'_, $N>) -> MathematicalResult<f32> {
4040                Self.run(ARCH, x, y)
4041            }
4042        }
4043    };
4044    ($($Ns:literal),*) => {
4045        $(dispatch_full_ip!($Ns);)*
4046    }
4047}
4048
4049dispatch_full_ip!(1, 2, 3, 4, 5, 6, 7, 8);
4050
4051///////////
4052// Tests //
4053///////////
4054
4055#[cfg(test)]
4056mod tests {
4057    use std::{collections::HashMap, fmt::Display, sync::LazyLock};
4058
4059    use diskann_utils::{Reborrow, lazy_format};
4060    use rand::{
4061        Rng, SeedableRng,
4062        distr::{Distribution, Uniform},
4063        rngs::StdRng,
4064        seq::IndexedRandom,
4065    };
4066
4067    use super::*;
4068    use crate::bits::{BoxedBitSlice, Representation, Unsigned};
4069
4070    type MR = MathematicalResult<u32>;
4071
4072    #[inline(always)]
4073    fn should_check_this_dimension(dim: usize) -> bool {
4074        if cfg!(miri) {
4075            return dim.is_power_of_two()
4076                || (dim > 1 && (dim - 1).is_power_of_two())
4077                || (dim < 64 && (dim % 8 == 7));
4078        }
4079
4080        true
4081    }
4082
4083    /////////////////////////
4084    // Unsigned Bit Slices //
4085    /////////////////////////
4086
4087    // This test works by generating random integer codes for the compressed vectors,
4088    // then uses the functions implemented in `vector` to compute the expected result of
4089    // the computation in "full precision integer space".
4090    //
4091    // We verify that the exact same results are returned by each computation.
4092    fn test_bitslice_distances<const NBITS: usize, R>(
4093        dim_max: usize,
4094        trials_per_dim: usize,
4095        evaluate_l2: &dyn Fn(USlice<'_, NBITS>, USlice<'_, NBITS>) -> MR,
4096        evaluate_ip: &dyn Fn(USlice<'_, NBITS>, USlice<'_, NBITS>) -> MR,
4097        context: &str,
4098        rng: &mut R,
4099    ) where
4100        Unsigned: Representation<NBITS>,
4101        R: Rng,
4102    {
4103        let domain = Unsigned::domain_const::<NBITS>();
4104        let min: i64 = *domain.start();
4105        let max: i64 = *domain.end();
4106
4107        let dist = Uniform::new_inclusive(min, max).unwrap();
4108
4109        for dim in 0..dim_max {
4110            if !should_check_this_dimension(dim) {
4111                continue;
4112            }
4113
4114            let mut x_reference: Vec<u8> = vec![0; dim];
4115            let mut y_reference: Vec<u8> = vec![0; dim];
4116
4117            let mut x = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(dim);
4118            let mut y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(dim);
4119
4120            for trial in 0..trials_per_dim {
4121                x_reference
4122                    .iter_mut()
4123                    .for_each(|i| *i = dist.sample(rng).try_into().unwrap());
4124                y_reference
4125                    .iter_mut()
4126                    .for_each(|i| *i = dist.sample(rng).try_into().unwrap());
4127
4128                // Fill the input slices with 1's so we can catch situations where we don't
4129                // correctly handle odd remaining elements.
4130                x.as_mut_slice().fill(u8::MAX);
4131                y.as_mut_slice().fill(u8::MAX);
4132
4133                for i in 0..dim {
4134                    x.set(i, x_reference[i].into()).unwrap();
4135                    y.set(i, y_reference[i].into()).unwrap();
4136                }
4137
4138                // Check L2
4139                let expected: MV<f32> =
4140                    diskann_vector::distance::SquaredL2::evaluate(&*x_reference, &*y_reference);
4141
4142                let got = evaluate_l2(x.reborrow(), y.reborrow()).unwrap();
4143
4144                // Integer computations should be exact.
4145                assert_eq!(
4146                    expected.into_inner(),
4147                    got.into_inner() as f32,
4148                    "failed SquaredL2 for NBITS = {}, dim = {}, trial = {} -- context {}",
4149                    NBITS,
4150                    dim,
4151                    trial,
4152                    context,
4153                );
4154
4155                // Check IP
4156                let expected: MV<f32> =
4157                    diskann_vector::distance::InnerProduct::evaluate(&*x_reference, &*y_reference);
4158
4159                let got = evaluate_ip(x.reborrow(), y.reborrow()).unwrap();
4160
4161                // Integer computations should be exact.
4162                assert_eq!(
4163                    expected.into_inner(),
4164                    got.into_inner() as f32,
4165                    "faild InnerProduct for NBITS = {}, dim = {}, trial = {} -- context {}",
4166                    NBITS,
4167                    dim,
4168                    trial,
4169                    context,
4170                );
4171            }
4172        }
4173
4174        // Test that we correctly return error types for length mismatches.
4175        let x = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(10);
4176        let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(11);
4177
4178        assert!(
4179            evaluate_l2(x.reborrow(), y.reborrow()).is_err(),
4180            "context: {}",
4181            context
4182        );
4183        assert!(
4184            evaluate_l2(y.reborrow(), x.reborrow()).is_err(),
4185            "context: {}",
4186            context
4187        );
4188
4189        assert!(
4190            evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
4191            "context: {}",
4192            context
4193        );
4194        assert!(
4195            evaluate_ip(y.reborrow(), x.reborrow()).is_err(),
4196            "context: {}",
4197            context
4198        );
4199    }
4200
4201    cfg_if::cfg_if! {
4202        if #[cfg(miri)] {
4203            const MAX_DIM: usize = 132;
4204            const TRIALS_PER_DIM: usize = 1;
4205        } else {
4206            const MAX_DIM: usize = 256;
4207            const TRIALS_PER_DIM: usize = 20;
4208        }
4209    }
4210
4211    // For the bit-slice kernels, we want to use different maximum dimensions for the distance
4212    // test depending on the implementation of the kernel, and whether or not we are running
4213    // under Miri.
4214    //
4215    // For implementations that use the scalar fallback, we need not set very high bounds
4216    // (particularly when running under miri) because the implementations are quite simple.
4217    //
4218    // However, some SIMD kernels (especially for the lower bit widths), require higher bounds
4219    // to trigger all possible corner cases.
4220    static BITSLICE_TEST_BOUNDS: LazyLock<HashMap<Key, Bounds>> = LazyLock::new(|| {
4221        use ArchKey::{Neon, Scalar, X86_64_V3, X86_64_V4};
4222        [
4223            (Key::new(1, Scalar), Bounds::new(64, 64)),
4224            (Key::new(1, X86_64_V3), Bounds::new(256, 256)),
4225            (Key::new(1, X86_64_V4), Bounds::new(256, 256)),
4226            (Key::new(1, Neon), Bounds::new(64, 64)),
4227            (Key::new(2, Scalar), Bounds::new(64, 64)),
4228            // Need a higher miri-amount due to the larget block size
4229            (Key::new(2, X86_64_V3), Bounds::new(512, 300)),
4230            (Key::new(2, X86_64_V4), Bounds::new(768, 600)), // main loop processes 256 items
4231            (Key::new(2, Neon), Bounds::new(256, 256)),
4232            (Key::new(3, Scalar), Bounds::new(64, 64)),
4233            (Key::new(3, X86_64_V3), Bounds::new(256, 96)),
4234            (Key::new(3, X86_64_V4), Bounds::new(256, 96)),
4235            (Key::new(3, Neon), Bounds::new(64, 64)),
4236            (Key::new(4, Scalar), Bounds::new(64, 64)),
4237            // Need a higher miri-amount due to the larget block size
4238            (Key::new(4, X86_64_V3), Bounds::new(256, 150)),
4239            (Key::new(4, X86_64_V4), Bounds::new(512, 300)),
4240            (Key::new(4, Neon), Bounds::new(128, 128)),
4241            (Key::new(5, Scalar), Bounds::new(64, 64)),
4242            (Key::new(5, X86_64_V3), Bounds::new(256, 96)),
4243            (Key::new(5, X86_64_V4), Bounds::new(256, 96)),
4244            (Key::new(5, Neon), Bounds::new(64, 64)),
4245            (Key::new(6, Scalar), Bounds::new(64, 64)),
4246            (Key::new(6, X86_64_V3), Bounds::new(256, 96)),
4247            (Key::new(6, X86_64_V4), Bounds::new(256, 96)),
4248            (Key::new(6, Neon), Bounds::new(64, 64)),
4249            (Key::new(7, Scalar), Bounds::new(64, 64)),
4250            (Key::new(7, X86_64_V3), Bounds::new(256, 96)),
4251            (Key::new(7, X86_64_V4), Bounds::new(256, 96)),
4252            (Key::new(7, Neon), Bounds::new(64, 64)),
4253            (Key::new(8, Scalar), Bounds::new(64, 64)),
4254            (Key::new(8, X86_64_V3), Bounds::new(256, 96)),
4255            (Key::new(8, X86_64_V4), Bounds::new(256, 96)),
4256            (Key::new(8, Neon), Bounds::new(256, 256)),
4257        ]
4258        .into_iter()
4259        .collect()
4260    });
4261
4262    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
4263    enum ArchKey {
4264        Scalar,
4265        #[expect(non_camel_case_types)]
4266        X86_64_V3,
4267        #[expect(non_camel_case_types)]
4268        X86_64_V4,
4269        Neon,
4270    }
4271
4272    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
4273    struct Key {
4274        nbits: usize,
4275        arch: ArchKey,
4276    }
4277
4278    impl Key {
4279        fn new(nbits: usize, arch: ArchKey) -> Self {
4280            Self { nbits, arch }
4281        }
4282    }
4283
4284    #[derive(Debug, Clone, Copy)]
4285    struct Bounds {
4286        standard: usize,
4287        miri: usize,
4288    }
4289
4290    impl Bounds {
4291        fn new(standard: usize, miri: usize) -> Self {
4292            Self { standard, miri }
4293        }
4294
4295        fn get(&self) -> usize {
4296            if cfg!(miri) { self.miri } else { self.standard }
4297        }
4298    }
4299
4300    macro_rules! test_bitslice {
4301        ($name:ident, $nbits:literal, $seed:literal) => {
4302            #[test]
4303            fn $name() {
4304                let mut rng = StdRng::seed_from_u64($seed);
4305
4306                let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::Scalar)].get();
4307
4308                test_bitslice_distances::<$nbits, _>(
4309                    max_dim,
4310                    TRIALS_PER_DIM,
4311                    &|x, y| SquaredL2::evaluate(x, y),
4312                    &|x, y| InnerProduct::evaluate(x, y),
4313                    "pure distance function",
4314                    &mut rng,
4315                );
4316
4317                test_bitslice_distances::<$nbits, _>(
4318                    max_dim,
4319                    TRIALS_PER_DIM,
4320                    &|x, y| diskann_wide::arch::Scalar::new().run2(SquaredL2, x, y),
4321                    &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
4322                    "scalar arch",
4323                    &mut rng,
4324                );
4325
4326                // Architecture Specific.
4327                #[cfg(target_arch = "x86_64")]
4328                if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
4329                    let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::X86_64_V3)].get();
4330                    test_bitslice_distances::<$nbits, _>(
4331                        max_dim,
4332                        TRIALS_PER_DIM,
4333                        &|x, y| arch.run2(SquaredL2, x, y),
4334                        &|x, y| arch.run2(InnerProduct, x, y),
4335                        "x86-64-v3",
4336                        &mut rng,
4337                    );
4338                }
4339
4340                #[cfg(target_arch = "x86_64")]
4341                if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
4342                    let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::X86_64_V4)].get();
4343                    test_bitslice_distances::<$nbits, _>(
4344                        max_dim,
4345                        TRIALS_PER_DIM,
4346                        &|x, y| arch.run2(SquaredL2, x, y),
4347                        &|x, y| arch.run2(InnerProduct, x, y),
4348                        "x86-64-v4",
4349                        &mut rng,
4350                    );
4351                }
4352
4353                #[cfg(target_arch = "aarch64")]
4354                if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4355                    let max_dim = BITSLICE_TEST_BOUNDS[&Key::new($nbits, ArchKey::Neon)].get();
4356                    test_bitslice_distances::<$nbits, _>(
4357                        max_dim,
4358                        TRIALS_PER_DIM,
4359                        &|x, y| arch.run2(SquaredL2, x, y),
4360                        &|x, y| arch.run2(InnerProduct, x, y),
4361                        "neon",
4362                        &mut rng,
4363                    );
4364                }
4365            }
4366        };
4367    }
4368
4369    test_bitslice!(test_bitslice_distances_8bit, 8, 0xf0330c6d880e08ff);
4370    test_bitslice!(test_bitslice_distances_7bit, 7, 0x98aa7f2d4c83844f);
4371    test_bitslice!(test_bitslice_distances_6bit, 6, 0xf2f7ad7a37764b4c);
4372    test_bitslice!(test_bitslice_distances_5bit, 5, 0xae878d14973fb43f);
4373    test_bitslice!(test_bitslice_distances_4bit, 4, 0x8d6dbb8a6b19a4f8);
4374    test_bitslice!(test_bitslice_distances_3bit, 3, 0x8f56767236e58da2);
4375    test_bitslice!(test_bitslice_distances_2bit, 2, 0xb04f741a257b61af);
4376    test_bitslice!(test_bitslice_distances_1bit, 1, 0x820ea031c379eab5);
4377
4378    ///////////////////////////
4379    // Hamming Bit Distances //
4380    ///////////////////////////
4381
4382    fn test_hamming_distances<R>(dim_max: usize, trials_per_dim: usize, rng: &mut R)
4383    where
4384        R: Rng,
4385    {
4386        let dist: [i8; 2] = [-1, 1];
4387
4388        for dim in 0..dim_max {
4389            if !should_check_this_dimension(dim) {
4390                continue;
4391            }
4392
4393            let mut x_reference: Vec<i8> = vec![1; dim];
4394            let mut y_reference: Vec<i8> = vec![1; dim];
4395
4396            let mut x = BoxedBitSlice::<1, Binary>::new_boxed(dim);
4397            let mut y = BoxedBitSlice::<1, Binary>::new_boxed(dim);
4398
4399            for _ in 0..trials_per_dim {
4400                x_reference
4401                    .iter_mut()
4402                    .for_each(|i| *i = *dist.choose(rng).unwrap());
4403                y_reference
4404                    .iter_mut()
4405                    .for_each(|i| *i = *dist.choose(rng).unwrap());
4406
4407                // Fill the input slices with 1's so we can catch situations where we don't
4408                // correctly handle odd remaining elements.
4409                x.as_mut_slice().fill(u8::MAX);
4410                y.as_mut_slice().fill(u8::MAX);
4411
4412                for i in 0..dim {
4413                    x.set(i, x_reference[i].into()).unwrap();
4414                    y.set(i, y_reference[i].into()).unwrap();
4415                }
4416
4417                // We can check equality by evaluating the L2 distance between the reference
4418                // vectors.
4419                //
4420                // This is proportional to the Hamming distance by a factor of 4 (since the
4421                // distance betwwen +1 and -1 is 2 - and 2^2 = 4.
4422                let expected: MV<f32> =
4423                    diskann_vector::distance::SquaredL2::evaluate(&*x_reference, &*y_reference);
4424                let got: MV<u32> = Hamming::evaluate(x.reborrow(), y.reborrow()).unwrap();
4425                assert_eq!(4.0 * (got.into_inner() as f32), expected.into_inner());
4426            }
4427        }
4428
4429        let x = BoxedBitSlice::<1, Binary>::new_boxed(10);
4430        let y = BoxedBitSlice::<1, Binary>::new_boxed(11);
4431        assert!(Hamming::evaluate(x.reborrow(), y.reborrow()).is_err());
4432        assert!(Hamming::evaluate(y.reborrow(), x.reborrow()).is_err());
4433    }
4434
4435    #[test]
4436    fn test_hamming_distance() {
4437        let mut rng = StdRng::seed_from_u64(0x2160419161246d97);
4438        test_hamming_distances(MAX_DIM, TRIALS_PER_DIM, &mut rng);
4439    }
4440
4441    ///////////////////
4442    // Heterogeneous //
4443    ///////////////////
4444
4445    fn test_bit_transpose_distances<R>(
4446        dim_max: usize,
4447        trials_per_dim: usize,
4448        evaluate_ip: &dyn Fn(USlice<'_, 4, BitTranspose>, USlice<'_, 1>) -> MR,
4449        context: &str,
4450        rng: &mut R,
4451    ) where
4452        R: Rng,
4453    {
4454        let dist_4bit = {
4455            let domain = Unsigned::domain_const::<4>();
4456            Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
4457        };
4458
4459        let dist_1bit = {
4460            let domain = Unsigned::domain_const::<1>();
4461            Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
4462        };
4463
4464        for dim in 0..dim_max {
4465            if !should_check_this_dimension(dim) {
4466                continue;
4467            }
4468
4469            let mut x_reference: Vec<u8> = vec![0; dim];
4470            let mut y_reference: Vec<u8> = vec![0; dim];
4471
4472            let mut x = BoxedBitSlice::<4, Unsigned, BitTranspose>::new_boxed(dim);
4473            let mut y = BoxedBitSlice::<1, Unsigned, Dense>::new_boxed(dim);
4474
4475            for trial in 0..trials_per_dim {
4476                x_reference
4477                    .iter_mut()
4478                    .for_each(|i| *i = dist_4bit.sample(rng).try_into().unwrap());
4479                y_reference
4480                    .iter_mut()
4481                    .for_each(|i| *i = dist_1bit.sample(rng).try_into().unwrap());
4482
4483                // First - pre-set all the values in the bit-slices to 1.
4484                x.as_mut_slice().fill(u8::MAX);
4485                y.as_mut_slice().fill(u8::MAX);
4486
4487                for i in 0..dim {
4488                    x.set(i, x_reference[i].into()).unwrap();
4489                    y.set(i, y_reference[i].into()).unwrap();
4490                }
4491
4492                // Check IP
4493                let expected: MV<f32> =
4494                    diskann_vector::distance::InnerProduct::evaluate(&*x_reference, &*y_reference);
4495
4496                let got = evaluate_ip(x.reborrow(), y.reborrow());
4497
4498                // Integer computations should be exact.
4499                assert_eq!(
4500                    expected.into_inner(),
4501                    got.unwrap().into_inner() as f32,
4502                    "faild InnerProduct for dim = {}, trial = {} -- context {}",
4503                    dim,
4504                    trial,
4505                    context,
4506                );
4507            }
4508        }
4509
4510        let x = BoxedBitSlice::<4, Unsigned, BitTranspose>::new_boxed(10);
4511        let y = BoxedBitSlice::<1, Unsigned>::new_boxed(11);
4512        assert!(
4513            evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
4514            "context: {}",
4515            context
4516        );
4517
4518        let y = BoxedBitSlice::<1, Unsigned>::new_boxed(9);
4519        assert!(
4520            evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
4521            "context: {}",
4522            context
4523        );
4524    }
4525
4526    #[test]
4527    fn test_bit_transpose_distance() {
4528        let mut rng = StdRng::seed_from_u64(0xe20e26e926d4b853);
4529
4530        test_bit_transpose_distances(
4531            MAX_DIM,
4532            TRIALS_PER_DIM,
4533            &|x, y| InnerProduct::evaluate(x, y),
4534            "pure distance function",
4535            &mut rng,
4536        );
4537
4538        test_bit_transpose_distances(
4539            MAX_DIM,
4540            TRIALS_PER_DIM,
4541            &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
4542            "scalar",
4543            &mut rng,
4544        );
4545
4546        // Architecture Specific.
4547        #[cfg(target_arch = "x86_64")]
4548        if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
4549            test_bit_transpose_distances(
4550                MAX_DIM,
4551                TRIALS_PER_DIM,
4552                &|x, y| arch.run2(InnerProduct, x, y),
4553                "x86-64-v3",
4554                &mut rng,
4555            );
4556        }
4557
4558        // Architecture Specific.
4559        #[cfg(target_arch = "x86_64")]
4560        if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
4561            test_bit_transpose_distances(
4562                MAX_DIM,
4563                TRIALS_PER_DIM,
4564                &|x, y| arch.run2(InnerProduct, x, y),
4565                "x86-64-v4",
4566                &mut rng,
4567            );
4568        }
4569
4570        // Architecture Specific.
4571        #[cfg(target_arch = "aarch64")]
4572        if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4573            test_bit_transpose_distances(
4574                MAX_DIM,
4575                TRIALS_PER_DIM,
4576                &|x, y| arch.run2(InnerProduct, x, y),
4577                "neon",
4578                &mut rng,
4579            );
4580        }
4581    }
4582
4583    //////////
4584    // Full //
4585    //////////
4586
4587    fn test_full_distances<const NBITS: usize>(
4588        dim_max: usize,
4589        trials_per_dim: usize,
4590        evaluate_ip: &dyn Fn(&[f32], USlice<'_, NBITS>) -> MathematicalResult<f32>,
4591        context: &str,
4592        rng: &mut impl Rng,
4593    ) where
4594        Unsigned: Representation<NBITS>,
4595    {
4596        // let dist_float = Uniform::new_inclusive(-2.0f32, 2.0f32).unwrap();
4597        let dist_float = [-2.0, -1.0, 0.0, 1.0, 2.0];
4598        let dist_bit = {
4599            let domain = Unsigned::domain_const::<NBITS>();
4600            Uniform::new_inclusive(*domain.start(), *domain.end()).unwrap()
4601        };
4602
4603        for dim in 0..dim_max {
4604            if !should_check_this_dimension(dim) {
4605                continue;
4606            }
4607
4608            let mut x: Vec<f32> = vec![0.0; dim];
4609
4610            let mut y_reference: Vec<u8> = vec![0; dim];
4611            let mut y = BoxedBitSlice::<NBITS, Unsigned, Dense>::new_boxed(dim);
4612
4613            for trial in 0..trials_per_dim {
4614                x.iter_mut()
4615                    .for_each(|i| *i = *dist_float.choose(rng).unwrap());
4616                y_reference
4617                    .iter_mut()
4618                    .for_each(|i| *i = dist_bit.sample(rng).try_into().unwrap());
4619
4620                // First - pre-set all the values in the bit-slices to 1.
4621                y.as_mut_slice().fill(u8::MAX);
4622
4623                let mut expected = 0.0;
4624                for i in 0..dim {
4625                    y.set(i, y_reference[i].into()).unwrap();
4626                    expected += y_reference[i] as f32 * x[i];
4627                }
4628
4629                // Check IP
4630                let got = evaluate_ip(&x, y.reborrow()).unwrap();
4631
4632                // Integer computations should be exact.
4633                assert_eq!(
4634                    expected,
4635                    got.into_inner(),
4636                    "faild InnerProduct for dim = {}, trial = {} -- context {}",
4637                    dim,
4638                    trial,
4639                    context,
4640                );
4641
4642                // Ensure that using the `Scalar` architecture providers the same
4643                // results.
4644                let scalar: MV<f32> = InnerProduct
4645                    .run(diskann_wide::arch::Scalar, x.as_slice(), y.reborrow())
4646                    .unwrap();
4647                assert_eq!(got.into_inner(), scalar.into_inner());
4648            }
4649        }
4650
4651        // Error Checking
4652        let x = vec![0.0; 10];
4653        let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(11);
4654        assert!(
4655            evaluate_ip(x.as_slice(), y.reborrow()).is_err(),
4656            "context: {}",
4657            context
4658        );
4659
4660        let y = BoxedBitSlice::<NBITS, Unsigned>::new_boxed(9);
4661        assert!(
4662            evaluate_ip(x.as_slice(), y.reborrow()).is_err(),
4663            "context: {}",
4664            context
4665        );
4666    }
4667
4668    macro_rules! test_full {
4669        ($name:ident, $nbits:literal, $seed:literal) => {
4670            #[test]
4671            fn $name() {
4672                let mut rng = StdRng::seed_from_u64($seed);
4673
4674                test_full_distances::<$nbits>(
4675                    MAX_DIM,
4676                    TRIALS_PER_DIM,
4677                    &|x, y| InnerProduct::evaluate(x, y),
4678                    "pure distance function",
4679                    &mut rng,
4680                );
4681
4682                test_full_distances::<$nbits>(
4683                    MAX_DIM,
4684                    TRIALS_PER_DIM,
4685                    &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
4686                    "scalar",
4687                    &mut rng,
4688                );
4689
4690                // Architecture Specific.
4691                #[cfg(target_arch = "x86_64")]
4692                if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
4693                    test_full_distances::<$nbits>(
4694                        MAX_DIM,
4695                        TRIALS_PER_DIM,
4696                        &|x, y| arch.run2(InnerProduct, x, y),
4697                        "x86-64-v3",
4698                        &mut rng,
4699                    );
4700                }
4701
4702                #[cfg(target_arch = "x86_64")]
4703                if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
4704                    test_full_distances::<$nbits>(
4705                        MAX_DIM,
4706                        TRIALS_PER_DIM,
4707                        &|x, y| arch.run2(InnerProduct, x, y),
4708                        "x86-64-v4",
4709                        &mut rng,
4710                    );
4711                }
4712
4713                #[cfg(target_arch = "aarch64")]
4714                if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4715                    test_full_distances::<$nbits>(
4716                        MAX_DIM,
4717                        TRIALS_PER_DIM,
4718                        &|x, y| arch.run2(InnerProduct, x, y),
4719                        "neon",
4720                        &mut rng,
4721                    );
4722                }
4723            }
4724        };
4725    }
4726
4727    test_full!(test_full_distance_1bit, 1, 0xe20e26e926d4b853);
4728    test_full!(test_full_distance_2bit, 2, 0xae9542700aecbf68);
4729    test_full!(test_full_distance_3bit, 3, 0xfffd04b26bb6068c);
4730    test_full!(test_full_distance_4bit, 4, 0x86db49fd1a1704ba);
4731    test_full!(test_full_distance_5bit, 5, 0x3a35dc7fa7931c41);
4732    test_full!(test_full_distance_6bit, 6, 0x1f69de79e418d336);
4733    test_full!(test_full_distance_7bit, 7, 0x3fcf17b82dadc5ab);
4734    test_full!(test_full_distance_8bit, 8, 0x85dcaf48b1399db2);
4735
4736    ///////////////////////////////////////////
4737    // Heterogeneous: USlice<8> × USlice<M> //
4738    ///////////////////////////////////////////
4739
4740    /// Helper that builds vectors from a fill function and asserts the
4741    /// inner product matches.
4742    struct HetCase<const M: usize> {
4743        x_vals: Vec<i64>,
4744        y_vals: Vec<i64>,
4745    }
4746
4747    impl<const M: usize> HetCase<M>
4748    where
4749        Unsigned: Representation<M>,
4750    {
4751        fn new(dim: usize, fill: impl FnMut(usize) -> (i64, i64)) -> Self {
4752            let (x_vals, y_vals) = (0..dim).map(fill).unzip();
4753            Self { x_vals, y_vals }
4754        }
4755
4756        fn check_with(
4757            &self,
4758            label: impl Display,
4759            evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4760        ) {
4761            let dim = self.x_vals.len();
4762            let mut x = BoxedBitSlice::<8, Unsigned>::new_boxed(dim);
4763            let mut y = BoxedBitSlice::<M, Unsigned>::new_boxed(dim);
4764            // Pre-fill with 0xFF to catch trailing-element bugs.
4765            x.as_mut_slice().fill(u8::MAX);
4766            y.as_mut_slice().fill(u8::MAX);
4767            for (i, (&xv, &yv)) in self.x_vals.iter().zip(&self.y_vals).enumerate() {
4768                x.set(i, xv).unwrap();
4769                y.set(i, yv).unwrap();
4770            }
4771            let expected: u32 = self
4772                .x_vals
4773                .iter()
4774                .zip(&self.y_vals)
4775                .map(|(&a, &b)| a as u32 * b as u32)
4776                .sum();
4777            let got = evaluate(x.reborrow(), y.reborrow()).unwrap().into_inner();
4778            assert_eq!(expected, got, "{} failed for dim = {}", label, dim);
4779        }
4780    }
4781
4782    /// Fuzz test helper: random vectors across many dimensions.
4783    fn fuzz_heterogeneous_ip<const M: usize>(
4784        dim_max: usize,
4785        trials_per_dim: usize,
4786        max_val: i64,
4787        evaluate_ip: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4788        context: &str,
4789        rng: &mut impl Rng,
4790    ) where
4791        Unsigned: Representation<M>,
4792    {
4793        let dist_8bit = Uniform::new_inclusive(0i64, 255i64).unwrap();
4794        let dist_mbit = Uniform::new_inclusive(0i64, max_val).unwrap();
4795
4796        for dim in 0..dim_max {
4797            for trial in 0..trials_per_dim {
4798                HetCase::<M>::new(dim, |_| {
4799                    (dist_8bit.sample(&mut *rng), dist_mbit.sample(&mut *rng))
4800                })
4801                .check_with(
4802                    lazy_format!("IP(8,{}) dim={dim}, trial={trial} -- {context}", M),
4803                    evaluate_ip,
4804                );
4805            }
4806
4807            // Length mismatch → error.
4808            let x = BoxedBitSlice::<8, Unsigned>::new_boxed(dim);
4809            let y = BoxedBitSlice::<M, Unsigned>::new_boxed(dim + 1);
4810            assert!(
4811                evaluate_ip(x.reborrow(), y.reborrow()).is_err(),
4812                "context: {}",
4813                context,
4814            );
4815        }
4816    }
4817
4818    /// All values at maximum: x[i] = 255, y[i] = max_val.
4819    /// Confirms no i16 saturation in vpmaddubsw.
4820    fn het_test_max_values<const M: usize>(
4821        max_val: i64,
4822        context: &str,
4823        evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4824    ) where
4825        Unsigned: Representation<M>,
4826    {
4827        let dims = [127, 128, 129, 255, 256, 512, 768, 896, 3072];
4828        for &dim in &dims {
4829            let case = HetCase::<M>::new(dim, |_| (255, max_val));
4830            case.check_with(lazy_format!("max-value {context} dim={dim}"), evaluate);
4831        }
4832    }
4833
4834    /// Known-answer tests to catch bit-ordering bugs.
4835    fn het_test_known_answers<const M: usize>(
4836        max_val: i64,
4837        evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4838    ) where
4839        Unsigned: Representation<M>,
4840    {
4841        // _mm256_addubs_epi8 unsigned treatment: x[i] = 200 (> 127), y[i] = max_val.
4842        // Correct: 200 × max_val per element.
4843        HetCase::<M>::new(64, |_| (200, max_val)).check_with("vpmaddubsw operand-order", evaluate);
4844
4845        // Ascending x, constant y.
4846        let y_val = (max_val / 2).max(1);
4847        HetCase::<M>::new(128, |i| ((i % 256) as i64, y_val))
4848            .check_with("ascending-x constant-y", evaluate);
4849
4850        // Single element (pure scalar fallback).
4851        HetCase::<M>::new(1, |_| (200, max_val)).check_with("single element", evaluate);
4852    }
4853
4854    /// Exhaustive edge-case coverage.
4855    fn het_test_edge_cases<const M: usize>(
4856        max_val: i64,
4857        block_size: usize,
4858        evaluate: &dyn Fn(USlice<'_, 8>, USlice<'_, M>) -> MR,
4859    ) where
4860        Unsigned: Representation<M>,
4861    {
4862        let y_half = (max_val / 2).max(1);
4863
4864        // One side zero.
4865        HetCase::<M>::new(64, |_| (0, max_val)).check_with("x-zero y-nonzero", evaluate);
4866        HetCase::<M>::new(64, |_| (255, 0)).check_with("y-zero x-nonzero", evaluate);
4867
4868        // Every dimension from 0..block_size+1 (scalar fallback boundary).
4869        for dim in 0..=(block_size + 1) {
4870            HetCase::<M>::new(dim, |_| (3, y_half)).check_with("uniform fill", evaluate);
4871        }
4872
4873        // Exact block boundaries.
4874        for &dim in &[block_size, 2 * block_size, 4 * block_size, 8 * block_size] {
4875            HetCase::<M>::new(dim, |_| (100, max_val)).check_with("exact block boundary", evaluate);
4876        }
4877
4878        // x varies, y constant.
4879        HetCase::<M>::new(300, |i| ((i % 256) as i64, 1))
4880            .check_with("x-varies y-constant", evaluate);
4881
4882        // x constant, y varies.
4883        HetCase::<M>::new(300, |i| (1, (i as i64) % (max_val + 1)))
4884            .check_with("x-constant y-varies", evaluate);
4885
4886        // Alternating pattern.
4887        HetCase::<M>::new(128, |i| if i % 2 == 0 { (255, max_val) } else { (0, 0) })
4888            .check_with("alternating pattern", evaluate);
4889
4890        // Opposite alternating pattern.
4891        HetCase::<M>::new(128, |i| if i % 2 == 0 { (0, 0) } else { (255, max_val) })
4892            .check_with("opposite alternating", evaluate);
4893
4894        // Large accumulation check for overflow.
4895        HetCase::<M>::new(1024, |_| (255, max_val)).check_with("large accumulation", evaluate);
4896
4897        // x > 127 sweep (vpmaddubsw unsigned treatment).
4898        for x_val in [128i64, 170, 200, 240, 255] {
4899            HetCase::<M>::new(block_size, move |_| (x_val, y_half))
4900                .check_with(lazy_format!("x > 127 (x_val={x_val})"), evaluate);
4901        }
4902
4903        // Dim = block_size - 1 (no full block, all scalar).
4904        HetCase::<M>::new(block_size - 1, |i| {
4905            (
4906                ((i * 7 + 3) % 256) as i64,
4907                ((i * 11 + 5) as i64) % (max_val + 1),
4908            )
4909        })
4910        .check_with("dim=block_size-1 (all scalar)", evaluate);
4911
4912        // 4× unroll boundary exercises.
4913        let unroll4 = 4 * block_size;
4914        for &dim in &[
4915            unroll4,
4916            unroll4 + 1,
4917            unroll4 + block_size,
4918            unroll4 + block_size + 1,
4919        ] {
4920            HetCase::<M>::new(dim, |i| {
4921                (((i + 1) % 256) as i64, ((i + 1) as i64) % (max_val + 1))
4922            })
4923            .check_with("unroll boundary", evaluate);
4924        }
4925    }
4926
4927    macro_rules! heterogeneous_ip_tests_8xM {
4928        (
4929            mod_name: $mod:ident,
4930            M: $M:literal,
4931            max_val: $max_val:literal,
4932            block_size: $block_size:literal,
4933            seed_fuzz: $seed_fuzz:literal,
4934        ) => {
4935            mod $mod {
4936                use super::*;
4937
4938                #[test]
4939                fn all_ip_dispatches() {
4940                    let mut rng = StdRng::seed_from_u64($seed_fuzz);
4941
4942                    fuzz_heterogeneous_ip::<$M>(
4943                        MAX_DIM,
4944                        TRIALS_PER_DIM,
4945                        $max_val,
4946                        &|x, y| InnerProduct::evaluate(x, y),
4947                        "pure distance function",
4948                        &mut rng,
4949                    );
4950                    fuzz_heterogeneous_ip::<$M>(
4951                        MAX_DIM,
4952                        TRIALS_PER_DIM,
4953                        $max_val,
4954                        &|x, y| diskann_wide::arch::Scalar::new().run2(InnerProduct, x, y),
4955                        "scalar arch",
4956                        &mut rng,
4957                    );
4958                    #[cfg(target_arch = "x86_64")]
4959                    if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
4960                        fuzz_heterogeneous_ip::<$M>(
4961                            MAX_DIM,
4962                            TRIALS_PER_DIM,
4963                            $max_val,
4964                            &|x, y| arch.run2(InnerProduct, x, y),
4965                            "x86-64-v3",
4966                            &mut rng,
4967                        );
4968                    }
4969                    #[cfg(target_arch = "x86_64")]
4970                    if let Some(arch) = diskann_wide::arch::x86_64::V4::new_checked_miri() {
4971                        fuzz_heterogeneous_ip::<$M>(
4972                            MAX_DIM,
4973                            TRIALS_PER_DIM,
4974                            $max_val,
4975                            &|x, y| arch.run2(InnerProduct, x, y),
4976                            "x86-64-v4",
4977                            &mut rng,
4978                        );
4979                    }
4980                    #[cfg(target_arch = "aarch64")]
4981                    if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
4982                        fuzz_heterogeneous_ip::<$M>(
4983                            MAX_DIM,
4984                            TRIALS_PER_DIM,
4985                            $max_val,
4986                            &|x, y| arch.run2(InnerProduct, x, y),
4987                            "neon",
4988                            &mut rng,
4989                        );
4990                    }
4991                }
4992
4993                #[test]
4994                fn max_values() {
4995                    het_test_max_values::<$M>($max_val, "dispatch", &|x, y| {
4996                        InnerProduct::evaluate(x, y)
4997                    });
4998                    #[cfg(target_arch = "x86_64")]
4999                    if let Some(arch) = diskann_wide::arch::x86_64::V3::new_checked() {
5000                        het_test_max_values::<$M>($max_val, "V3", &|x, y| {
5001                            arch.run2(InnerProduct, x, y)
5002                        });
5003                    }
5004                    #[cfg(target_arch = "aarch64")]
5005                    if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
5006                        het_test_max_values::<$M>($max_val, "neon", &|x, y| {
5007                            arch.run2(InnerProduct, x, y)
5008                        });
5009                    }
5010                }
5011
5012                #[test]
5013                fn known_answers() {
5014                    het_test_known_answers::<$M>($max_val, &|x, y| InnerProduct::evaluate(x, y));
5015                    #[cfg(target_arch = "aarch64")]
5016                    if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
5017                        het_test_known_answers::<$M>($max_val, &|x, y| {
5018                            arch.run2(InnerProduct, x, y)
5019                        });
5020                    }
5021                }
5022
5023                #[test]
5024                fn edge_cases() {
5025                    het_test_edge_cases::<$M>($max_val, $block_size, &|x, y| {
5026                        InnerProduct::evaluate(x, y)
5027                    });
5028                    #[cfg(target_arch = "aarch64")]
5029                    if let Some(arch) = diskann_wide::arch::aarch64::Neon::new_checked() {
5030                        het_test_edge_cases::<$M>($max_val, $block_size, &|x, y| {
5031                            arch.run2(InnerProduct, x, y)
5032                        });
5033                    }
5034                }
5035            }
5036        };
5037    }
5038
5039    heterogeneous_ip_tests_8xM! {
5040        mod_name: heterogeneous_ip_8x4,
5041        M: 4,
5042        max_val: 15,
5043        block_size: 32,
5044        seed_fuzz: 0xd3a7f1c09b2e4856,
5045    }
5046
5047    heterogeneous_ip_tests_8xM! {
5048        mod_name: heterogeneous_ip_8x2,
5049        M: 2,
5050        max_val: 3,
5051        block_size: 64,
5052        seed_fuzz: 0x82c4a6e809f1d3b5,
5053    }
5054
5055    heterogeneous_ip_tests_8xM! {
5056        mod_name: heterogeneous_ip_8x1,
5057        M: 1,
5058        max_val: 1,
5059        block_size: 32,
5060        seed_fuzz: 0x1b17_a5e7c2d0f839,
5061    }
5062}