1use std::sync::OnceLock;
16
17use crate::scalar;
18#[cfg(target_arch = "x86_64")]
19use crate::{avx2, avx512};
20#[cfg(target_arch = "aarch64")]
21use crate::neon;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Tier {
26 Scalar,
28 Avx2,
30 Avx512,
32 Neon,
34}
35
36impl Tier {
37 #[must_use]
39 pub const fn as_str(self) -> &'static str {
40 match self {
41 Tier::Scalar => "scalar",
42 Tier::Avx2 => "avx2",
43 Tier::Avx512 => "avx512",
44 Tier::Neon => "neon",
45 }
46 }
47}
48
49#[must_use]
55pub fn detect_tier() -> Tier {
56 #[cfg(target_arch = "x86_64")]
57 {
58 if is_x86_feature_detected!("avx512f") {
59 return Tier::Avx512;
60 }
61 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
62 return Tier::Avx2;
63 }
64 }
65 #[cfg(target_arch = "aarch64")]
66 {
67 if std::arch::is_aarch64_feature_detected!("neon") {
68 return Tier::Neon;
69 }
70 }
71 Tier::Scalar
72}
73
74#[must_use]
76pub fn active_tier() -> Tier {
77 static TIER: OnceLock<Tier> = OnceLock::new();
78 *TIER.get_or_init(detect_tier)
79}
80
81pub type BinaryKernel = fn(&[f32], &[f32]) -> f32;
83
84#[cfg(target_arch = "x86_64")]
89fn l2_sq_avx2(a: &[f32], b: &[f32]) -> f32 {
90 unsafe { avx2::l2_sq(a, b) }
92}
93#[cfg(target_arch = "x86_64")]
94fn l2_sq_avx512(a: &[f32], b: &[f32]) -> f32 {
95 unsafe { avx512::l2_sq(a, b) }
97}
98#[cfg(target_arch = "x86_64")]
99fn dot_avx2(a: &[f32], b: &[f32]) -> f32 {
100 unsafe { avx2::dot(a, b) }
102}
103#[cfg(target_arch = "x86_64")]
104fn dot_avx512(a: &[f32], b: &[f32]) -> f32 {
105 unsafe { avx512::dot(a, b) }
107}
108
109#[cfg(target_arch = "aarch64")]
110fn l2_sq_neon(a: &[f32], b: &[f32]) -> f32 {
111 unsafe { neon::l2_sq(a, b) }
113}
114#[cfg(target_arch = "aarch64")]
115fn dot_neon(a: &[f32], b: &[f32]) -> f32 {
116 unsafe { neon::dot(a, b) }
118}
119
120fn resolve_l2_sq() -> BinaryKernel {
121 match active_tier() {
122 #[cfg(target_arch = "x86_64")]
123 Tier::Avx512 => l2_sq_avx512,
124 #[cfg(target_arch = "x86_64")]
125 Tier::Avx2 => l2_sq_avx2,
126 #[cfg(target_arch = "aarch64")]
127 Tier::Neon => l2_sq_neon,
128 _ => scalar::l2_sq,
129 }
130}
131
132fn resolve_dot() -> BinaryKernel {
133 match active_tier() {
134 #[cfg(target_arch = "x86_64")]
135 Tier::Avx512 => dot_avx512,
136 #[cfg(target_arch = "x86_64")]
137 Tier::Avx2 => dot_avx2,
138 #[cfg(target_arch = "aarch64")]
139 Tier::Neon => dot_neon,
140 _ => scalar::dot,
141 }
142}
143
144#[must_use]
146pub fn l2_sq_kernel() -> BinaryKernel {
147 static K: OnceLock<BinaryKernel> = OnceLock::new();
148 *K.get_or_init(resolve_l2_sq)
149}
150
151#[must_use]
153pub fn dot_kernel() -> BinaryKernel {
154 static K: OnceLock<BinaryKernel> = OnceLock::new();
155 *K.get_or_init(resolve_dot)
156}
157
158#[must_use]
163pub fn cosine_parts(a: &[f32], b: &[f32]) -> (f32, f32, f32) {
164 match active_tier() {
165 #[cfg(target_arch = "x86_64")]
166 Tier::Avx512 => unsafe { avx512::cosine_parts(a, b) }, #[cfg(target_arch = "x86_64")]
168 Tier::Avx2 => unsafe { avx2::cosine_parts(a, b) }, #[cfg(target_arch = "aarch64")]
170 Tier::Neon => unsafe { neon::cosine_parts(a, b) }, _ => {
172 let mut d = 0.0f32;
174 let mut na = 0.0f32;
175 let mut nb = 0.0f32;
176 for i in 0..a.len() {
177 d += a[i] * b[i];
178 na += a[i] * a[i];
179 nb += b[i] * b[i];
180 }
181 (d, na, nb)
182 }
183 }
184}
185
186#[must_use]
200fn f16_simd_ok() -> bool {
201 static OK: OnceLock<bool> = OnceLock::new();
202 *OK.get_or_init(|| match active_tier() {
203 #[cfg(target_arch = "x86_64")]
204 Tier::Avx512 => true,
205 #[cfg(target_arch = "x86_64")]
206 Tier::Avx2 => is_x86_feature_detected!("f16c"),
207 #[cfg(target_arch = "aarch64")]
208 Tier::Neon => std::arch::is_aarch64_feature_detected!("fp16"),
209 _ => false,
210 })
211}
212
213#[must_use]
215pub fn l2_sq_f16(query: &[f32], stored: &[u8]) -> f32 {
216 if f16_simd_ok() {
217 match active_tier() {
218 #[cfg(target_arch = "x86_64")]
219 Tier::Avx512 => return unsafe { avx512::l2_sq_f16(query, stored) }, #[cfg(target_arch = "x86_64")]
221 Tier::Avx2 => return unsafe { avx2::l2_sq_f16(query, stored) }, #[cfg(target_arch = "aarch64")]
223 Tier::Neon => return unsafe { neon::l2_sq_f16(query, stored) }, _ => {}
225 }
226 }
227 scalar::l2_sq_f16(query, stored)
228}
229
230#[must_use]
232pub fn dot_f16(query: &[f32], stored: &[u8]) -> f32 {
233 if f16_simd_ok() {
234 match active_tier() {
235 #[cfg(target_arch = "x86_64")]
236 Tier::Avx512 => return unsafe { avx512::dot_f16(query, stored) }, #[cfg(target_arch = "x86_64")]
238 Tier::Avx2 => return unsafe { avx2::dot_f16(query, stored) }, #[cfg(target_arch = "aarch64")]
240 Tier::Neon => return unsafe { neon::dot_f16(query, stored) }, _ => {}
242 }
243 }
244 scalar::dot_f16(query, stored)
245}
246
247#[must_use]
249pub fn cosine_parts_f16(query: &[f32], stored: &[u8]) -> (f32, f32, f32) {
250 if f16_simd_ok() {
251 match active_tier() {
252 #[cfg(target_arch = "x86_64")]
253 Tier::Avx512 => return unsafe { avx512::cosine_parts_f16(query, stored) }, #[cfg(target_arch = "x86_64")]
255 Tier::Avx2 => return unsafe { avx2::cosine_parts_f16(query, stored) }, #[cfg(target_arch = "aarch64")]
257 Tier::Neon => return unsafe { neon::cosine_parts_f16(query, stored) }, _ => {}
259 }
260 }
261 scalar::cosine_parts_f16(query, stored)
262}
263
264#[must_use]
266pub fn l2_sq_i8(query: &[f32], scale: f32, codes: &[i8]) -> f32 {
267 match active_tier() {
268 #[cfg(target_arch = "x86_64")]
269 Tier::Avx512 => unsafe { avx512::l2_sq_i8(query, scale, codes) }, #[cfg(target_arch = "x86_64")]
271 Tier::Avx2 => unsafe { avx2::l2_sq_i8(query, scale, codes) }, #[cfg(target_arch = "aarch64")]
273 Tier::Neon => unsafe { neon::l2_sq_i8(query, scale, codes) }, _ => scalar::l2_sq_i8(query, scale, codes),
275 }
276}
277
278#[must_use]
280pub fn dot_i8(query: &[f32], scale: f32, codes: &[i8]) -> f32 {
281 match active_tier() {
282 #[cfg(target_arch = "x86_64")]
283 Tier::Avx512 => unsafe { avx512::dot_i8(query, scale, codes) }, #[cfg(target_arch = "x86_64")]
285 Tier::Avx2 => unsafe { avx2::dot_i8(query, scale, codes) }, #[cfg(target_arch = "aarch64")]
287 Tier::Neon => unsafe { neon::dot_i8(query, scale, codes) }, _ => scalar::dot_i8(query, scale, codes),
289 }
290}
291
292#[must_use]
294pub fn cosine_parts_i8(query: &[f32], scale: f32, codes: &[i8]) -> (f32, f32, f32) {
295 match active_tier() {
296 #[cfg(target_arch = "x86_64")]
297 Tier::Avx512 => unsafe { avx512::cosine_parts_i8(query, scale, codes) }, #[cfg(target_arch = "x86_64")]
299 Tier::Avx2 => unsafe { avx2::cosine_parts_i8(query, scale, codes) }, #[cfg(target_arch = "aarch64")]
301 Tier::Neon => unsafe { neon::cosine_parts_i8(query, scale, codes) }, _ => scalar::cosine_parts_i8(query, scale, codes),
303 }
304}