1#[cfg(feature = "std")]
9use crate::Module;
10#[cfg(feature = "std")]
11use std::{path::Path, string::String, vec::Vec};
12#[cfg(feature = "std")]
13use torsh_core::error::{Result, TorshError};
14
15#[cfg(feature = "serialize")]
16use serde_json;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum TargetDevice {
21 Cpu,
23 Gpu,
25 Cuda,
27 Mobile,
29 Wasm,
31 Web,
33 Custom(u32),
35}
36
37impl Default for TargetDevice {
38 fn default() -> Self {
39 Self::Cpu
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum ExportFormat {
46 Onnx,
48 TorchScript,
50 TorshBinary,
52 Json,
54}
55
56#[derive(Debug, Clone)]
58pub struct ExportConfig {
59 pub format: ExportFormat,
61 pub include_training: bool,
63 pub optimization_level: OptimizationLevel,
65 pub target_device: TargetDevice,
67 pub include_metadata: bool,
69 pub input_shapes: Vec<Vec<usize>>,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum OptimizationLevel {
76 None,
78 Basic,
80 Aggressive,
82}
83
84impl Default for ExportConfig {
85 fn default() -> Self {
86 Self {
87 format: ExportFormat::TorshBinary,
88 include_training: false,
89 optimization_level: OptimizationLevel::Basic,
90 target_device: TargetDevice::Cpu,
91 include_metadata: true,
92 input_shapes: vec![],
93 }
94 }
95}
96
97pub struct ModelExporter {
99 config: ExportConfig,
100}
101
102impl ModelExporter {
103 pub fn new(config: ExportConfig) -> Self {
105 Self { config }
106 }
107
108 pub fn onnx() -> Self {
110 Self::new(ExportConfig {
111 format: ExportFormat::Onnx,
112 ..Default::default()
113 })
114 }
115
116 pub fn torchscript() -> Self {
118 Self::new(ExportConfig {
119 format: ExportFormat::TorchScript,
120 ..Default::default()
121 })
122 }
123
124 pub fn export<M: Module>(&self, model: &M, path: &Path) -> Result<()> {
126 match self.config.format {
131 ExportFormat::Onnx => self.export_onnx(model, path),
132 ExportFormat::TorchScript => self.export_torchscript(model, path),
133 ExportFormat::TorshBinary => self.export_torsh_binary(model, path),
134 ExportFormat::Json => self.export_json(model, path),
135 }
136 }
137
138 fn export_onnx<M: Module>(&self, _model: &M, _path: &Path) -> Result<()> {
151 Err(TorshError::NotImplemented(
152 "ONNX export not yet implemented: it requires tracing the module into ONNX \
153 operator nodes and serializing a protobuf ModelProto, which the Module trait \
154 does not yet support. Refusing to write a placeholder that would masquerade \
155 as a valid .onnx model."
156 .to_string(),
157 ))
158 }
159
160 fn export_torchscript<M: Module>(&self, _model: &M, _path: &Path) -> Result<()> {
170 Err(TorshError::NotImplemented(
171 "TorchScript export not yet implemented: it requires serializing a traced module \
172 into PyTorch's TorchScript archive format. Refusing to write a placeholder that \
173 would masquerade as a valid TorchScript module."
174 .to_string(),
175 ))
176 }
177
178 fn export_torsh_binary<M: Module>(&self, model: &M, path: &Path) -> Result<()> {
180 let binary_data = self.serialize_to_binary(model)?;
182
183 std::fs::write(path, binary_data).map_err(|e| TorshError::IoError(e.to_string()))?;
184
185 Ok(())
186 }
187
188 fn export_json<M: Module>(&self, model: &M, path: &Path) -> Result<()> {
190 let json_data = self.serialize_to_json(model)?;
191
192 std::fs::write(path, json_data).map_err(|e| TorshError::IoError(e.to_string()))?;
193
194 Ok(())
195 }
196
197 pub fn export_to_bytes<M: Module>(&self, model: &M) -> Result<Vec<u8>> {
203 match self.config.format {
204 ExportFormat::Onnx => Err(TorshError::NotImplemented(
205 "ONNX export not yet implemented; cannot serialize model to ONNX bytes."
206 .to_string(),
207 )),
208 ExportFormat::TorchScript => Err(TorshError::NotImplemented(
209 "TorchScript export not yet implemented; cannot serialize model to \
210 TorchScript bytes."
211 .to_string(),
212 )),
213 ExportFormat::TorshBinary => self.serialize_to_binary(model),
214 ExportFormat::Json => {
215 let json_data = self.serialize_to_json(model)?;
216 Ok(json_data.into_bytes())
217 }
218 }
219 }
220
221 fn serialize_to_binary<M: Module>(&self, model: &M) -> Result<Vec<u8>> {
231 let mut data = Vec::new();
232
233 data.extend_from_slice(b"TORSH_V1");
235
236 let params = model.parameters();
239 let mut sorted_params: Vec<_> = params.into_iter().collect();
240 sorted_params.sort_by(|(a, _), (b, _)| a.cmp(b));
241
242 data.extend_from_slice(&(sorted_params.len() as u32).to_le_bytes());
243
244 for (name, param) in sorted_params {
245 let tensor_arc = param.tensor();
246 let tensor = tensor_arc.read();
247 let tensor_shape = tensor.shape();
248 let shape = tensor_shape.dims().to_vec();
249
250 let name_bytes = name.as_bytes();
253 data.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
254 data.extend_from_slice(name_bytes);
255
256 data.extend_from_slice(&(shape.len() as u32).to_le_bytes());
258 for &dim in &shape {
259 data.extend_from_slice(&(dim as u32).to_le_bytes());
260 }
261
262 let values = tensor.to_vec()?;
264 data.extend_from_slice(&(values.len() as u32).to_le_bytes());
265 for value in &values {
266 data.extend_from_slice(&value.to_le_bytes());
267 }
268 }
269
270 Ok(data)
271 }
272
273 #[cfg(feature = "serialize")]
275 fn serialize_to_json<M: Module>(&self, model: &M) -> Result<String> {
276 let mut json_obj = serde_json::Map::new();
277
278 json_obj.insert(
280 "format".to_string(),
281 serde_json::Value::String("torsh_nn".to_string()),
282 );
283 json_obj.insert(
284 "version".to_string(),
285 serde_json::Value::String("0.1.0".to_string()),
286 );
287
288 let params = model.parameters();
290 let mut params_info = Vec::new();
291
292 for (i, (name, param)) in params.iter().enumerate() {
293 let tensor_arc = param.tensor();
294 let tensor = tensor_arc.read();
295 let shape_obj = tensor.shape();
296 let shape = shape_obj.dims();
297
298 let param_obj = serde_json::json!({
299 "index": i,
300 "name": name,
301 "shape": shape,
302 "numel": shape.iter().product::<usize>(),
303 "requires_grad": param.requires_grad()
304 });
305
306 params_info.push(param_obj);
307 }
308
309 json_obj.insert(
310 "parameters".to_string(),
311 serde_json::Value::Array(params_info),
312 );
313
314 if self.config.include_metadata {
316 let config_obj = serde_json::json!({
317 "optimization_level": format!("{:?}", self.config.optimization_level),
318 "target_device": format!("{:?}", self.config.target_device),
319 "input_shapes": self.config.input_shapes
320 });
321 json_obj.insert("export_config".to_string(), config_obj);
322 }
323
324 serde_json::to_string_pretty(&json_obj)
325 .map_err(|e| TorshError::SerializationError(e.to_string()))
326 }
327
328 #[cfg(not(feature = "serialize"))]
330 fn serialize_to_json<M: Module>(&self, _model: &M) -> Result<String> {
331 Err(TorshError::ConfigError(
332 "JSON serialization requires 'serialize' feature to be enabled".to_string(),
333 ))
334 }
335}
336
337pub struct DeploymentOptimizer {
339 target_device: TargetDevice,
340 #[allow(dead_code)]
341 optimization_level: OptimizationLevel,
342}
343
344impl DeploymentOptimizer {
345 pub fn new(target_device: TargetDevice, optimization_level: OptimizationLevel) -> Self {
347 Self {
348 target_device,
349 optimization_level,
350 }
351 }
352
353 pub fn optimize<M: Module>(&self, model: &M) -> Result<OptimizedModel> {
355 match self.target_device {
356 TargetDevice::Cpu => self.optimize_for_cpu(model),
357 TargetDevice::Gpu => self.optimize_for_cuda(model), TargetDevice::Cuda => self.optimize_for_cuda(model),
359 TargetDevice::Mobile => self.optimize_for_mobile(model),
360 TargetDevice::Wasm => self.optimize_for_web(model), TargetDevice::Web => self.optimize_for_web(model),
362 TargetDevice::Custom(_) => self.optimize_for_cpu(model), }
364 }
365
366 fn optimize_for_cpu<M: Module>(&self, model: &M) -> Result<OptimizedModel> {
368 Ok(OptimizedModel::new(model, TargetDevice::Cpu))
375 }
376
377 fn optimize_for_cuda<M: Module>(&self, model: &M) -> Result<OptimizedModel> {
379 Ok(OptimizedModel::new(model, TargetDevice::Cuda))
386 }
387
388 fn optimize_for_mobile<M: Module>(&self, model: &M) -> Result<OptimizedModel> {
390 Ok(OptimizedModel::new(model, TargetDevice::Mobile))
397 }
398
399 fn optimize_for_web<M: Module>(&self, model: &M) -> Result<OptimizedModel> {
401 Ok(OptimizedModel::new(model, TargetDevice::Web))
408 }
409}
410
411pub struct OptimizedModel {
413 target_device: TargetDevice,
415 optimizations_applied: Vec<String>,
416}
417
418impl OptimizedModel {
419 fn new<M: Module>(_model: &M, target_device: TargetDevice) -> Self {
420 Self {
424 target_device,
425 optimizations_applied: Vec::new(),
426 }
427 }
428
429 pub fn target_device(&self) -> TargetDevice {
431 self.target_device
432 }
433
434 pub fn optimizations_applied(&self) -> &[String] {
436 &self.optimizations_applied
437 }
438}
439
440pub mod benchmarks {
442 use super::*;
443 use std::collections::HashMap;
444 use std::time::{Duration, Instant};
445
446 #[derive(Debug, Clone)]
448 pub struct ExportMetrics {
449 pub export_time: Duration,
451 pub export_size: usize,
453 pub peak_memory_mb: f32,
455 pub throughput: f32,
457 pub compression_ratio: f32,
459 pub target_device: TargetDevice,
461 pub export_format: ExportFormat,
463 }
464
465 #[derive(Debug, Clone)]
467 pub struct ConversionMetrics {
468 pub conversion_time: Duration,
470 pub peak_memory_mb: f32,
472 pub layers_converted: usize,
474 pub parameters_converted: usize,
476 pub success_rate: f32,
478 pub source_format: String,
480 pub target_format: String,
481 }
482
483 #[derive(Debug, Clone)]
485 pub struct BenchmarkResults {
486 pub export_metrics: HashMap<String, ExportMetrics>,
488 pub conversion_metrics: HashMap<String, ConversionMetrics>,
490 pub summary: BenchmarkSummary,
492 }
493
494 #[derive(Debug, Clone)]
496 pub struct BenchmarkSummary {
497 pub total_time: Duration,
499 pub fastest_export: String,
501 pub most_compact_export: String,
503 pub recommended_config: String,
505 }
506
507 pub struct ExportBenchmarker {
509 configurations: Vec<(String, ExportConfig)>,
510 warmup_runs: usize,
511 benchmark_runs: usize,
512 }
513
514 impl ExportBenchmarker {
515 pub fn new() -> Self {
517 let mut configurations = Vec::new();
518
519 configurations.push((
521 "onnx_basic".to_string(),
522 ExportConfig {
523 format: ExportFormat::Onnx,
524 optimization_level: OptimizationLevel::Basic,
525 target_device: TargetDevice::Cpu,
526 ..Default::default()
527 },
528 ));
529
530 configurations.push((
531 "onnx_aggressive".to_string(),
532 ExportConfig {
533 format: ExportFormat::Onnx,
534 optimization_level: OptimizationLevel::Aggressive,
535 target_device: TargetDevice::Cpu,
536 ..Default::default()
537 },
538 ));
539
540 configurations.push((
541 "torchscript_basic".to_string(),
542 ExportConfig {
543 format: ExportFormat::TorchScript,
544 optimization_level: OptimizationLevel::Basic,
545 target_device: TargetDevice::Cpu,
546 ..Default::default()
547 },
548 ));
549
550 configurations.push((
551 "binary_fast".to_string(),
552 ExportConfig {
553 format: ExportFormat::TorshBinary,
554 optimization_level: OptimizationLevel::None,
555 target_device: TargetDevice::Cpu,
556 ..Default::default()
557 },
558 ));
559
560 configurations.push((
561 "json_debug".to_string(),
562 ExportConfig {
563 format: ExportFormat::Json,
564 optimization_level: OptimizationLevel::None,
565 target_device: TargetDevice::Cpu,
566 include_metadata: true,
567 ..Default::default()
568 },
569 ));
570
571 Self {
572 configurations,
573 warmup_runs: 3,
574 benchmark_runs: 10,
575 }
576 }
577
578 pub fn add_configuration(&mut self, name: String, config: ExportConfig) {
580 self.configurations.push((name, config));
581 }
582
583 pub fn set_runs(&mut self, warmup_runs: usize, benchmark_runs: usize) {
585 self.warmup_runs = warmup_runs;
586 self.benchmark_runs = benchmark_runs;
587 }
588
589 pub fn configurations(&self) -> &Vec<(String, ExportConfig)> {
591 &self.configurations
592 }
593
594 pub fn warmup_runs(&self) -> usize {
596 self.warmup_runs
597 }
598
599 pub fn benchmark_runs(&self) -> usize {
601 self.benchmark_runs
602 }
603
604 pub fn benchmark_model<M: Module + Clone>(&self, model: &M) -> Result<BenchmarkResults> {
606 let mut export_metrics = HashMap::new();
607 let benchmark_start = Instant::now();
608
609 for (config_name, config) in &self.configurations {
610 println!("Benchmarking export configuration: {}", config_name);
611
612 let metrics = self.benchmark_single_export(model, config)?;
613 export_metrics.insert(config_name.clone(), metrics);
614 }
615
616 let total_time = benchmark_start.elapsed();
618 let summary = self.create_summary(&export_metrics, total_time);
619
620 let conversion_metrics = HashMap::new();
622
623 Ok(BenchmarkResults {
624 export_metrics,
625 conversion_metrics,
626 summary,
627 })
628 }
629
630 fn benchmark_single_export<M: Module + Clone>(
632 &self,
633 model: &M,
634 config: &ExportConfig,
635 ) -> Result<ExportMetrics> {
636 let exporter = ModelExporter::new(config.clone());
637
638 for _ in 0..self.warmup_runs {
640 let _result = exporter.export_to_bytes(model)?;
641 }
642
643 let mut times = Vec::new();
645 let mut export_size = 0;
646
647 for _ in 0..self.benchmark_runs {
648 let start = Instant::now();
649 let exported_bytes = exporter.export_to_bytes(model)?;
650 let elapsed = start.elapsed();
651
652 times.push(elapsed);
653 export_size = exported_bytes.len();
654 }
655
656 let avg_time = times.iter().sum::<Duration>() / times.len() as u32;
658 let throughput = 1.0 / avg_time.as_secs_f32();
659
660 let peak_memory_mb = (export_size as f32) / (1024.0 * 1024.0) * 1.5; let compression_ratio = match config.optimization_level {
663 OptimizationLevel::None => 1.0,
664 OptimizationLevel::Basic => 1.2,
665 OptimizationLevel::Aggressive => 1.8,
666 };
667
668 Ok(ExportMetrics {
669 export_time: avg_time,
670 export_size,
671 peak_memory_mb,
672 throughput,
673 compression_ratio,
674 target_device: config.target_device,
675 export_format: config.format,
676 })
677 }
678
679 fn create_summary(
681 &self,
682 export_metrics: &HashMap<String, ExportMetrics>,
683 total_time: Duration,
684 ) -> BenchmarkSummary {
685 let mut fastest_export = String::new();
686 let mut most_compact_export = String::new();
687 let mut fastest_time = Duration::from_secs(u64::MAX);
688 let mut smallest_size = usize::MAX;
689
690 for (name, metrics) in export_metrics {
691 if metrics.export_time < fastest_time {
692 fastest_time = metrics.export_time;
693 fastest_export = name.clone();
694 }
695
696 if metrics.export_size < smallest_size {
697 smallest_size = metrics.export_size;
698 most_compact_export = name.clone();
699 }
700 }
701
702 let recommended_config = if export_metrics.contains_key("onnx_basic") {
704 "onnx_basic".to_string()
705 } else {
706 fastest_export.clone()
707 };
708
709 BenchmarkSummary {
710 total_time,
711 fastest_export,
712 most_compact_export,
713 recommended_config,
714 }
715 }
716 }
717
718 impl Default for ExportBenchmarker {
719 fn default() -> Self {
720 Self::new()
721 }
722 }
723
724 pub struct ConversionBenchmarker {
726 conversion_paths: Vec<(String, String, String)>, }
728
729 impl ConversionBenchmarker {
730 pub fn new() -> Self {
732 let conversion_paths = vec![
733 (
734 "pytorch_to_onnx".to_string(),
735 "pytorch".to_string(),
736 "onnx".to_string(),
737 ),
738 (
739 "tensorflow_to_onnx".to_string(),
740 "tensorflow".to_string(),
741 "onnx".to_string(),
742 ),
743 (
744 "onnx_to_torsh".to_string(),
745 "onnx".to_string(),
746 "torsh".to_string(),
747 ),
748 (
749 "torsh_to_onnx".to_string(),
750 "torsh".to_string(),
751 "onnx".to_string(),
752 ),
753 ];
754
755 Self { conversion_paths }
756 }
757
758 pub fn benchmark_conversions(&self) -> Result<HashMap<String, ConversionMetrics>> {
760 let mut metrics = HashMap::new();
761
762 for (name, source, target) in &self.conversion_paths {
763 let start = Instant::now();
764
765 std::thread::sleep(Duration::from_millis(10));
767
768 let conversion_time = start.elapsed();
769
770 let metric = ConversionMetrics {
771 conversion_time,
772 peak_memory_mb: 128.0, layers_converted: 10, parameters_converted: 1000, success_rate: 0.95, source_format: source.clone(),
777 target_format: target.clone(),
778 };
779
780 metrics.insert(name.clone(), metric);
781 }
782
783 Ok(metrics)
784 }
785 }
786
787 impl Default for ConversionBenchmarker {
788 fn default() -> Self {
789 Self::new()
790 }
791 }
792
793 pub mod utils {
795 use super::*;
796
797 pub fn create_benchmark_report(results: &BenchmarkResults) -> String {
799 let mut report = String::new();
800
801 report.push_str("# Export/Conversion Performance Benchmark Report\n\n");
802
803 report.push_str("## Summary\n");
805 report.push_str(&format!(
806 "- Total benchmark time: {:?}\n",
807 results.summary.total_time
808 ));
809 report.push_str(&format!(
810 "- Fastest export: {}\n",
811 results.summary.fastest_export
812 ));
813 report.push_str(&format!(
814 "- Most compact export: {}\n",
815 results.summary.most_compact_export
816 ));
817 report.push_str(&format!(
818 "- Recommended config: {}\n\n",
819 results.summary.recommended_config
820 ));
821
822 report.push_str("## Export Performance\n");
824 for (name, metrics) in &results.export_metrics {
825 report.push_str(&format!("### {}\n", name));
826 report.push_str(&format!("- Export time: {:?}\n", metrics.export_time));
827 report.push_str(&format!("- Export size: {} bytes\n", metrics.export_size));
828 report.push_str(&format!(
829 "- Peak memory: {:.2} MB\n",
830 metrics.peak_memory_mb
831 ));
832 report.push_str(&format!(
833 "- Throughput: {:.2} exports/sec\n",
834 metrics.throughput
835 ));
836 report.push_str(&format!(
837 "- Compression ratio: {:.2}x\n\n",
838 metrics.compression_ratio
839 ));
840 }
841
842 if !results.conversion_metrics.is_empty() {
844 report.push_str("## Conversion Performance\n");
845 for (name, metrics) in &results.conversion_metrics {
846 report.push_str(&format!("### {}\n", name));
847 report.push_str(&format!(
848 "- Conversion time: {:?}\n",
849 metrics.conversion_time
850 ));
851 report.push_str(&format!(
852 "- Peak memory: {:.2} MB\n",
853 metrics.peak_memory_mb
854 ));
855 report.push_str(&format!(
856 "- Layers converted: {}\n",
857 metrics.layers_converted
858 ));
859 report.push_str(&format!(
860 "- Success rate: {:.1}%\n\n",
861 metrics.success_rate * 100.0
862 ));
863 }
864 }
865
866 report
867 }
868
869 pub fn compare_benchmarks(
871 results1: &BenchmarkResults,
872 results2: &BenchmarkResults,
873 name1: &str,
874 name2: &str,
875 ) -> String {
876 let mut comparison = String::new();
877
878 comparison.push_str(&format!(
879 "# Benchmark Comparison: {} vs {}\n\n",
880 name1, name2
881 ));
882
883 for (config_name, metrics1) in &results1.export_metrics {
885 if let Some(metrics2) = results2.export_metrics.get(config_name) {
886 comparison.push_str(&format!("## {}\n", config_name));
887
888 let time_ratio =
889 metrics2.export_time.as_secs_f32() / metrics1.export_time.as_secs_f32();
890 let size_ratio = metrics2.export_size as f32 / metrics1.export_size as f32;
891
892 comparison.push_str(&format!(
893 "- Export time: {:.2}x {}\n",
894 time_ratio,
895 if time_ratio > 1.0 { "slower" } else { "faster" }
896 ));
897 comparison.push_str(&format!(
898 "- Export size: {:.2}x {}\n",
899 size_ratio,
900 if size_ratio > 1.0 {
901 "larger"
902 } else {
903 "smaller"
904 }
905 ));
906 comparison.push_str("\n");
907 }
908 }
909
910 comparison
911 }
912 }
913}
914
915#[cfg(test)]
916mod tests {
917 use super::*;
918 use std::collections::HashMap;
919
920 #[test]
921 fn test_export_config_default() {
922 let config = ExportConfig::default();
923 assert_eq!(config.format, ExportFormat::TorshBinary);
924 assert!(!config.include_training);
925 assert_eq!(config.optimization_level, OptimizationLevel::Basic);
926 }
927
928 #[test]
929 fn test_model_exporter_creation() {
930 let exporter = ModelExporter::onnx();
931 assert_eq!(exporter.config.format, ExportFormat::Onnx);
932
933 let exporter = ModelExporter::torchscript();
934 assert_eq!(exporter.config.format, ExportFormat::TorchScript);
935 }
936
937 #[test]
938 fn test_deployment_optimizer() {
939 let optimizer = DeploymentOptimizer::new(TargetDevice::Cpu, OptimizationLevel::Basic);
940
941 assert_eq!(optimizer.target_device, TargetDevice::Cpu);
942 assert_eq!(optimizer.optimization_level, OptimizationLevel::Basic);
943 }
944
945 #[test]
946 fn test_export_benchmarker() {
947 let benchmarker = benchmarks::ExportBenchmarker::new();
948 assert!(!benchmarker.configurations().is_empty());
949 assert_eq!(benchmarker.warmup_runs(), 3);
950 assert_eq!(benchmarker.benchmark_runs(), 10);
951 }
952
953 #[test]
954 fn test_conversion_benchmarker() {
955 let benchmarker = benchmarks::ConversionBenchmarker::new();
956 let results = benchmarker.benchmark_conversions().unwrap();
957 assert!(!results.is_empty());
958
959 for (name, metrics) in &results {
960 assert!(!name.is_empty());
961 assert!(metrics.conversion_time.as_millis() >= 10); assert!(metrics.success_rate > 0.0 && metrics.success_rate <= 1.0);
963 }
964 }
965
966 #[test]
967 fn test_benchmark_report_generation() {
968 use benchmarks::*;
969 use std::time::Duration;
970
971 let mut export_metrics = HashMap::new();
972 export_metrics.insert(
973 "test_config".to_string(),
974 ExportMetrics {
975 export_time: Duration::from_millis(100),
976 export_size: 1024,
977 peak_memory_mb: 64.0,
978 throughput: 10.0,
979 compression_ratio: 1.5,
980 target_device: TargetDevice::Cpu,
981 export_format: ExportFormat::Onnx,
982 },
983 );
984
985 let results = BenchmarkResults {
986 export_metrics,
987 conversion_metrics: HashMap::new(),
988 summary: BenchmarkSummary {
989 total_time: Duration::from_secs(1),
990 fastest_export: "test_config".to_string(),
991 most_compact_export: "test_config".to_string(),
992 recommended_config: "test_config".to_string(),
993 },
994 };
995
996 let report = utils::create_benchmark_report(&results);
997 assert!(report.contains("Export/Conversion Performance Benchmark Report"));
998 assert!(report.contains("test_config"));
999 assert!(report.contains("100ms"));
1000 }
1001
1002 #[test]
1003 fn test_benchmark_comparison() {
1004 use benchmarks::*;
1005 use std::time::Duration;
1006
1007 let mut export_metrics1 = HashMap::new();
1008 export_metrics1.insert(
1009 "config1".to_string(),
1010 ExportMetrics {
1011 export_time: Duration::from_millis(100),
1012 export_size: 1024,
1013 peak_memory_mb: 64.0,
1014 throughput: 10.0,
1015 compression_ratio: 1.5,
1016 target_device: TargetDevice::Cpu,
1017 export_format: ExportFormat::Onnx,
1018 },
1019 );
1020
1021 let mut export_metrics2 = HashMap::new();
1022 export_metrics2.insert(
1023 "config1".to_string(),
1024 ExportMetrics {
1025 export_time: Duration::from_millis(200),
1026 export_size: 2048,
1027 peak_memory_mb: 128.0,
1028 throughput: 5.0,
1029 compression_ratio: 1.5,
1030 target_device: TargetDevice::Cpu,
1031 export_format: ExportFormat::Onnx,
1032 },
1033 );
1034
1035 let results1 = BenchmarkResults {
1036 export_metrics: export_metrics1,
1037 conversion_metrics: HashMap::new(),
1038 summary: BenchmarkSummary {
1039 total_time: Duration::from_secs(1),
1040 fastest_export: "config1".to_string(),
1041 most_compact_export: "config1".to_string(),
1042 recommended_config: "config1".to_string(),
1043 },
1044 };
1045
1046 let results2 = BenchmarkResults {
1047 export_metrics: export_metrics2,
1048 conversion_metrics: HashMap::new(),
1049 summary: BenchmarkSummary {
1050 total_time: Duration::from_secs(2),
1051 fastest_export: "config1".to_string(),
1052 most_compact_export: "config1".to_string(),
1053 recommended_config: "config1".to_string(),
1054 },
1055 };
1056
1057 let comparison = utils::compare_benchmarks(&results1, &results2, "baseline", "optimized");
1058 assert!(comparison.contains("Benchmark Comparison"));
1059 assert!(comparison.contains("2.00x slower"));
1060 assert!(comparison.contains("2.00x larger"));
1061 }
1062}