use std::arch::x86_64::*;
use super::{
InstructionTier, KernelArm, KernelBackendTag, KernelTable, MAX_DOT_I8_DIMENSION, scalar,
};
const I8_LANES: usize = 32;
const I8_UNROLL: usize = 4; const F32_LANES: usize = 8;
const HAMMING_LANES: usize = 32;
const HAMMING_UNROLL: usize = 4;
pub(super) fn table() -> KernelTable {
KernelTable {
backend: KernelBackendTag::Avx2,
arm: KernelArm::Avx2,
tier: InstructionTier::Avx2,
dot_i8,
dot_f32,
dot_f16,
hamming_u1,
dot_i8_batch,
hamming_u1_batch,
dot_bit4: scalar::dot_bit4,
dot_bit4_prepared: scalar::dot_bit4_prepared,
dot_bit4_batch: scalar::dot_bit4_batch,
score_bit4_prepared_batch: scalar::score_bit4_prepared_batch,
score_bit4_ptrs: scalar::score_bit4_ptrs,
}
}
#[inline]
fn dot_i8(a: &[i8], b: &[i8]) -> i32 {
debug_assert_eq!(a.len(), b.len(), "kernel lengths must be pre-validated");
debug_assert!(
a.len() <= MAX_DOT_I8_DIMENSION,
"i8 dimension must be pre-validated"
);
unsafe { dot_i8_inner(a, b) }
}
#[target_feature(enable = "avx2")]
unsafe fn dot_i8_inner(a: &[i8], b: &[i8]) -> i32 {
unsafe {
let mut biased0 = _mm256_setzero_si256();
let mut biased1 = _mm256_setzero_si256();
let mut biased2 = _mm256_setzero_si256();
let mut biased3 = _mm256_setzero_si256();
let mut correction0 = _mm256_setzero_si256();
let mut correction1 = _mm256_setzero_si256();
let mut correction2 = _mm256_setzero_si256();
let mut correction3 = _mm256_setzero_si256();
let processed = a.len() / I8_LANES * I8_LANES;
let mut base = 0_usize;
let mut block = 0_usize;
while base < processed {
let a_vector = _mm256_loadu_si256(a.as_ptr().wrapping_add(base).cast());
let b_vector = _mm256_loadu_si256(b.as_ptr().wrapping_add(base).cast());
let (biased, correction) = signed_i8_chunk(a_vector, b_vector);
match block % I8_UNROLL {
0 => {
biased0 = _mm256_add_epi32(biased0, biased);
correction0 = _mm256_add_epi32(correction0, correction);
}
1 => {
biased1 = _mm256_add_epi32(biased1, biased);
correction1 = _mm256_add_epi32(correction1, correction);
}
2 => {
biased2 = _mm256_add_epi32(biased2, biased);
correction2 = _mm256_add_epi32(correction2, correction);
}
_ => {
biased3 = _mm256_add_epi32(biased3, biased);
correction3 = _mm256_add_epi32(correction3, correction);
}
}
base += I8_LANES;
block += 1;
}
let biased = _mm256_add_epi32(
_mm256_add_epi32(biased0, biased1),
_mm256_add_epi32(biased2, biased3),
);
let correction = _mm256_add_epi32(
_mm256_add_epi32(correction0, correction1),
_mm256_add_epi32(correction2, correction3),
);
let sum = hsum_i32(biased) - 128 * hsum_i32(correction);
add_i8_tail(sum, a, b, processed)
}
}
#[target_feature(enable = "avx2")]
unsafe fn signed_i8_chunk(a: __m256i, b: __m256i) -> (__m256i, __m256i) {
let bias = _mm256_set1_epi8(i8::MIN);
let even_bytes = _mm256_set1_epi16(0x00ff);
let odd_bytes = _mm256_set1_epi16(-256);
let ones = _mm256_set1_epi16(1);
let biased_a = _mm256_xor_si256(a, bias);
let even_products = _mm256_maddubs_epi16(_mm256_and_si256(biased_a, even_bytes), b);
let odd_products = _mm256_maddubs_epi16(_mm256_and_si256(biased_a, odd_bytes), b);
let biased = _mm256_add_epi32(
_mm256_madd_epi16(even_products, ones),
_mm256_madd_epi16(odd_products, ones),
);
let low = _mm256_cvtepi8_epi16(_mm256_castsi256_si128(b));
let high = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(b, 1));
let correction = _mm256_add_epi32(_mm256_madd_epi16(low, ones), _mm256_madd_epi16(high, ones));
(biased, correction)
}
fn add_i8_tail(sum: i32, a: &[i8], b: &[i8], processed: usize) -> i32 {
let Some(a_tail) = a.get(processed..) else {
return sum;
};
let Some(b_tail) = b.get(processed..) else {
return sum;
};
sum + a_tail
.iter()
.zip(b_tail)
.map(|(&left, &right)| i32::from(left) * i32::from(right))
.sum::<i32>()
}
#[inline]
fn dot_f32(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "kernel lengths must be pre-validated");
unsafe { dot_f32_inner(a, b) }
}
#[target_feature(enable = "avx2")]
unsafe fn dot_f32_inner(a: &[f32], b: &[f32]) -> f32 {
let mut sum = 0.0_f32;
let mut a_chunks = a.chunks_exact(F32_LANES);
let mut b_chunks = b.chunks_exact(F32_LANES);
for (a_chunk, b_chunk) in a_chunks.by_ref().zip(b_chunks.by_ref()) {
let mut products = [0.0_f32; F32_LANES];
unsafe {
let lanes = _mm256_mul_ps(
_mm256_loadu_ps(a_chunk.as_ptr()),
_mm256_loadu_ps(b_chunk.as_ptr()),
);
_mm256_storeu_ps(products.as_mut_ptr(), lanes);
}
for product in products {
sum += product;
}
}
for (&left, &right) in a_chunks.remainder().iter().zip(b_chunks.remainder()) {
sum += left * right;
}
sum
}
#[inline]
fn dot_f16(a: &[u16], b: &[u16]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "kernel lengths must be pre-validated");
unsafe { dot_f16_inner(a, b) }
}
#[target_feature(enable = "avx2")]
unsafe fn dot_f16_inner(a: &[u16], b: &[u16]) -> f32 {
let mut sum = 0.0_f32;
let mut a_chunks = a.chunks_exact(F32_LANES);
let mut b_chunks = b.chunks_exact(F32_LANES);
for (a_chunk, b_chunk) in a_chunks.by_ref().zip(b_chunks.by_ref()) {
let mut converted_a = [0.0_f32; F32_LANES];
let mut converted_b = [0.0_f32; F32_LANES];
for ((left_out, right_out), (&left, &right)) in converted_a
.iter_mut()
.zip(converted_b.iter_mut())
.zip(a_chunk.iter().zip(b_chunk))
{
*left_out = scalar::f16_to_f32(left);
*right_out = scalar::f16_to_f32(right);
}
let mut products = [0.0_f32; F32_LANES];
unsafe {
let lanes = _mm256_mul_ps(
_mm256_loadu_ps(converted_a.as_ptr()),
_mm256_loadu_ps(converted_b.as_ptr()),
);
_mm256_storeu_ps(products.as_mut_ptr(), lanes);
}
for product in products {
sum += product;
}
}
for (&left, &right) in a_chunks.remainder().iter().zip(b_chunks.remainder()) {
sum += scalar::f16_to_f32(left) * scalar::f16_to_f32(right);
}
sum
}
#[inline]
fn hamming_u1(a: &[u8], b: &[u8]) -> u32 {
debug_assert_eq!(a.len(), b.len(), "kernel lengths must be pre-validated");
unsafe { hamming_u1_inner(a, b) }
}
#[target_feature(enable = "avx2,popcnt")]
unsafe fn hamming_u1_inner(a: &[u8], b: &[u8]) -> u32 {
unsafe {
let processed = a.len() / HAMMING_LANES * HAMMING_LANES;
let mut sums = [0_u32; HAMMING_UNROLL];
let mut base = 0_usize;
let mut block = 0_usize;
while base < processed {
let left = _mm256_loadu_si256(a.as_ptr().wrapping_add(base).cast());
let right = _mm256_loadu_si256(b.as_ptr().wrapping_add(base).cast());
let xor = _mm256_xor_si256(left, right);
let mut words = [0_u64; 4];
_mm256_storeu_si256(words.as_mut_ptr().cast(), xor);
let count: u32 = words.into_iter().map(u64::count_ones).sum();
if let Some(slot) = sums.get_mut(block % HAMMING_UNROLL) {
*slot += count;
}
base += HAMMING_LANES;
block += 1;
}
let mut sum: u32 = sums.into_iter().sum();
if let (Some(a_tail), Some(b_tail)) = (a.get(processed..), b.get(processed..)) {
sum += scalar::hamming_u1(a_tail, b_tail);
}
sum
}
}
fn dot_i8_batch(q: &[i8], rows: &[i8], d: usize, out: &mut [i32]) {
debug_assert_eq!(q.len(), d, "query dimension must be pre-validated");
debug_assert!(
d <= MAX_DOT_I8_DIMENSION,
"i8 dimension must be pre-validated"
);
debug_assert_eq!(
rows.len(),
d.saturating_mul(out.len()),
"batch shape must be pre-validated"
);
if d == 0 {
out.fill(0);
return;
}
for (row, result) in rows.chunks_exact(d).zip(out.iter_mut()) {
*result = dot_i8(q, row);
}
}
fn hamming_u1_batch(q: &[u8], rows: &[u8], d_bytes: usize, out: &mut [u32]) {
debug_assert_eq!(q.len(), d_bytes, "query dimension must be pre-validated");
debug_assert_eq!(
rows.len(),
d_bytes.saturating_mul(out.len()),
"batch shape must be pre-validated"
);
if d_bytes == 0 {
out.fill(0);
return;
}
for (row, result) in rows.chunks_exact(d_bytes).zip(out.iter_mut()) {
*result = hamming_u1(q, row);
}
}
#[target_feature(enable = "avx2")]
unsafe fn hsum_i32(vector: __m256i) -> i32 {
let mut lanes = [0_i32; 8];
unsafe { _mm256_storeu_si256(lanes.as_mut_ptr().cast(), vector) };
lanes.into_iter().sum()
}