use crate::adsampling::ADSamplingPruner;
use crate::common::{H_DIM_SIZE, KnnCandidate};
use crate::distance::l2_squared;
use std::mem::MaybeUninit;
fn collect_survivors(distances: &[f32], threshold: f32, positions: &mut Vec<u32>) -> usize {
positions.clear();
positions.reserve(distances.len());
let count = fill_survivors(distances, threshold, positions.spare_capacity_mut());
unsafe { positions.set_len(count) };
count
}
fn fill_survivors_scalar(distances: &[f32], threshold: f32, out: &mut [MaybeUninit<u32>]) -> usize {
let mut count = 0usize;
for (i, &dist) in distances.iter().enumerate() {
unsafe { out.get_unchecked_mut(count).write(i as u32) };
count += (dist < threshold) as usize;
}
count
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
fn fill_survivors(distances: &[f32], threshold: f32, out: &mut [MaybeUninit<u32>]) -> usize {
fill_survivors_scalar(distances, threshold, out)
}
#[cfg(target_arch = "x86_64")]
fn fill_survivors(distances: &[f32], threshold: f32, out: &mut [MaybeUninit<u32>]) -> usize {
if is_x86_feature_detected!("avx512f") {
unsafe { fill_survivors_avx512(distances, threshold, out) }
} else if is_x86_feature_detected!("avx2") {
unsafe { fill_survivors_avx2(distances, threshold, out) }
} else {
fill_survivors_scalar(distances, threshold, out)
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn fill_survivors_avx2(
distances: &[f32],
threshold: f32,
out: &mut [MaybeUninit<u32>],
) -> usize {
use std::arch::x86_64::*;
unsafe {
let n = distances.len();
let simd_n = n & !7;
let mut count = 0usize;
let thr = _mm256_set1_ps(threshold);
let mut i = 0;
while i < simd_n {
let cmp = _mm256_cmp_ps::<_CMP_LT_OQ>(_mm256_loadu_ps(distances.as_ptr().add(i)), thr);
let mask = _mm256_movemask_ps(cmp);
if mask != 0 {
for k in 0..8 {
out.get_unchecked_mut(count).write((i + k) as u32);
count += ((mask >> k) & 1) as usize;
}
}
i += 8;
}
while i < n {
out.get_unchecked_mut(count).write(i as u32);
count += (*distances.get_unchecked(i) < threshold) as usize;
i += 1;
}
count
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx512f")]
unsafe fn fill_survivors_avx512(
distances: &[f32],
threshold: f32,
out: &mut [MaybeUninit<u32>],
) -> usize {
use std::arch::x86_64::*;
unsafe {
let n = distances.len();
let simd_n = n & !15;
let mut count = 0usize;
let thr = _mm512_set1_ps(threshold);
let iota = _mm512_set_epi32(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
let mut i = 0;
while i < simd_n {
let mask =
_mm512_cmp_ps_mask::<_CMP_LT_OQ>(_mm512_loadu_ps(distances.as_ptr().add(i)), thr);
if mask != 0 {
let indices = _mm512_add_epi32(_mm512_set1_epi32(i as i32), iota);
_mm512_mask_compressstoreu_epi32(out.as_mut_ptr().add(count).cast(), mask, indices);
count += mask.count_ones() as usize;
}
i += 16;
}
while i < n {
out.get_unchecked_mut(count).write(i as u32);
count += (*distances.get_unchecked(i) < threshold) as usize;
i += 1;
}
count
}
}
#[cfg(target_arch = "aarch64")]
fn fill_survivors(distances: &[f32], threshold: f32, out: &mut [MaybeUninit<u32>]) -> usize {
use std::arch::aarch64::*;
let n = distances.len();
let simd_n = n & !3;
let mut count = 0usize;
unsafe {
let thr = vdupq_n_f32(threshold);
let mut i = 0;
while i < simd_n {
let cmp = vcltq_f32(vld1q_f32(distances.as_ptr().add(i)), thr);
if vmaxvq_u32(cmp) != 0 {
let mut mask = [0u32; 4];
vst1q_u32(mask.as_mut_ptr(), cmp);
for (k, &m) in mask.iter().enumerate() {
out.get_unchecked_mut(count).write((i + k) as u32);
count += (m != 0) as usize;
}
}
i += 4;
}
while i < n {
out.get_unchecked_mut(count).write(i as u32);
count += (*distances.get_unchecked(i) < threshold) as usize;
i += 1;
}
}
count
}
pub fn top1_partial_search(
pruner: &ADSamplingPruner,
query: &[f32], centroids: &[f32], batch_n_y: usize,
d: usize,
vertical_d: usize,
horizontal_d: usize,
partial_distances: &mut [f32], partial_d: usize,
prev_top_1: u32,
prev_threshold: f32,
centroid_global_offset: u32,
positions_buf: &mut Vec<u32>,
) -> (KnnCandidate, usize) {
let ratios = &pruner.ratios;
let mut top = KnnCandidate::new(prev_top_1, prev_threshold);
let init_threshold = top.distance * ratios[partial_d];
let initial_not_pruned = collect_survivors(
&partial_distances[..batch_n_y],
init_threshold,
positions_buf,
);
if positions_buf.len() == 1 {
let p = positions_buf[0];
if centroid_global_offset + p == prev_top_1 {
positions_buf.clear();
return (top, initial_not_pruned);
}
}
let mut current_dim = partial_d;
let mut current_horizontal = 0;
while !positions_buf.is_empty() && current_horizontal < horizontal_d {
let block_size = H_DIM_SIZE.min(horizontal_d - current_horizontal);
let offset = vertical_d + current_horizontal;
let q_slice = &query[offset..offset + block_size];
for &v_idx in positions_buf.iter() {
let i = v_idx as usize;
let row = ¢roids[i * d + offset..i * d + offset + block_size];
partial_distances[i] += l2_squared(q_slice, row);
}
current_horizontal += block_size;
current_dim += block_size;
let threshold = top.distance * ratios[current_dim];
positions_buf.retain(|&v_idx| partial_distances[v_idx as usize] < threshold);
}
if !positions_buf.is_empty() && partial_d < vertical_d {
let dims_left = vertical_d - partial_d;
let offset = partial_d;
let q_slice = &query[offset..offset + dims_left];
for &v_idx in positions_buf.iter() {
let i = v_idx as usize;
let row = ¢roids[i * d + offset..i * d + offset + dims_left];
partial_distances[i] += l2_squared(q_slice, row);
}
current_dim = d;
let threshold = top.distance * ratios[current_dim];
positions_buf.retain(|&v_idx| partial_distances[v_idx as usize] < threshold);
}
for &v_idx in positions_buf.iter() {
let i = v_idx as usize;
let dist = partial_distances[i];
if dist < top.distance {
top.distance = dist;
top.index = centroid_global_offset + v_idx;
}
}
positions_buf.clear();
(top, initial_not_pruned)
}