Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,347 @@
//! SIMD-optimized distance functions for vector similarity search
//!
//! This module provides high-performance distance calculations with:
//! - AVX-512 support (16 floats per operation)
//! - AVX2 support (8 floats per operation)
//! - ARM NEON support (4 floats per operation)
//! - Scalar fallback for all platforms
pub mod scalar;
pub mod simd;
pub use scalar::*;
pub use simd::*;
use std::sync::OnceLock;
/// Distance metric types
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DistanceMetric {
/// L2 (Euclidean) distance: sqrt(sum((a[i] - b[i])^2))
Euclidean,
/// Cosine distance: 1 - (a·b)/(‖a‖‖b‖)
Cosine,
/// Negative inner product: -sum(a[i] * b[i])
InnerProduct,
/// L1 (Manhattan) distance: sum(|a[i] - b[i]|)
Manhattan,
/// Hamming distance (for binary vectors)
Hamming,
}
/// SIMD capability levels
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SimdCapability {
/// AVX-512 (512-bit, 16 floats)
Avx512,
/// AVX2 (256-bit, 8 floats)
Avx2,
/// ARM NEON (128-bit, 4 floats)
Neon,
/// Scalar fallback
Scalar,
}
impl std::fmt::Display for SimdCapability {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SimdCapability::Avx512 => write!(f, "avx512"),
SimdCapability::Avx2 => write!(f, "avx2"),
SimdCapability::Neon => write!(f, "neon"),
SimdCapability::Scalar => write!(f, "scalar"),
}
}
}
/// Detected SIMD capability (cached)
static SIMD_CAPABILITY: OnceLock<SimdCapability> = OnceLock::new();
/// Function pointer table for distance calculations
pub struct DistanceFunctions {
pub euclidean: fn(&[f32], &[f32]) -> f32,
pub cosine: fn(&[f32], &[f32]) -> f32,
pub inner_product: fn(&[f32], &[f32]) -> f32,
pub manhattan: fn(&[f32], &[f32]) -> f32,
}
static DISTANCE_FNS: OnceLock<DistanceFunctions> = OnceLock::new();
/// Initialize SIMD dispatch (called at extension load)
pub fn init_simd_dispatch() {
let cap = detect_simd_capability();
SIMD_CAPABILITY.get_or_init(|| cap);
DISTANCE_FNS.get_or_init(|| create_distance_functions(cap));
}
/// Detect best available SIMD capability
fn detect_simd_capability() -> SimdCapability {
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("avx512vl") {
return SimdCapability::Avx512;
}
if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
return SimdCapability::Avx2;
}
}
#[cfg(target_arch = "aarch64")]
{
// NEON is always available on aarch64
return SimdCapability::Neon;
}
#[allow(unreachable_code)]
SimdCapability::Scalar
}
/// Create distance function table for the detected capability
///
/// Uses the new optimized functions that integrate simsimd 5.9 and
/// dimension-specialized implementations for maximum performance.
fn create_distance_functions(cap: SimdCapability) -> DistanceFunctions {
match cap {
SimdCapability::Avx512 => DistanceFunctions {
// Use optimized functions that auto-dispatch based on dimensions
euclidean: simd::l2_distance_optimized,
cosine: simd::cosine_distance_optimized,
inner_product: simd::inner_product_distance_optimized,
manhattan: simd::manhattan_distance_avx2_wrapper,
},
SimdCapability::Avx2 => DistanceFunctions {
// Use optimized functions with simsimd + 4x unrolled AVX2
euclidean: simd::l2_distance_optimized,
cosine: simd::cosine_distance_optimized,
inner_product: simd::inner_product_distance_optimized,
manhattan: simd::manhattan_distance_avx2_wrapper,
},
SimdCapability::Neon => DistanceFunctions {
// Use simsimd on NEON (auto-dispatched)
euclidean: simd::l2_distance_simsimd,
cosine: simd::cosine_distance_simsimd,
inner_product: simd::inner_product_distance_simsimd,
manhattan: scalar::manhattan_distance, // NEON manhattan not critical
},
SimdCapability::Scalar => DistanceFunctions {
// Use simsimd even on scalar (it has good fallbacks)
euclidean: simd::l2_distance_simsimd,
cosine: simd::cosine_distance_simsimd,
inner_product: simd::inner_product_distance_simsimd,
manhattan: scalar::manhattan_distance,
},
}
}
/// Get SIMD info string
pub fn simd_info() -> &'static str {
match SIMD_CAPABILITY.get() {
Some(SimdCapability::Avx512) => "avx512",
Some(SimdCapability::Avx2) => "avx2",
Some(SimdCapability::Neon) => "neon",
Some(SimdCapability::Scalar) => "scalar",
None => "uninitialized",
}
}
/// Get detailed SIMD info
pub fn simd_info_detailed() -> String {
let cap = SIMD_CAPABILITY
.get()
.copied()
.unwrap_or(SimdCapability::Scalar);
#[cfg(target_arch = "x86_64")]
{
let mut features = Vec::new();
if is_x86_feature_detected!("avx512f") {
features.push("avx512f");
}
if is_x86_feature_detected!("avx512vl") {
features.push("avx512vl");
}
if is_x86_feature_detected!("avx2") {
features.push("avx2");
}
if is_x86_feature_detected!("fma") {
features.push("fma");
}
if is_x86_feature_detected!("sse4.2") {
features.push("sse4.2");
}
let floats_per_op = match cap {
SimdCapability::Avx512 => 16,
SimdCapability::Avx2 => 8,
_ => 1,
};
return format!(
"architecture: x86_64, active: {}, features: [{}], floats_per_op: {}",
cap,
features.join(", "),
floats_per_op
);
}
#[cfg(target_arch = "aarch64")]
{
return format!("architecture: aarch64, active: neon, floats_per_op: 4");
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
format!("architecture: unknown, active: scalar, floats_per_op: 1")
}
}
// ============================================================================
// Public Distance Functions (dispatch to optimal implementation)
// ============================================================================
/// Calculate Euclidean (L2) distance
#[inline]
pub fn euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "Vector dimensions must match");
if let Some(fns) = DISTANCE_FNS.get() {
(fns.euclidean)(a, b)
} else {
scalar::euclidean_distance(a, b)
}
}
/// Calculate Cosine distance
#[inline]
pub fn cosine_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "Vector dimensions must match");
if let Some(fns) = DISTANCE_FNS.get() {
(fns.cosine)(a, b)
} else {
scalar::cosine_distance(a, b)
}
}
/// Calculate negative Inner Product distance
#[inline]
pub fn inner_product_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "Vector dimensions must match");
if let Some(fns) = DISTANCE_FNS.get() {
(fns.inner_product)(a, b)
} else {
scalar::inner_product_distance(a, b)
}
}
/// Calculate Manhattan (L1) distance
#[inline]
pub fn manhattan_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "Vector dimensions must match");
if let Some(fns) = DISTANCE_FNS.get() {
(fns.manhattan)(a, b)
} else {
scalar::manhattan_distance(a, b)
}
}
/// Calculate distance using specified metric
#[inline]
pub fn distance(a: &[f32], b: &[f32], metric: DistanceMetric) -> f32 {
match metric {
DistanceMetric::Euclidean => euclidean_distance(a, b),
DistanceMetric::Cosine => cosine_distance(a, b),
DistanceMetric::InnerProduct => inner_product_distance(a, b),
DistanceMetric::Manhattan => manhattan_distance(a, b),
DistanceMetric::Hamming => {
// For f32 vectors, treat as binary (sign bit)
scalar::hamming_distance_f32(a, b)
}
}
}
/// Fast cosine distance for pre-normalized vectors
/// Only computes dot product (avoids norm calculation)
#[inline]
pub fn cosine_distance_normalized(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "Vector dimensions must match");
simd::cosine_distance_normalized(a, b)
}
/// Batch distance calculation with parallelism
pub fn batch_distances(query: &[f32], vectors: &[&[f32]], metric: DistanceMetric) -> Vec<f32> {
use rayon::prelude::*;
vectors
.par_iter()
.map(|v| distance(query, v, metric))
.collect()
}
// ============================================================================
// Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
fn init_for_tests() {
let _ = SIMD_CAPABILITY.get_or_init(detect_simd_capability);
let cap = *SIMD_CAPABILITY.get().unwrap();
let _ = DISTANCE_FNS.get_or_init(|| create_distance_functions(cap));
}
#[test]
fn test_euclidean() {
init_for_tests();
let a = vec![0.0, 0.0, 0.0];
let b = vec![3.0, 4.0, 0.0];
let dist = euclidean_distance(&a, &b);
assert!((dist - 5.0).abs() < 1e-5);
}
#[test]
fn test_cosine() {
init_for_tests();
let a = vec![1.0, 0.0, 0.0];
let b = vec![1.0, 0.0, 0.0];
let dist = cosine_distance(&a, &b);
assert!(dist.abs() < 1e-5); // Same direction = 0 distance
}
#[test]
fn test_inner_product() {
init_for_tests();
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 5.0, 6.0];
let dist = inner_product_distance(&a, &b);
assert!((dist - (-32.0)).abs() < 1e-5); // -(1*4 + 2*5 + 3*6) = -32
}
#[test]
fn test_manhattan() {
init_for_tests();
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 6.0, 8.0];
let dist = manhattan_distance(&a, &b);
assert!((dist - 12.0).abs() < 1e-5); // |3| + |4| + |5| = 12
}
#[test]
fn test_simd_matches_scalar() {
init_for_tests();
let a: Vec<f32> = (0..128).map(|i| i as f32 * 0.01).collect();
let b: Vec<f32> = (0..128).map(|i| (128 - i) as f32 * 0.01).collect();
let scalar_euclidean = scalar::euclidean_distance(&a, &b);
let simd_euclidean = euclidean_distance(&a, &b);
assert!((scalar_euclidean - simd_euclidean).abs() < 1e-4);
let scalar_cosine = scalar::cosine_distance(&a, &b);
let simd_cosine = cosine_distance(&a, &b);
assert!((scalar_cosine - simd_cosine).abs() < 1e-4);
}
}

View File

@@ -0,0 +1,306 @@
//! Scalar (non-SIMD) distance implementations
//!
//! These are fallback implementations that work on all platforms.
/// Euclidean (L2) distance - scalar implementation
#[inline]
pub fn euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let sum: f32 = a
.iter()
.zip(b.iter())
.map(|(x, y)| {
let diff = x - y;
diff * diff
})
.sum();
sum.sqrt()
}
/// Squared Euclidean distance (avoids sqrt for comparisons)
#[inline]
pub fn euclidean_distance_squared(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
a.iter()
.zip(b.iter())
.map(|(x, y)| {
let diff = x - y;
diff * diff
})
.sum()
}
/// Cosine distance - scalar implementation
#[inline]
pub fn cosine_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let mut dot = 0.0f32;
let mut norm_a = 0.0f32;
let mut norm_b = 0.0f32;
for (x, y) in a.iter().zip(b.iter()) {
dot += x * y;
norm_a += x * x;
norm_b += y * y;
}
let denominator = (norm_a * norm_b).sqrt();
if denominator == 0.0 {
return 1.0; // Max distance if either vector is zero
}
1.0 - (dot / denominator)
}
/// Cosine similarity (1 - distance)
#[inline]
pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
1.0 - cosine_distance(a, b)
}
/// Inner product (dot product) distance - scalar implementation
/// Returns negative for use with ORDER BY ASC
#[inline]
pub fn inner_product_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
-dot
}
/// Dot product (positive value)
#[inline]
pub fn dot_product(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
/// Manhattan (L1) distance - scalar implementation
#[inline]
pub fn manhattan_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
a.iter().zip(b.iter()).map(|(x, y)| (x - y).abs()).sum()
}
/// Hamming distance for f32 vectors (based on sign bit)
#[inline]
pub fn hamming_distance_f32(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let count: u32 = a
.iter()
.zip(b.iter())
.map(|(x, y)| {
let sign_a = x.to_bits() >> 31;
let sign_b = y.to_bits() >> 31;
(sign_a ^ sign_b) as u32
})
.sum();
count as f32
}
/// Hamming distance for binary vectors (u64)
#[inline]
pub fn hamming_distance_binary(a: &[u64], b: &[u64]) -> u32 {
debug_assert_eq!(a.len(), b.len());
a.iter()
.zip(b.iter())
.map(|(x, y)| (x ^ y).count_ones())
.sum()
}
/// Jaccard distance for sparse binary vectors
#[inline]
pub fn jaccard_distance(a: &[u64], b: &[u64]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let mut intersection = 0u32;
let mut union = 0u32;
for (x, y) in a.iter().zip(b.iter()) {
intersection += (x & y).count_ones();
union += (x | y).count_ones();
}
if union == 0 {
return 0.0;
}
1.0 - (intersection as f32 / union as f32)
}
/// Chebyshev (L∞) distance
#[inline]
pub fn chebyshev_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).abs())
.fold(0.0f32, f32::max)
}
/// Minkowski distance with parameter p
#[inline]
pub fn minkowski_distance(a: &[f32], b: &[f32], p: f32) -> f32 {
debug_assert_eq!(a.len(), b.len());
if p == 1.0 {
return manhattan_distance(a, b);
}
if p == 2.0 {
return euclidean_distance(a, b);
}
if p == f32::INFINITY {
return chebyshev_distance(a, b);
}
let sum: f32 = a
.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).abs().powf(p))
.sum();
sum.powf(1.0 / p)
}
/// Canberra distance
#[inline]
pub fn canberra_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
a.iter()
.zip(b.iter())
.map(|(x, y)| {
let num = (x - y).abs();
let denom = x.abs() + y.abs();
if denom == 0.0 {
0.0
} else {
num / denom
}
})
.sum()
}
/// Bray-Curtis distance
#[inline]
pub fn bray_curtis_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let mut sum_diff = 0.0f32;
let mut sum_total = 0.0f32;
for (x, y) in a.iter().zip(b.iter()) {
sum_diff += (x - y).abs();
sum_total += x.abs() + y.abs();
}
if sum_total == 0.0 {
return 0.0;
}
sum_diff / sum_total
}
// ============================================================================
// Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_euclidean() {
let a = vec![0.0, 0.0];
let b = vec![3.0, 4.0];
assert!((euclidean_distance(&a, &b) - 5.0).abs() < 1e-6);
}
#[test]
fn test_euclidean_squared() {
let a = vec![0.0, 0.0];
let b = vec![3.0, 4.0];
assert!((euclidean_distance_squared(&a, &b) - 25.0).abs() < 1e-6);
}
#[test]
fn test_cosine_same_direction() {
let a = vec![1.0, 0.0, 0.0];
let b = vec![2.0, 0.0, 0.0];
assert!(cosine_distance(&a, &b).abs() < 1e-6);
}
#[test]
fn test_cosine_opposite() {
let a = vec![1.0, 0.0, 0.0];
let b = vec![-1.0, 0.0, 0.0];
assert!((cosine_distance(&a, &b) - 2.0).abs() < 1e-6);
}
#[test]
fn test_cosine_orthogonal() {
let a = vec![1.0, 0.0, 0.0];
let b = vec![0.0, 1.0, 0.0];
assert!((cosine_distance(&a, &b) - 1.0).abs() < 1e-6);
}
#[test]
fn test_inner_product() {
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 5.0, 6.0];
// dot = 4 + 10 + 18 = 32
assert!((inner_product_distance(&a, &b) - (-32.0)).abs() < 1e-6);
}
#[test]
fn test_manhattan() {
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 6.0, 8.0];
// |3| + |4| + |5| = 12
assert!((manhattan_distance(&a, &b) - 12.0).abs() < 1e-6);
}
#[test]
fn test_hamming_binary() {
let a = vec![0b1010_1010u64];
let b = vec![0b1111_0000u64];
let dist = hamming_distance_binary(&a, &b);
assert_eq!(dist, 4);
}
#[test]
fn test_chebyshev() {
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 10.0, 5.0];
// max(|3|, |8|, |2|) = 8
assert!((chebyshev_distance(&a, &b) - 8.0).abs() < 1e-6);
}
#[test]
fn test_minkowski_p1() {
let a = vec![1.0, 2.0];
let b = vec![4.0, 6.0];
// Same as manhattan
assert!((minkowski_distance(&a, &b, 1.0) - manhattan_distance(&a, &b)).abs() < 1e-6);
}
#[test]
fn test_minkowski_p2() {
let a = vec![0.0, 0.0];
let b = vec![3.0, 4.0];
// Same as euclidean
assert!((minkowski_distance(&a, &b, 2.0) - euclidean_distance(&a, &b)).abs() < 1e-6);
}
}

File diff suppressed because it is too large Load Diff