1#[cfg(target_arch = "x86_64")]
6use std::arch::x86_64::*;
7
8#[cfg(target_arch = "aarch64")]
9use std::arch::aarch64::*;
10
11#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
12use std::arch::wasm32::*;
13
14use super::simd_config;
15
16#[cfg(target_arch = "x86_64")]
17use super::dot_product::{horizontal_sum_avx2, horizontal_sum_avx512};
18
19#[cfg(target_arch = "aarch64")]
20use super::dot_product::horizontal_sum_neon;
21
22#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
23use super::dot_product::horizontal_sum_simd128;
24
25#[inline]
27pub fn normalize(vector: &mut [f32]) {
28 let config = simd_config();
29
30 #[cfg(target_arch = "x86_64")]
31 {
32 if config.avx512f_enabled {
33 return unsafe { normalize_avx512_unrolled(vector) };
37 }
38 if config.avx2_enabled && config.fma_enabled {
39 return unsafe { normalize_avx2_unrolled(vector) };
43 }
44 }
45
46 #[cfg(target_arch = "aarch64")]
47 {
48 if config.neon_enabled {
49 return unsafe { normalize_neon_unrolled(vector) };
53 }
54 }
55
56 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
57 {
58 if config.simd128_enabled() {
59 return unsafe { normalize_simd128_unrolled(vector) };
64 }
65 }
66
67 normalize_scalar(vector)
68}
69
70pub(crate) fn normalize_scalar(vector: &mut [f32]) {
72 let norm: f32 = vector.iter().map(|x| x * x).sum::<f32>().sqrt();
73 if norm > 0.0 {
74 let inv_norm = 1.0 / norm;
75 vector.iter_mut().for_each(|x| *x *= inv_norm);
76 }
77}
78
79#[cfg(target_arch = "x86_64")]
85#[target_feature(enable = "avx512f")]
86unsafe fn normalize_avx512_unrolled(vector: &mut [f32]) {
87 const SIMD_WIDTH: usize = 16;
88 const UNROLL: usize = 4;
89 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL;
90
91 let n = vector.len();
92 let chunks = n / CHUNK_SIZE;
93 let main_processed = chunks * CHUNK_SIZE;
94 let remaining = n - main_processed;
95 let remaining_chunks = remaining / SIMD_WIDTH;
96
97 let mut norm0 = _mm512_setzero_ps();
99 let mut norm1 = _mm512_setzero_ps();
100 let mut norm2 = _mm512_setzero_ps();
101 let mut norm3 = _mm512_setzero_ps();
102
103 for i in 0..chunks {
104 let base = i * CHUNK_SIZE;
105
106 let v0 = _mm512_loadu_ps(vector.as_ptr().add(base));
107 norm0 = _mm512_fmadd_ps(v0, v0, norm0);
108
109 let v1 = _mm512_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH));
110 norm1 = _mm512_fmadd_ps(v1, v1, norm1);
111
112 let v2 = _mm512_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 2));
113 norm2 = _mm512_fmadd_ps(v2, v2, norm2);
114
115 let v3 = _mm512_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 3));
116 norm3 = _mm512_fmadd_ps(v3, v3, norm3);
117 }
118
119 let norm_vec = _mm512_add_ps(_mm512_add_ps(norm0, norm1), _mm512_add_ps(norm2, norm3));
120
121 let mut norm_remainder = _mm512_setzero_ps();
123 for i in 0..remaining_chunks {
124 let offset = main_processed + i * SIMD_WIDTH;
125 let v = _mm512_loadu_ps(vector.as_ptr().add(offset));
126 norm_remainder = _mm512_fmadd_ps(v, v, norm_remainder);
127 }
128
129 let mut norm_sq = horizontal_sum_avx512(norm_vec) + horizontal_sum_avx512(norm_remainder);
130
131 for i in (main_processed + remaining_chunks * SIMD_WIDTH)..n {
133 norm_sq += vector[i] * vector[i];
134 }
135
136 let norm = norm_sq.sqrt();
137 if norm.is_nan() || norm <= 0.0 {
142 return;
143 }
144
145 let inv_norm = 1.0 / norm;
146 let inv_norm_vec = _mm512_set1_ps(inv_norm);
147
148 for i in 0..chunks {
150 let base = i * CHUNK_SIZE;
151
152 let v0 = _mm512_loadu_ps(vector.as_ptr().add(base));
153 _mm512_storeu_ps(
154 vector.as_mut_ptr().add(base),
155 _mm512_mul_ps(v0, inv_norm_vec),
156 );
157
158 let v1 = _mm512_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH));
159 _mm512_storeu_ps(
160 vector.as_mut_ptr().add(base + SIMD_WIDTH),
161 _mm512_mul_ps(v1, inv_norm_vec),
162 );
163
164 let v2 = _mm512_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 2));
165 _mm512_storeu_ps(
166 vector.as_mut_ptr().add(base + SIMD_WIDTH * 2),
167 _mm512_mul_ps(v2, inv_norm_vec),
168 );
169
170 let v3 = _mm512_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 3));
171 _mm512_storeu_ps(
172 vector.as_mut_ptr().add(base + SIMD_WIDTH * 3),
173 _mm512_mul_ps(v3, inv_norm_vec),
174 );
175 }
176
177 for i in 0..remaining_chunks {
179 let offset = main_processed + i * SIMD_WIDTH;
180 let v = _mm512_loadu_ps(vector.as_ptr().add(offset));
181 _mm512_storeu_ps(
182 vector.as_mut_ptr().add(offset),
183 _mm512_mul_ps(v, inv_norm_vec),
184 );
185 }
186
187 for i in (main_processed + remaining_chunks * SIMD_WIDTH)..n {
189 vector[i] *= inv_norm;
190 }
191}
192
193#[cfg(target_arch = "x86_64")]
199#[target_feature(enable = "avx2", enable = "fma")]
200unsafe fn normalize_avx2_unrolled(vector: &mut [f32]) {
201 const SIMD_WIDTH: usize = 8;
202 const UNROLL: usize = 4;
203 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL;
204 let n = vector.len();
205 let chunks = n / CHUNK_SIZE;
206
207 let mut norm0 = _mm256_setzero_ps();
209 let mut norm1 = _mm256_setzero_ps();
210 let mut norm2 = _mm256_setzero_ps();
211 let mut norm3 = _mm256_setzero_ps();
212
213 for i in 0..chunks {
214 let base = i * CHUNK_SIZE;
215
216 let v0 = _mm256_loadu_ps(vector.as_ptr().add(base));
217 norm0 = _mm256_fmadd_ps(v0, v0, norm0);
218
219 let v1 = _mm256_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH));
220 norm1 = _mm256_fmadd_ps(v1, v1, norm1);
221
222 let v2 = _mm256_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 2));
223 norm2 = _mm256_fmadd_ps(v2, v2, norm2);
224
225 let v3 = _mm256_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 3));
226 norm3 = _mm256_fmadd_ps(v3, v3, norm3);
227 }
228
229 let norm_vec = _mm256_add_ps(_mm256_add_ps(norm0, norm1), _mm256_add_ps(norm2, norm3));
230 let mut norm_sq = horizontal_sum_avx2(norm_vec);
231
232 for i in (chunks * CHUNK_SIZE)..n {
234 norm_sq += vector[i] * vector[i];
235 }
236
237 let norm = norm_sq.sqrt();
238 if norm.is_nan() || norm <= 0.0 {
242 return;
243 }
244
245 let inv_norm = 1.0 / norm;
246 let inv_norm_vec = _mm256_set1_ps(inv_norm);
247
248 for i in 0..chunks {
250 let base = i * CHUNK_SIZE;
251
252 let v0 = _mm256_loadu_ps(vector.as_ptr().add(base));
253 _mm256_storeu_ps(
254 vector.as_mut_ptr().add(base),
255 _mm256_mul_ps(v0, inv_norm_vec),
256 );
257
258 let v1 = _mm256_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH));
259 _mm256_storeu_ps(
260 vector.as_mut_ptr().add(base + SIMD_WIDTH),
261 _mm256_mul_ps(v1, inv_norm_vec),
262 );
263
264 let v2 = _mm256_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 2));
265 _mm256_storeu_ps(
266 vector.as_mut_ptr().add(base + SIMD_WIDTH * 2),
267 _mm256_mul_ps(v2, inv_norm_vec),
268 );
269
270 let v3 = _mm256_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 3));
271 _mm256_storeu_ps(
272 vector.as_mut_ptr().add(base + SIMD_WIDTH * 3),
273 _mm256_mul_ps(v3, inv_norm_vec),
274 );
275 }
276
277 for i in (chunks * CHUNK_SIZE)..n {
279 vector[i] *= inv_norm;
280 }
281}
282
283#[cfg(target_arch = "aarch64")]
289#[inline]
290unsafe fn normalize_neon_unrolled(vector: &mut [f32]) {
291 const SIMD_WIDTH: usize = 4;
292 const UNROLL: usize = 4;
293 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL;
294 let n = vector.len();
295 let chunks = n / CHUNK_SIZE;
296
297 let mut norm0 = vdupq_n_f32(0.0);
299 let mut norm1 = vdupq_n_f32(0.0);
300 let mut norm2 = vdupq_n_f32(0.0);
301 let mut norm3 = vdupq_n_f32(0.0);
302
303 for i in 0..chunks {
304 let base = i * CHUNK_SIZE;
305
306 let v0 = vld1q_f32(vector.as_ptr().add(base));
307 norm0 = vfmaq_f32(norm0, v0, v0);
308
309 let v1 = vld1q_f32(vector.as_ptr().add(base + SIMD_WIDTH));
310 norm1 = vfmaq_f32(norm1, v1, v1);
311
312 let v2 = vld1q_f32(vector.as_ptr().add(base + SIMD_WIDTH * 2));
313 norm2 = vfmaq_f32(norm2, v2, v2);
314
315 let v3 = vld1q_f32(vector.as_ptr().add(base + SIMD_WIDTH * 3));
316 norm3 = vfmaq_f32(norm3, v3, v3);
317 }
318
319 let norm_vec = vaddq_f32(vaddq_f32(norm0, norm1), vaddq_f32(norm2, norm3));
320 let mut norm_sq = horizontal_sum_neon(norm_vec);
321
322 for val in vector.iter().skip(chunks * CHUNK_SIZE) {
323 norm_sq += val * val;
324 }
325
326 if norm_sq.is_nan() || norm_sq <= 0.0 {
332 return;
333 }
334
335 let norm_sq_v = vdupq_n_f32(norm_sq);
339 let y0 = vrsqrteq_f32(norm_sq_v);
340 let y1 = vmulq_f32(y0, vrsqrtsq_f32(norm_sq_v, vmulq_f32(y0, y0)));
341 let y2 = vmulq_f32(y1, vrsqrtsq_f32(norm_sq_v, vmulq_f32(y1, y1)));
342 let mut inv_norm = vgetq_lane_f32(y2, 0);
345 if !inv_norm.is_finite() {
349 inv_norm = 1.0 / norm_sq.sqrt();
350 }
351 let inv_norm_vec = vdupq_n_f32(inv_norm);
352
353 for i in 0..chunks {
355 let base = i * CHUNK_SIZE;
356
357 let v0 = vld1q_f32(vector.as_ptr().add(base));
358 vst1q_f32(vector.as_mut_ptr().add(base), vmulq_f32(v0, inv_norm_vec));
359
360 let v1 = vld1q_f32(vector.as_ptr().add(base + SIMD_WIDTH));
361 vst1q_f32(
362 vector.as_mut_ptr().add(base + SIMD_WIDTH),
363 vmulq_f32(v1, inv_norm_vec),
364 );
365
366 let v2 = vld1q_f32(vector.as_ptr().add(base + SIMD_WIDTH * 2));
367 vst1q_f32(
368 vector.as_mut_ptr().add(base + SIMD_WIDTH * 2),
369 vmulq_f32(v2, inv_norm_vec),
370 );
371
372 let v3 = vld1q_f32(vector.as_ptr().add(base + SIMD_WIDTH * 3));
373 vst1q_f32(
374 vector.as_mut_ptr().add(base + SIMD_WIDTH * 3),
375 vmulq_f32(v3, inv_norm_vec),
376 );
377 }
378
379 for val in vector.iter_mut().skip(chunks * CHUNK_SIZE) {
381 *val *= inv_norm;
382 }
383}
384
385#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
391#[inline]
392unsafe fn normalize_simd128_unrolled(vector: &mut [f32]) {
393 const SIMD_WIDTH: usize = 4;
394 const UNROLL: usize = 4;
395 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL;
396 let n = vector.len();
397 let chunks = n / CHUNK_SIZE;
398
399 let mut norm0 = f32x4_splat(0.0);
401 let mut norm1 = f32x4_splat(0.0);
402 let mut norm2 = f32x4_splat(0.0);
403 let mut norm3 = f32x4_splat(0.0);
404
405 for i in 0..chunks {
406 let base = i * CHUNK_SIZE;
407
408 let v0 = v128_load(vector.as_ptr().add(base) as *const v128);
409 norm0 = f32x4_add(norm0, f32x4_mul(v0, v0));
410
411 let v1 = v128_load(vector.as_ptr().add(base + SIMD_WIDTH) as *const v128);
412 norm1 = f32x4_add(norm1, f32x4_mul(v1, v1));
413
414 let v2 = v128_load(vector.as_ptr().add(base + SIMD_WIDTH * 2) as *const v128);
415 norm2 = f32x4_add(norm2, f32x4_mul(v2, v2));
416
417 let v3 = v128_load(vector.as_ptr().add(base + SIMD_WIDTH * 3) as *const v128);
418 norm3 = f32x4_add(norm3, f32x4_mul(v3, v3));
419 }
420
421 let norm_vec = f32x4_add(f32x4_add(norm0, norm1), f32x4_add(norm2, norm3));
422 let mut norm_sq = horizontal_sum_simd128(norm_vec);
423
424 for i in (chunks * CHUNK_SIZE)..n {
426 norm_sq += vector[i] * vector[i];
427 }
428
429 let norm = norm_sq.sqrt();
430 if norm.is_nan() || norm <= 0.0 {
434 return;
435 }
436
437 let inv_norm = 1.0 / norm;
438 let inv_norm_vec = f32x4_splat(inv_norm);
439
440 for i in 0..chunks {
442 let base = i * CHUNK_SIZE;
443
444 let v0 = v128_load(vector.as_ptr().add(base) as *const v128);
445 v128_store(
446 vector.as_mut_ptr().add(base) as *mut v128,
447 f32x4_mul(v0, inv_norm_vec),
448 );
449
450 let v1 = v128_load(vector.as_ptr().add(base + SIMD_WIDTH) as *const v128);
451 v128_store(
452 vector.as_mut_ptr().add(base + SIMD_WIDTH) as *mut v128,
453 f32x4_mul(v1, inv_norm_vec),
454 );
455
456 let v2 = v128_load(vector.as_ptr().add(base + SIMD_WIDTH * 2) as *const v128);
457 v128_store(
458 vector.as_mut_ptr().add(base + SIMD_WIDTH * 2) as *mut v128,
459 f32x4_mul(v2, inv_norm_vec),
460 );
461
462 let v3 = v128_load(vector.as_ptr().add(base + SIMD_WIDTH * 3) as *const v128);
463 v128_store(
464 vector.as_mut_ptr().add(base + SIMD_WIDTH * 3) as *mut v128,
465 f32x4_mul(v3, inv_norm_vec),
466 );
467 }
468
469 for i in (chunks * CHUNK_SIZE)..n {
471 vector[i] *= inv_norm;
472 }
473}