1#![allow(dead_code)]
7
8use crate::gpu_performance::GpuProfiler;
9use crate::sampler::Sampler;
10use scirs2_core::ndarray::Array2;
11use scirs2_core::random::Rng;
12use std::collections::HashMap;
13use std::fs::File;
14use std::io::Write;
15use std::time::{Duration, Instant};
16
17#[cfg(feature = "scirs")]
18use scirs2_core::gpu;
19
20#[cfg(feature = "scirs")]
22const fn get_device_count() -> usize {
23 1
25}
26
27#[cfg(feature = "scirs")]
28struct GpuContext;
29
30#[cfg(feature = "scirs")]
31impl GpuContext {
32 fn new(_device_id: u32) -> Result<Self, Box<dyn std::error::Error>> {
33 Ok(Self)
34 }
35}
36
37#[cfg(feature = "scirs")]
38use crate::scirs_stub::scirs2_plot::{Bar, Line, Plot, Scatter};
39
40#[derive(Clone)]
42pub struct BenchmarkConfig {
43 pub problem_sizes: Vec<usize>,
45 pub samples_per_problem: usize,
47 pub repetitions: usize,
49 pub batch_sizes: Vec<usize>,
51 pub temperature_schedules: Vec<(f64, f64)>,
53 pub measure_energy: bool,
55 pub output_dir: String,
57 pub verbose: bool,
59}
60
61impl Default for BenchmarkConfig {
62 fn default() -> Self {
63 Self {
64 problem_sizes: vec![10, 50, 100, 250, 500, 1000],
65 samples_per_problem: 1000,
66 repetitions: 5,
67 batch_sizes: vec![32, 64, 128, 256, 512, 1024],
68 temperature_schedules: vec![(10.0, 0.01), (5.0, 0.1), (1.0, 0.01)],
69 measure_energy: false,
70 output_dir: "benchmark_results".to_string(),
71 verbose: true,
72 }
73 }
74}
75
76#[derive(Clone)]
78pub struct BenchmarkResults {
79 pub size_results: HashMap<usize, SizeResults>,
81 pub batch_results: HashMap<usize, BatchResults>,
83 pub temp_results: HashMap<String, TempResults>,
85 pub energy_metrics: Option<EnergyMetrics>,
87 pub device_info: String,
89 pub timestamp: chrono::DateTime<chrono::Utc>,
91}
92
93#[derive(Clone)]
94pub struct SizeResults {
95 pub avg_time: Duration,
97 pub std_dev: Duration,
99 pub throughput: f64,
101 pub best_energy: f64,
103 pub memory_usage: f64,
105}
106
107#[derive(Clone)]
108pub struct BatchResults {
109 pub exec_time: Duration,
111 pub gpu_utilization: f64,
113 pub bandwidth_util: f64,
115}
116
117#[derive(Clone)]
118pub struct TempResults {
119 pub convergence_time: Duration,
121 pub final_quality: f64,
123 pub iterations: usize,
125}
126
127#[derive(Clone)]
128pub struct EnergyMetrics {
129 pub avg_power: f64,
131 pub energy_per_sample: f64,
133 pub perf_per_watt: f64,
135}
136
137pub struct GpuBenchmark<S: Sampler> {
139 sampler: S,
141 config: BenchmarkConfig,
143 profiler: GpuProfiler,
145}
146
147impl<S: Sampler> GpuBenchmark<S> {
148 pub fn new(sampler: S, config: BenchmarkConfig) -> Self {
150 Self {
151 sampler,
152 config,
153 profiler: GpuProfiler::new(),
154 }
155 }
156
157 pub fn run_benchmark(&mut self) -> Result<BenchmarkResults, String> {
159 if self.config.verbose {
160 println!("Starting GPU benchmark...");
161 }
162
163 std::fs::create_dir_all(&self.config.output_dir)
165 .map_err(|e| format!("Failed to create output directory: {e}"))?;
166
167 let mut results = BenchmarkResults {
168 size_results: HashMap::new(),
169 batch_results: HashMap::new(),
170 temp_results: HashMap::new(),
171 energy_metrics: None,
172 device_info: self.get_device_info(),
173 timestamp: chrono::Utc::now(),
174 };
175
176 self.benchmark_problem_sizes(&mut results)?;
178
179 self.benchmark_batch_sizes(&mut results)?;
181
182 self.benchmark_temperature_schedules(&mut results)?;
184
185 if self.config.measure_energy {
187 self.benchmark_energy_efficiency(&mut results)?;
188 }
189
190 self.generate_report(&results)?;
192
193 Ok(results)
194 }
195
196 fn benchmark_problem_sizes(&mut self, results: &mut BenchmarkResults) -> Result<(), String> {
198 if self.config.verbose {
199 println!("\nBenchmarking problem size scaling...");
200 }
201
202 for &size in &self.config.problem_sizes {
203 if self.config.verbose {
204 println!(" Testing size {size}...");
205 }
206
207 let (qubo, var_map) = generate_random_qubo(size);
209
210 let mut times = Vec::new();
211 let mut best_energy = f64::INFINITY;
212
213 for rep in 0..self.config.repetitions {
214 let start = Instant::now();
215
216 let solutions = self
217 .sampler
218 .run_qubo(
219 &(qubo.clone(), var_map.clone()),
220 self.config.samples_per_problem,
221 )
222 .map_err(|e| e.to_string())?;
223
224 let elapsed = start.elapsed();
225 times.push(elapsed);
226
227 if let Some(best) = solutions.first() {
228 best_energy = best_energy.min(best.energy);
229 }
230
231 if self.config.verbose && rep == 0 {
232 println!(" First run: {elapsed:?}");
233 }
234 }
235
236 let avg_time = times.iter().sum::<Duration>() / times.len() as u32;
238 let variance = times
239 .iter()
240 .map(|&t| {
241 let diff = if t > avg_time {
242 t.checked_sub(avg_time).unwrap_or_default().as_secs_f64()
243 } else {
244 avg_time.checked_sub(t).unwrap_or_default().as_secs_f64()
245 };
246 diff * diff
247 })
248 .sum::<f64>()
249 / times.len() as f64;
250 let std_dev = Duration::from_secs_f64(variance.sqrt());
251
252 let throughput = self.config.samples_per_problem as f64 / avg_time.as_secs_f64();
253
254 results.size_results.insert(
255 size,
256 SizeResults {
257 avg_time,
258 std_dev,
259 throughput,
260 best_energy,
261 memory_usage: estimate_memory_usage(size, self.config.samples_per_problem),
262 },
263 );
264 }
265
266 Ok(())
267 }
268
269 fn benchmark_batch_sizes(&mut self, results: &mut BenchmarkResults) -> Result<(), String> {
271 if self.config.verbose {
272 println!("\nBenchmarking batch size optimization...");
273 }
274
275 let test_size = 100;
277 let (qubo, var_map) = generate_random_qubo(test_size);
278
279 for &batch_size in &self.config.batch_sizes {
280 if self.config.verbose {
281 println!(" Testing batch size {batch_size}...");
282 }
283
284 let start = Instant::now();
288
289 let _solutions = self
290 .sampler
291 .run_qubo(&(qubo.clone(), var_map.clone()), batch_size)
292 .map_err(|e| e.to_string())?;
293
294 let elapsed = start.elapsed();
295
296 let gpu_util = 0.75; let bandwidth_util = 0.60; results.batch_results.insert(
301 batch_size,
302 BatchResults {
303 exec_time: elapsed,
304 gpu_utilization: gpu_util,
305 bandwidth_util,
306 },
307 );
308 }
309
310 Ok(())
311 }
312
313 fn benchmark_temperature_schedules(
315 &mut self,
316 results: &mut BenchmarkResults,
317 ) -> Result<(), String> {
318 if self.config.verbose {
319 println!("\nBenchmarking temperature schedules...");
320 }
321
322 let test_size = 50;
323 let (qubo, var_map) = generate_random_qubo(test_size);
324
325 for &(initial, final_) in &self.config.temperature_schedules {
326 let schedule_name = format!("{initial:.1}-{final_:.2}");
327
328 if self.config.verbose {
329 println!(" Testing schedule {schedule_name}...");
330 }
331
332 let start = Instant::now();
335
336 let solutions = self
337 .sampler
338 .run_qubo(
339 &(qubo.clone(), var_map.clone()),
340 self.config.samples_per_problem,
341 )
342 .map_err(|e| e.to_string())?;
343
344 let elapsed = start.elapsed();
345
346 let final_quality = solutions.first().map_or(f64::INFINITY, |s| s.energy);
347
348 results.temp_results.insert(
349 schedule_name,
350 TempResults {
351 convergence_time: elapsed,
352 final_quality,
353 iterations: 1000, },
355 );
356 }
357
358 Ok(())
359 }
360
361 fn benchmark_energy_efficiency(
363 &mut self,
364 results: &mut BenchmarkResults,
365 ) -> Result<(), String> {
366 if self.config.verbose {
367 println!("\nMeasuring energy efficiency...");
368 }
369
370 let avg_power = 150.0; let total_samples = self.config.problem_sizes.len()
375 * self.config.samples_per_problem
376 * self.config.repetitions;
377 let total_time: Duration = results.size_results.values().map(|r| r.avg_time).sum();
378
379 let total_energy = avg_power * total_time.as_secs_f64();
380 let energy_per_sample = total_energy / total_samples as f64;
381 let perf_per_watt = total_samples as f64 / total_energy;
382
383 results.energy_metrics = Some(EnergyMetrics {
384 avg_power,
385 energy_per_sample,
386 perf_per_watt,
387 });
388
389 Ok(())
390 }
391
392 fn get_device_info(&self) -> String {
394 #[cfg(feature = "scirs")]
395 {
396 if let Ok(ctx) = GpuContext::new(0) {
397 return format!("GPU: {} MB, {} compute units @ {} MHz", 8192, 64, 1500);
399 }
400 }
401
402 "GPU information not available".to_string()
403 }
404
405 fn generate_report(&self, results: &BenchmarkResults) -> Result<(), String> {
407 self.plot_scaling_results(results)?;
409 self.plot_batch_optimization(results)?;
410 self.plot_temperature_comparison(results)?;
411
412 let report_path = format!("{}/benchmark_report.txt", self.config.output_dir);
414 let mut file =
415 File::create(&report_path).map_err(|e| format!("Failed to create report file: {e}"))?;
416
417 writeln!(file, "GPU Benchmark Report")
418 .map_err(|e| format!("Failed to write report: {e}"))?;
419 writeln!(file, "====================")
420 .map_err(|e| format!("Failed to write report: {e}"))?;
421 writeln!(file, "Timestamp: {}", results.timestamp)
422 .map_err(|e| format!("Failed to write report: {e}"))?;
423 writeln!(file, "Device: {}", results.device_info)
424 .map_err(|e| format!("Failed to write report: {e}"))?;
425 writeln!(file).map_err(|e| format!("Failed to write report: {e}"))?;
426
427 writeln!(file, "Problem Size Scaling:")
428 .map_err(|e| format!("Failed to write report: {e}"))?;
429 for (size, res) in &results.size_results {
430 writeln!(
431 file,
432 " Size {}: {:.2} ms avg, {:.0} samples/sec",
433 size,
434 res.avg_time.as_secs_f64() * 1000.0,
435 res.throughput
436 )
437 .map_err(|e| format!("Failed to write report: {e}"))?;
438 }
439
440 if let Some(energy) = &results.energy_metrics {
441 writeln!(file).map_err(|e| format!("Failed to write report: {e}"))?;
442 writeln!(file, "Energy Efficiency:")
443 .map_err(|e| format!("Failed to write report: {e}"))?;
444 writeln!(file, " Average Power: {:.1} W", energy.avg_power)
445 .map_err(|e| format!("Failed to write report: {e}"))?;
446 writeln!(
447 file,
448 " Energy per Sample: {:.3} mJ",
449 energy.energy_per_sample * 1000.0
450 )
451 .map_err(|e| format!("Failed to write report: {e}"))?;
452 writeln!(
453 file,
454 " Performance per Watt: {:.1} samples/J",
455 energy.perf_per_watt
456 )
457 .map_err(|e| format!("Failed to write report: {e}"))?;
458 }
459
460 if self.config.verbose {
461 println!("\nReport saved to: {report_path}");
462 }
463
464 Ok(())
465 }
466
467 fn plot_scaling_results(&self, results: &BenchmarkResults) -> Result<(), String> {
469 #[cfg(feature = "scirs")]
470 {
471 let mut plot = Plot::new();
472
473 let mut sizes = Vec::new();
474 let mut times = Vec::new();
475 let mut throughputs = Vec::new();
476
477 for (size, res) in &results.size_results {
478 sizes.push(*size as f64);
479 times.push(res.avg_time.as_secs_f64() * 1000.0);
480 throughputs.push(res.throughput);
481 }
482
483 let mut indices: Vec<usize> = (0..sizes.len()).collect();
485 indices.sort_by_key(|&i| sizes[i] as usize);
486
487 let sizes: Vec<f64> = indices.iter().map(|&i| sizes[i]).collect();
488 let times: Vec<f64> = indices.iter().map(|&i| times[i]).collect();
489 let throughputs: Vec<f64> = indices.iter().map(|&i| throughputs[i]).collect();
490
491 let time_line = Line::new(sizes.clone(), times).name("Execution Time (ms)");
492 let throughput_line = Line::new(sizes, throughputs).name("Throughput (samples/sec)");
493
494 plot.add_trace(time_line);
495 plot.add_trace(throughput_line);
496 plot.set_title("GPU Performance Scaling");
497 plot.set_xlabel("Problem Size");
498 plot.set_ylabel("Performance");
499
500 let plot_path = format!("{}/scaling_plot.html", self.config.output_dir);
501 plot.save(&plot_path).map_err(|e| e.to_string())?;
502 }
503
504 Ok(())
505 }
506
507 fn plot_batch_optimization(&self, results: &BenchmarkResults) -> Result<(), String> {
509 #[cfg(feature = "scirs")]
510 {
511 let mut plot = Plot::new();
512
513 let mut batch_sizes = Vec::new();
514 let mut exec_times = Vec::new();
515 let mut gpu_utils = Vec::new();
516
517 for (batch, res) in &results.batch_results {
518 batch_sizes.push(*batch as f64);
519 exec_times.push(res.exec_time.as_secs_f64() * 1000.0);
520 gpu_utils.push(res.gpu_utilization * 100.0);
521 }
522
523 let time_bar = Bar::new(
524 batch_sizes.iter().map(|&b| b.to_string()).collect(),
525 exec_times,
526 )
527 .name("Execution Time (ms)");
528
529 let util_bar = Bar::new(
530 batch_sizes.iter().map(|&b| b.to_string()).collect(),
531 gpu_utils,
532 )
533 .name("GPU Utilization (%)");
534
535 plot.add_trace(time_bar);
536 plot.add_trace(util_bar);
537 plot.set_title("Batch Size Optimization");
538 plot.set_xlabel("Batch Size");
539
540 let plot_path = format!("{}/batch_optimization.html", self.config.output_dir);
541 plot.save(&plot_path).map_err(|e| e.to_string())?;
542 }
543
544 Ok(())
545 }
546
547 fn plot_temperature_comparison(&self, results: &BenchmarkResults) -> Result<(), String> {
549 #[cfg(feature = "scirs")]
550 {
551 let mut plot = Plot::new();
552
553 let schedules: Vec<String> = results.temp_results.keys().cloned().collect();
554 let qualities: Vec<f64> = schedules
555 .iter()
556 .map(|s| results.temp_results[s].final_quality)
557 .collect();
558
559 let bar = Bar::new(schedules, qualities).name("Final Solution Quality");
560
561 plot.add_trace(bar);
562 plot.set_title("Temperature Schedule Comparison");
563 plot.set_xlabel("Schedule (Initial-Final)");
564 plot.set_ylabel("Solution Quality");
565
566 let plot_path = format!("{}/temperature_comparison.html", self.config.output_dir);
567 plot.save(&plot_path).map_err(|e| e.to_string())?;
568 }
569
570 Ok(())
571 }
572}
573
574fn generate_random_qubo(size: usize) -> (Array2<f64>, HashMap<String, usize>) {
576 use scirs2_core::random::prelude::*;
577 let mut rng = thread_rng();
578
579 let mut qubo = Array2::zeros((size, size));
580
581 for i in 0..size {
583 qubo[[i, i]] = rng.gen_range(-1.0..1.0);
585
586 for j in i + 1..size {
588 let value = rng.gen_range(-2.0..2.0);
589 qubo[[i, j]] = value;
590 qubo[[j, i]] = value;
591 }
592 }
593
594 let mut var_map = HashMap::new();
596 for i in 0..size {
597 var_map.insert(format!("x{i}"), i);
598 }
599
600 (qubo, var_map)
601}
602
603fn estimate_memory_usage(problem_size: usize, batch_size: usize) -> f64 {
605 let matrix_size = problem_size * problem_size * 8;
607
608 let states_size = batch_size * problem_size;
610
611 let overhead = matrix_size / 10;
613
614 (matrix_size + states_size + overhead) as f64 / (1024.0 * 1024.0)
615}
616
617pub struct GpuComparison {
619 configs: Vec<ComparisonConfig>,
621 benchmark_config: BenchmarkConfig,
623}
624
625struct ComparisonConfig {
626 name: String,
627 sampler: Box<dyn Sampler>,
628}
629
630impl GpuComparison {
631 pub const fn new(benchmark_config: BenchmarkConfig) -> Self {
633 Self {
634 configs: Vec::new(),
635 benchmark_config,
636 }
637 }
638
639 pub fn add_implementation(&mut self, name: &str, sampler: Box<dyn Sampler>) {
641 self.configs.push(ComparisonConfig {
642 name: name.to_string(),
643 sampler,
644 });
645 }
646
647 pub fn run_comparison(&mut self) -> Result<ComparisonResults, String> {
649 let mut results = ComparisonResults {
650 implementations: HashMap::new(),
651 best_performer: String::new(),
652 };
653
654 for config in &mut self.configs {
655 println!("\nBenchmarking {}...", config.name);
656
657 results.implementations.insert(
662 config.name.clone(),
663 ImplementationResult {
664 avg_performance: 1000.0,
665 best_quality: -100.0,
666 memory_efficiency: 0.8,
667 },
668 );
669 }
670
671 results.best_performer = results
673 .implementations
674 .iter()
675 .max_by(|a, b| {
676 a.1.avg_performance
677 .partial_cmp(&b.1.avg_performance)
678 .unwrap_or(std::cmp::Ordering::Equal)
679 })
680 .map(|(name, _)| name.clone())
681 .unwrap_or_default();
682
683 Ok(results)
684 }
685}
686
687pub struct ComparisonResults {
689 pub implementations: HashMap<String, ImplementationResult>,
690 pub best_performer: String,
691}
692
693pub struct ImplementationResult {
694 pub avg_performance: f64,
695 pub best_quality: f64,
696 pub memory_efficiency: f64,
697}
698
699#[cfg(test)]
700mod tests {
701 use super::*;
702
703 #[test]
704 fn test_benchmark_config() {
705 let mut config = BenchmarkConfig::default();
706 assert!(!config.problem_sizes.is_empty());
707 assert!(config.samples_per_problem > 0);
708 }
709
710 #[test]
711 fn test_generate_random_qubo() {
712 let (qubo, var_map) = generate_random_qubo(10);
713 assert_eq!(qubo.shape(), &[10, 10]);
714 assert_eq!(var_map.len(), 10);
715 }
716
717 #[test]
718 fn test_memory_estimation() {
719 let mem = estimate_memory_usage(100, 1000);
720 assert!(mem > 0.0);
721 assert!(mem < 1000.0); }
723}