/home/noah/src/realizar/src/quantize/simd.rs
Line | Count | Source |
1 | | //! Quantization SIMD Helpers (PMAT-802) |
2 | | //! |
3 | | //! Extracted from quantize/mod.rs - Shared SIMD utility functions. |
4 | | //! |
5 | | //! ## Contents |
6 | | //! - f16 conversion: `f16_to_f32`, `read_f16` |
7 | | //! - Scale extraction: `extract_scale_min`, `extract_scale_min_from_slice` |
8 | | //! - Horizontal sum helpers: `hsum_epi32_128`, `hsum_epi32_256`, etc. |
9 | | //! - SIMD activations: `softmax_simd`, `fused_swiglu_simd`, `apply_rope_rotation_simd` |
10 | | |
11 | | // ============================================================================ |
12 | | // f16 Conversion (Manual Implementation) |
13 | | // ============================================================================ |
14 | | |
15 | | /// Convert IEEE 754 half-precision (f16) to single-precision (f32) |
16 | | /// |
17 | | /// Handles normal values, subnormals, infinities, and NaN. |
18 | | #[inline] |
19 | 111 | pub fn f16_to_f32(h: u16) -> f32 { |
20 | 111 | let sign = (h >> 15) & 1; |
21 | 111 | let exp = (h >> 10) & 0x1F; |
22 | 111 | let mantissa = h & 0x3FF; |
23 | | |
24 | 111 | if exp == 0 { |
25 | | // Subnormal or zero |
26 | 5 | if mantissa == 0 { |
27 | | // Zero (preserve sign) |
28 | 2 | if sign == 1 { |
29 | 1 | -0.0 |
30 | | } else { |
31 | 1 | 0.0 |
32 | | } |
33 | | } else { |
34 | | // Subnormal: (mantissa / 1024) * 2^-14 |
35 | 3 | let value = (mantissa as f32 / 1024.0) * (2.0_f32).powi(-14); |
36 | 3 | if sign == 1 { |
37 | 0 | -value |
38 | | } else { |
39 | 3 | value |
40 | | } |
41 | | } |
42 | 106 | } else if exp == 31 { |
43 | | // Infinity or NaN |
44 | 3 | if mantissa == 0 { |
45 | 2 | if sign == 1 { |
46 | 1 | f32::NEG_INFINITY |
47 | | } else { |
48 | 1 | f32::INFINITY |
49 | | } |
50 | | } else { |
51 | 1 | f32::NAN |
52 | | } |
53 | | } else { |
54 | | // Normal value: (1 + mantissa/1024) * 2^(exp-15) |
55 | 103 | let value = (1.0 + mantissa as f32 / 1024.0) * (2.0_f32).powi(exp as i32 - 15); |
56 | 103 | if sign == 1 { |
57 | 1 | -value |
58 | | } else { |
59 | 102 | value |
60 | | } |
61 | | } |
62 | 111 | } |
63 | | |
64 | | /// Helper: Read f16 from bytes and convert to f32 |
65 | | #[inline] |
66 | 6 | pub fn read_f16(bytes: &[u8]) -> f32 { |
67 | 6 | let bits = u16::from_le_bytes([bytes[0], bytes[1]]); |
68 | 6 | half::f16::from_bits(bits).to_f32() |
69 | 6 | } |
70 | | |
71 | | // ============================================================================ |
72 | | // Scale Extraction for K-Quantization |
73 | | // ============================================================================ |
74 | | |
75 | | /// Extract 6-bit scale and min values from packed scales array |
76 | | /// |
77 | | /// PAR-001 FIX: Matches llama.cpp's get_scale_min_k4 packing scheme: |
78 | | /// - Blocks 0-3: scale = q[j] & 63, min = q[j+4] & 63 |
79 | | /// - Blocks 4-7: scale = (q[j+4] & 0xF) | ((q[j-4] >> 6) << 4) |
80 | | /// min = (q[j+4] >> 4) | ((q[j] >> 6) << 4) |
81 | | #[inline] |
82 | 10.0M | pub fn extract_scale_min(scales: &[u8; 12], block_idx: usize) -> (f32, f32) { |
83 | 10.0M | let j = block_idx; |
84 | 10.0M | let (scale_bits, min_bits) = if j < 4 { |
85 | | // First 4 blocks: simple layout |
86 | 5.04M | let d = scales[j] & 63; |
87 | 5.04M | let m = scales[j + 4] & 63; |
88 | 5.04M | (d, m) |
89 | | } else { |
90 | | // Last 4 blocks: packed layout using high bits from first 4 bytes |
91 | 5.04M | let d = (scales[j + 4] & 0x0F) | ((scales[j - 4] >> 6) << 4); |
92 | 5.04M | let m = (scales[j + 4] >> 4) | ((scales[j] >> 6) << 4); |
93 | 5.04M | (d, m) |
94 | | }; |
95 | | |
96 | | // Return raw 6-bit values as floats |
97 | | // The GGUF header's d/dmin values already include the /63 normalization |
98 | 10.0M | let scale = f32::from(scale_bits); |
99 | 10.0M | let min = f32::from(min_bits); |
100 | | |
101 | 10.0M | (scale, min) |
102 | 10.0M | } |
103 | | |
104 | | /// Extract scale and min from packed 6-bit scales (helper for InterleavedQ4K) |
105 | 1 | pub fn extract_scale_min_from_slice(scales: &[u8], idx: usize) -> (f32, f32) { |
106 | | // Same logic as extract_scale_min but works with slice |
107 | 1 | let scale_idx = idx / 2; |
108 | 1 | let min_idx = idx / 2 + 4; |
109 | | |
110 | 1 | let (scale_raw, min_raw) = if idx.is_multiple_of(2) { |
111 | 1 | (scales[scale_idx] & 0x3F, scales[min_idx] & 0x3F) |
112 | | } else { |
113 | 0 | ( |
114 | 0 | (scales[scale_idx] >> 6) | ((scales[scale_idx + 2] & 0x0F) << 2), |
115 | 0 | (scales[min_idx] >> 6) | ((scales[min_idx + 2] & 0x0F) << 2), |
116 | 0 | ) |
117 | | }; |
118 | | |
119 | 1 | (scale_raw as f32, min_raw as f32) |
120 | 1 | } |
121 | | |
122 | | // ============================================================================ |
123 | | // x86_64 SIMD Horizontal Sum Helpers |
124 | | // ============================================================================ |
125 | | |
126 | | /// Fast horizontal sum of 4 i32 in __m128i |
127 | | /// |
128 | | /// # Safety |
129 | | /// Requires AVX2 support. Caller must verify CPU feature availability. |
130 | | #[cfg(target_arch = "x86_64")] |
131 | | #[target_feature(enable = "avx2")] |
132 | | #[inline] |
133 | 0 | pub unsafe fn hsum_epi32_128(v: std::arch::x86_64::__m128i) -> i32 { |
134 | | use std::arch::x86_64::{_mm_cvtsi128_si32, _mm_hadd_epi32}; |
135 | 0 | let sum64 = _mm_hadd_epi32(v, v); |
136 | 0 | let sum32 = _mm_hadd_epi32(sum64, sum64); |
137 | 0 | _mm_cvtsi128_si32(sum32) |
138 | 0 | } |
139 | | |
140 | | /// Fast horizontal sum of 8 i32 in __m256i |
141 | | /// |
142 | | /// # Safety |
143 | | /// Requires AVX2 support. Caller must verify CPU feature availability. |
144 | | #[cfg(target_arch = "x86_64")] |
145 | | #[target_feature(enable = "avx2")] |
146 | | #[inline] |
147 | 0 | pub unsafe fn hsum_epi32_256(v: std::arch::x86_64::__m256i) -> i32 { |
148 | | use std::arch::x86_64::{_mm256_castsi256_si128, _mm256_extracti128_si256, _mm_add_epi32}; |
149 | | // SAFETY: Unsafe operation with validated invariants |
150 | | unsafe { |
151 | 0 | let lo = _mm256_castsi256_si128(v); |
152 | 0 | let hi = _mm256_extracti128_si256(v, 1); |
153 | 0 | hsum_epi32_128(_mm_add_epi32(lo, hi)) |
154 | | } |
155 | 0 | } |
156 | | |
157 | | /// Helper: horizontal sum of 8 int32 values in a 256-bit register |
158 | | /// |
159 | | /// # Safety |
160 | | /// Requires AVX2 support. Caller must verify CPU feature availability. |
161 | | #[cfg(target_arch = "x86_64")] |
162 | | #[target_feature(enable = "avx2")] |
163 | | #[inline] |
164 | 0 | pub unsafe fn horizontal_sum_epi32_256(v: std::arch::x86_64::__m256i) -> i32 { |
165 | | use std::arch::x86_64::{ |
166 | | _mm256_castsi256_si128, _mm256_extracti128_si256, _mm_add_epi32, _mm_cvtsi128_si32, |
167 | | _mm_hadd_epi32, |
168 | | }; |
169 | | |
170 | | // Add high 128 bits to low 128 bits |
171 | 0 | let hi = _mm256_extracti128_si256(v, 1); |
172 | 0 | let lo = _mm256_castsi256_si128(v); |
173 | 0 | let sum128 = _mm_add_epi32(lo, hi); |
174 | | |
175 | | // Horizontal add within 128 bits |
176 | 0 | let sum64 = _mm_hadd_epi32(sum128, sum128); |
177 | 0 | let sum32 = _mm_hadd_epi32(sum64, sum64); |
178 | | |
179 | 0 | _mm_cvtsi128_si32(sum32) |
180 | 0 | } |
181 | | |
182 | | /// Helper: horizontal sum of 16 int16 values in a 256-bit register |
183 | | /// |
184 | | /// # Safety |
185 | | /// Requires AVX2 support. Caller must verify CPU feature availability. |
186 | | #[cfg(target_arch = "x86_64")] |
187 | | #[target_feature(enable = "avx2")] |
188 | | #[inline] |
189 | | #[allow(unsafe_op_in_unsafe_fn)] |
190 | 0 | pub unsafe fn horizontal_sum_epi16_256(v: std::arch::x86_64::__m256i) -> i32 { |
191 | | use std::arch::x86_64::{_mm256_madd_epi16, _mm256_set1_epi16}; |
192 | | |
193 | | // Use madd to sum pairs of i16 to i32 |
194 | 0 | let ones = _mm256_set1_epi16(1); |
195 | 0 | let sum_i32 = _mm256_madd_epi16(v, ones); |
196 | | |
197 | | // Now sum the 8 i32 values |
198 | 0 | horizontal_sum_epi32_256(sum_i32) |
199 | 0 | } |
200 | | |
201 | | /// Helper: Horizontal sum of 8 i32 values to single i32 |
202 | | /// |
203 | | /// # Safety |
204 | | /// Requires AVX2 support. Caller must verify CPU feature availability. |
205 | | #[cfg(target_arch = "x86_64")] |
206 | | #[target_feature(enable = "avx2")] |
207 | | #[inline] |
208 | 0 | pub unsafe fn hsum_epi32(v: std::arch::x86_64::__m256i) -> i32 { |
209 | | #[allow(clippy::wildcard_imports)] |
210 | | use std::arch::x86_64::*; |
211 | | |
212 | | // All intrinsics are unsafe and we're in an unsafe fn with target_feature |
213 | 0 | let sum128 = _mm_add_epi32(_mm256_castsi256_si128(v), _mm256_extracti128_si256(v, 1)); |
214 | 0 | let sum64 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, 0b10_11_00_01)); |
215 | 0 | let sum32 = _mm_add_epi32(sum64, _mm_shuffle_epi32(sum64, 0b00_00_10_10)); |
216 | 0 | _mm_cvtsi128_si32(sum32) |
217 | 0 | } |
218 | | |
219 | | // ============================================================================ |
220 | | // SIMD Activation Functions |
221 | | // ============================================================================ |
222 | | |
223 | | /// SIMD-accelerated in-place softmax |
224 | | /// |
225 | | /// Uses AVX2 for parallel max-finding and normalization. |
226 | | /// Falls back to scalar on non-x86_64 or when AVX2 is unavailable. |
227 | | /// |
228 | | /// # Arguments |
229 | | /// * `x` - Input/output slice, modified in-place to contain softmax values |
230 | 216 | pub fn softmax_simd(x: &mut [f32]) { |
231 | 216 | if x.is_empty() { |
232 | 1 | return; |
233 | 215 | } |
234 | | |
235 | | #[cfg(target_arch = "x86_64")] |
236 | | { |
237 | 215 | if is_x86_feature_detected!("avx2") && x.len() >= 8 { |
238 | | // SAFETY: AVX2 is available and slice is large enough |
239 | 192 | unsafe { |
240 | 192 | softmax_simd_avx2(x); |
241 | 192 | } |
242 | 192 | return; |
243 | 23 | } |
244 | | } |
245 | | |
246 | | // Scalar fallback |
247 | 23 | softmax_scalar(x); |
248 | 216 | } |
249 | | |
250 | | /// Scalar softmax implementation |
251 | 23 | fn softmax_scalar(x: &mut [f32]) { |
252 | | // Find max for numerical stability |
253 | 23 | let max_val = x.iter().copied().fold(f32::NEG_INFINITY, f32::max); |
254 | | |
255 | | // Compute exp(x - max) and sum |
256 | 23 | let mut sum = 0.0f32; |
257 | 98 | for val in x23 .iter_mut23 () { |
258 | 98 | *val = (*val - max_val).exp(); |
259 | 98 | sum += *val; |
260 | 98 | } |
261 | | |
262 | | // Normalize |
263 | 23 | let inv_sum = 1.0 / sum; |
264 | 98 | for val in x23 .iter_mut23 () { |
265 | 98 | *val *= inv_sum; |
266 | 98 | } |
267 | 23 | } |
268 | | |
269 | | /// AVX2 softmax implementation |
270 | | #[cfg(target_arch = "x86_64")] |
271 | | #[target_feature(enable = "avx2")] |
272 | | #[allow(unsafe_op_in_unsafe_fn)] |
273 | 192 | unsafe fn softmax_simd_avx2(x: &mut [f32]) { |
274 | | #[allow(clippy::wildcard_imports)] |
275 | | use std::arch::x86_64::*; |
276 | | |
277 | 192 | let len = x.len(); |
278 | | |
279 | | // Phase 1: Find max using SIMD |
280 | 192 | let mut max_vec = _mm256_set1_ps(f32::NEG_INFINITY); |
281 | 192 | let chunks = len / 8; |
282 | | |
283 | 762 | for i in 0..chunks192 { |
284 | 762 | let v = _mm256_loadu_ps(x.as_ptr().add(i * 8)); |
285 | 762 | max_vec = _mm256_max_ps(max_vec, v); |
286 | 762 | } |
287 | | |
288 | | // Horizontal max |
289 | 192 | let max_128 = _mm_max_ps(_mm256_castps256_ps128(max_vec), _mm256_extractf128_ps(max_vec, 1)); |
290 | 192 | let max_64 = _mm_max_ps(max_128, _mm_movehl_ps(max_128, max_128)); |
291 | 192 | let max_32 = _mm_max_ss(max_64, _mm_shuffle_ps(max_64, max_64, 1)); |
292 | 192 | let mut max_val = _mm_cvtss_f32(max_32); |
293 | | |
294 | | // Check remaining elements |
295 | 628 | for i in (chunks * 8)192 ..len192 { |
296 | 628 | max_val = max_val.max(x[i]); |
297 | 628 | } |
298 | | |
299 | | // Phase 2: Compute exp(x - max) and sum using SIMD |
300 | 192 | let max_vec = _mm256_set1_ps(max_val); |
301 | 192 | let mut sum_vec = _mm256_setzero_ps(); |
302 | | |
303 | 762 | for i in 0..chunks192 { |
304 | 762 | let v = _mm256_loadu_ps(x.as_ptr().add(i * 8)); |
305 | 762 | let diff = _mm256_sub_ps(v, max_vec); |
306 | | |
307 | | // Use polynomial approximation for exp (faster but less accurate) |
308 | | // For production, could use vectorized exp from a math library |
309 | | // Here we fall back to scalar exp for accuracy |
310 | 762 | let mut exp_vals = [0.0f32; 8]; |
311 | 762 | let diff_arr: [f32; 8] = std::mem::transmute(diff); |
312 | 6.09k | for (j, &d) in diff_arr762 .iter762 ().enumerate762 () { |
313 | 6.09k | exp_vals[j] = d.exp(); |
314 | 6.09k | } |
315 | 762 | let exp_vec = _mm256_loadu_ps(exp_vals.as_ptr()); |
316 | | |
317 | 762 | _mm256_storeu_ps(x.as_mut_ptr().add(i * 8), exp_vec); |
318 | 762 | sum_vec = _mm256_add_ps(sum_vec, exp_vec); |
319 | | } |
320 | | |
321 | | // Handle remaining elements |
322 | 192 | let mut sum_scalar = 0.0f32; |
323 | 628 | for i in (chunks * 8)192 ..len192 { |
324 | 628 | x[i] = (x[i] - max_val).exp(); |
325 | 628 | sum_scalar += x[i]; |
326 | 628 | } |
327 | | |
328 | | // Horizontal sum |
329 | 192 | let sum_128 = _mm_add_ps(_mm256_castps256_ps128(sum_vec), _mm256_extractf128_ps(sum_vec, 1)); |
330 | 192 | let sum_64 = _mm_add_ps(sum_128, _mm_movehl_ps(sum_128, sum_128)); |
331 | 192 | let sum_32 = _mm_add_ss(sum_64, _mm_shuffle_ps(sum_64, sum_64, 1)); |
332 | 192 | let sum = _mm_cvtss_f32(sum_32) + sum_scalar; |
333 | | |
334 | | // Phase 3: Normalize |
335 | 192 | let inv_sum = 1.0 / sum; |
336 | 192 | let inv_sum_vec = _mm256_set1_ps(inv_sum); |
337 | | |
338 | 762 | for i in 0..chunks192 { |
339 | 762 | let v = _mm256_loadu_ps(x.as_ptr().add(i * 8)); |
340 | 762 | let normalized = _mm256_mul_ps(v, inv_sum_vec); |
341 | 762 | _mm256_storeu_ps(x.as_mut_ptr().add(i * 8), normalized); |
342 | 762 | } |
343 | | |
344 | 628 | for i in (chunks * 8)192 ..len192 { |
345 | 628 | x[i] *= inv_sum; |
346 | 628 | } |
347 | 192 | } |
348 | | |
349 | | /// Fused SiLU (Swish) with gating: gate = gate * sigmoid(gate) * up |
350 | | /// |
351 | | /// Modifies `gate` in-place: gate[i] = silu(gate[i]) * up[i] |
352 | | /// |
353 | | /// # Arguments |
354 | | /// * `gate` - Gate values, modified in-place |
355 | | /// * `up` - Up-projection values (must be same length as gate) |
356 | | /// |
357 | | /// # Panics |
358 | | /// Panics if `gate.len() != up.len()` |
359 | 33 | pub fn fused_swiglu_simd(gate: &mut [f32], up: &[f32]) { |
360 | 33 | assert_eq!( |
361 | 33 | gate.len(), |
362 | 33 | up.len(), |
363 | 1 | "fused_swiglu_simd: gate and up must have same length" |
364 | | ); |
365 | | |
366 | | #[cfg(target_arch = "x86_64")] |
367 | | { |
368 | 32 | if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") && gate.len() >= 8 { |
369 | | // SAFETY: AVX2+FMA available, slices are same length |
370 | 28 | unsafe { |
371 | 28 | fused_swiglu_simd_avx2(gate, up); |
372 | 28 | } |
373 | 28 | return; |
374 | 4 | } |
375 | | } |
376 | | |
377 | | // Scalar fallback |
378 | 4 | fused_swiglu_scalar(gate, up); |
379 | 32 | } |
380 | | |
381 | | /// Scalar SwiGLU implementation |
382 | 4 | fn fused_swiglu_scalar(gate: &mut [f32], up: &[f32]) { |
383 | 16 | for (g, &u) in gate4 .iter_mut4 ().zip4 (up4 .iter4 ()) { |
384 | 16 | // SiLU: x * sigmoid(x) = x / (1 + exp(-x)) |
385 | 16 | let silu = *g / (1.0 + (-*g).exp()); |
386 | 16 | *g = silu * u; |
387 | 16 | } |
388 | 4 | } |
389 | | |
390 | | /// AVX2 SwiGLU implementation |
391 | | #[cfg(target_arch = "x86_64")] |
392 | | #[target_feature(enable = "avx2", enable = "fma")] |
393 | | #[allow(unsafe_op_in_unsafe_fn)] |
394 | 28 | unsafe fn fused_swiglu_simd_avx2(gate: &mut [f32], up: &[f32]) { |
395 | | #[allow(clippy::wildcard_imports)] |
396 | | use std::arch::x86_64::*; |
397 | | |
398 | 28 | let len = gate.len(); |
399 | 28 | let chunks = len / 8; |
400 | 28 | let _one = _mm256_set1_ps(1.0); |
401 | | |
402 | 116 | for i in 0..chunks28 { |
403 | 116 | let g = _mm256_loadu_ps(gate.as_ptr().add(i * 8)); |
404 | 116 | let u = _mm256_loadu_ps(up.as_ptr().add(i * 8)); |
405 | | |
406 | | // Compute sigmoid using scalar (for accuracy) |
407 | | // A fully vectorized version would use polynomial approximation |
408 | 116 | let mut sigmoid_vals = [0.0f32; 8]; |
409 | 116 | let g_arr: [f32; 8] = std::mem::transmute(g); |
410 | 928 | for (j, &gv) in g_arr116 .iter116 ().enumerate116 () { |
411 | 928 | sigmoid_vals[j] = 1.0 / (1.0 + (-gv).exp()); |
412 | 928 | } |
413 | 116 | let sigmoid = _mm256_loadu_ps(sigmoid_vals.as_ptr()); |
414 | | |
415 | | // SiLU = g * sigmoid(g) |
416 | 116 | let silu = _mm256_mul_ps(g, sigmoid); |
417 | | |
418 | | // Result = silu * up |
419 | 116 | let result = _mm256_mul_ps(silu, u); |
420 | | |
421 | 116 | _mm256_storeu_ps(gate.as_mut_ptr().add(i * 8), result); |
422 | | } |
423 | | |
424 | | // Handle remaining elements |
425 | 81 | for i in (chunks * 8)28 ..len28 { |
426 | 81 | let g = gate[i]; |
427 | 81 | let sigmoid = 1.0 / (1.0 + (-g).exp()); |
428 | 81 | gate[i] = g * sigmoid * up[i]; |
429 | 81 | } |
430 | 28 | } |
431 | | |
432 | | /// Apply RoPE (Rotary Position Embeddings) rotation with SIMD |
433 | | /// |
434 | | /// Applies rotary position encoding to query/key vectors. |
435 | | /// |
436 | | /// # Arguments |
437 | | /// * `x` - Input tensor, modified in-place (shape: [seq_len, num_heads, head_dim]) |
438 | | /// * `freqs_cis` - Complex exponentials (cos, sin) for each position |
439 | | /// * `head_dim` - Dimension per head (must be even) |
440 | 32 | pub fn apply_rope_rotation_simd(x: &mut [f32], freqs_cos: &[f32], freqs_sin: &[f32], head_dim: usize) { |
441 | 32 | debug_assert!(head_dim.is_multiple_of(2), "head_dim must be even"0 ); |
442 | 32 | debug_assert_eq!(freqs_cos.len(), freqs_sin.len()); |
443 | | |
444 | 32 | let half_dim = head_dim / 2; |
445 | | |
446 | | #[cfg(target_arch = "x86_64")] |
447 | | { |
448 | 32 | if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") && half_dim >= 8 { |
449 | | // SAFETY: AVX2+FMA available |
450 | 1 | unsafe { |
451 | 1 | apply_rope_rotation_avx2(x, freqs_cos, freqs_sin, head_dim); |
452 | 1 | } |
453 | 1 | return; |
454 | 31 | } |
455 | | } |
456 | | |
457 | | // Scalar fallback |
458 | 31 | apply_rope_rotation_scalar(x, freqs_cos, freqs_sin, head_dim); |
459 | 32 | } |
460 | | |
461 | | /// Scalar RoPE implementation |
462 | 31 | fn apply_rope_rotation_scalar(x: &mut [f32], freqs_cos: &[f32], freqs_sin: &[f32], head_dim: usize) { |
463 | 31 | let half_dim = head_dim / 2; |
464 | | |
465 | | // Process pairs of values |
466 | 119 | for i in 0..half_dim31 { |
467 | 119 | if i >= freqs_cos.len() { |
468 | 1 | break; |
469 | 118 | } |
470 | | |
471 | 118 | let cos = freqs_cos[i]; |
472 | 118 | let sin = freqs_sin[i]; |
473 | | |
474 | 118 | let x0 = x[i]; |
475 | 118 | let x1 = x[i + half_dim]; |
476 | | |
477 | | // Apply rotation: [cos -sin; sin cos] @ [x0; x1] |
478 | 118 | x[i] = x0 * cos - x1 * sin; |
479 | 118 | x[i + half_dim] = x0 * sin + x1 * cos; |
480 | | } |
481 | 31 | } |
482 | | |
483 | | /// AVX2 RoPE implementation |
484 | | #[cfg(target_arch = "x86_64")] |
485 | | #[target_feature(enable = "avx2", enable = "fma")] |
486 | | #[allow(unsafe_op_in_unsafe_fn)] |
487 | 1 | unsafe fn apply_rope_rotation_avx2( |
488 | 1 | x: &mut [f32], |
489 | 1 | freqs_cos: &[f32], |
490 | 1 | freqs_sin: &[f32], |
491 | 1 | head_dim: usize, |
492 | 1 | ) { |
493 | | #[allow(clippy::wildcard_imports)] |
494 | | use std::arch::x86_64::*; |
495 | | |
496 | 1 | let half_dim = head_dim / 2; |
497 | 1 | let chunks = half_dim / 8; |
498 | | |
499 | 1 | for i in 0..chunks { |
500 | 1 | let offset = i * 8; |
501 | 1 | |
502 | 1 | // Load x0 and x1 (first and second half of dimension) |
503 | 1 | let x0 = _mm256_loadu_ps(x.as_ptr().add(offset)); |
504 | 1 | let x1 = _mm256_loadu_ps(x.as_ptr().add(offset + half_dim)); |
505 | 1 | |
506 | 1 | // Load cos and sin values |
507 | 1 | let cos = _mm256_loadu_ps(freqs_cos.as_ptr().add(offset)); |
508 | 1 | let sin = _mm256_loadu_ps(freqs_sin.as_ptr().add(offset)); |
509 | 1 | |
510 | 1 | // Compute: x0' = x0 * cos - x1 * sin |
511 | 1 | let x0_new = _mm256_fmsub_ps(x0, cos, _mm256_mul_ps(x1, sin)); |
512 | 1 | |
513 | 1 | // Compute: x1' = x0 * sin + x1 * cos |
514 | 1 | let x1_new = _mm256_fmadd_ps(x0, sin, _mm256_mul_ps(x1, cos)); |
515 | 1 | |
516 | 1 | // Store results |
517 | 1 | _mm256_storeu_ps(x.as_mut_ptr().add(offset), x0_new); |
518 | 1 | _mm256_storeu_ps(x.as_mut_ptr().add(offset + half_dim), x1_new); |
519 | 1 | } |
520 | | |
521 | | // Handle remaining elements |
522 | 1 | let remaining_start = chunks * 8; |
523 | 1 | for i0 in remaining_start..half_dim { |
524 | 0 | if i >= freqs_cos.len() { |
525 | 0 | break; |
526 | 0 | } |
527 | | |
528 | 0 | let cos = freqs_cos[i]; |
529 | 0 | let sin = freqs_sin[i]; |
530 | | |
531 | 0 | let x0 = x[i]; |
532 | 0 | let x1 = x[i + half_dim]; |
533 | | |
534 | 0 | x[i] = x0 * cos - x1 * sin; |
535 | 0 | x[i + half_dim] = x0 * sin + x1 * cos; |
536 | | } |
537 | 1 | } |