#[inline]
pub(crate) fn cmp_finite_f32_then_index(
lhs_value: f32,
lhs_index: usize,
rhs_value: f32,
rhs_index: usize,
) -> std::cmp::Ordering {
if lhs_value == rhs_value {
lhs_index.cmp(&rhs_index)
} else {
lhs_value.total_cmp(&rhs_value)
}
}
#[inline]
pub(crate) fn result_buffer_len(nq: usize, k: usize) -> usize {
nq.checked_mul(k)
.expect("search result buffer length (nq * k) overflows usize")
}
#[inline]
pub(crate) fn checked_new_count(current: usize, adding: usize, elems_per_vec: usize) -> usize {
let new_n = current
.checked_add(adding)
.expect("ordvec: n_vectors overflows usize");
assert!(
new_n <= crate::rank_io::MAX_VECTORS,
"ordvec: index would exceed MAX_VECTORS ({}); had {current}, adding {adding}",
crate::rank_io::MAX_VECTORS,
);
new_n
.checked_mul(elems_per_vec)
.expect("ordvec: index buffer length (n_vectors * elems_per_vec) overflows usize");
new_n
}
const L2_NORMALISE_EPSILON: f32 = 1e-12;
pub(crate) fn l2_normalise(v: &[f32]) -> Vec<f32> {
let norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm <= L2_NORMALISE_EPSILON {
vec![0.0; v.len()]
} else {
let inv = 1.0 / norm;
v.iter().map(|&x| x * inv).collect()
}
}
pub(crate) fn l2_normalise_into(out: &mut Vec<f32>, v: &[f32]) {
out.clear();
let norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm <= L2_NORMALISE_EPSILON {
out.resize(v.len(), 0.0);
} else {
let inv = 1.0 / norm;
out.extend(v.iter().map(|&x| x * inv));
}
}
#[inline]
pub(crate) fn assert_all_finite(v: &[f32]) {
assert!(
v.iter().all(|x| x.is_finite()),
"ordvec: input contains non-finite (NaN or ±Inf) values; embeddings must be finite"
);
}
#[inline]
pub(crate) fn and_popcount(doc: &[u64], q: &[u64]) -> u32 {
assert_eq!(
doc.len(),
q.len(),
"popcount: doc and query bitmap rows must be equal length"
);
#[cfg(target_arch = "aarch64")]
{
unsafe { and_popcount_neon(doc, q) }
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
{
and_popcount_simd128(doc, q)
}
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "wasm32", target_feature = "simd128")
)))]
{
and_popcount_scalar(doc, q)
}
}
#[inline]
pub(crate) fn xor_popcount(doc: &[u64], q: &[u64]) -> u32 {
assert_eq!(
doc.len(),
q.len(),
"popcount: doc and query bitmap rows must be equal length"
);
#[cfg(target_arch = "aarch64")]
{
unsafe { xor_popcount_neon(doc, q) }
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
{
xor_popcount_simd128(doc, q)
}
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "wasm32", target_feature = "simd128")
)))]
{
xor_popcount_scalar(doc, q)
}
}
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "wasm32", target_feature = "simd128")
)))]
#[inline]
fn and_popcount_scalar(doc: &[u64], q: &[u64]) -> u32 {
doc.iter().zip(q).map(|(d, qq)| (d & qq).count_ones()).sum()
}
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "wasm32", target_feature = "simd128")
)))]
#[inline]
fn xor_popcount_scalar(doc: &[u64], q: &[u64]) -> u32 {
doc.iter().zip(q).map(|(d, qq)| (d ^ qq).count_ones()).sum()
}
#[cfg(target_arch = "aarch64")]
#[inline]
unsafe fn and_popcount_neon(doc: &[u64], q: &[u64]) -> u32 {
use std::arch::aarch64::*;
unsafe {
let qpv = doc.len();
let dptr = doc.as_ptr() as *const u8;
let qptr = q.as_ptr() as *const u8;
let mut acc = 0u32;
let mut w = 0usize;
while w + 2 <= qpv {
let dv = vld1q_u8(dptr.add(w * 8));
let qv = vld1q_u8(qptr.add(w * 8));
acc += vaddvq_u8(vcntq_u8(vandq_u8(dv, qv))) as u32;
w += 2;
}
if w < qpv {
acc += (doc[w] & q[w]).count_ones();
}
acc
}
}
#[cfg(target_arch = "aarch64")]
#[inline]
unsafe fn xor_popcount_neon(doc: &[u64], q: &[u64]) -> u32 {
use std::arch::aarch64::*;
unsafe {
let qpv = doc.len();
let dptr = doc.as_ptr() as *const u8;
let qptr = q.as_ptr() as *const u8;
let mut acc = 0u32;
let mut w = 0usize;
while w + 2 <= qpv {
let dv = vld1q_u8(dptr.add(w * 8));
let qv = vld1q_u8(qptr.add(w * 8));
acc += vaddvq_u8(vcntq_u8(veorq_u8(dv, qv))) as u32;
w += 2;
}
if w < qpv {
acc += (doc[w] ^ q[w]).count_ones();
}
acc
}
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[inline]
fn and_popcount_simd128(doc: &[u64], q: &[u64]) -> u32 {
use std::arch::wasm32::*;
let qpv = doc.len();
let dptr = doc.as_ptr() as *const u8;
let qptr = q.as_ptr() as *const u8;
let mut acc = 0u32;
let mut w = 0usize;
while w + 2 <= qpv {
let dv = unsafe { v128_load(dptr.add(w * 8) as *const v128) };
let qv = unsafe { v128_load(qptr.add(w * 8) as *const v128) };
let pc = u8x16_popcnt(v128_and(dv, qv));
let s16 = u16x8_extadd_pairwise_u8x16(pc);
let s32 = u32x4_extadd_pairwise_u16x8(s16);
acc += u32x4_extract_lane::<0>(s32)
+ u32x4_extract_lane::<1>(s32)
+ u32x4_extract_lane::<2>(s32)
+ u32x4_extract_lane::<3>(s32);
w += 2;
}
if w < qpv {
acc += (doc[w] & q[w]).count_ones();
}
acc
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[inline]
fn xor_popcount_simd128(doc: &[u64], q: &[u64]) -> u32 {
use std::arch::wasm32::*;
let qpv = doc.len();
let dptr = doc.as_ptr() as *const u8;
let qptr = q.as_ptr() as *const u8;
let mut acc = 0u32;
let mut w = 0usize;
while w + 2 <= qpv {
let dv = unsafe { v128_load(dptr.add(w * 8) as *const v128) };
let qv = unsafe { v128_load(qptr.add(w * 8) as *const v128) };
let pc = u8x16_popcnt(v128_xor(dv, qv));
let s16 = u16x8_extadd_pairwise_u8x16(pc);
let s32 = u32x4_extadd_pairwise_u16x8(s16);
acc += u32x4_extract_lane::<0>(s32)
+ u32x4_extract_lane::<1>(s32)
+ u32x4_extract_lane::<2>(s32)
+ u32x4_extract_lane::<3>(s32);
w += 2;
}
if w < qpv {
acc += (doc[w] ^ q[w]).count_ones();
}
acc
}
pub(crate) struct TopK {
k: usize,
scores: Vec<f32>,
indices: Vec<i64>,
tie_keys: Vec<i64>,
tie_key_by_index: Option<Vec<i64>>,
score_offset: f32,
filled: usize,
worst_pos: usize,
worst_val: f32,
worst_tie_key: i64,
}
impl TopK {
pub(crate) fn new(k: usize) -> Self {
Self {
k,
scores: vec![f32::NEG_INFINITY; k],
indices: vec![-1; k],
tie_keys: vec![i64::MAX; k],
tie_key_by_index: None,
score_offset: 0.0,
filled: 0,
worst_pos: 0,
worst_val: f32::INFINITY,
worst_tie_key: i64::MAX,
}
}
#[allow(dead_code)]
pub(crate) fn new_with_tie_keys(k: usize, tie_key_by_index: &[u32]) -> Self {
let mut top = Self::new(k);
top.tie_key_by_index = Some(tie_key_by_index.iter().map(|&id| i64::from(id)).collect());
top
}
#[inline]
#[cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
pub(crate) fn set_score_offset(&mut self, score_offset: f32) {
self.score_offset = score_offset;
}
#[inline]
pub(crate) fn maybe_insert(&mut self, score: f32, idx: usize) {
let score = score + self.score_offset;
let id = i64::try_from(idx).expect("ordvec: doc_id exceeds i64::MAX");
let tie_key = self
.tie_key_by_index
.as_ref()
.map(|keys| keys[idx])
.unwrap_or(id);
if self.filled < self.k {
self.scores[self.filled] = score;
self.indices[self.filled] = id;
self.tie_keys[self.filled] = tie_key;
self.filled += 1;
if self.filled == self.k {
self.recompute_worst();
}
} else {
let better = match score.total_cmp(&self.worst_val) {
std::cmp::Ordering::Greater => true,
std::cmp::Ordering::Equal => tie_key < self.worst_tie_key,
std::cmp::Ordering::Less => false,
};
if better {
self.scores[self.worst_pos] = score;
self.indices[self.worst_pos] = id;
self.tie_keys[self.worst_pos] = tie_key;
self.recompute_worst();
}
}
}
fn recompute_worst(&mut self) {
let mut wv = f32::INFINITY;
let mut wt = i64::MIN;
let mut wp = 0;
for i in 0..self.filled {
let s = self.scores[i];
let tie_key = self.tie_keys[i];
let worse = match s.total_cmp(&wv) {
std::cmp::Ordering::Less => true,
std::cmp::Ordering::Equal => tie_key > wt,
std::cmp::Ordering::Greater => false,
};
if worse {
wv = s;
wt = tie_key;
wp = i;
}
}
self.worst_val = wv;
self.worst_tie_key = wt;
self.worst_pos = wp;
}
pub(crate) fn reset_with_tie_keys(&mut self, k: usize, tie_key_by_index: &[u32]) {
self.k = k;
self.scores.clear();
self.scores.resize(k, f32::NEG_INFINITY);
self.indices.clear();
self.indices.resize(k, -1);
self.tie_keys.clear();
self.tie_keys.resize(k, i64::MAX);
let buf = self.tie_key_by_index.get_or_insert_with(Vec::new);
buf.clear();
buf.extend(tie_key_by_index.iter().map(|&id| i64::from(id)));
self.score_offset = 0.0;
self.filled = 0;
self.worst_pos = 0;
self.worst_val = f32::INFINITY;
self.worst_tie_key = i64::MAX;
}
pub(crate) fn finalize_into(&self, out_scores: &mut [f32], out_indices: &mut [i64]) {
let mut order_buf = Vec::new();
self.finalize_into_with_scratch(&mut order_buf, out_scores, out_indices);
}
pub(crate) fn finalize_into_with_scratch(
&self,
order_buf: &mut Vec<(f32, i64, i64, usize)>,
out_scores: &mut [f32],
out_indices: &mut [i64],
) {
debug_assert_eq!(out_scores.len(), out_indices.len());
for s in out_scores.iter_mut() {
*s = f32::NEG_INFINITY;
}
for i in out_indices.iter_mut() {
*i = -1;
}
order_buf.clear();
order_buf.extend(
self.scores
.iter()
.zip(self.indices.iter())
.zip(self.tie_keys.iter())
.enumerate()
.take(self.filled)
.map(|(slot, ((&s, &i), &tie_key))| (s, i, tie_key, slot)),
);
order_buf.sort_unstable_by(|a, b| {
b.0.total_cmp(&a.0)
.then_with(|| a.2.cmp(&b.2))
.then_with(|| a.3.cmp(&b.3))
});
for (slot, &(s, i, _, _)) in order_buf.iter().enumerate() {
if slot >= out_scores.len() {
break;
}
out_scores[slot] = s;
out_indices[slot] = i;
}
}
#[cfg(test)]
pub(crate) fn scores_capacity_for_test(&self) -> usize {
self.scores.capacity()
}
}
#[cfg(test)]
mod tests {
use super::{
and_popcount, checked_new_count, l2_normalise, l2_normalise_into, xor_popcount, TopK,
L2_NORMALISE_EPSILON,
};
use rand::{RngExt, SeedableRng};
use rand_chacha::ChaCha8Rng;
fn naive_and(d: &[u64], q: &[u64]) -> u32 {
d.iter().zip(q).map(|(a, b)| (a & b).count_ones()).sum()
}
fn naive_xor(d: &[u64], q: &[u64]) -> u32 {
d.iter().zip(q).map(|(a, b)| (a ^ b).count_ones()).sum()
}
#[test]
fn popcount_helpers_match_naive() {
let mut rng = ChaCha8Rng::seed_from_u64(0xC0FFEE);
for qpv in [1usize, 2, 3, 4, 7, 8, 15, 16, 17, 31] {
for _ in 0..64 {
let d: Vec<u64> = (0..qpv).map(|_| rng.random()).collect();
let q: Vec<u64> = (0..qpv).map(|_| rng.random()).collect();
assert_eq!(and_popcount(&d, &q), naive_and(&d, &q), "AND qpv={qpv}");
assert_eq!(xor_popcount(&d, &q), naive_xor(&d, &q), "XOR qpv={qpv}");
}
}
}
#[test]
fn topk_zero_k_is_inert() {
let mut top = TopK::new(0);
top.maybe_insert(1.0, 0);
top.maybe_insert(f32::NEG_INFINITY, 7);
let mut scores: [f32; 0] = [];
let mut indices: [i64; 0] = [];
top.finalize_into(&mut scores, &mut indices);
assert!(scores.is_empty() && indices.is_empty());
}
#[test]
fn topk_duplicate_candidate_ties_have_total_final_order() {
let mut top = TopK::new_with_tie_keys(2, &[7, 7, 7]);
top.maybe_insert(0.0, 0);
top.maybe_insert(0.0, 1);
top.maybe_insert(0.0, 2);
let mut scores = [f32::NEG_INFINITY; 2];
let mut indices = [-1; 2];
top.finalize_into(&mut scores, &mut indices);
assert_eq!(scores, [0.0, 0.0]);
assert_eq!(indices, [0, 1]);
}
#[test]
fn topk_score_offset_is_part_of_eviction_key() {
let mut top = TopK::new_with_tie_keys(1, &[10, 3]);
top.set_score_offset(16_777_216.0);
top.maybe_insert(1.0, 0);
top.maybe_insert(0.0, 1);
let mut scores = [f32::NEG_INFINITY; 1];
let mut indices = [-1; 1];
top.finalize_into(&mut scores, &mut indices);
assert_eq!(scores, [16_777_216.0]);
assert_eq!(indices, [1]);
}
#[test]
fn checked_new_count_accepts_up_to_max() {
use crate::rank_io::MAX_VECTORS;
assert_eq!(checked_new_count(0, MAX_VECTORS, 1), MAX_VECTORS);
assert_eq!(checked_new_count(MAX_VECTORS - 1, 1, 1), MAX_VECTORS);
assert_eq!(checked_new_count(MAX_VECTORS, 0, 1), MAX_VECTORS);
#[cfg(target_pointer_width = "64")]
{
assert_eq!(checked_new_count(0, MAX_VECTORS, 4096), MAX_VECTORS);
}
}
#[test]
#[should_panic(expected = "MAX_VECTORS")]
fn checked_new_count_rejects_one_past_max() {
use crate::rank_io::MAX_VECTORS;
let _ = checked_new_count(MAX_VECTORS, 1, 1);
}
#[test]
#[should_panic(expected = "n_vectors overflows usize")]
fn checked_new_count_rejects_usize_overflow() {
let _ = checked_new_count(usize::MAX, 1, 1);
}
#[test]
#[should_panic(expected = "buffer length")]
fn checked_new_count_rejects_buffer_overflow() {
let _ = checked_new_count(0, 2, usize::MAX);
}
#[test]
fn topk_reset_and_finalize_with_scratch_match_fresh() {
use super::TopK;
let tie = [10u32, 20, 30, 40];
let mut a = TopK::new_with_tie_keys(2, &tie);
a.maybe_insert(1.0, 0);
a.maybe_insert(3.0, 1);
a.maybe_insert(2.0, 2);
let mut s_ref = vec![f32::NEG_INFINITY; 2];
let mut i_ref = vec![-1i64; 2];
a.finalize_into(&mut s_ref, &mut i_ref);
let mut b = TopK::new(0);
b.reset_with_tie_keys(2, &tie);
b.maybe_insert(1.0, 0);
b.maybe_insert(3.0, 1);
b.maybe_insert(2.0, 2);
let mut order_buf = Vec::new();
let mut s = vec![f32::NEG_INFINITY; 2];
let mut i = vec![-1i64; 2];
b.finalize_into_with_scratch(&mut order_buf, &mut s, &mut i);
assert_eq!(s, s_ref);
assert_eq!(i, i_ref);
let cap_top = b.scores_capacity_for_test();
let cap_buf = order_buf.capacity();
b.reset_with_tie_keys(2, &tie);
b.maybe_insert(5.0, 3);
b.maybe_insert(1.0, 0);
b.finalize_into_with_scratch(&mut order_buf, &mut s, &mut i);
assert_eq!(
b.scores_capacity_for_test(),
cap_top,
"TopK reset must reuse capacity"
);
assert_eq!(
order_buf.capacity(),
cap_buf,
"finalize order_buf must reuse capacity"
);
assert_eq!(i, vec![3, 0]); }
#[test]
fn l2_normalise_into_matches_l2_normalise_and_reuses_capacity() {
let v = vec![3.0f32, 0.0, 4.0, 0.0]; let expected = l2_normalise(&v);
let mut out: Vec<f32> = Vec::new();
l2_normalise_into(&mut out, &v);
assert_eq!(out, expected);
let z = vec![0.0f32; 4];
l2_normalise_into(&mut out, &z);
assert_eq!(out, vec![0.0f32; 4]);
let cap = {
l2_normalise_into(&mut out, &v);
out.capacity()
};
l2_normalise_into(&mut out, &v);
assert_eq!(out.capacity(), cap, "l2_normalise_into must reuse capacity");
}
#[test]
fn l2_normalise_threshold_edges_are_pinned() {
let below = vec![L2_NORMALISE_EPSILON * 0.5, 0.0];
assert_eq!(l2_normalise(&below), vec![0.0, 0.0]);
let at = vec![L2_NORMALISE_EPSILON, 0.0];
assert_eq!(l2_normalise(&at), vec![0.0, 0.0]);
let above = vec![L2_NORMALISE_EPSILON * 2.0, 0.0];
assert_eq!(l2_normalise(&above), vec![1.0, 0.0]);
let mut out = Vec::new();
l2_normalise_into(&mut out, &below);
assert_eq!(out, vec![0.0, 0.0]);
l2_normalise_into(&mut out, &above);
assert_eq!(out, vec![1.0, 0.0]);
}
}