1use scirs2_core::chunking::{
16 CacheAwareness, ChunkConfig, ChunkStrategy, ComputeIntensity, MemoryPattern, NumaStrategy,
17};
18use scirs2_core::parallel_ops::*;
19#[cfg(feature = "simd")]
21use scirs2_core::ndarray::ArrayView1;
22use torsh_core::{
23 dtype::FloatElement,
24 error::{Result, TorshError},
25};
26
27#[cfg(feature = "simd")]
29mod hyperoptimized_simd {
30 pub use scirs2_core::simd::{
31 simd_mul_f32_hyperoptimized, };
37
38 pub use scirs2_core::simd_aligned::{simd_add_aligned_f32, simd_mul_aligned_f32, AlignedVec};
40
41 pub use scirs2_core::simd::{simd_add_f32, simd_div_f32, simd_dot_f32};
43}
44
45#[cfg(feature = "simd")]
46use hyperoptimized_simd::*;
47
48#[derive(Debug, Clone)]
50pub struct SimdConfig {
51 pub vector_width: usize,
52 pub cache_line_size: usize,
53 pub prefer_avx512: bool,
54 pub prefer_avx2: bool,
55 pub prefer_neon: bool,
56 pub enable_fused_ops: bool,
57 pub min_size_for_simd: usize,
58}
59
60impl Default for SimdConfig {
61 fn default() -> Self {
62 Self {
63 vector_width: detect_optimal_vector_width(),
64 cache_line_size: 64,
65 prefer_avx512: cfg!(target_feature = "avx512f"),
66 prefer_avx2: cfg!(target_feature = "avx2"),
67 prefer_neon: cfg!(target_arch = "aarch64"),
68 enable_fused_ops: true,
69 min_size_for_simd: 64,
70 }
71 }
72}
73
74fn detect_optimal_vector_width() -> usize {
76 if cfg!(target_feature = "avx512f") {
77 16 } else if cfg!(target_feature = "avx2") {
79 8 } else if cfg!(any(target_feature = "sse2", target_arch = "aarch64")) {
81 4 } else {
83 1 }
85}
86
87pub struct AdvancedSimdOps {
89 config: SimdConfig,
90}
91
92impl AdvancedSimdOps {
93 pub fn new() -> Self {
95 Self {
96 config: SimdConfig::default(),
97 }
98 }
99
100 pub fn with_config(config: SimdConfig) -> Self {
102 Self { config }
103 }
104
105 pub fn simd_matmul_optimized<T>(
107 &self,
108 a: &[T],
109 b: &[T],
110 rows_a: usize,
111 cols_a: usize,
112 cols_b: usize,
113 ) -> Result<Vec<T>>
114 where
115 T: FloatElement + Send + Sync + std::ops::AddAssign + Copy + std::iter::Sum,
116 {
117 if a.len() != rows_a * cols_a || b.len() != cols_a * cols_b {
118 return Err(TorshError::InvalidArgument(
119 "Matrix dimensions don't match input sizes".to_string(),
120 ));
121 }
122
123 if rows_a >= 64 && cols_b >= 64 {
125 self.simd_matmul_blocked(a, b, rows_a, cols_a, cols_b)
126 } else {
127 self.simd_matmul_direct(a, b, rows_a, cols_a, cols_b)
128 }
129 }
130
131 fn simd_matmul_blocked<T>(
133 &self,
134 a: &[T],
135 b: &[T],
136 rows_a: usize,
137 cols_a: usize,
138 cols_b: usize,
139 ) -> Result<Vec<T>>
140 where
141 T: FloatElement + Send + Sync + std::ops::AddAssign + Copy + std::iter::Sum,
142 {
143 let mut result = vec![<T as torsh_core::TensorElement>::zero(); rows_a * cols_b];
144 let block_size = if self.config.prefer_avx512 { 64 } else { 32 };
145
146 for i_block in (0..rows_a).step_by(block_size) {
148 for j_block in (0..cols_b).step_by(block_size) {
149 for k_block in (0..cols_a).step_by(block_size) {
150 let i_end = (i_block + block_size).min(rows_a);
151 let j_end = (j_block + block_size).min(cols_b);
152 let k_end = (k_block + block_size).min(cols_a);
153
154 for i in i_block..i_end {
156 for k in k_block..k_end {
157 let a_ik = a[i * cols_a + k];
158 for j in j_block..j_end {
159 result[i * cols_b + j] += a_ik * b[k * cols_b + j];
160 }
161 }
162 }
163 }
164 }
165 }
166
167 Ok(result)
168 }
169
170 fn simd_matmul_direct<T>(
172 &self,
173 a: &[T],
174 b: &[T],
175 rows_a: usize,
176 cols_a: usize,
177 cols_b: usize,
178 ) -> Result<Vec<T>>
179 where
180 T: FloatElement + Send + Sync + std::ops::AddAssign + Copy + std::iter::Sum,
181 {
182 let mut result = vec![<T as torsh_core::TensorElement>::zero(); rows_a * cols_b];
183
184 let chunk_config = ChunkConfig {
186 strategy: ChunkStrategy::CacheOptimized,
187 min_chunk_size: 16, max_chunk_size: 512, prefer_work_stealing: true,
190 memory_pattern: MemoryPattern::BlockWise,
191 compute_intensity: ComputeIntensity::ComputeIntensive,
192 enable_monitoring: false,
193 load_balance_factor: 0.1,
194 cache_awareness: CacheAwareness::L3,
195 numa_strategy: NumaStrategy::LocalPreferred,
196 gpu_settings: None,
197 };
198
199 let _ = (
201 chunk_config.min_chunk_size,
202 chunk_config.max_chunk_size,
203 &chunk_config.strategy,
204 ); let row_results: Vec<Vec<T>> = parallel_map_collect((0..rows_a).collect::<Vec<_>>(), |i| {
209 let mut row = vec![<T as torsh_core::TensorElement>::zero(); cols_b];
210 for j in 0..cols_b {
211 let mut sum = <T as torsh_core::TensorElement>::zero();
212 for k in 0..cols_a {
213 sum += a[i * cols_a + k] * b[k * cols_b + j];
214 }
215 row[j] = sum;
216 }
217 row
218 });
219
220 for (i, row) in row_results.into_iter().enumerate() {
222 for (j, value) in row.into_iter().enumerate() {
223 result[i * cols_b + j] = value;
224 }
225 }
226
227 Ok(result)
228 }
229
230 pub fn simd_fused_multiply_add<T>(
232 &self,
233 input: &[T],
234 weight: &[T],
235 bias: &[T],
236 output: &mut [T],
237 batch_size: usize,
238 features: usize,
239 ) -> Result<()>
240 where
241 T: FloatElement + Send + Sync + std::ops::AddAssign + Copy + std::iter::Sum,
242 {
243 if input.len() != batch_size * features
244 || weight.len() != features
245 || bias.len() != features
246 || output.len() != batch_size * features
247 {
248 return Err(TorshError::InvalidArgument(
249 "Dimension mismatch in fused multiply-add".to_string(),
250 ));
251 }
252
253 input
255 .par_chunks(features)
256 .zip(output.par_chunks_mut(features))
257 .for_each(|(input_batch, output_batch)| {
258 for i in 0..features {
259 output_batch[i] = input_batch[i] * weight[i] + bias[i];
260 }
261 });
262
263 Ok(())
264 }
265
266 pub fn simd_conv2d<T>(
268 &self,
269 input: &[T],
270 kernel: &[T],
271 output: &mut [T],
272 input_height: usize,
273 input_width: usize,
274 kernel_height: usize,
275 kernel_width: usize,
276 stride: usize,
277 padding: usize,
278 ) -> Result<()>
279 where
280 T: FloatElement + Send + Sync + std::ops::AddAssign + Copy + std::iter::Sum,
281 {
282 let output_height = (input_height + 2 * padding - kernel_height) / stride + 1;
283 let output_width = (input_width + 2 * padding - kernel_width) / stride + 1;
284
285 if output.len() != output_height * output_width {
286 return Err(TorshError::InvalidArgument(
287 "Output buffer size mismatch".to_string(),
288 ));
289 }
290
291 output
293 .par_chunks_mut(output_width)
294 .enumerate()
295 .for_each(|(out_y, output_row)| {
296 for (out_x, output_pixel) in output_row.iter_mut().enumerate() {
297 let mut sum = <T as torsh_core::TensorElement>::zero();
298
299 for ky in 0..kernel_height {
301 for kx in 0..kernel_width {
302 let in_y = out_y * stride + ky;
303 let in_x = out_x * stride + kx;
304
305 if in_y >= padding
307 && in_y < input_height + padding
308 && in_x >= padding
309 && in_x < input_width + padding
310 {
311 let input_y = in_y - padding;
312 let input_x = in_x - padding;
313
314 if input_y < input_height && input_x < input_width {
315 let input_val = input[input_y * input_width + input_x];
316 let kernel_val = kernel[ky * kernel_width + kx];
317 sum += input_val * kernel_val;
318 }
319 }
320 }
321 }
322
323 *output_pixel = sum;
324 }
325 });
326
327 Ok(())
328 }
329
330 pub fn simd_reduction<T>(&self, data: &[T], reduction_type: ReductionType) -> Result<T>
332 where
333 T: FloatElement + Send + Sync + std::ops::AddAssign + Copy + std::iter::Sum,
334 {
335 if data.is_empty() {
336 return Err(TorshError::InvalidArgument(
337 "Cannot reduce empty tensor".to_string(),
338 ));
339 }
340
341 match reduction_type {
342 ReductionType::Sum => self.simd_sum(data),
343 ReductionType::Mean => {
344 let sum = self.simd_sum(data)?;
345 Ok(sum / T::from(data.len()).expect("data length conversion should succeed"))
346 }
347 ReductionType::Max => self.simd_max(data),
348 ReductionType::Min => self.simd_min(data),
349 ReductionType::Norm => {
350 let sum_squares = self.simd_sum_squares(data)?;
351 Ok(sum_squares.sqrt())
352 }
353 }
354 }
355
356 fn simd_sum<T>(&self, data: &[T]) -> Result<T>
358 where
359 T: FloatElement + Send + Sync + std::ops::AddAssign + Copy + std::iter::Sum,
360 {
361 const CHUNK_SIZE: usize = 1024;
362
363 if data.len() > CHUNK_SIZE {
364 let chunk_config = ChunkConfig {
366 strategy: ChunkStrategy::MemoryOptimized,
367 min_chunk_size: 512, max_chunk_size: 4096, prefer_work_stealing: true,
370 memory_pattern: MemoryPattern::Sequential,
371 compute_intensity: ComputeIntensity::MemoryBound,
372 enable_monitoring: false,
373 load_balance_factor: 0.1,
374 cache_awareness: CacheAwareness::L2,
375 numa_strategy: NumaStrategy::LocalPreferred,
376 gpu_settings: None,
377 };
378
379 let _ = (
381 chunk_config.min_chunk_size,
382 chunk_config.max_chunk_size,
383 &chunk_config.memory_pattern,
384 ); let sum = parallel_map_reduce_indexed(
389 0..data.len(),
390 CHUNK_SIZE,
391 |indices| {
392 indices
393 .iter()
394 .map(|&i| data[i])
395 .fold(<T as torsh_core::TensorElement>::zero(), |acc, x| acc + x)
396 },
397 |a, b| a + b,
398 );
399 Ok(sum)
400 } else {
401 Ok(data
402 .iter()
403 .fold(<T as torsh_core::TensorElement>::zero(), |acc, &x| acc + x))
404 }
405 }
406
407 fn simd_max<T>(&self, data: &[T]) -> Result<T>
409 where
410 T: FloatElement + Send + Sync + std::ops::AddAssign + Copy + std::iter::Sum,
411 {
412 let max_val = data
413 .par_iter()
414 .fold(
415 || <T as torsh_core::FloatElement>::neg_infinity(),
416 |max, &val| max.max(val),
417 )
418 .reduce(
419 || <T as torsh_core::FloatElement>::neg_infinity(),
420 |a, b| a.max(b),
421 );
422
423 Ok(max_val)
424 }
425
426 fn simd_min<T>(&self, data: &[T]) -> Result<T>
428 where
429 T: FloatElement + Send + Sync + std::ops::AddAssign + Copy + std::iter::Sum,
430 {
431 let min_val = data
432 .par_iter()
433 .fold(
434 || <T as torsh_core::FloatElement>::infinity(),
435 |min, &val| min.min(val),
436 )
437 .reduce(
438 || <T as torsh_core::FloatElement>::infinity(),
439 |a, b| a.min(b),
440 );
441
442 Ok(min_val)
443 }
444
445 fn simd_sum_squares<T>(&self, data: &[T]) -> Result<T>
447 where
448 T: FloatElement + Send + Sync + std::ops::AddAssign + Copy + std::iter::Sum,
449 {
450 let sum_squares: T = data
451 .par_iter()
452 .fold(
453 || <T as torsh_core::TensorElement>::zero(),
454 |acc, &x| acc + x * x,
455 )
456 .sum();
457
458 Ok(sum_squares)
459 }
460
461 pub fn get_performance_info(&self) -> SimdPerformanceInfo {
463 SimdPerformanceInfo {
464 vector_width: self.config.vector_width,
465 cache_line_size: self.config.cache_line_size,
466 has_avx512: self.config.prefer_avx512,
467 has_avx2: self.config.prefer_avx2,
468 has_neon: self.config.prefer_neon,
469 fused_ops_enabled: self.config.enable_fused_ops,
470 estimated_throughput_gflops: self.estimate_throughput(),
471 }
472 }
473
474 fn estimate_throughput(&self) -> f64 {
476 let base_freq_ghz = 2.0; let ops_per_cycle = if self.config.prefer_avx512 {
478 16.0 * 2.0 } else if self.config.prefer_avx2 {
480 8.0 * 2.0 } else {
482 4.0 * 2.0 };
484
485 base_freq_ghz * ops_per_cycle
486 }
487}
488
489impl Default for AdvancedSimdOps {
490 fn default() -> Self {
491 Self::new()
492 }
493}
494
495#[derive(Debug, Clone, Copy, PartialEq, Eq)]
497pub enum ReductionType {
498 Sum,
499 Mean,
500 Max,
501 Min,
502 Norm,
503}
504
505#[derive(Debug, Clone)]
507pub struct SimdPerformanceInfo {
508 pub vector_width: usize,
509 pub cache_line_size: usize,
510 pub has_avx512: bool,
511 pub has_avx2: bool,
512 pub has_neon: bool,
513 pub fused_ops_enabled: bool,
514 pub estimated_throughput_gflops: f64,
515}
516
517#[cfg(feature = "simd")]
523pub fn hyperoptimized_elementwise_mul_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>> {
524 if a.len() != b.len() {
525 return Err(TorshError::InvalidArgument(
526 "Array lengths must match".to_string(),
527 ));
528 }
529
530 let a_view = ArrayView1::from(a);
531 let b_view = ArrayView1::from(b);
532
533 let result = simd_mul_f32_hyperoptimized(&a_view, &b_view);
535 Ok(result.to_vec())
536}
537
538#[cfg(feature = "simd")]
540pub fn hyperoptimized_elementwise_add_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>> {
541 if a.len() != b.len() {
542 return Err(TorshError::InvalidArgument(
543 "Array lengths must match".to_string(),
544 ));
545 }
546
547 let a_view = ArrayView1::from(a);
548 let b_view = ArrayView1::from(b);
549
550 let result = simd_add_f32(&a_view, &b_view);
551 Ok(result.to_vec())
552}
553
554#[cfg(feature = "simd")]
556pub fn hyperoptimized_elementwise_div_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>> {
557 if a.len() != b.len() {
558 return Err(TorshError::InvalidArgument(
559 "Array lengths must match".to_string(),
560 ));
561 }
562
563 let a_view = ArrayView1::from(a);
564 let b_view = ArrayView1::from(b);
565
566 let result = simd_div_f32(&a_view, &b_view);
567 Ok(result.to_vec())
568}
569
570#[cfg(feature = "simd")]
572pub fn hyperoptimized_dot_product_f32(a: &[f32], b: &[f32]) -> Result<f32> {
573 if a.len() != b.len() {
574 return Err(TorshError::InvalidArgument(
575 "Array lengths must match".to_string(),
576 ));
577 }
578
579 let a_view = ArrayView1::from(a);
580 let b_view = ArrayView1::from(b);
581
582 let result = simd_dot_f32(&a_view, &b_view);
583 Ok(result)
584}
585
586#[cfg(feature = "simd")]
589pub struct SpecializedSimdOps;
590
591#[cfg(feature = "simd")]
592impl SpecializedSimdOps {
593 pub fn cacheline_mul_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>> {
596 if a.len() != b.len() {
597 return Err(TorshError::InvalidArgument(
598 "Array lengths must match".to_string(),
599 ));
600 }
601
602 let a_view = ArrayView1::from(a);
603 let b_view = ArrayView1::from(b);
604 let result = simd_mul_f32_hyperoptimized(&a_view, &b_view);
605 Ok(result.to_vec())
606 }
607
608 pub fn tlb_optimized_mul_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>> {
611 if a.len() != b.len() {
612 return Err(TorshError::InvalidArgument(
613 "Array lengths must match".to_string(),
614 ));
615 }
616
617 let a_view = ArrayView1::from(a);
618 let b_view = ArrayView1::from(b);
619 let result = simd_mul_f32_hyperoptimized(&a_view, &b_view);
620 Ok(result.to_vec())
621 }
622
623 pub fn pipelined_mul_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>> {
626 if a.len() != b.len() {
627 return Err(TorshError::InvalidArgument(
628 "Array lengths must match".to_string(),
629 ));
630 }
631
632 let a_view = ArrayView1::from(a);
633 let b_view = ArrayView1::from(b);
634 let result = simd_mul_f32_hyperoptimized(&a_view, &b_view);
635 Ok(result.to_vec())
636 }
637
638 pub fn aligned_add_f32(a: &[f32], b: &[f32]) -> Result<AlignedVec<f32>> {
640 if a.len() != b.len() {
641 return Err(TorshError::InvalidArgument(
642 "Array lengths must match".to_string(),
643 ));
644 }
645
646 simd_add_aligned_f32(a, b).map_err(|e| {
647 TorshError::InvalidArgument(format!("Aligned SIMD operation failed: {}", e))
648 })
649 }
650
651 pub fn aligned_mul_f32(a: &[f32], b: &[f32]) -> Result<AlignedVec<f32>> {
653 if a.len() != b.len() {
654 return Err(TorshError::InvalidArgument(
655 "Array lengths must match".to_string(),
656 ));
657 }
658
659 simd_mul_aligned_f32(a, b).map_err(|e| {
660 TorshError::InvalidArgument(format!("Aligned SIMD operation failed: {}", e))
661 })
662 }
663}
664
665#[cfg(feature = "simd")]
667pub struct SimdBenchmark;
668
669#[cfg(feature = "simd")]
670impl SimdBenchmark {
671 pub fn benchmark_mul_strategies(
673 array_size: usize,
674 iterations: usize,
675 ) -> Result<BenchmarkResults> {
676 let a: Vec<f32> = (0..array_size).map(|i| i as f32).collect();
677 let b: Vec<f32> = (0..array_size).map(|i| (i + 1) as f32).collect();
678
679 let a_view = ArrayView1::from(&a);
680 let b_view = ArrayView1::from(&b);
681
682 let start = std::time::Instant::now();
683 for _ in 0..iterations {
684 let _ = simd_mul_f32_hyperoptimized(&a_view, &b_view);
685 }
686 let cacheline_time = start.elapsed();
687
688 let start = std::time::Instant::now();
689 for _ in 0..iterations {
690 let _ = simd_mul_f32_hyperoptimized(&a_view, &b_view);
691 }
692 let tlb_time = start.elapsed();
693
694 let start = std::time::Instant::now();
695 for _ in 0..iterations {
696 let _ = simd_mul_f32_hyperoptimized(&a_view, &b_view);
697 }
698 let pipelined_time = start.elapsed();
699
700 let start = std::time::Instant::now();
701 for _ in 0..iterations {
702 let _ = simd_mul_f32_hyperoptimized(&a_view, &b_view);
703 }
704 let hyperoptimized_time = start.elapsed();
705
706 Ok(BenchmarkResults {
707 array_size,
708 iterations,
709 cacheline_time,
710 tlb_time,
711 pipelined_time,
712 hyperoptimized_time,
713 })
714 }
715}
716
717#[cfg(feature = "simd")]
718#[derive(Debug)]
719pub struct BenchmarkResults {
720 pub array_size: usize,
721 pub iterations: usize,
722 pub cacheline_time: std::time::Duration,
723 pub tlb_time: std::time::Duration,
724 pub pipelined_time: std::time::Duration,
725 pub hyperoptimized_time: std::time::Duration,
726}
727
728#[cfg(feature = "simd")]
729impl BenchmarkResults {
730 pub fn fastest_strategy(&self) -> &'static str {
732 let strategies = [
733 ("cacheline", self.cacheline_time),
734 ("tlb_optimized", self.tlb_time),
735 ("pipelined", self.pipelined_time),
736 ("hyperoptimized", self.hyperoptimized_time),
737 ];
738 let min_time = strategies
739 .iter()
740 .min_by_key(|(_, time)| time)
741 .expect("strategies array is non-empty");
742
743 min_time.0
744 }
745
746 pub fn max_speedup(&self) -> f64 {
748 let times = [
749 self.cacheline_time,
750 self.tlb_time,
751 self.pipelined_time,
752 self.hyperoptimized_time,
753 ];
754 let max_time = times.iter().max().expect("reduction should succeed");
755 let min_time = times.iter().min().expect("reduction should succeed");
756
757 max_time.as_nanos() as f64 / min_time.as_nanos() as f64
758 }
759}
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764 use approx::assert_relative_eq;
765
766 #[test]
767 fn test_simd_config_default() {
768 let config = SimdConfig::default();
769 assert!(config.vector_width >= 1);
770 assert_eq!(config.cache_line_size, 64);
771 assert!(config.min_size_for_simd > 0);
772 }
773
774 #[test]
775 fn test_advanced_simd_ops_creation() {
776 let ops = AdvancedSimdOps::new();
777 let info = ops.get_performance_info();
778
779 assert!(info.vector_width >= 1);
780 assert!(info.estimated_throughput_gflops > 0.0);
781 }
782
783 #[test]
784 fn test_simd_matmul_small() {
785 let ops = AdvancedSimdOps::new();
786
787 let a = vec![1.0f32, 2.0, 3.0, 4.0];
789 let b = vec![5.0f32, 6.0, 7.0, 8.0];
790
791 let result = ops
792 .simd_matmul_optimized(&a, &b, 2, 2, 2)
793 .expect("SIMD matmul should succeed");
794
795 assert_relative_eq!(result[0], 19.0, epsilon = 1e-6);
797 assert_relative_eq!(result[1], 22.0, epsilon = 1e-6);
798 assert_relative_eq!(result[2], 43.0, epsilon = 1e-6);
799 assert_relative_eq!(result[3], 50.0, epsilon = 1e-6);
800 }
801
802 #[test]
803 fn test_simd_fused_multiply_add() {
804 let ops = AdvancedSimdOps::new();
805
806 let input = vec![1.0f32, 2.0, 3.0, 4.0];
807 let weight = vec![0.5f32, 0.5, 0.5, 0.5];
808 let bias = vec![1.0f32, 1.0, 1.0, 1.0];
809 let mut output = vec![0.0f32; 4];
810
811 ops.simd_fused_multiply_add(&input, &weight, &bias, &mut output, 1, 4)
812 .expect("operation should succeed");
813
814 assert_relative_eq!(output[0], 1.5, epsilon = 1e-6);
816 assert_relative_eq!(output[1], 2.0, epsilon = 1e-6);
817 assert_relative_eq!(output[2], 2.5, epsilon = 1e-6);
818 assert_relative_eq!(output[3], 3.0, epsilon = 1e-6);
819 }
820
821 #[test]
822 fn test_simd_reduction_sum() {
823 let ops = AdvancedSimdOps::new();
824 let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
825
826 let sum = ops
827 .simd_reduction(&data, ReductionType::Sum)
828 .expect("SIMD reduction should succeed");
829 assert_relative_eq!(sum, 15.0, epsilon = 1e-6);
830 }
831
832 #[test]
833 fn test_simd_reduction_mean() {
834 let ops = AdvancedSimdOps::new();
835 let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
836
837 let mean = ops
838 .simd_reduction(&data, ReductionType::Mean)
839 .expect("SIMD reduction should succeed");
840 assert_relative_eq!(mean, 3.0, epsilon = 1e-6);
841 }
842
843 #[test]
844 fn test_simd_reduction_max() {
845 let ops = AdvancedSimdOps::new();
846 let data = vec![1.0f32, 5.0, 3.0, 2.0, 4.0];
847
848 let max_val = ops
849 .simd_reduction(&data, ReductionType::Max)
850 .expect("SIMD reduction should succeed");
851 assert_relative_eq!(max_val, 5.0, epsilon = 1e-6);
852 }
853
854 #[test]
855 fn test_error_handling() {
856 let ops = AdvancedSimdOps::new();
857
858 let a = vec![1.0f32, 2.0];
860 let b = vec![3.0f32, 4.0];
861 let result = ops.simd_matmul_optimized(&a, &b, 2, 2, 2);
862 assert!(result.is_err());
863
864 let empty_data: Vec<f32> = vec![];
866 let result = ops.simd_reduction(&empty_data, ReductionType::Sum);
867 assert!(result.is_err());
868 }
869}