Skip to main content

ruvector_cnn/simd/
avx2.rs

1//! AVX2 and AVX-512 SIMD Implementations
2//!
3//! Provides x86_64-specific SIMD implementations for CNN operations.
4//! Includes AVX2 (8 floats) and AVX-512 (16 floats) variants.
5
6#[cfg(target_arch = "x86_64")]
7use std::arch::x86_64::*;
8
9/// AVX2 dot product with FMA
10#[cfg(target_arch = "x86_64")]
11#[target_feature(enable = "avx2", enable = "fma")]
12pub unsafe fn dot_product_avx2_fma(a: &[f32], b: &[f32]) -> f32 {
13    debug_assert_eq!(a.len(), b.len());
14    
15    let mut sum = _mm256_setzero_ps();
16    let chunks = a.len() / 8;
17    
18    for i in 0..chunks {
19        let va = _mm256_loadu_ps(a.as_ptr().add(i * 8));
20        let vb = _mm256_loadu_ps(b.as_ptr().add(i * 8));
21        sum = _mm256_fmadd_ps(va, vb, sum);
22    }
23    
24    // Horizontal sum
25    let sum128 = _mm_add_ps(_mm256_extractf128_ps(sum, 0), _mm256_extractf128_ps(sum, 1));
26    let sum64 = _mm_add_ps(sum128, _mm_movehl_ps(sum128, sum128));
27    let sum32 = _mm_add_ss(sum64, _mm_shuffle_ps(sum64, sum64, 1));
28    
29    let mut result = 0.0f32;
30    _mm_store_ss(&mut result, sum32);
31    
32    // Handle remainder
33    for i in (chunks * 8)..a.len() {
34        result += a[i] * b[i];
35    }
36    
37    result
38}
39
40/// AVX2 dot product without FMA
41#[cfg(target_arch = "x86_64")]
42#[target_feature(enable = "avx2")]
43pub unsafe fn dot_product_avx2(a: &[f32], b: &[f32]) -> f32 {
44    debug_assert_eq!(a.len(), b.len());
45    
46    let mut sum = _mm256_setzero_ps();
47    let chunks = a.len() / 8;
48    
49    for i in 0..chunks {
50        let va = _mm256_loadu_ps(a.as_ptr().add(i * 8));
51        let vb = _mm256_loadu_ps(b.as_ptr().add(i * 8));
52        let prod = _mm256_mul_ps(va, vb);
53        sum = _mm256_add_ps(sum, prod);
54    }
55    
56    // Horizontal sum
57    let sum128 = _mm_add_ps(_mm256_extractf128_ps(sum, 0), _mm256_extractf128_ps(sum, 1));
58    let sum64 = _mm_add_ps(sum128, _mm_movehl_ps(sum128, sum128));
59    let sum32 = _mm_add_ss(sum64, _mm_shuffle_ps(sum64, sum64, 1));
60    
61    let mut result = 0.0f32;
62    _mm_store_ss(&mut result, sum32);
63    
64    // Handle remainder
65    for i in (chunks * 8)..a.len() {
66        result += a[i] * b[i];
67    }
68    
69    result
70}
71
72/// AVX-512 dot product
73#[cfg(target_arch = "x86_64")]
74#[target_feature(enable = "avx512f")]
75pub unsafe fn dot_product_avx512(a: &[f32], b: &[f32]) -> f32 {
76    debug_assert_eq!(a.len(), b.len());
77    
78    let mut sum = _mm512_setzero_ps();
79    let chunks = a.len() / 16;
80    
81    for i in 0..chunks {
82        let va = _mm512_loadu_ps(a.as_ptr().add(i * 16));
83        let vb = _mm512_loadu_ps(b.as_ptr().add(i * 16));
84        sum = _mm512_fmadd_ps(va, vb, sum);
85    }
86    
87    let mut result = _mm512_reduce_add_ps(sum);
88    
89    // Handle remainder
90    for i in (chunks * 16)..a.len() {
91        result += a[i] * b[i];
92    }
93    
94    result
95}
96
97/// AVX2 ReLU activation
98#[cfg(target_arch = "x86_64")]
99#[target_feature(enable = "avx2")]
100pub unsafe fn relu_avx2(input: &[f32], output: &mut [f32]) {
101    debug_assert_eq!(input.len(), output.len());
102    
103    let zero = _mm256_setzero_ps();
104    let chunks = input.len() / 8;
105    
106    for i in 0..chunks {
107        let v = _mm256_loadu_ps(input.as_ptr().add(i * 8));
108        let result = _mm256_max_ps(v, zero);
109        _mm256_storeu_ps(output.as_mut_ptr().add(i * 8), result);
110    }
111    
112    // Handle remainder
113    for i in (chunks * 8)..input.len() {
114        output[i] = input[i].max(0.0);
115    }
116}
117
118/// AVX2 ReLU6 activation
119#[cfg(target_arch = "x86_64")]
120#[target_feature(enable = "avx2")]
121pub unsafe fn relu6_avx2(input: &[f32], output: &mut [f32]) {
122    debug_assert_eq!(input.len(), output.len());
123    
124    let zero = _mm256_setzero_ps();
125    let six = _mm256_set1_ps(6.0);
126    let chunks = input.len() / 8;
127    
128    for i in 0..chunks {
129        let v = _mm256_loadu_ps(input.as_ptr().add(i * 8));
130        let result = _mm256_min_ps(_mm256_max_ps(v, zero), six);
131        _mm256_storeu_ps(output.as_mut_ptr().add(i * 8), result);
132    }
133    
134    // Handle remainder
135    for i in (chunks * 8)..input.len() {
136        output[i] = input[i].max(0.0).min(6.0);
137    }
138}
139
140/// AVX2 batch normalization
141#[cfg(target_arch = "x86_64")]
142#[target_feature(enable = "avx2", enable = "fma")]
143pub unsafe fn batch_norm_avx2(
144    input: &[f32],
145    output: &mut [f32],
146    gamma: &[f32],
147    beta: &[f32],
148    mean: &[f32],
149    var: &[f32],
150    epsilon: f32,
151    channels: usize,
152) {
153    debug_assert_eq!(input.len(), output.len());
154    
155    // Pre-compute scale and shift for each channel
156    let mut scale = vec![0.0f32; channels];
157    let mut shift = vec![0.0f32; channels];
158    
159    for c in 0..channels {
160        let inv_std = 1.0 / (var[c] + epsilon).sqrt();
161        scale[c] = gamma[c] * inv_std;
162        shift[c] = beta[c] - mean[c] * scale[c];
163    }
164    
165    let spatial = input.len() / channels;
166    
167    // Process 8 spatial positions at a time if channels == 8
168    if channels == 8 {
169        let scale_v = _mm256_loadu_ps(scale.as_ptr());
170        let shift_v = _mm256_loadu_ps(shift.as_ptr());
171        
172        for s in 0..spatial {
173            let offset = s * channels;
174            let v = _mm256_loadu_ps(input.as_ptr().add(offset));
175            let result = _mm256_fmadd_ps(v, scale_v, shift_v);
176            _mm256_storeu_ps(output.as_mut_ptr().add(offset), result);
177        }
178    } else {
179        // Fallback for other channel counts
180        for (i, (out, &inp)) in output.iter_mut().zip(input.iter()).enumerate() {
181            let c = i % channels;
182            *out = inp * scale[c] + shift[c];
183        }
184    }
185}
186
187/// AVX2 3x3 convolution with FMA (4x loop unrolling + multiple accumulators)
188///
189/// Processes 8 output channels simultaneously using AVX2 FMA instructions.
190/// Uses 4x loop unrolling on input channels with 4 independent accumulators
191/// for improved instruction-level parallelism (ILP).
192///
193/// Optimizations applied:
194/// - 4x unrolled input channel loop (reduces loop overhead)
195/// - 4 independent FMA accumulators (enables CPU pipelining)
196/// - Hoisted bounds checks out of inner loop
197///
198/// Expected speedup: 1.3-1.5x over non-unrolled version.
199#[cfg(target_arch = "x86_64")]
200#[target_feature(enable = "avx2", enable = "fma")]
201pub unsafe fn conv_3x3_avx2_fma(
202    input: &[f32],
203    kernel: &[f32],
204    output: &mut [f32],
205    in_h: usize,
206    in_w: usize,
207    in_c: usize,
208    out_c: usize,
209    stride: usize,
210    padding: usize,
211) {
212    let out_h = (in_h + 2 * padding - 3) / stride + 1;
213    let out_w = (in_w + 2 * padding - 3) / stride + 1;
214
215    // Process 8 output channels at a time
216    let out_c_chunks = out_c / 8;
217
218    // Pre-compute input channel unrolling parameters
219    let ic_chunks = in_c / 4;
220    let ic_remainder_start = ic_chunks * 4;
221
222    for oh in 0..out_h {
223        for ow in 0..out_w {
224            let out_spatial_idx = oh * out_w + ow;
225
226            // Process 8 output channels at once
227            for oc_chunk in 0..out_c_chunks {
228                let oc_base = oc_chunk * 8;
229
230                // Use 4 independent accumulators for ILP
231                let mut sum0 = _mm256_setzero_ps();
232                let mut sum1 = _mm256_setzero_ps();
233                let mut sum2 = _mm256_setzero_ps();
234                let mut sum3 = _mm256_setzero_ps();
235
236                // Convolve over 3x3 kernel
237                for kh in 0..3 {
238                    for kw in 0..3 {
239                        let ih = (oh * stride + kh) as isize - padding as isize;
240                        let iw = (ow * stride + kw) as isize - padding as isize;
241
242                        if ih >= 0 && ih < in_h as isize && iw >= 0 && iw < in_w as isize {
243                            let ih = ih as usize;
244                            let iw = iw as usize;
245                            let input_base = (ih * in_w + iw) * in_c;
246                            let kernel_offset = kh * 3 + kw;
247
248                            // 4x unrolled input channel loop
249                            for ic_chunk_idx in 0..ic_chunks {
250                                let ic_base = ic_chunk_idx * 4;
251
252                                // Load 4 input values and broadcast each
253                                let input_val0 = _mm256_set1_ps(*input.get_unchecked(input_base + ic_base));
254                                let input_val1 = _mm256_set1_ps(*input.get_unchecked(input_base + ic_base + 1));
255                                let input_val2 = _mm256_set1_ps(*input.get_unchecked(input_base + ic_base + 2));
256                                let input_val3 = _mm256_set1_ps(*input.get_unchecked(input_base + ic_base + 3));
257
258                                // Gather 8 kernel weights for each of the 4 input channels
259                                let mut kv0 = [0.0f32; 8];
260                                let mut kv1 = [0.0f32; 8];
261                                let mut kv2 = [0.0f32; 8];
262                                let mut kv3 = [0.0f32; 8];
263
264                                for i in 0..8 {
265                                    let oc_idx = oc_base + i;
266                                    kv0[i] = *kernel.get_unchecked((oc_idx * in_c + ic_base) * 9 + kernel_offset);
267                                    kv1[i] = *kernel.get_unchecked((oc_idx * in_c + ic_base + 1) * 9 + kernel_offset);
268                                    kv2[i] = *kernel.get_unchecked((oc_idx * in_c + ic_base + 2) * 9 + kernel_offset);
269                                    kv3[i] = *kernel.get_unchecked((oc_idx * in_c + ic_base + 3) * 9 + kernel_offset);
270                                }
271
272                                let kernel_v0 = _mm256_loadu_ps(kv0.as_ptr());
273                                let kernel_v1 = _mm256_loadu_ps(kv1.as_ptr());
274                                let kernel_v2 = _mm256_loadu_ps(kv2.as_ptr());
275                                let kernel_v3 = _mm256_loadu_ps(kv3.as_ptr());
276
277                                // FMA into 4 independent accumulators (better ILP)
278                                sum0 = _mm256_fmadd_ps(input_val0, kernel_v0, sum0);
279                                sum1 = _mm256_fmadd_ps(input_val1, kernel_v1, sum1);
280                                sum2 = _mm256_fmadd_ps(input_val2, kernel_v2, sum2);
281                                sum3 = _mm256_fmadd_ps(input_val3, kernel_v3, sum3);
282                            }
283
284                            // Handle remainder input channels (0-3 channels)
285                            for ic in ic_remainder_start..in_c {
286                                let input_val = _mm256_set1_ps(*input.get_unchecked(input_base + ic));
287
288                                let mut kernel_vals = [0.0f32; 8];
289                                for i in 0..8 {
290                                    kernel_vals[i] = *kernel.get_unchecked(((oc_base + i) * in_c + ic) * 9 + kernel_offset);
291                                }
292                                let kernel_v = _mm256_loadu_ps(kernel_vals.as_ptr());
293
294                                sum0 = _mm256_fmadd_ps(input_val, kernel_v, sum0);
295                            }
296                        }
297                    }
298                }
299
300                // Combine 4 accumulators (tree reduction for better pipelining)
301                let sum01 = _mm256_add_ps(sum0, sum1);
302                let sum23 = _mm256_add_ps(sum2, sum3);
303                let sum = _mm256_add_ps(sum01, sum23);
304
305                // Store 8 output values
306                let out_base = out_spatial_idx * out_c + oc_base;
307                _mm256_storeu_ps(output.as_mut_ptr().add(out_base), sum);
308            }
309
310            // Handle remainder output channels with scalar (0-7 channels)
311            for oc in (out_c_chunks * 8)..out_c {
312                let mut sum = 0.0f32;
313
314                for kh in 0..3 {
315                    for kw in 0..3 {
316                        let ih = (oh * stride + kh) as isize - padding as isize;
317                        let iw = (ow * stride + kw) as isize - padding as isize;
318
319                        if ih >= 0 && ih < in_h as isize && iw >= 0 && iw < in_w as isize {
320                            let ih = ih as usize;
321                            let iw = iw as usize;
322
323                            for ic in 0..in_c {
324                                let input_idx = (ih * in_w + iw) * in_c + ic;
325                                let kernel_idx = (oc * in_c + ic) * 9 + kh * 3 + kw;
326                                sum += input[input_idx] * kernel[kernel_idx];
327                            }
328                        }
329                    }
330                }
331
332                output[out_spatial_idx * out_c + oc] = sum;
333            }
334        }
335    }
336}
337
338/// AVX2 3x3 convolution (without FMA, 4x loop unrolling + multiple accumulators)
339///
340/// Processes 8 output channels simultaneously using AVX2 instructions.
341/// Uses 4x loop unrolling on input channels with 4 independent accumulators.
342/// For CPUs without FMA support (uses mul + add instead).
343#[cfg(target_arch = "x86_64")]
344#[target_feature(enable = "avx2")]
345pub unsafe fn conv_3x3_avx2(
346    input: &[f32],
347    kernel: &[f32],
348    output: &mut [f32],
349    in_h: usize,
350    in_w: usize,
351    in_c: usize,
352    out_c: usize,
353    stride: usize,
354    padding: usize,
355) {
356    let out_h = (in_h + 2 * padding - 3) / stride + 1;
357    let out_w = (in_w + 2 * padding - 3) / stride + 1;
358
359    let out_c_chunks = out_c / 8;
360    let ic_chunks = in_c / 4;
361    let ic_remainder_start = ic_chunks * 4;
362
363    for oh in 0..out_h {
364        for ow in 0..out_w {
365            let out_spatial_idx = oh * out_w + ow;
366
367            // Process 8 output channels at once
368            for oc_chunk in 0..out_c_chunks {
369                let oc_base = oc_chunk * 8;
370
371                // 4 independent accumulators for ILP
372                let mut sum0 = _mm256_setzero_ps();
373                let mut sum1 = _mm256_setzero_ps();
374                let mut sum2 = _mm256_setzero_ps();
375                let mut sum3 = _mm256_setzero_ps();
376
377                for kh in 0..3 {
378                    for kw in 0..3 {
379                        let ih = (oh * stride + kh) as isize - padding as isize;
380                        let iw = (ow * stride + kw) as isize - padding as isize;
381
382                        if ih >= 0 && ih < in_h as isize && iw >= 0 && iw < in_w as isize {
383                            let ih = ih as usize;
384                            let iw = iw as usize;
385                            let input_base = (ih * in_w + iw) * in_c;
386                            let kernel_offset = kh * 3 + kw;
387
388                            // 4x unrolled input channel loop
389                            for ic_chunk_idx in 0..ic_chunks {
390                                let ic_base = ic_chunk_idx * 4;
391
392                                let input_val0 = _mm256_set1_ps(*input.get_unchecked(input_base + ic_base));
393                                let input_val1 = _mm256_set1_ps(*input.get_unchecked(input_base + ic_base + 1));
394                                let input_val2 = _mm256_set1_ps(*input.get_unchecked(input_base + ic_base + 2));
395                                let input_val3 = _mm256_set1_ps(*input.get_unchecked(input_base + ic_base + 3));
396
397                                let mut kv0 = [0.0f32; 8];
398                                let mut kv1 = [0.0f32; 8];
399                                let mut kv2 = [0.0f32; 8];
400                                let mut kv3 = [0.0f32; 8];
401
402                                for i in 0..8 {
403                                    let oc_idx = oc_base + i;
404                                    kv0[i] = *kernel.get_unchecked((oc_idx * in_c + ic_base) * 9 + kernel_offset);
405                                    kv1[i] = *kernel.get_unchecked((oc_idx * in_c + ic_base + 1) * 9 + kernel_offset);
406                                    kv2[i] = *kernel.get_unchecked((oc_idx * in_c + ic_base + 2) * 9 + kernel_offset);
407                                    kv3[i] = *kernel.get_unchecked((oc_idx * in_c + ic_base + 3) * 9 + kernel_offset);
408                                }
409
410                                let kernel_v0 = _mm256_loadu_ps(kv0.as_ptr());
411                                let kernel_v1 = _mm256_loadu_ps(kv1.as_ptr());
412                                let kernel_v2 = _mm256_loadu_ps(kv2.as_ptr());
413                                let kernel_v3 = _mm256_loadu_ps(kv3.as_ptr());
414
415                                // mul + add (no FMA)
416                                sum0 = _mm256_add_ps(sum0, _mm256_mul_ps(input_val0, kernel_v0));
417                                sum1 = _mm256_add_ps(sum1, _mm256_mul_ps(input_val1, kernel_v1));
418                                sum2 = _mm256_add_ps(sum2, _mm256_mul_ps(input_val2, kernel_v2));
419                                sum3 = _mm256_add_ps(sum3, _mm256_mul_ps(input_val3, kernel_v3));
420                            }
421
422                            // Remainder input channels
423                            for ic in ic_remainder_start..in_c {
424                                let input_val = _mm256_set1_ps(*input.get_unchecked(input_base + ic));
425
426                                let mut kernel_vals = [0.0f32; 8];
427                                for i in 0..8 {
428                                    kernel_vals[i] = *kernel.get_unchecked(((oc_base + i) * in_c + ic) * 9 + kernel_offset);
429                                }
430                                let kernel_v = _mm256_loadu_ps(kernel_vals.as_ptr());
431
432                                sum0 = _mm256_add_ps(sum0, _mm256_mul_ps(input_val, kernel_v));
433                            }
434                        }
435                    }
436                }
437
438                // Combine accumulators
439                let sum01 = _mm256_add_ps(sum0, sum1);
440                let sum23 = _mm256_add_ps(sum2, sum3);
441                let sum = _mm256_add_ps(sum01, sum23);
442
443                let out_base = out_spatial_idx * out_c + oc_base;
444                _mm256_storeu_ps(output.as_mut_ptr().add(out_base), sum);
445            }
446
447            // Remainder output channels
448            for oc in (out_c_chunks * 8)..out_c {
449                let mut sum = 0.0f32;
450                for kh in 0..3 {
451                    for kw in 0..3 {
452                        let ih = (oh * stride + kh) as isize - padding as isize;
453                        let iw = (ow * stride + kw) as isize - padding as isize;
454
455                        if ih >= 0 && ih < in_h as isize && iw >= 0 && iw < in_w as isize {
456                            let ih = ih as usize;
457                            let iw = iw as usize;
458                            for ic in 0..in_c {
459                                let input_idx = (ih * in_w + iw) * in_c + ic;
460                                let kernel_idx = (oc * in_c + ic) * 9 + kh * 3 + kw;
461                                sum += input[input_idx] * kernel[kernel_idx];
462                            }
463                        }
464                    }
465                }
466                output[out_spatial_idx * out_c + oc] = sum;
467            }
468        }
469    }
470}
471
472/// AVX2 depthwise 3x3 convolution (kernel position unrolling)
473///
474/// Processes 8 channels simultaneously. Each channel has its own 3x3 kernel.
475/// Uses 3x3=9 kernel positions unrolled into 3 groups of 3 for better ILP.
476#[cfg(target_arch = "x86_64")]
477#[target_feature(enable = "avx2")]
478pub unsafe fn depthwise_conv_3x3_avx2(
479    input: &[f32],
480    kernel: &[f32],
481    output: &mut [f32],
482    h: usize,
483    w: usize,
484    c: usize,
485    stride: usize,
486    padding: usize,
487) {
488    let out_h = (h + 2 * padding - 3) / stride + 1;
489    let out_w = (w + 2 * padding - 3) / stride + 1;
490    let c_chunks = c / 8;
491
492    for oh in 0..out_h {
493        for ow in 0..out_w {
494            // Process 8 channels at a time
495            for c_chunk in 0..c_chunks {
496                let c_base = c_chunk * 8;
497
498                // 3 accumulators for 3 rows (better ILP than single accumulator)
499                let mut sum_row0 = _mm256_setzero_ps();
500                let mut sum_row1 = _mm256_setzero_ps();
501                let mut sum_row2 = _mm256_setzero_ps();
502
503                // Pre-load kernel weights for this channel group (8 channels x 9 positions)
504                let mut kernel_cache = [[_mm256_setzero_ps(); 3]; 3];
505                for kh in 0..3 {
506                    for kw in 0..3 {
507                        let mut kvals = [0.0f32; 8];
508                        for i in 0..8 {
509                            kvals[i] = *kernel.get_unchecked((c_base + i) * 9 + kh * 3 + kw);
510                        }
511                        kernel_cache[kh][kw] = _mm256_loadu_ps(kvals.as_ptr());
512                    }
513                }
514
515                // Process row 0 of kernel (kh=0)
516                let ih0 = (oh * stride) as isize - padding as isize;
517                if ih0 >= 0 && ih0 < h as isize {
518                    let ih0 = ih0 as usize;
519                    for kw in 0..3 {
520                        let iw = (ow * stride + kw) as isize - padding as isize;
521                        if iw >= 0 && iw < w as isize {
522                            let input_base = (ih0 * w + iw as usize) * c + c_base;
523                            let input_v = _mm256_loadu_ps(input.as_ptr().add(input_base));
524                            sum_row0 = _mm256_add_ps(sum_row0, _mm256_mul_ps(input_v, kernel_cache[0][kw]));
525                        }
526                    }
527                }
528
529                // Process row 1 of kernel (kh=1)
530                let ih1 = (oh * stride + 1) as isize - padding as isize;
531                if ih1 >= 0 && ih1 < h as isize {
532                    let ih1 = ih1 as usize;
533                    for kw in 0..3 {
534                        let iw = (ow * stride + kw) as isize - padding as isize;
535                        if iw >= 0 && iw < w as isize {
536                            let input_base = (ih1 * w + iw as usize) * c + c_base;
537                            let input_v = _mm256_loadu_ps(input.as_ptr().add(input_base));
538                            sum_row1 = _mm256_add_ps(sum_row1, _mm256_mul_ps(input_v, kernel_cache[1][kw]));
539                        }
540                    }
541                }
542
543                // Process row 2 of kernel (kh=2)
544                let ih2 = (oh * stride + 2) as isize - padding as isize;
545                if ih2 >= 0 && ih2 < h as isize {
546                    let ih2 = ih2 as usize;
547                    for kw in 0..3 {
548                        let iw = (ow * stride + kw) as isize - padding as isize;
549                        if iw >= 0 && iw < w as isize {
550                            let input_base = (ih2 * w + iw as usize) * c + c_base;
551                            let input_v = _mm256_loadu_ps(input.as_ptr().add(input_base));
552                            sum_row2 = _mm256_add_ps(sum_row2, _mm256_mul_ps(input_v, kernel_cache[2][kw]));
553                        }
554                    }
555                }
556
557                // Combine row accumulators
558                let sum = _mm256_add_ps(_mm256_add_ps(sum_row0, sum_row1), sum_row2);
559
560                let out_base = (oh * out_w + ow) * c + c_base;
561                _mm256_storeu_ps(output.as_mut_ptr().add(out_base), sum);
562            }
563
564            // Handle remainder channels
565            for ch in (c_chunks * 8)..c {
566                let mut sum = 0.0f32;
567                for kh in 0..3 {
568                    for kw in 0..3 {
569                        let ih = (oh * stride + kh) as isize - padding as isize;
570                        let iw = (ow * stride + kw) as isize - padding as isize;
571
572                        if ih >= 0 && ih < h as isize && iw >= 0 && iw < w as isize {
573                            let ih = ih as usize;
574                            let iw = iw as usize;
575                            let input_idx = (ih * w + iw) * c + ch;
576                            let kernel_idx = ch * 9 + kh * 3 + kw;
577                            sum += input[input_idx] * kernel[kernel_idx];
578                        }
579                    }
580                }
581                output[(oh * out_w + ow) * c + ch] = sum;
582            }
583        }
584    }
585}
586
587/// AVX2 global average pooling
588///
589/// Averages over H*W spatial dimensions, processing 8 channels at a time.
590#[cfg(target_arch = "x86_64")]
591#[target_feature(enable = "avx2")]
592pub unsafe fn global_avg_pool_avx2(input: &[f32], output: &mut [f32], h: usize, w: usize, c: usize) {
593    let spatial = h * w;
594    let c_chunks = c / 8;
595    let inv_spatial = _mm256_set1_ps(1.0 / spatial as f32);
596
597    // Process 8 channels at a time
598    for c_chunk in 0..c_chunks {
599        let c_base = c_chunk * 8;
600        let mut sum = _mm256_setzero_ps();
601
602        // Sum over all spatial positions
603        for s in 0..spatial {
604            let input_base = s * c + c_base;
605            let v = _mm256_loadu_ps(input.as_ptr().add(input_base));
606            sum = _mm256_add_ps(sum, v);
607        }
608
609        // Divide by spatial size
610        let avg = _mm256_mul_ps(sum, inv_spatial);
611        _mm256_storeu_ps(output.as_mut_ptr().add(c_base), avg);
612    }
613
614    // Handle remainder channels
615    let inv_spatial_scalar = 1.0 / spatial as f32;
616    for ch in (c_chunks * 8)..c {
617        let mut sum = 0.0f32;
618        for s in 0..spatial {
619            sum += input[s * c + ch];
620        }
621        output[ch] = sum * inv_spatial_scalar;
622    }
623}
624
625/// AVX2 max pooling 2x2
626///
627/// Computes max over 2x2 windows, processing 8 channels at a time.
628#[cfg(target_arch = "x86_64")]
629#[target_feature(enable = "avx2")]
630pub unsafe fn max_pool_2x2_avx2(
631    input: &[f32],
632    output: &mut [f32],
633    h: usize,
634    w: usize,
635    c: usize,
636    stride: usize,
637) {
638    let out_h = (h - 2) / stride + 1;
639    let out_w = (w - 2) / stride + 1;
640    let c_chunks = c / 8;
641
642    for oh in 0..out_h {
643        for ow in 0..out_w {
644            let ih = oh * stride;
645            let iw = ow * stride;
646
647            // Process 8 channels at a time
648            for c_chunk in 0..c_chunks {
649                let c_base = c_chunk * 8;
650
651                // Load 4 values from 2x2 window for 8 channels each
652                let idx00 = (ih * w + iw) * c + c_base;
653                let idx01 = (ih * w + iw + 1) * c + c_base;
654                let idx10 = ((ih + 1) * w + iw) * c + c_base;
655                let idx11 = ((ih + 1) * w + iw + 1) * c + c_base;
656
657                let v00 = _mm256_loadu_ps(input.as_ptr().add(idx00));
658                let v01 = _mm256_loadu_ps(input.as_ptr().add(idx01));
659                let v10 = _mm256_loadu_ps(input.as_ptr().add(idx10));
660                let v11 = _mm256_loadu_ps(input.as_ptr().add(idx11));
661
662                // Compute max of all 4 values
663                let max01 = _mm256_max_ps(v00, v01);
664                let max23 = _mm256_max_ps(v10, v11);
665                let max_all = _mm256_max_ps(max01, max23);
666
667                let out_base = (oh * out_w + ow) * c + c_base;
668                _mm256_storeu_ps(output.as_mut_ptr().add(out_base), max_all);
669            }
670
671            // Handle remainder channels
672            for ch in (c_chunks * 8)..c {
673                let v00 = input[(ih * w + iw) * c + ch];
674                let v01 = input[(ih * w + iw + 1) * c + ch];
675                let v10 = input[((ih + 1) * w + iw) * c + ch];
676                let v11 = input[((ih + 1) * w + iw + 1) * c + ch];
677                output[(oh * out_w + ow) * c + ch] = v00.max(v01).max(v10).max(v11);
678            }
679        }
680    }
681}
682
683// Non-x86_64 stubs to allow compilation
684#[cfg(not(target_arch = "x86_64"))]
685pub unsafe fn dot_product_avx2_fma(_a: &[f32], _b: &[f32]) -> f32 { 0.0 }
686#[cfg(not(target_arch = "x86_64"))]
687pub unsafe fn dot_product_avx2(_a: &[f32], _b: &[f32]) -> f32 { 0.0 }
688#[cfg(not(target_arch = "x86_64"))]
689pub unsafe fn dot_product_avx512(_a: &[f32], _b: &[f32]) -> f32 { 0.0 }
690#[cfg(not(target_arch = "x86_64"))]
691pub unsafe fn relu_avx2(_input: &[f32], _output: &mut [f32]) {}
692#[cfg(not(target_arch = "x86_64"))]
693pub unsafe fn relu6_avx2(_input: &[f32], _output: &mut [f32]) {}
694#[cfg(not(target_arch = "x86_64"))]
695pub unsafe fn batch_norm_avx2(_input: &[f32], _output: &mut [f32], _gamma: &[f32], _beta: &[f32], _mean: &[f32], _var: &[f32], _epsilon: f32, _channels: usize) {}
696#[cfg(not(target_arch = "x86_64"))]
697pub unsafe fn conv_3x3_avx2_fma(_input: &[f32], _kernel: &[f32], _output: &mut [f32], _in_h: usize, _in_w: usize, _in_c: usize, _out_c: usize, _stride: usize, _padding: usize) {}
698#[cfg(not(target_arch = "x86_64"))]
699pub unsafe fn conv_3x3_avx2(_input: &[f32], _kernel: &[f32], _output: &mut [f32], _in_h: usize, _in_w: usize, _in_c: usize, _out_c: usize, _stride: usize, _padding: usize) {}
700#[cfg(not(target_arch = "x86_64"))]
701pub unsafe fn depthwise_conv_3x3_avx2(_input: &[f32], _kernel: &[f32], _output: &mut [f32], _h: usize, _w: usize, _c: usize, _stride: usize, _padding: usize) {}
702#[cfg(not(target_arch = "x86_64"))]
703pub unsafe fn global_avg_pool_avx2(_input: &[f32], _output: &mut [f32], _h: usize, _w: usize, _c: usize) {}
704#[cfg(not(target_arch = "x86_64"))]
705pub unsafe fn max_pool_2x2_avx2(_input: &[f32], _output: &mut [f32], _h: usize, _w: usize, _c: usize, _stride: usize) {}