1use crate::error::{FFTError, FFTResult};
32use crate::sparse_fft::{SparseFFTAlgorithm, WindowFunction};
33use crate::sparse_fft_gpu_kernels::{
34 GPUKernel, KernelConfig, KernelFactory, KernelImplementation, KernelLauncher, KernelStats,
35};
36use scirs2_core::numeric::Complex64;
37use scirs2_core::numeric::NumCast;
38use std::fmt::Debug;
39use std::time::{Duration, Instant};
40
41#[derive(Debug, Clone)]
43pub struct PerformanceProfile {
44 pub signal_size: usize,
46 pub algorithm: SparseFFTAlgorithm,
48 pub window_function: WindowFunction,
50 pub kernel_config: KernelConfig,
52 pub stats: KernelStats,
54 pub accuracy: f64,
56}
57
58#[derive(Debug, Clone)]
60pub struct AutoTuneConfig {
61 pub signal_sizes: Vec<usize>,
63 pub algorithms: Vec<SparseFFTAlgorithm>,
65 pub window_functions: Vec<WindowFunction>,
67 pub block_sizes: Vec<usize>,
69 pub test_mixed_precision: bool,
71 pub test_tensor_cores: bool,
73 pub max_tuning_time_seconds: u64,
75 pub min_accuracy: f64,
77}
78
79impl Default for AutoTuneConfig {
80 fn default() -> Self {
81 Self {
82 signal_sizes: vec![1024, 4096, 16384, 65536],
83 algorithms: vec![
84 SparseFFTAlgorithm::Sublinear,
85 SparseFFTAlgorithm::CompressedSensing,
86 SparseFFTAlgorithm::Iterative,
87 SparseFFTAlgorithm::Deterministic,
88 SparseFFTAlgorithm::FrequencyPruning,
89 SparseFFTAlgorithm::SpectralFlatness,
90 ],
91 window_functions: vec![
92 WindowFunction::None,
93 WindowFunction::Hann,
94 WindowFunction::Hamming,
95 WindowFunction::Blackman,
96 WindowFunction::FlatTop,
97 WindowFunction::Kaiser,
98 ],
99 block_sizes: vec![128, 256, 512, 1024],
100 test_mixed_precision: true,
101 test_tensor_cores: true,
102 max_tuning_time_seconds: 300, min_accuracy: 0.95,
104 }
105 }
106}
107
108#[derive(Debug, Clone)]
110pub struct AutoTuneResult {
111 pub best_configs: Vec<(usize, KernelConfig, SparseFFTAlgorithm, WindowFunction)>,
113 pub profiles: Vec<PerformanceProfile>,
115 pub tuning_time: Duration,
117}
118
119#[derive(Debug, Clone)]
121pub struct PerformanceCollector {
122 profiles: Vec<PerformanceProfile>,
124 start_time: Instant,
126}
127
128impl Default for PerformanceCollector {
129 fn default() -> Self {
130 Self::new()
131 }
132}
133
134impl PerformanceCollector {
135 pub fn new() -> Self {
137 Self {
138 profiles: Vec::new(),
139 start_time: Instant::now(),
140 }
141 }
142
143 pub fn add_profile(&mut self, profile: PerformanceProfile) {
145 self.profiles.push(profile);
146 }
147
148 pub fn get_profiles(&self) -> &[PerformanceProfile] {
150 &self.profiles
151 }
152
153 pub fn elapsed(&self) -> Duration {
155 self.start_time.elapsed()
156 }
157
158 pub fn get_best_profile(&self, signal_size: usize) -> Option<&PerformanceProfile> {
160 self.profiles
161 .iter()
162 .filter(|p| p.signal_size == signal_size)
163 .min_by(|a, b| {
164 a.stats
165 .execution_time_ms
166 .partial_cmp(&b.stats.execution_time_ms)
167 .unwrap_or(std::cmp::Ordering::Equal)
168 })
169 }
170
171 pub fn get_best_algorithm(&self, signal_size: usize) -> Option<SparseFFTAlgorithm> {
173 self.get_best_profile(signal_size).map(|p| p.algorithm)
174 }
175
176 pub fn get_best_window_function(&self, signal_size: usize) -> Option<WindowFunction> {
178 self.get_best_profile(signal_size)
179 .map(|p| p.window_function)
180 }
181
182 pub fn get_best_kernel_config(&self, signal_size: usize) -> Option<KernelConfig> {
184 self.get_best_profile(signal_size)
185 .map(|p| p.kernel_config.clone())
186 }
187}
188
189pub struct SparseFftAutoTuner {
191 config: AutoTuneConfig,
193 collector: PerformanceCollector,
195 factory: KernelFactory,
197}
198
199impl SparseFftAutoTuner {
200 pub fn new(config: AutoTuneConfig, factory: KernelFactory) -> Self {
202 Self {
203 config,
204 collector: PerformanceCollector::new(),
205 factory,
206 }
207 }
208
209 pub fn run_tuning<T>(&mut self, reference_signals: &[Vec<T>]) -> FFTResult<AutoTuneResult>
211 where
212 T: NumCast + Copy + Debug + 'static,
213 {
214 let mut launcher = KernelLauncher::new(self.factory.clone());
216
217 let start_time = Instant::now();
219
220 for (i, signal) in reference_signals.iter().enumerate() {
222 let signal_size = signal.len();
223
224 if start_time.elapsed().as_secs() > self.config.max_tuning_time_seconds {
226 break;
227 }
228
229 println!(
230 "Auto-tuning for signal size {}: {} of {}",
231 signal_size,
232 i + 1,
233 reference_signals.len()
234 );
235
236 let (input_address, output_values_address, output_indices_address) =
238 launcher.allocate_sparse_fft_memory(signal_size, 10)?;
239
240 'algorithms: for &algorithm in &self.config.algorithms {
248 for &window_function in &self.config.window_functions {
250 for &block_size in &self.config.block_sizes {
252 if start_time.elapsed().as_secs() > self.config.max_tuning_time_seconds {
254 break 'algorithms;
255 }
256
257 let mut kernel = self.factory.create_sparse_fft_kernel(
259 signal_size,
260 10,
261 input_address,
262 output_values_address,
263 output_indices_address,
264 algorithm,
265 window_function,
266 )?;
267
268 let mut config = KernelConfig {
270 block_size,
271 grid_size: signal_size.div_ceil(block_size),
272 ..KernelConfig::default()
273 };
274
275 let mixed_precision_options = if self.config.test_mixed_precision {
277 vec![false, true]
278 } else {
279 vec![false]
280 };
281
282 for use_mixed_precision in mixed_precision_options {
283 config.use_mixed_precision = use_mixed_precision;
285
286 let tensor_core_options = if self.config.test_tensor_cores
288 && self.factory.can_use_tensor_cores()
289 {
290 vec![false, true]
291 } else {
292 vec![false]
293 };
294
295 for use_tensor_cores in tensor_core_options {
296 config.use_tensor_cores = use_tensor_cores;
298
299 kernel.set_config(config.clone());
301
302 let stats = kernel.execute()?;
304
305 let accuracy = self.calculate_accuracy(signal, &kernel)?;
307
308 let profile = PerformanceProfile {
310 signal_size,
311 algorithm,
312 window_function,
313 kernel_config: config.clone(),
314 stats,
315 accuracy,
316 };
317
318 self.collector.add_profile(profile);
320 }
321 }
322 }
323 }
324 }
325
326 launcher.free_all_memory();
328 }
329
330 let tuning_time = start_time.elapsed();
332
333 let mut best_configs = Vec::new();
335
336 for &signal_size in &self.config.signal_sizes {
337 if let Some(profile) = self.collector.get_best_profile(signal_size) {
338 if profile.accuracy >= self.config.min_accuracy {
340 best_configs.push((
341 signal_size,
342 profile.kernel_config.clone(),
343 profile.algorithm,
344 profile.window_function,
345 ));
346 }
347 }
348 }
349
350 let result = AutoTuneResult {
352 best_configs,
353 profiles: self.collector.profiles.clone(),
354 tuning_time,
355 };
356
357 Ok(result)
358 }
359
360 fn calculate_accuracy<T>(&self, signal: &[T], kernel: &dyn GPUKernel) -> FFTResult<f64>
373 where
374 T: NumCast + Copy + Debug + 'static,
375 {
376 use crate::sparse_fft::{
377 reconstruct_time_domain, SparseFFT, SparseFFTConfig, SparsityEstimationMethod,
378 };
379
380 let n = signal.len();
381 if n == 0 {
382 return Ok(1.0);
383 }
384
385 let signal_complex: Vec<Complex64> = signal
387 .iter()
388 .map(|&val| {
389 let v = NumCast::from(val).ok_or_else(|| {
390 FFTError::ValueError(format!("Could not convert {val:?} to f64"))
391 })?;
392 Ok(Complex64::new(v, 0.0))
393 })
394 .collect::<FFTResult<Vec<_>>>()?;
395
396 let config = SparseFFTConfig {
398 estimation_method: SparsityEstimationMethod::Manual,
399 sparsity: self.factory_default_sparsity(),
400 algorithm: self.algorithm_for_kernel(kernel),
401 window_function: WindowFunction::None,
402 ..SparseFFTConfig::default()
403 };
404 let mut processor = SparseFFT::new(config);
405 let sparse_result = processor.sparse_fft(&signal_complex)?;
406
407 let reconstructed = reconstruct_time_domain(&sparse_result, n)?;
409
410 let mut err_sq = 0.0_f64;
412 let mut ref_sq = 0.0_f64;
413 for (orig, recon) in signal_complex.iter().zip(reconstructed.iter()) {
414 err_sq += (orig - recon).norm_sqr();
415 ref_sq += orig.norm_sqr();
416 }
417
418 if ref_sq <= f64::MIN_POSITIVE {
419 return Ok(1.0);
421 }
422
423 let rel_error = (err_sq / ref_sq).sqrt();
424 Ok((1.0 - rel_error).clamp(0.0, 1.0))
425 }
426
427 fn factory_default_sparsity(&self) -> usize {
430 10
431 }
432
433 fn algorithm_for_kernel(&self, kernel: &dyn GPUKernel) -> SparseFFTAlgorithm {
435 match kernel.name() {
436 "SparseFFT_Kernel" => SparseFFTAlgorithm::Sublinear,
437 _ => SparseFFTAlgorithm::Sublinear,
438 }
439 }
440
441 pub fn get_collector(&self) -> &PerformanceCollector {
443 &self.collector
444 }
445}
446
447pub trait KernelFactoryExt {
449 fn can_use_tensor_cores(&self) -> bool;
451
452 fn get_optimal_config(
454 &self,
455 signal_size: usize,
456 algorithm: SparseFFTAlgorithm,
457 window_function: WindowFunction,
458 ) -> KernelConfig;
459}
460
461impl KernelFactoryExt for KernelFactory {
462 fn can_use_tensor_cores(&self) -> bool {
463 !self.compute_capabilities().is_empty() && self.compute_capabilities()[0].0 >= 7
466 }
467
468 fn get_optimal_config(
469 &self,
470 signal_size: usize,
471 algorithm: SparseFFTAlgorithm,
472 window_function: WindowFunction,
473 ) -> KernelConfig {
474 let mut config = KernelConfig::default();
479
480 if signal_size < 4096 {
482 config.block_size = 256;
483 } else if signal_size < 16384 {
484 config.block_size = 512;
485 } else {
486 config.block_size = 1024;
487 }
488
489 match algorithm {
491 SparseFFTAlgorithm::Sublinear => { }
492 SparseFFTAlgorithm::CompressedSensing => {
493 config.block_size = config.block_size.max(512);
495 }
496 SparseFFTAlgorithm::Iterative => {
497 config.block_size = config.block_size.min(256);
499 }
500 SparseFFTAlgorithm::Deterministic => { }
501 SparseFFTAlgorithm::FrequencyPruning => { }
502 SparseFFTAlgorithm::SpectralFlatness => {
503 config.block_size = config.block_size.max(512);
505 }
506 }
507
508 config.block_size = config.block_size.min(self.max_threads_per_block());
510
511 config.grid_size = signal_size.div_ceil(config.block_size);
513
514 if window_function != WindowFunction::None {
516 config.shared_memory_size = 32 * 1024; } else {
519 config.shared_memory_size = 16 * 1024; }
521
522 config.shared_memory_size =
524 std::cmp::min(config.shared_memory_size, self.shared_memory_per_block());
525
526 if !self.compute_capabilities().is_empty()
528 && (self.compute_capabilities()[0].0 >= 7
529 || (self.compute_capabilities()[0].0 == 6 && self.compute_capabilities()[0].1 >= 1))
530 {
531 match algorithm {
533 SparseFFTAlgorithm::Sublinear
534 | SparseFFTAlgorithm::Deterministic
535 | SparseFFTAlgorithm::FrequencyPruning => {
536 config.use_mixed_precision = true;
537 }
538 _ => {
539 config.use_mixed_precision = false;
540 }
541 }
542 }
543
544 if !self.compute_capabilities().is_empty() && self.compute_capabilities()[0].0 >= 7 {
546 match algorithm {
548 SparseFFTAlgorithm::CompressedSensing | SparseFFTAlgorithm::SpectralFlatness => {
549 config.use_tensor_cores = true;
550 }
551 _ => {
552 config.use_tensor_cores = false;
553 }
554 }
555 }
556
557 config
558 }
559}
560
561#[allow(dead_code)]
563pub fn get_optimal_algorithm<T>(signal: &[T]) -> SparseFFTAlgorithm
564where
565 T: NumCast + Copy + Debug + 'static,
566{
567 let n = signal.len();
571
572 if n < 4096 {
573 SparseFFTAlgorithm::Sublinear } else if n < 16384 {
575 SparseFFTAlgorithm::FrequencyPruning } else {
577 SparseFFTAlgorithm::SpectralFlatness }
579}
580
581#[allow(dead_code)]
583pub fn get_optimal_window_function<T>(signal: &[T]) -> WindowFunction
584where
585 T: NumCast + Copy + Debug + 'static,
586{
587 let signal_f64: FFTResult<Vec<f64>> = signal
593 .iter()
594 .map(|&val| {
595 NumCast::from(val)
596 .ok_or_else(|| FFTError::ValueError(format!("Could not convert {val:?} to f64")))
597 })
598 .collect();
599
600 if let Ok(signal_f64) = signal_f64 {
601 let mean = signal_f64.iter().sum::<f64>() / signal_f64.len() as f64;
603 let variance =
604 signal_f64.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / signal_f64.len() as f64;
605 let std_dev = variance.sqrt();
606
607 let peak = signal_f64.iter().map(|&x| x.abs()).fold(0.0, f64::max);
609 let snr_estimate = if std_dev > 0.0 {
610 peak / std_dev
611 } else {
612 f64::INFINITY
613 };
614
615 if snr_estimate > 100.0 {
617 WindowFunction::Hamming
619 } else if snr_estimate > 20.0 {
620 WindowFunction::Hann
622 } else {
623 WindowFunction::Blackman
625 }
626 } else {
627 WindowFunction::Hann
629 }
630}
631
632pub struct PerformanceManager {
634 auto_tuner: Option<SparseFftAutoTuner>,
636 best_configs: Vec<(usize, KernelConfig, SparseFFTAlgorithm, WindowFunction)>,
638 auto_tuned: bool,
640}
641
642impl Default for PerformanceManager {
643 fn default() -> Self {
644 Self::new()
645 }
646}
647
648impl PerformanceManager {
649 pub fn new() -> Self {
651 Self {
652 auto_tuner: None,
653 best_configs: Vec::new(),
654 auto_tuned: false,
655 }
656 }
657
658 pub fn init_auto_tuner(&mut self, config: AutoTuneConfig, factory: KernelFactory) {
660 self.auto_tuner = Some(SparseFftAutoTuner::new(config, factory));
661 }
662
663 pub fn run_auto_tuning<T>(&mut self, reference_signals: &[Vec<T>]) -> FFTResult<()>
665 where
666 T: NumCast + Copy + Debug + 'static,
667 {
668 if let Some(auto_tuner) = &mut self.auto_tuner {
669 let result = auto_tuner.run_tuning(reference_signals)?;
670 self.best_configs = result.best_configs;
671 self.auto_tuned = true;
672 Ok(())
673 } else {
674 Err(FFTError::ValueError(
675 "Auto-tuner not initialized".to_string(),
676 ))
677 }
678 }
679
680 pub fn get_best_config(
682 &self,
683 signal_size: usize,
684 ) -> Option<(KernelConfig, SparseFFTAlgorithm, WindowFunction)> {
685 self.best_configs
687 .iter()
688 .min_by_key(|(size, _, _, _)| (*size as isize - signal_size as isize).abs())
689 .map(|(_, config, algorithm, window_function)| {
690 (config.clone(), *algorithm, *window_function)
691 })
692 }
693
694 pub fn get_auto_tuner(&self) -> Option<&SparseFftAutoTuner> {
696 self.auto_tuner.as_ref()
697 }
698
699 pub fn is_auto_tuned(&self) -> bool {
701 self.auto_tuned
702 }
703}
704
705#[allow(dead_code)]
721pub fn auto_tune_sparse_fft<T>(
722 reference_signals: &[Vec<T>],
723 gpu_arch: &str,
724 compute_capability: (i32, i32),
725 available_memory: usize,
726) -> FFTResult<AutoTuneResult>
727where
728 T: NumCast + Copy + Debug + 'static,
729{
730 let factory = KernelFactory::new(
732 gpu_arch.to_string(),
733 vec![compute_capability],
734 available_memory,
735 48 * 1024, 1024, );
738
739 let mut auto_tuner = SparseFftAutoTuner::new(AutoTuneConfig::default(), factory);
741
742 auto_tuner.run_tuning(reference_signals)
744}
745
746#[allow(dead_code)]
764pub fn optimized_sparse_fft<T>(
765 signal: &[T],
766 sparsity: usize,
767 auto_tune_result: &AutoTuneResult,
768 gpu_arch: &str,
769 compute_capability: (i32, i32),
770 available_memory: usize,
771) -> FFTResult<(Vec<Complex64>, Vec<usize>)>
772where
773 T: NumCast + Copy + Debug + 'static,
774{
775 let signal_size = signal.len();
777 let (algorithm, window_function) = auto_tune_result
778 .best_configs
779 .iter()
780 .min_by_key(|(size, _, _, _)| (*size as isize - signal_size as isize).abs())
781 .map(|(_, _config, alg, win)| (*alg, *win))
782 .unwrap_or((SparseFFTAlgorithm::Sublinear, WindowFunction::Hann));
783
784 let (values, indices, _stats) = crate::sparse_fft_gpu_kernels::execute_sparse_fft_kernel(
786 signal,
787 sparsity,
788 algorithm,
789 window_function,
790 gpu_arch,
791 compute_capability,
792 available_memory,
793 )?;
794
795 Ok((values, indices))
796}
797
798#[cfg(test)]
799mod tests {
800 use super::*;
801 use std::f64::consts::PI;
802
803 fn create_sparse_signal(n: usize, frequencies: &[(usize, f64)]) -> Vec<f64> {
805 let mut signal = vec![0.0; n];
806
807 for i in 0..n {
808 let t = 2.0 * PI * (i as f64) / (n as f64);
809 for &(freq, amp) in frequencies {
810 signal[i] += amp * (freq as f64 * t).sin();
811 }
812 }
813
814 signal
815 }
816
817 #[test]
818 fn test_optimal_algorithm_selection() {
819 let small_signal = create_sparse_signal(2048, &[(3, 1.0), (7, 0.5)]);
821 let small_algorithm = get_optimal_algorithm(&small_signal);
822 assert_eq!(small_algorithm, SparseFFTAlgorithm::Sublinear);
823
824 let medium_signal = create_sparse_signal(8192, &[(3, 1.0), (7, 0.5)]);
826 let medium_algorithm = get_optimal_algorithm(&medium_signal);
827 assert_eq!(medium_algorithm, SparseFFTAlgorithm::FrequencyPruning);
828
829 let large_signal = create_sparse_signal(32768, &[(3, 1.0), (7, 0.5)]);
831 let large_algorithm = get_optimal_algorithm(&large_signal);
832 assert_eq!(large_algorithm, SparseFFTAlgorithm::SpectralFlatness);
833 }
834
835 #[test]
854 fn test_optimal_window_selection() {
855 let mut high_snr = vec![0.0_f64; 50_000];
863 high_snr[0] = 1.0;
864 let high_snr_window = get_optimal_window_function(&high_snr);
865 assert_eq!(high_snr_window, WindowFunction::Hamming);
866
867 let mut medium_snr = vec![0.0_f64; 2_500];
868 medium_snr[0] = 1.0;
869 let medium_snr_window = get_optimal_window_function(&medium_snr);
870 assert_eq!(medium_snr_window, WindowFunction::Hann);
871
872 let low_snr = create_sparse_signal(1024, &[(3, 1.0), (7, 0.5)]);
875 let low_snr_window = get_optimal_window_function(&low_snr);
876 assert_eq!(low_snr_window, WindowFunction::Blackman);
877
878 assert_ne!(high_snr_window, medium_snr_window);
880 assert_ne!(medium_snr_window, low_snr_window);
881 assert_ne!(high_snr_window, low_snr_window);
882 }
883
884 #[test]
885 fn test_kernel_factory_extension() {
886 let factory = KernelFactory::new(
888 "NVIDIA GeForce RTX 3080".to_string(),
889 vec![(8, 6)],
890 10 * 1024 * 1024 * 1024, 48 * 1024, 1024, );
894
895 assert!(factory.can_use_tensor_cores());
897
898 let config_small =
900 factory.get_optimal_config(2048, SparseFFTAlgorithm::Sublinear, WindowFunction::Hann);
901
902 let config_large =
903 factory.get_optimal_config(32768, SparseFFTAlgorithm::Sublinear, WindowFunction::Hann);
904
905 assert!(config_large.block_size >= config_small.block_size);
907 }
908}