lattice_embed/simd/
normalize.rs1#[cfg(target_arch = "x86_64")]
4use std::arch::x86_64::*;
5
6#[cfg(target_arch = "aarch64")]
7use std::arch::aarch64::*;
8
9use super::simd_config;
10
11#[cfg(target_arch = "x86_64")]
12use super::dot_product::{horizontal_sum_avx2, horizontal_sum_avx512};
13
14#[cfg(target_arch = "aarch64")]
15use super::dot_product::horizontal_sum_neon;
16
17#[inline]
19pub fn normalize(vector: &mut [f32]) {
20 let config = simd_config();
21
22 #[cfg(target_arch = "x86_64")]
23 {
24 if config.avx512f_enabled {
25 return unsafe { normalize_avx512_unrolled(vector) };
29 }
30 if config.avx2_enabled && config.fma_enabled {
31 return unsafe { normalize_avx2_unrolled(vector) };
35 }
36 }
37
38 #[cfg(target_arch = "aarch64")]
39 {
40 if config.neon_enabled {
41 return unsafe { normalize_neon_unrolled(vector) };
45 }
46 }
47
48 normalize_scalar(vector)
49}
50
51pub(crate) fn normalize_scalar(vector: &mut [f32]) {
53 let norm: f32 = vector.iter().map(|x| x * x).sum::<f32>().sqrt();
54 if norm > 0.0 {
55 let inv_norm = 1.0 / norm;
56 vector.iter_mut().for_each(|x| *x *= inv_norm);
57 }
58}
59
60#[cfg(target_arch = "x86_64")]
76#[target_feature(enable = "avx512f")]
77unsafe fn normalize_avx512_unrolled(vector: &mut [f32]) {
78 const SIMD_WIDTH: usize = 16;
79 const UNROLL: usize = 4;
80 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL;
81
82 let n = vector.len();
83 let chunks = n / CHUNK_SIZE;
84 let main_processed = chunks * CHUNK_SIZE;
85 let remaining = n - main_processed;
86 let remaining_chunks = remaining / SIMD_WIDTH;
87
88 let mut norm0 = _mm512_setzero_ps();
90 let mut norm1 = _mm512_setzero_ps();
91 let mut norm2 = _mm512_setzero_ps();
92 let mut norm3 = _mm512_setzero_ps();
93
94 for i in 0..chunks {
95 let base = i * CHUNK_SIZE;
96
97 let v0 = _mm512_loadu_ps(vector.as_ptr().add(base));
98 norm0 = _mm512_fmadd_ps(v0, v0, norm0);
99
100 let v1 = _mm512_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH));
101 norm1 = _mm512_fmadd_ps(v1, v1, norm1);
102
103 let v2 = _mm512_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 2));
104 norm2 = _mm512_fmadd_ps(v2, v2, norm2);
105
106 let v3 = _mm512_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 3));
107 norm3 = _mm512_fmadd_ps(v3, v3, norm3);
108 }
109
110 let norm_vec = _mm512_add_ps(_mm512_add_ps(norm0, norm1), _mm512_add_ps(norm2, norm3));
111
112 let mut norm_remainder = _mm512_setzero_ps();
114 for i in 0..remaining_chunks {
115 let offset = main_processed + i * SIMD_WIDTH;
116 let v = _mm512_loadu_ps(vector.as_ptr().add(offset));
117 norm_remainder = _mm512_fmadd_ps(v, v, norm_remainder);
118 }
119
120 let mut norm_sq = horizontal_sum_avx512(norm_vec) + horizontal_sum_avx512(norm_remainder);
121
122 for i in (main_processed + remaining_chunks * SIMD_WIDTH)..n {
124 norm_sq += vector[i] * vector[i];
125 }
126
127 let norm = norm_sq.sqrt();
128 if norm.is_nan() || norm <= 0.0 {
133 return;
134 }
135
136 let inv_norm = 1.0 / norm;
137 let inv_norm_vec = _mm512_set1_ps(inv_norm);
138
139 for i in 0..chunks {
141 let base = i * CHUNK_SIZE;
142
143 let v0 = _mm512_loadu_ps(vector.as_ptr().add(base));
144 _mm512_storeu_ps(
145 vector.as_mut_ptr().add(base),
146 _mm512_mul_ps(v0, inv_norm_vec),
147 );
148
149 let v1 = _mm512_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH));
150 _mm512_storeu_ps(
151 vector.as_mut_ptr().add(base + SIMD_WIDTH),
152 _mm512_mul_ps(v1, inv_norm_vec),
153 );
154
155 let v2 = _mm512_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 2));
156 _mm512_storeu_ps(
157 vector.as_mut_ptr().add(base + SIMD_WIDTH * 2),
158 _mm512_mul_ps(v2, inv_norm_vec),
159 );
160
161 let v3 = _mm512_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 3));
162 _mm512_storeu_ps(
163 vector.as_mut_ptr().add(base + SIMD_WIDTH * 3),
164 _mm512_mul_ps(v3, inv_norm_vec),
165 );
166 }
167
168 for i in 0..remaining_chunks {
170 let offset = main_processed + i * SIMD_WIDTH;
171 let v = _mm512_loadu_ps(vector.as_ptr().add(offset));
172 _mm512_storeu_ps(
173 vector.as_mut_ptr().add(offset),
174 _mm512_mul_ps(v, inv_norm_vec),
175 );
176 }
177
178 for i in (main_processed + remaining_chunks * SIMD_WIDTH)..n {
180 vector[i] *= inv_norm;
181 }
182}
183
184#[cfg(target_arch = "x86_64")]
196#[target_feature(enable = "avx2", enable = "fma")]
197unsafe fn normalize_avx2_unrolled(vector: &mut [f32]) {
198 const SIMD_WIDTH: usize = 8;
199 const UNROLL: usize = 4;
200 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL;
201 let n = vector.len();
202 let chunks = n / CHUNK_SIZE;
203
204 let mut norm0 = _mm256_setzero_ps();
206 let mut norm1 = _mm256_setzero_ps();
207 let mut norm2 = _mm256_setzero_ps();
208 let mut norm3 = _mm256_setzero_ps();
209
210 for i in 0..chunks {
211 let base = i * CHUNK_SIZE;
212
213 let v0 = _mm256_loadu_ps(vector.as_ptr().add(base));
214 norm0 = _mm256_fmadd_ps(v0, v0, norm0);
215
216 let v1 = _mm256_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH));
217 norm1 = _mm256_fmadd_ps(v1, v1, norm1);
218
219 let v2 = _mm256_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 2));
220 norm2 = _mm256_fmadd_ps(v2, v2, norm2);
221
222 let v3 = _mm256_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 3));
223 norm3 = _mm256_fmadd_ps(v3, v3, norm3);
224 }
225
226 let norm_vec = _mm256_add_ps(_mm256_add_ps(norm0, norm1), _mm256_add_ps(norm2, norm3));
227 let mut norm_sq = horizontal_sum_avx2(norm_vec);
228
229 for i in (chunks * CHUNK_SIZE)..n {
231 norm_sq += vector[i] * vector[i];
232 }
233
234 let norm = norm_sq.sqrt();
235 if norm.is_nan() || norm <= 0.0 {
239 return;
240 }
241
242 let inv_norm = 1.0 / norm;
243 let inv_norm_vec = _mm256_set1_ps(inv_norm);
244
245 for i in 0..chunks {
247 let base = i * CHUNK_SIZE;
248
249 let v0 = _mm256_loadu_ps(vector.as_ptr().add(base));
250 _mm256_storeu_ps(
251 vector.as_mut_ptr().add(base),
252 _mm256_mul_ps(v0, inv_norm_vec),
253 );
254
255 let v1 = _mm256_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH));
256 _mm256_storeu_ps(
257 vector.as_mut_ptr().add(base + SIMD_WIDTH),
258 _mm256_mul_ps(v1, inv_norm_vec),
259 );
260
261 let v2 = _mm256_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 2));
262 _mm256_storeu_ps(
263 vector.as_mut_ptr().add(base + SIMD_WIDTH * 2),
264 _mm256_mul_ps(v2, inv_norm_vec),
265 );
266
267 let v3 = _mm256_loadu_ps(vector.as_ptr().add(base + SIMD_WIDTH * 3));
268 _mm256_storeu_ps(
269 vector.as_mut_ptr().add(base + SIMD_WIDTH * 3),
270 _mm256_mul_ps(v3, inv_norm_vec),
271 );
272 }
273
274 for i in (chunks * CHUNK_SIZE)..n {
276 vector[i] *= inv_norm;
277 }
278}
279
280#[cfg(target_arch = "aarch64")]
298#[inline]
299unsafe fn normalize_neon_unrolled(vector: &mut [f32]) {
300 const SIMD_WIDTH: usize = 4;
301 const UNROLL: usize = 4;
302 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL;
303 let n = vector.len();
304 let chunks = n / CHUNK_SIZE;
305
306 let mut norm0 = vdupq_n_f32(0.0);
308 let mut norm1 = vdupq_n_f32(0.0);
309 let mut norm2 = vdupq_n_f32(0.0);
310 let mut norm3 = vdupq_n_f32(0.0);
311
312 for i in 0..chunks {
313 let base = i * CHUNK_SIZE;
314
315 let v0 = vld1q_f32(vector.as_ptr().add(base));
316 norm0 = vfmaq_f32(norm0, v0, v0);
317
318 let v1 = vld1q_f32(vector.as_ptr().add(base + SIMD_WIDTH));
319 norm1 = vfmaq_f32(norm1, v1, v1);
320
321 let v2 = vld1q_f32(vector.as_ptr().add(base + SIMD_WIDTH * 2));
322 norm2 = vfmaq_f32(norm2, v2, v2);
323
324 let v3 = vld1q_f32(vector.as_ptr().add(base + SIMD_WIDTH * 3));
325 norm3 = vfmaq_f32(norm3, v3, v3);
326 }
327
328 let norm_vec = vaddq_f32(vaddq_f32(norm0, norm1), vaddq_f32(norm2, norm3));
329 let mut norm_sq = horizontal_sum_neon(norm_vec);
330
331 for val in vector.iter().skip(chunks * CHUNK_SIZE) {
332 norm_sq += val * val;
333 }
334
335 if norm_sq.is_nan() || norm_sq <= 0.0 {
341 return;
342 }
343
344 let norm_sq_v = vdupq_n_f32(norm_sq);
348 let y0 = vrsqrteq_f32(norm_sq_v);
349 let y1 = vmulq_f32(y0, vrsqrtsq_f32(norm_sq_v, vmulq_f32(y0, y0)));
350 let y2 = vmulq_f32(y1, vrsqrtsq_f32(norm_sq_v, vmulq_f32(y1, y1)));
351 let mut inv_norm = vgetq_lane_f32(y2, 0);
354 if !inv_norm.is_finite() {
358 inv_norm = 1.0 / norm_sq.sqrt();
359 }
360 let inv_norm_vec = vdupq_n_f32(inv_norm);
361
362 for i in 0..chunks {
364 let base = i * CHUNK_SIZE;
365
366 let v0 = vld1q_f32(vector.as_ptr().add(base));
367 vst1q_f32(vector.as_mut_ptr().add(base), vmulq_f32(v0, inv_norm_vec));
368
369 let v1 = vld1q_f32(vector.as_ptr().add(base + SIMD_WIDTH));
370 vst1q_f32(
371 vector.as_mut_ptr().add(base + SIMD_WIDTH),
372 vmulq_f32(v1, inv_norm_vec),
373 );
374
375 let v2 = vld1q_f32(vector.as_ptr().add(base + SIMD_WIDTH * 2));
376 vst1q_f32(
377 vector.as_mut_ptr().add(base + SIMD_WIDTH * 2),
378 vmulq_f32(v2, inv_norm_vec),
379 );
380
381 let v3 = vld1q_f32(vector.as_ptr().add(base + SIMD_WIDTH * 3));
382 vst1q_f32(
383 vector.as_mut_ptr().add(base + SIMD_WIDTH * 3),
384 vmulq_f32(v3, inv_norm_vec),
385 );
386 }
387
388 for val in vector.iter_mut().skip(chunks * CHUNK_SIZE) {
390 *val *= inv_norm;
391 }
392}