nodedb_vector/distance/simd/
runtime.rs1use super::bbq;
6use super::hamming::fast_hamming;
7use super::scalar::{scalar_cosine, scalar_ip, scalar_l2};
8use crate::distance::typed_scalar;
9
10#[cfg(target_arch = "x86_64")]
11use super::{avx2, avx512};
12
13#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
16use super::neon;
17
18#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
19use super::wasm_simd128;
20
21type HalfFn = fn(&[u8], &[u8], usize) -> f32;
23
24pub type BbqFn = fn(&[u8], &[u8], f32, usize) -> f32;
27
28pub struct SimdRuntime {
30 pub l2_squared: fn(&[f32], &[f32]) -> f32,
31 pub cosine_distance: fn(&[f32], &[f32]) -> f32,
32 pub neg_inner_product: fn(&[f32], &[f32]) -> f32,
33 pub hamming: fn(&[u8], &[u8]) -> u32,
34 pub name: &'static str,
35 pub l2_squared_f16: HalfFn,
37 pub cosine_distance_f16: HalfFn,
38 pub neg_inner_product_f16: HalfFn,
39 pub l2_squared_bf16: HalfFn,
41 pub cosine_distance_bf16: HalfFn,
42 pub neg_inner_product_bf16: HalfFn,
43 pub l2_bbq: BbqFn,
45}
46
47impl SimdRuntime {
48 pub fn detect() -> Self {
50 #[cfg(target_arch = "x86_64")]
51 {
52 let has_vpopcntdq = std::is_x86_feature_detected!("avx512vpopcntdq");
53
54 if std::is_x86_feature_detected!("avx512f") {
55 let name = if has_vpopcntdq {
56 "avx512+vpopcntdq"
57 } else {
58 "avx512"
59 };
60 let rt = Self {
61 l2_squared: avx512::l2_squared,
62 cosine_distance: avx512::cosine_distance,
63 neg_inner_product: avx512::neg_inner_product,
64 hamming: fast_hamming,
65 name,
66 l2_squared_f16: typed_scalar::l2_squared_f16,
67 cosine_distance_f16: typed_scalar::cosine_f16,
68 neg_inner_product_f16: typed_scalar::neg_inner_product_f16,
69 l2_squared_bf16: typed_scalar::l2_squared_bf16,
70 cosine_distance_bf16: typed_scalar::cosine_bf16,
71 neg_inner_product_bf16: typed_scalar::neg_inner_product_bf16,
72 l2_bbq: avx512::l2_bbq,
73 };
74 tracing::info!(kernel = rt.name, "vector SIMD kernel selected");
75 debug_assert!(
76 !has_vpopcntdq || rt.name == "avx512+vpopcntdq",
77 "AVX-512 VPOPCNTDQ available but kernel did not select it"
78 );
79 return rt;
80 }
81 if std::is_x86_feature_detected!("avx2") && std::is_x86_feature_detected!("fma") {
82 let rt = Self {
83 l2_squared: avx2::l2_squared,
84 cosine_distance: avx2::cosine_distance,
85 neg_inner_product: avx2::neg_inner_product,
86 hamming: fast_hamming,
87 name: "avx2+fma",
88 l2_squared_f16: typed_scalar::l2_squared_f16,
89 cosine_distance_f16: typed_scalar::cosine_f16,
90 neg_inner_product_f16: typed_scalar::neg_inner_product_f16,
91 l2_squared_bf16: typed_scalar::l2_squared_bf16,
92 cosine_distance_bf16: typed_scalar::cosine_bf16,
93 neg_inner_product_bf16: typed_scalar::neg_inner_product_bf16,
94 l2_bbq: avx2::l2_bbq,
95 };
96 tracing::info!(kernel = rt.name, "vector SIMD kernel selected");
97 return rt;
98 }
99 }
100 #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
101 {
102 let rt = Self {
103 l2_squared: neon::l2_squared,
104 cosine_distance: neon::cosine_distance,
105 neg_inner_product: neon::neg_inner_product,
106 hamming: fast_hamming,
107 name: "neon",
108 l2_squared_f16: typed_scalar::l2_squared_f16,
109 cosine_distance_f16: typed_scalar::cosine_f16,
110 neg_inner_product_f16: typed_scalar::neg_inner_product_f16,
111 l2_squared_bf16: typed_scalar::l2_squared_bf16,
112 cosine_distance_bf16: typed_scalar::cosine_bf16,
113 neg_inner_product_bf16: typed_scalar::neg_inner_product_bf16,
114 l2_bbq: neon::l2_bbq,
115 };
116 tracing::info!(kernel = rt.name, "vector SIMD kernel selected");
117 return rt;
118 }
119 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
123 {
124 let rt = Self {
125 l2_squared: wasm_simd128::l2_squared,
126 cosine_distance: wasm_simd128::cosine_distance,
127 neg_inner_product: wasm_simd128::neg_inner_product,
128 hamming: fast_hamming,
129 name: "wasm-simd128",
130 l2_squared_f16: typed_scalar::l2_squared_f16,
131 cosine_distance_f16: typed_scalar::cosine_f16,
132 neg_inner_product_f16: typed_scalar::neg_inner_product_f16,
133 l2_squared_bf16: typed_scalar::l2_squared_bf16,
134 cosine_distance_bf16: typed_scalar::cosine_bf16,
135 neg_inner_product_bf16: typed_scalar::neg_inner_product_bf16,
136 l2_bbq: wasm_simd128::l2_bbq,
137 };
138 tracing::info!(kernel = rt.name, "vector SIMD kernel selected");
139 return rt;
140 }
141 #[allow(unreachable_code)]
142 {
143 let rt = Self {
144 l2_squared: scalar_l2,
145 cosine_distance: scalar_cosine,
146 neg_inner_product: scalar_ip,
147 hamming: fast_hamming,
148 name: "scalar",
149 l2_squared_f16: typed_scalar::l2_squared_f16,
150 cosine_distance_f16: typed_scalar::cosine_f16,
151 neg_inner_product_f16: typed_scalar::neg_inner_product_f16,
152 l2_squared_bf16: typed_scalar::l2_squared_bf16,
153 cosine_distance_bf16: typed_scalar::cosine_bf16,
154 neg_inner_product_bf16: typed_scalar::neg_inner_product_bf16,
155 l2_bbq: bbq::l2_bbq,
156 };
157 tracing::info!(kernel = rt.name, "vector SIMD kernel selected");
158 rt
159 }
160 }
161}
162
163static RUNTIME: std::sync::OnceLock<SimdRuntime> = std::sync::OnceLock::new();
165
166pub fn runtime() -> &'static SimdRuntime {
168 RUNTIME.get_or_init(SimdRuntime::detect)
169}
170
171#[cfg(test)]
172mod tests {
173 use super::super::hamming::fast_hamming;
174 use super::super::scalar::{scalar_cosine, scalar_ip, scalar_l2};
175 use super::*;
176
177 #[test]
178 fn runtime_detects_features() {
179 let rt = SimdRuntime::detect();
180 assert!(!rt.name.is_empty());
181 tracing::info!("SIMD runtime: {}", rt.name);
182 }
183
184 #[test]
185 fn l2_matches_scalar() {
186 let rt = runtime();
187 let a: Vec<f32> = (0..768).map(|i| (i as f32) * 0.01).collect();
188 let b: Vec<f32> = (0..768).map(|i| (i as f32) * 0.01 + 0.001).collect();
189
190 let simd_result = (rt.l2_squared)(&a, &b);
191 let scalar_result = scalar_l2(&a, &b);
192 assert!(
193 (simd_result - scalar_result).abs() < 0.01,
194 "simd={simd_result}, scalar={scalar_result}"
195 );
196 }
197
198 #[test]
199 fn cosine_matches_scalar() {
200 let rt = runtime();
201 let a: Vec<f32> = (0..768).map(|i| (i as f32).sin()).collect();
202 let b: Vec<f32> = (0..768).map(|i| (i as f32).cos()).collect();
203
204 let simd_result = (rt.cosine_distance)(&a, &b);
205 let scalar_result = scalar_cosine(&a, &b);
206 assert!(
207 (simd_result - scalar_result).abs() < 0.001,
208 "simd={simd_result}, scalar={scalar_result}"
209 );
210 }
211
212 #[test]
213 fn ip_matches_scalar() {
214 let rt = runtime();
215 let a: Vec<f32> = (0..128).map(|i| (i as f32) * 0.1).collect();
216 let b: Vec<f32> = (0..128).map(|i| (i as f32) * 0.2).collect();
217
218 let simd_result = (rt.neg_inner_product)(&a, &b);
219 let scalar_result = scalar_ip(&a, &b);
220 assert!(
221 (simd_result - scalar_result).abs() < 0.1,
222 "simd={simd_result}, scalar={scalar_result}"
223 );
224 }
225
226 #[test]
227 fn hamming_matches() {
228 let a = vec![0b10101010u8; 16];
229 let b = vec![0b01010101u8; 16];
230 assert_eq!(fast_hamming(&a, &b), 128);
231 }
232
233 #[test]
234 fn small_vectors() {
235 let rt = runtime();
236 let a = [1.0f32, 2.0, 3.0];
237 let b = [4.0f32, 5.0, 6.0];
238 let l2 = (rt.l2_squared)(&a, &b);
239 assert!((l2 - 27.0).abs() < 0.01);
240 }
241}