|
67 | 67 | //! | `USlice<1>` | `USlice<1>` | `MV<u32>` | Optimized | Optimized | Uses V3 | Optimized | |
68 | 68 | //! | `USlice<2>` | `USlice<2>` | `MV<u32>` | Fallback | Yes | Yes | Fallback | |
69 | 69 | //! | `USlice<3>` | `USlice<3>` | `MV<u32>` | Fallback | No | Uses V3 | Fallback | |
70 | | -//! | `USlice<4>` | `USlice<4>` | `MV<u32>` | Fallback | Yes | Uses V3 | Fallback | |
| 70 | +//! | `USlice<4>` | `USlice<4>` | `MV<u32>` | Fallback | Yes | Uses V3 | Optimized | |
71 | 71 | //! | `USlice<5>` | `USlice<5>` | `MV<u32>` | Fallback | No | Uses V3 | Fallback | |
72 | 72 | //! | `USlice<6>` | `USlice<6>` | `MV<u32>` | Fallback | No | Uses V3 | Fallback | |
73 | 73 | //! | `USlice<7>` | `USlice<7>` | `MV<u32>` | Fallback | No | Uses V3 | Fallback | |
@@ -114,6 +114,9 @@ use diskann_wide::{ |
114 | 114 | SIMDCast, SIMDDotProduct, SIMDMulAdd, SIMDReinterpret, SIMDSumTree, SIMDVector, |
115 | 115 | }; |
116 | 116 |
|
| 117 | +#[cfg(target_arch = "aarch64")] |
| 118 | +use diskann_wide::{SIMDDotProduct, SIMDSumTree, SIMDVector}; |
| 119 | + |
117 | 120 | use super::{Binary, BitSlice, BitTranspose, Dense, Representation, Unsigned}; |
118 | 121 | use crate::distances::{Hamming, InnerProduct, MV, MathematicalResult, SquaredL2, check_lengths}; |
119 | 122 |
|
@@ -1565,6 +1568,103 @@ impl Target2<diskann_wide::arch::x86_64::V3, MathematicalResult<u32>, USlice<'_, |
1565 | 1568 | } |
1566 | 1569 | } |
1567 | 1570 |
|
| 1571 | +#[cfg(target_arch = "aarch64")] |
| 1572 | +impl |
| 1573 | + Target2< |
| 1574 | + diskann_wide::arch::aarch64::Neon, |
| 1575 | + MathematicalResult<u32>, |
| 1576 | + USlice<'_, 4>, |
| 1577 | + USlice<'_, 4>, |
| 1578 | + > for InnerProduct |
| 1579 | +{ |
| 1580 | + #[inline(always)] |
| 1581 | + fn run( |
| 1582 | + self, |
| 1583 | + arch: diskann_wide::arch::aarch64::Neon, |
| 1584 | + x: USlice<'_, 4>, |
| 1585 | + y: USlice<'_, 4>, |
| 1586 | + ) -> MathematicalResult<u32> { |
| 1587 | + let len = check_lengths!(x, y)?; |
| 1588 | + |
| 1589 | + diskann_wide::alias!(u8s = <diskann_wide::arch::aarch64::Neon>::u8x16); |
| 1590 | + diskann_wide::alias!(u32s = <diskann_wide::arch::aarch64::Neon>::u32x4); |
| 1591 | + |
| 1592 | + let px_u8: *const u8 = x.as_ptr().cast(); |
| 1593 | + let py_u8: *const u8 = y.as_ptr().cast(); |
| 1594 | + |
| 1595 | + let mut i = 0; |
| 1596 | + let mut s: u32 = 0; |
| 1597 | + |
| 1598 | + // number of bytes over the underlying slice |
| 1599 | + let bytes = len / 2; |
| 1600 | + if i < bytes { |
| 1601 | + let mut s0 = u32s::default(arch); |
| 1602 | + let mut s1 = u32s::default(arch); |
| 1603 | + let mask = u8s::splat(arch, 0x0f); |
| 1604 | + while i + 16 <= bytes { |
| 1605 | + // SAFETY: load simd loads 16 bytes from offset `i` |
| 1606 | + // we have already verified that i + 16 <= bytes |
| 1607 | + let x_vec = unsafe { u8s::load_simd(arch, px_u8.add(i)) }; |
| 1608 | + // SAFETY: same logic applies for y and same conditions hold |
| 1609 | + // since the lengths are element sizes of x and y are equal. |
| 1610 | + let y_vec = unsafe { u8s::load_simd(arch, py_u8.add(i)) }; |
| 1611 | + |
| 1612 | + // compute dot product for lower 4 bits |
| 1613 | + // each set of 4 results is reduced to one lane |
| 1614 | + let first_x: u8s = x_vec & mask; |
| 1615 | + let first_y: u8s = y_vec & mask; |
| 1616 | + s0 = s0.dot_simd(first_x, first_y); |
| 1617 | + |
| 1618 | + // repeat for upper 4 bits |
| 1619 | + let second_x: u8s = (x_vec >> 4) & mask; |
| 1620 | + let second_y: u8s = (y_vec >> 4) & mask; |
| 1621 | + s1 = s1.dot_simd(second_x, second_y); |
| 1622 | + // repeat for next block |
| 1623 | + i += 16; |
| 1624 | + } |
| 1625 | + |
| 1626 | + let remaining_bytes = len / 2 - i; |
| 1627 | + |
| 1628 | + if remaining_bytes > 0 { |
| 1629 | + let remaining_vec1 = remaining_bytes.min(16); |
| 1630 | + // SAFETY: up to `remaining_bytes` can be loaded from the offset `i` |
| 1631 | + // since the floor division ensures that we only read bytes that are fully |
| 1632 | + // packed with elements from the slice. |
| 1633 | + let x_vec = unsafe { u8s::load_simd_first(arch, px_u8.add(i), remaining_vec1) }; |
| 1634 | + // SAFETY: same logic applies for y and same conditions hold |
| 1635 | + // since the lengths are element sizes of x and y are equal. |
| 1636 | + let y_vec = unsafe { u8s::load_simd_first(arch, py_u8.add(i), remaining_vec1) }; |
| 1637 | + |
| 1638 | + let first_x: u8s = x_vec & mask; |
| 1639 | + let first_y: u8s = y_vec & mask; |
| 1640 | + s0 = s0.dot_simd(first_x, first_y); |
| 1641 | + |
| 1642 | + // compute dot product for upper 4 bits, result is stored as 32x4 |
| 1643 | + let second_x: u8s = (x_vec >> 4) & mask; |
| 1644 | + let second_y: u8s = (y_vec >> 4) & mask; |
| 1645 | + s1 = s1.dot_simd(second_x, second_y); |
| 1646 | + i += remaining_bytes; |
| 1647 | + } |
| 1648 | + s = (s0 + s1).sum_tree(); |
| 1649 | + } |
| 1650 | + // Convert bytes to nibble indexes. |
| 1651 | + i *= 2; |
| 1652 | + |
| 1653 | + // Deal with the remainder the slow way (at most 1 element). |
| 1654 | + debug_assert!(len - i <= 1); |
| 1655 | + |
| 1656 | + if i != len { |
| 1657 | + // SAFETY: `i` is guaranteed to be less than `x.len()`. |
| 1658 | + let ix = unsafe { x.get_unchecked(i) } as i32; |
| 1659 | + // SAFETY: `i` is guaranteed to be less than `y.len()`. |
| 1660 | + let iy = unsafe { y.get_unchecked(i) } as i32; |
| 1661 | + s += (ix * iy) as u32; |
| 1662 | + } |
| 1663 | + |
| 1664 | + Ok(MV::new(s)) |
| 1665 | + } |
| 1666 | +} |
| 1667 | + |
1568 | 1668 | /// Compute the inner product between bitvectors `x` and `y`. |
1569 | 1669 | /// |
1570 | 1670 | /// Returns an error if the arguments have different lengths. |
@@ -2108,7 +2208,6 @@ retarget!( |
2108 | 2208 | 7, |
2109 | 2209 | 6, |
2110 | 2210 | 5, |
2111 | | - 4, |
2112 | 2211 | 3, |
2113 | 2212 | 2, |
2114 | 2213 | (8, 4), |
@@ -2943,7 +3042,7 @@ mod tests { |
2943 | 3042 | // Need a higher miri-amount due to the larget block size |
2944 | 3043 | (Key::new(4, X86_64_V3), Bounds::new(256, 150)), |
2945 | 3044 | (Key::new(4, X86_64_V4), Bounds::new(512, 300)), |
2946 | | - (Key::new(4, Neon), Bounds::new(64, 64)), |
| 3045 | + (Key::new(4, Neon), Bounds::new(128, 128)), |
2947 | 3046 | (Key::new(5, Scalar), Bounds::new(64, 64)), |
2948 | 3047 | (Key::new(5, X86_64_V3), Bounds::new(256, 96)), |
2949 | 3048 | (Key::new(5, X86_64_V4), Bounds::new(256, 96)), |
|
0 commit comments