velesdb_core/simd_native/dispatch/euclidean.rs
1#[allow(unused_imports)] // simd_level used only on x86_64/aarch64 targets
2use super::{dot::dot_product_native, simd_level, SimdLevel};
3
4/// Squared L2 distance with runtime SIMD dispatch.
5///
6/// # Panics
7///
8/// Panics if `a.len() != b.len()`.
9#[allow(clippy::inline_always)]
10#[inline(always)]
11#[must_use]
12pub fn squared_l2_native(a: &[f32], b: &[f32]) -> f32 {
13 assert_eq!(a.len(), b.len(), "Vector dimensions must match");
14 match simd_level() {
15 #[cfg(target_arch = "x86_64")]
16 SimdLevel::Avx512 if a.len() >= 1024 => {
17 // SAFETY: AVX-512 8-acc squared-L2 kernel requires CPU feature + minimum dim.
18 // - Condition 1: `simd_level()` selected `Avx512` after runtime detection.
19 // SAFETY: 8-accumulator variant for very large dimensions (stride 128).
20 unsafe { crate::simd_native::squared_l2_avx512_8acc(a, b) }
21 }
22 #[cfg(target_arch = "x86_64")]
23 SimdLevel::Avx512 if a.len() >= 512 => {
24 // SAFETY: AVX-512 squared-L2 kernel requires CPU feature + minimum dim.
25 // - Condition 1: `simd_level()` selected `Avx512` after runtime detection.
26 // SAFETY: call specialized kernel for higher throughput.
27 unsafe { crate::simd_native::squared_l2_avx512_4acc(a, b) }
28 }
29 #[cfg(target_arch = "x86_64")]
30 SimdLevel::Avx512 => {
31 // SAFETY: AVX-512 squared-L2 kernel requires CPU feature.
32 // - Condition 1: `simd_level()` selected `Avx512` after runtime detection.
33 // SAFETY: call specialized kernel for higher throughput.
34 unsafe { crate::simd_native::squared_l2_avx512(a, b) }
35 }
36 #[cfg(target_arch = "x86_64")]
37 SimdLevel::Avx2 if a.len() >= 256 => {
38 // SAFETY: AVX2 squared-L2 kernel requires CPU feature + minimum dim.
39 // - Condition 1: `simd_level()` selected `Avx2` after runtime detection.
40 // SAFETY: call specialized kernel for higher throughput.
41 unsafe { crate::simd_native::squared_l2_avx2_4acc(a, b) }
42 }
43 #[cfg(target_arch = "x86_64")]
44 SimdLevel::Avx2 if a.len() >= 64 => {
45 // SAFETY: AVX2 squared-L2 kernel requires CPU feature + minimum dim.
46 // - Condition 1: `simd_level()` selected `Avx2` after runtime detection.
47 // SAFETY: call specialized kernel for higher throughput.
48 unsafe { crate::simd_native::squared_l2_avx2(a, b) }
49 }
50 #[cfg(target_arch = "x86_64")]
51 SimdLevel::Avx2 if a.len() >= 8 => {
52 // SAFETY: AVX2 squared-L2 kernel requires CPU feature + minimum dim.
53 // - Condition 1: `simd_level()` selected `Avx2` after runtime detection.
54 // SAFETY: call specialized kernel for higher throughput.
55 unsafe { crate::simd_native::squared_l2_avx2_1acc(a, b) }
56 }
57 #[cfg(target_arch = "aarch64")]
58 SimdLevel::Neon if a.len() >= 4 => crate::simd_native::squared_l2_neon(a, b),
59 _ => super::squared_l2_scalar(a, b),
60 }
61}
62
63/// Euclidean distance with runtime SIMD dispatch.
64#[allow(clippy::inline_always)]
65#[inline(always)]
66#[must_use]
67pub fn euclidean_native(a: &[f32], b: &[f32]) -> f32 {
68 squared_l2_native(a, b).sqrt()
69}
70
71/// L2 norm with runtime SIMD dispatch.
72#[allow(clippy::inline_always)]
73#[inline(always)]
74#[must_use]
75pub fn norm_native(v: &[f32]) -> f32 {
76 dot_product_native(v, v).sqrt()
77}
78
79/// In-place normalization with runtime SIMD dispatch.
80///
81/// F-07: The scaling phase now uses SIMD (AVX2/AVX-512) instead of a scalar loop.
82/// For 768D vectors, this is ~4-8x faster on the scaling phase.
83#[allow(clippy::inline_always)]
84#[inline(always)]
85pub fn normalize_inplace_native(v: &mut [f32]) {
86 let n = norm_native(v);
87 if n > 0.0 {
88 let inv_norm = 1.0 / n;
89 scale_inplace_native(v, inv_norm);
90 }
91}
92
93/// Scales all elements of a mutable slice by a constant factor using SIMD.
94///
95/// F-07: Replaces scalar `for x in v { *x *= factor }` with SIMD broadcast+mul.
96/// AVX-512 and AVX2 share the AVX2 kernel — sufficient throughput for `scale`
97/// because the operation is memory-bound on every microarchitecture we ship
98/// to. An AVX-512 variant becomes reachable now that `_mm512_storeu_ps` sits
99/// inside the workspace MSRV of 1.89, but is intentionally not written until
100/// a profile shows it would beat the AVX2 path.
101#[inline]
102fn scale_inplace_native(v: &mut [f32], factor: f32) {
103 match simd_level() {
104 #[cfg(target_arch = "x86_64")]
105 SimdLevel::Avx512 | SimdLevel::Avx2 if v.len() >= 8 => {
106 // SAFETY: AVX2 scale kernel requires CPU feature + minimum dim.
107 // - Condition 1: `simd_level()` confirmed AVX2+ after runtime detection.
108 // SAFETY: broadcast factor and multiply 8 floats per iteration.
109 unsafe { scale_inplace_avx2(v, factor) };
110 }
111 _ => {
112 for x in v.iter_mut() {
113 *x *= factor;
114 }
115 }
116 }
117}
118
119/// AVX2 in-place scale: `v[i] *= factor` for all elements.
120///
121/// # Safety
122///
123/// Caller must ensure CPU supports AVX2 (enforced by runtime detection).
124#[cfg(target_arch = "x86_64")]
125#[target_feature(enable = "avx2")]
126#[inline]
127unsafe fn scale_inplace_avx2(v: &mut [f32], factor: f32) {
128 use std::arch::x86_64::{_mm256_loadu_ps, _mm256_mul_ps, _mm256_set1_ps, _mm256_storeu_ps};
129
130 let len = v.len();
131 let simd_len = len / 8;
132 let ptr = v.as_mut_ptr();
133 let scale = _mm256_set1_ps(factor);
134
135 for i in 0..simd_len {
136 let offset = i * 8;
137 // SAFETY: offset + 8 <= simd_len * 8 <= len, so within bounds.
138 // `_mm256_loadu_ps` / `_mm256_storeu_ps` handle unaligned access.
139 let val = _mm256_loadu_ps(ptr.add(offset));
140 let scaled = _mm256_mul_ps(val, scale);
141 _mm256_storeu_ps(ptr.add(offset), scaled);
142 }
143
144 // Scalar remainder (0-7 elements)
145 let base = simd_len * 8;
146 for i in base..len {
147 // SAFETY: `i` is in range `base..len` where `base = simd_len * 8 <= len`,
148 // so `i < len` is guaranteed. Bounds check would be elided by LLVM anyway.
149 *v.get_unchecked_mut(i) *= factor;
150 }
151}
152
153// AVX-512 scale_inplace is intentionally not implemented — the AVX2 kernel
154// already saturates memory bandwidth on the typical embedding-size workload,
155// and AVX-512 CPUs hit it via the AVX2 dispatch arm above.
156
157/// Batch squared L2 distance with cross-platform multi-level prefetch hints.
158#[inline]
159#[must_use]
160pub fn batch_squared_l2_native(candidates: &[&[f32]], query: &[f32]) -> Vec<f32> {
161 super::batch_with_prefetch(candidates, query, squared_l2_native)
162}
163
164/// Batch euclidean distance with cross-platform multi-level prefetch hints.
165#[inline]
166#[must_use]
167pub fn batch_euclidean_native(candidates: &[&[f32]], query: &[f32]) -> Vec<f32> {
168 super::batch_with_prefetch(candidates, query, euclidean_native)
169}
170
171#[allow(unused_variables)] // dim used only on x86_64 for dimension-based dispatch
172pub(super) fn resolve_squared_l2(level: SimdLevel, dim: usize) -> fn(&[f32], &[f32]) -> f32 {
173 match level {
174 #[cfg(target_arch = "x86_64")]
175 SimdLevel::Avx512 if dim >= 1024 => {
176 |a, b| {
177 // SAFETY: Resolver emitted AVX-512 8-acc implementation for this dimension.
178 // - Condition 1: caller chose this function pointer via `resolve_squared_l2`.
179 // SAFETY: execute AVX-512 8-accumulator squared-L2 for very large dims.
180 unsafe { crate::simd_native::squared_l2_avx512_8acc(a, b) }
181 }
182 }
183 #[cfg(target_arch = "x86_64")]
184 SimdLevel::Avx512 if dim >= 512 => {
185 |a, b| {
186 // SAFETY: Resolver emitted AVX-512 implementation for this dimension.
187 // - Condition 1: caller chose this function pointer via `resolve_squared_l2`.
188 // SAFETY: execute AVX-512 specialized squared-L2 implementation.
189 unsafe { crate::simd_native::squared_l2_avx512_4acc(a, b) }
190 }
191 }
192 #[cfg(target_arch = "x86_64")]
193 SimdLevel::Avx512 => |a, b| {
194 // SAFETY: Resolver emitted AVX-512 implementation for this dimension.
195 // - Condition 1: caller chose this function pointer via `resolve_squared_l2`.
196 // SAFETY: execute AVX-512 specialized squared-L2 implementation.
197 unsafe { crate::simd_native::squared_l2_avx512(a, b) }
198 },
199 #[cfg(target_arch = "x86_64")]
200 SimdLevel::Avx2 if dim >= 256 => {
201 |a, b| {
202 // SAFETY: Resolver emitted AVX2 implementation for this dimension.
203 // - Condition 1: caller chose this function pointer via `resolve_squared_l2`.
204 // SAFETY: execute AVX2 specialized squared-L2 implementation.
205 unsafe { crate::simd_native::squared_l2_avx2_4acc(a, b) }
206 }
207 }
208 #[cfg(target_arch = "x86_64")]
209 SimdLevel::Avx2 if dim >= 64 => |a, b| {
210 // SAFETY: Resolver emitted AVX2 implementation for this dimension.
211 // - Condition 1: caller chose this function pointer via `resolve_squared_l2`.
212 // SAFETY: execute AVX2 specialized squared-L2 implementation.
213 unsafe { crate::simd_native::squared_l2_avx2(a, b) }
214 },
215 #[cfg(target_arch = "x86_64")]
216 SimdLevel::Avx2 if dim >= 8 => {
217 |a, b| {
218 // SAFETY: Resolver emitted AVX2 implementation for this dimension.
219 // - Condition 1: caller chose this function pointer via `resolve_squared_l2`.
220 // SAFETY: execute AVX2 specialized squared-L2 implementation.
221 unsafe { crate::simd_native::squared_l2_avx2_1acc(a, b) }
222 }
223 }
224 #[cfg(target_arch = "aarch64")]
225 SimdLevel::Neon if dim >= 4 => |a, b| crate::simd_native::squared_l2_neon(a, b),
226 _ => super::squared_l2_scalar,
227 }
228}