1use crate::config::{ObserverType, QuantConfig};
17
18#[cfg(not(feature = "std"))]
19extern crate alloc;
20
21#[cfg(not(feature = "std"))]
22use alloc::{collections::BTreeMap as HashMap, format, string::String, vec::Vec};
23
24use torsh_core::{
25 dtype::DType,
26 error::{Result as TorshResult, TorshError},
27};
28use torsh_tensor::Tensor;
29
30#[derive(Debug, Clone)]
32pub struct QuantizationMetrics {
33 pub mse: f32,
35 pub psnr: f32,
37 pub snr: f32,
39 pub mae: f32,
41 pub max_error: f32,
43 pub zero_error_percentage: f32,
45 pub cosine_similarity: f32,
47 pub compression_ratio: f32,
49}
50
51impl Default for QuantizationMetrics {
52 fn default() -> Self {
53 Self {
54 mse: 0.0,
55 psnr: 0.0,
56 snr: 0.0,
57 mae: 0.0,
58 max_error: 0.0,
59 zero_error_percentage: 100.0,
60 cosine_similarity: 1.0,
61 compression_ratio: 1.0,
62 }
63 }
64}
65
66pub fn calculate_quantization_metrics(
68 original: &Tensor,
69 quantized: &Tensor,
70 original_bits: u32,
71 quantized_bits: u32,
72) -> TorshResult<QuantizationMetrics> {
73 if original.shape() != quantized.shape() {
74 return Err(TorshError::InvalidArgument(format!(
75 "Shape mismatch: expected {:?}, got {:?}",
76 original.shape(),
77 quantized.shape()
78 )));
79 }
80
81 let original_data = original.data()?;
82 let quantized_data = quantized.data()?;
83
84 if original_data.len() != quantized_data.len() {
85 return Err(TorshError::InvalidArgument(
86 "Data length mismatch between tensors".to_string(),
87 ));
88 }
89
90 if original_data.is_empty() {
91 return Ok(QuantizationMetrics::default());
92 }
93
94 let mse = original_data
96 .iter()
97 .zip(quantized_data.iter())
98 .map(|(a, b)| (a - b).powi(2))
99 .sum::<f32>()
100 / original_data.len() as f32;
101
102 let mae = original_data
104 .iter()
105 .zip(quantized_data.iter())
106 .map(|(a, b)| (a - b).abs())
107 .sum::<f32>()
108 / original_data.len() as f32;
109
110 let max_error = original_data
112 .iter()
113 .zip(quantized_data.iter())
114 .map(|(a, b)| (a - b).abs())
115 .fold(0.0, f32::max);
116
117 let zero_errors = original_data
119 .iter()
120 .zip(quantized_data.iter())
121 .filter(|(a, b)| (*a - *b).abs() < 1e-7)
122 .count();
123 let zero_error_percentage = (zero_errors as f32 / original_data.len() as f32) * 100.0;
124
125 let signal_power =
127 original_data.iter().map(|x| x.powi(2)).sum::<f32>() / original_data.len() as f32;
128
129 let max_signal = original_data
131 .iter()
132 .fold(0.0f32, |acc, &x| acc.max(x.abs()));
133 let psnr = if mse > 0.0 {
134 20.0 * (max_signal / mse.sqrt()).log10()
135 } else {
136 f32::INFINITY
137 };
138
139 let snr = if mse > 0.0 && signal_power > 0.0 {
141 10.0 * (signal_power / mse).log10()
142 } else {
143 f32::INFINITY
144 };
145
146 let dot_product = original_data
148 .iter()
149 .zip(quantized_data.iter())
150 .map(|(a, b)| a * b)
151 .sum::<f32>();
152
153 let original_norm = original_data.iter().map(|x| x.powi(2)).sum::<f32>().sqrt();
154 let quantized_norm = quantized_data.iter().map(|x| x.powi(2)).sum::<f32>().sqrt();
155
156 let cosine_similarity = if original_norm > 0.0 && quantized_norm > 0.0 {
157 dot_product / (original_norm * quantized_norm)
158 } else {
159 0.0
160 };
161
162 let compression_ratio = original_bits as f32 / quantized_bits as f32;
164
165 Ok(QuantizationMetrics {
166 mse,
167 psnr,
168 snr,
169 mae,
170 max_error,
171 zero_error_percentage,
172 cosine_similarity,
173 compression_ratio,
174 })
175}
176
177pub fn compare_quantization_configs(
179 tensor: &Tensor,
180 configs: &[QuantConfig],
181) -> TorshResult<Vec<(QuantConfig, QuantizationMetrics, f64)>> {
182 let mut results = Vec::new();
183
184 for config in configs {
185 let start = std::time::Instant::now();
187
188 let quantize_result = crate::algorithms::quantize_with_config_full(tensor, config);
192
193 let duration = start.elapsed().as_secs_f64();
194
195 match quantize_result {
196 Ok(quantized) => {
197 let dequantized = quantized.dequantize()?;
199
200 let original_bits = match tensor.dtype() {
202 DType::F32 => 32,
203 DType::F16 => 16,
204 _ => 8,
205 };
206
207 let quantized_bits = match config.dtype {
208 DType::I8 | DType::U8 => 8,
209 DType::I16 => 16,
210 DType::I32 => 32,
211 DType::F16 => 16,
212 DType::F32 => 32,
213 _ => 8,
214 };
215
216 let metrics = calculate_quantization_metrics(
217 tensor,
218 &dequantized,
219 original_bits,
220 quantized_bits,
221 )?;
222
223 results.push((config.clone(), metrics, duration));
224 }
225 Err(_) => {
226 let worst_metrics = QuantizationMetrics {
228 mse: f32::INFINITY,
229 psnr: f32::NEG_INFINITY,
230 snr: f32::NEG_INFINITY,
231 mae: f32::INFINITY,
232 max_error: f32::INFINITY,
233 zero_error_percentage: 0.0,
234 cosine_similarity: 0.0,
235 compression_ratio: 1.0,
236 };
237
238 results.push((config.clone(), worst_metrics, duration));
239 }
240 }
241 }
242
243 results.sort_by(|a, b| {
245 b.1.psnr
246 .partial_cmp(&a.1.psnr)
247 .unwrap_or(core::cmp::Ordering::Equal)
248 });
249
250 Ok(results)
251}
252
253pub fn auto_calibrate_quantization(
255 calibration_tensors: &[&Tensor],
256 target_accuracy_threshold: f32,
257 max_compression_ratio: f32,
258) -> TorshResult<QuantConfig> {
259 if calibration_tensors.is_empty() {
260 return Err(TorshError::InvalidArgument(
261 "No calibration tensors provided".to_string(),
262 ));
263 }
264
265 let candidate_configs = vec![
267 QuantConfig::int8(),
268 QuantConfig::int8().with_observer(ObserverType::Histogram),
269 QuantConfig::per_channel(0),
270 QuantConfig::per_channel(1),
271 QuantConfig::group_wise(0, 8),
272 QuantConfig::group_wise(1, 16),
273 QuantConfig::int4(),
274 QuantConfig::ternary(),
275 ];
276
277 let mut best_config = None;
278 let mut best_score = f32::NEG_INFINITY;
279
280 for config in candidate_configs {
282 let mut total_metrics = QuantizationMetrics::default();
283 let mut successful_tests = 0;
284
285 for tensor in calibration_tensors {
286 if let Ok(comparison) =
287 compare_quantization_configs(tensor, std::slice::from_ref(&config))
288 {
289 if let Some((_, metrics, _)) = comparison.first() {
290 if metrics.psnr.is_finite() {
291 total_metrics.mse += metrics.mse;
292 total_metrics.psnr += metrics.psnr;
293 total_metrics.snr += metrics.snr;
294 total_metrics.mae += metrics.mae;
295 total_metrics.max_error = total_metrics.max_error.max(metrics.max_error);
296 total_metrics.zero_error_percentage += metrics.zero_error_percentage;
297 total_metrics.cosine_similarity += metrics.cosine_similarity;
298 total_metrics.compression_ratio += metrics.compression_ratio;
299 successful_tests += 1;
300 }
301 }
302 }
303 }
304
305 if successful_tests > 0 {
306 let avg_metrics = QuantizationMetrics {
308 mse: total_metrics.mse / successful_tests as f32,
309 psnr: total_metrics.psnr / successful_tests as f32,
310 snr: total_metrics.snr / successful_tests as f32,
311 mae: total_metrics.mae / successful_tests as f32,
312 max_error: total_metrics.max_error,
313 zero_error_percentage: total_metrics.zero_error_percentage
314 / successful_tests as f32,
315 cosine_similarity: total_metrics.cosine_similarity / successful_tests as f32,
316 compression_ratio: total_metrics.compression_ratio / successful_tests as f32,
317 };
318
319 let score = if avg_metrics.psnr >= target_accuracy_threshold
321 && avg_metrics.compression_ratio <= max_compression_ratio
322 {
323 avg_metrics.compression_ratio + avg_metrics.psnr / 100.0
325 } else {
326 avg_metrics.psnr / avg_metrics.compression_ratio
328 };
329
330 if score > best_score {
331 best_score = score;
332 best_config = Some(config.clone());
333 }
334 }
335 }
336
337 best_config
338 .ok_or_else(|| TorshError::InvalidArgument("No suitable configuration found".to_string()))
339}
340
341pub fn generate_quantization_report(
343 original: &Tensor,
344 configs: &[QuantConfig],
345) -> TorshResult<String> {
346 let mut report = String::new();
347
348 report.push_str("# Quantization Analysis Report\n\n");
349 report.push_str(&format!(
350 "**Original Tensor Shape:** {:?}\n",
351 original.shape()
352 ));
353 report.push_str(&format!(
354 "**Original Tensor DType:** {:?}\n",
355 original.dtype()
356 ));
357 report.push_str(&format!(
358 "**Number of Elements:** {}\n\n",
359 original.shape().numel()
360 ));
361
362 let data = original.data()?;
364 let min_val = data.iter().fold(f32::INFINITY, |acc, &x| acc.min(x));
365 let max_val = data.iter().fold(f32::NEG_INFINITY, |acc, &x| acc.max(x));
366 let mean = data.iter().sum::<f32>() / data.len() as f32;
367 let std_dev = (data.iter().map(|x| (x - mean).powi(2)).sum::<f32>() / data.len() as f32).sqrt();
368
369 report.push_str("**Original Tensor Statistics:**\n");
370 report.push_str(&format!("- Min: {min_val:.6}\n"));
371 report.push_str(&format!("- Max: {max_val:.6}\n"));
372 report.push_str(&format!("- Mean: {mean:.6}\n"));
373 report.push_str(&format!("- Std Dev: {std_dev:.6}\n"));
374 report.push_str(&format!("- Dynamic Range: {:.6}\n\n", max_val - min_val));
375
376 let comparison_results = compare_quantization_configs(original, configs)?;
378
379 report.push_str("## Quantization Configuration Comparison\n\n");
380 report.push_str(
381 "| Rank | Scheme | Observer | PSNR (dB) | SNR (dB) | MAE | Compression | Time (ms) |\n",
382 );
383 report.push_str(
384 "|------|--------|----------|-----------|----------|-----|-------------|----------|\n",
385 );
386
387 for (rank, (config, metrics, duration)) in comparison_results.iter().enumerate() {
388 report.push_str(&format!(
389 "| {} | {:?} | {:?} | {:.2} | {:.2} | {:.6} | {:.1}x | {:.2} |\n",
390 rank + 1,
391 config.scheme,
392 config.observer_type,
393 metrics.psnr,
394 metrics.snr,
395 metrics.mae,
396 metrics.compression_ratio,
397 duration * 1000.0
398 ));
399 }
400
401 report.push_str("\n## Detailed Metrics\n\n");
402
403 for (rank, (config, metrics, _)) in comparison_results.iter().enumerate() {
404 report.push_str(&format!(
405 "### Configuration #{} - {:?}\n",
406 rank + 1,
407 config.scheme
408 ));
409 report.push_str(&format!("- **MSE:** {:.8}\n", metrics.mse));
410 report.push_str(&format!("- **PSNR:** {:.2} dB\n", metrics.psnr));
411 report.push_str(&format!("- **SNR:** {:.2} dB\n", metrics.snr));
412 report.push_str(&format!("- **MAE:** {:.6}\n", metrics.mae));
413 report.push_str(&format!("- **Max Error:** {:.6}\n", metrics.max_error));
414 report.push_str(&format!(
415 "- **Zero Error %:** {:.2}%\n",
416 metrics.zero_error_percentage
417 ));
418 report.push_str(&format!(
419 "- **Cosine Similarity:** {:.6}\n",
420 metrics.cosine_similarity
421 ));
422 report.push_str(&format!(
423 "- **Compression Ratio:** {:.1}x\n\n",
424 metrics.compression_ratio
425 ));
426 }
427
428 report.push_str("## Recommendations\n\n");
429
430 if let Some((best_config, best_metrics, _)) = comparison_results.first() {
431 report.push_str(&format!(
432 "**Best Configuration:** {:?} with {:?} observer\n",
433 best_config.scheme, best_config.observer_type
434 ));
435 report.push_str(&format!(
436 "- Achieves {:.2} dB PSNR with {:.1}x compression\n",
437 best_metrics.psnr, best_metrics.compression_ratio
438 ));
439
440 if best_metrics.psnr > 40.0 {
441 report.push_str("- ✅ Excellent quality preservation\n");
442 } else if best_metrics.psnr > 30.0 {
443 report.push_str("- ✅ Good quality preservation\n");
444 } else if best_metrics.psnr > 20.0 {
445 report.push_str("- ⚠️ Moderate quality loss\n");
446 } else {
447 report.push_str("- ❌ Significant quality loss\n");
448 }
449 }
450
451 Ok(report)
452}
453
454pub fn generate_optimization_hints(
456 tensor: &Tensor,
457 config: &QuantConfig,
458) -> TorshResult<Vec<String>> {
459 let mut hints = Vec::new();
460 let shape = tensor.shape();
461 let data = tensor.data()?;
462
463 if !data.is_empty() {
465 let min_val = data.iter().fold(f32::INFINITY, |acc, &x| acc.min(x));
466 let max_val = data.iter().fold(f32::NEG_INFINITY, |acc, &x| acc.max(x));
467 let mean = data.iter().sum::<f32>() / data.len() as f32;
468 let std_dev =
469 (data.iter().map(|x| (x - mean).powi(2)).sum::<f32>() / data.len() as f32).sqrt();
470
471 let dynamic_range = max_val - min_val;
473 if dynamic_range > 100.0 {
474 hints.push("Large dynamic range detected. Consider using Histogram or Percentile observer for better quantization parameters.".to_string());
475 }
476
477 let zero_count = data.iter().filter(|&&x| x.abs() < 1e-6).count();
479 let sparsity = zero_count as f32 / data.len() as f32;
480 if sparsity > 0.5 {
481 hints.push(
482 "High sparsity detected. Sparse quantization schemes may be more efficient."
483 .to_string(),
484 );
485 }
486
487 let outlier_threshold = mean + 3.0 * std_dev;
489 let outlier_count = data
490 .iter()
491 .filter(|&&x| x.abs() > outlier_threshold)
492 .count();
493 if outlier_count > 0 {
494 hints.push("Outliers detected. Percentile-based observers may provide better quantization parameters.".to_string());
495 }
496
497 if data.len() > 1_000_000 {
499 hints.push("For large tensors, Histogram observer may be more memory-efficient than Percentile observer.".to_string());
500 }
501 }
502
503 if shape.dims().len() >= 2 && shape.dims().iter().any(|&dim| dim > 16) {
505 hints.push("Multi-channel tensor detected. Per-channel or group-wise quantization may provide better accuracy.".to_string());
506 }
507
508 match config.scheme {
510 crate::config::QScheme::PerChannelAffine | crate::config::QScheme::PerChannelSymmetric => {
511 if let Some(axis) = config.ch_axis {
512 if axis >= shape.dims().len() {
513 hints.push(
514 "Channel axis is out of bounds. This will cause an error.".to_string(),
515 );
516 } else if shape.dims()[axis] < 4 {
517 hints.push(
518 "Few channels detected. Per-tensor quantization might be sufficient."
519 .to_string(),
520 );
521 }
522 }
523 }
524 crate::config::QScheme::GroupWise => {
525 if let (Some(axis), Some(group_size)) = (config.ch_axis, config.group_size) {
526 if axis < shape.dims().len() {
527 let num_channels = shape.dims()[axis];
528 let num_groups = num_channels.div_ceil(group_size);
529 if num_groups == 1 {
530 hints.push("Only one group will be created. Consider per-tensor quantization instead.".to_string());
531 } else if num_groups == num_channels {
532 hints.push("Each channel forms its own group. Consider per-channel quantization instead.".to_string());
533 }
534 }
535 }
536 }
537 _ => {}
538 }
539
540 Ok(hints)
541}
542
543pub fn benchmark_quantization_performance(
545 tensor: &Tensor,
546 configs: &[QuantConfig],
547 num_iterations: usize,
548) -> TorshResult<Vec<(QuantConfig, f64, f64)>> {
549 let mut results = Vec::new();
550
551 for config in configs {
552 let mut total_quantize_time = 0.0;
553 let mut total_dequantize_time = 0.0;
554 let mut successful_runs = 0;
555
556 for _ in 0..num_iterations {
557 let quantize_start = std::time::Instant::now();
559 let quantize_result = crate::algorithms::quantize_with_config(tensor, config);
560 let quantize_time = quantize_start.elapsed().as_secs_f64();
561
562 if let Ok((quantized, scale, zero_point)) = quantize_result {
563 let dequantize_start = std::time::Instant::now();
565 let _dequantized = crate::algorithms::dequantize(&quantized, scale, zero_point)?;
566 let dequantize_time = dequantize_start.elapsed().as_secs_f64();
567
568 total_quantize_time += quantize_time;
569 total_dequantize_time += dequantize_time;
570 successful_runs += 1;
571 }
572 }
573
574 if successful_runs > 0 {
575 let avg_quantize_time = total_quantize_time / successful_runs as f64;
576 let avg_dequantize_time = total_dequantize_time / successful_runs as f64;
577 results.push((config.clone(), avg_quantize_time, avg_dequantize_time));
578 }
579 }
580
581 Ok(results)
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587
588 use torsh_tensor::creation::tensor_1d;
589
590 #[test]
591 fn test_calculate_quantization_metrics() {
592 let original_data = vec![1.0, 2.0, 3.0, 4.0];
593 let quantized_data = vec![1.1, 2.1, 2.9, 3.9];
594
595 let original = tensor_1d(&original_data).unwrap();
596 let quantized = tensor_1d(&quantized_data).unwrap();
597
598 let metrics = calculate_quantization_metrics(&original, &quantized, 32, 8).unwrap();
599
600 assert!(metrics.mse > 0.0);
602 assert!(metrics.mse < 1.0); assert!(metrics.mae > 0.0);
604 assert!(metrics.mae < 1.0);
605 assert!(metrics.psnr > 0.0);
606 assert!(metrics.snr > 0.0);
607 assert!(metrics.max_error >= 0.0);
608 assert!(metrics.zero_error_percentage >= 0.0);
609 assert!(metrics.zero_error_percentage <= 100.0);
610 assert!(metrics.cosine_similarity > 0.8); assert_eq!(metrics.compression_ratio, 4.0); let metrics_perfect = calculate_quantization_metrics(&original, &original, 32, 16).unwrap();
615 assert_eq!(metrics_perfect.mse, 0.0);
616 assert_eq!(metrics_perfect.mae, 0.0);
617 assert_eq!(metrics_perfect.max_error, 0.0);
618 assert_eq!(metrics_perfect.zero_error_percentage, 100.0);
619 assert!((metrics_perfect.cosine_similarity - 1.0).abs() < 1e-6);
620 assert!(metrics_perfect.psnr.is_infinite());
621 assert!(metrics_perfect.snr.is_infinite());
622 assert_eq!(metrics_perfect.compression_ratio, 2.0); }
624
625 #[test]
626 fn test_compare_quantization_configs() {
627 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
628 let tensor = tensor_1d(&data).unwrap();
629
630 let configs = vec![
631 QuantConfig::int8(),
632 QuantConfig::binary(),
633 QuantConfig::ternary(),
634 ];
635
636 let results = compare_quantization_configs(&tensor, &configs).unwrap();
637
638 assert_eq!(results.len(), 3);
640
641 for (config, metrics, duration) in &results {
643 assert!(configs.iter().any(|c| c.scheme == config.scheme));
644 assert!(duration >= &0.0);
645
646 if metrics.psnr.is_finite() {
648 assert!(metrics.psnr > 0.0);
649 assert!(metrics.compression_ratio >= 1.0);
650 assert!(metrics.mae >= 0.0);
651 assert!(metrics.mse >= 0.0);
652 }
653 }
654
655 for i in 1..results.len() {
657 let prev_psnr = results[i - 1].1.psnr;
658 let curr_psnr = results[i].1.psnr;
659 if prev_psnr.is_finite() && curr_psnr.is_finite() {
660 assert!(prev_psnr >= curr_psnr);
661 }
662 }
663 }
664
665 #[test]
666 fn test_auto_calibrate_quantization() {
667 let tensor1 = tensor_1d(&[1.0, 2.0, 3.0, 4.0]).unwrap();
668 let tensor2 = tensor_1d(&[2.0, 3.0, 4.0, 5.0]).unwrap();
669 let tensor3 = tensor_1d(&[0.5, 1.5, 2.5, 3.5]).unwrap();
670
671 let calibration_tensors = vec![&tensor1, &tensor2, &tensor3];
672
673 let result = auto_calibrate_quantization(&calibration_tensors, 20.0, 10.0);
675 assert!(result.is_ok());
676
677 let config = result.unwrap();
678 assert!(config.validate().is_ok());
679
680 let result_strict = auto_calibrate_quantization(&calibration_tensors, 100.0, 1.1);
682 assert!(result_strict.is_ok());
683
684 let empty_tensors = vec![];
686 let result_empty = auto_calibrate_quantization(&empty_tensors, 20.0, 10.0);
687 assert!(result_empty.is_err());
688 }
689
690 #[test]
691 fn test_generate_quantization_report() {
692 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
693 let tensor = tensor_1d(&data).unwrap();
694
695 let configs = vec![QuantConfig::int8(), QuantConfig::binary()];
696
697 let report_result = generate_quantization_report(&tensor, &configs);
698 assert!(report_result.is_ok());
699
700 let report = report_result.unwrap();
701
702 assert!(report.contains("# Quantization Analysis Report"));
704 assert!(report.contains("**Original Tensor Shape:**"));
705 assert!(report.contains("**Original Tensor Statistics:**"));
706 assert!(report.contains("## Quantization Configuration Comparison"));
707 assert!(report.contains("## Detailed Metrics"));
708 assert!(report.contains("## Recommendations"));
709
710 assert!(report.contains("PerTensorAffine"));
712 assert!(report.contains("Binary"));
713
714 assert!(report.contains("Min:"));
716 assert!(report.contains("Max:"));
717 assert!(report.contains("Mean:"));
718 assert!(report.contains("Std Dev:"));
719 assert!(report.contains("Dynamic Range:"));
720
721 assert!(report.contains("PSNR (dB)"));
723 assert!(report.contains("SNR (dB)"));
724 assert!(report.contains("MAE"));
725 assert!(report.contains("Compression"));
726 assert!(report.contains("Time (ms)"));
727
728 assert!(report.contains("**Best Configuration:**"));
730 }
731
732 #[test]
733 fn test_generate_optimization_hints() {
734 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
735 let tensor = tensor_1d(&data).unwrap();
736 let config = QuantConfig::int8();
737
738 let hints = generate_optimization_hints(&tensor, &config).unwrap();
739 assert!(hints.is_empty() || !hints.is_empty());
741
742 let per_channel_config = QuantConfig::per_channel(0);
744 let hints = generate_optimization_hints(&tensor, &per_channel_config).unwrap();
745 assert!(hints.is_empty() || !hints.is_empty());
747 }
748
749 #[test]
750 fn test_benchmark_quantization_performance() {
751 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
752 let tensor = tensor_1d(&data).unwrap();
753
754 let configs = vec![QuantConfig::int8(), QuantConfig::binary()];
755
756 let results = benchmark_quantization_performance(&tensor, &configs, 3).unwrap();
757
758 assert!(results.len() <= configs.len());
760
761 for (config, quantize_time, dequantize_time) in &results {
762 assert!(configs.iter().any(|c| c.scheme == config.scheme));
763 assert!(quantize_time >= &0.0);
764 assert!(dequantize_time >= &0.0);
765 }
766 }
767
768 #[test]
769 fn test_quantization_metrics_edge_cases() {
770 let tensor1 = tensor_1d(&[1.0, 2.0]).unwrap();
772 let tensor2 = tensor_1d(&[1.0, 2.0, 3.0]).unwrap();
773
774 let result = calculate_quantization_metrics(&tensor1, &tensor2, 32, 8);
775 assert!(result.is_err());
776
777 let zero_tensor = tensor_1d(&[0.0, 0.0, 0.0]).unwrap();
779 let metrics = calculate_quantization_metrics(&zero_tensor, &zero_tensor, 32, 8).unwrap();
780
781 assert_eq!(metrics.mse, 0.0);
782 assert_eq!(metrics.mae, 0.0);
783 assert_eq!(metrics.max_error, 0.0);
784 assert_eq!(metrics.zero_error_percentage, 100.0);
785 assert!(metrics.psnr.is_infinite());
786 assert_eq!(metrics.cosine_similarity, 0.0); let original = tensor_1d(&[1.0, 2.0, 3.0]).unwrap();
790 let almost_same = tensor_1d(&[1.0000001, 2.0000001, 3.0000001]).unwrap();
791
792 let metrics = calculate_quantization_metrics(&original, &almost_same, 32, 8).unwrap();
793 assert!(metrics.mse < 1e-12);
794 assert!(metrics.mae < 1e-6);
795 assert!(metrics.cosine_similarity > 0.999999);
796 assert!(metrics.psnr > 100.0); }
798
799 #[test]
800 fn test_metrics_default() {
801 let default_metrics = QuantizationMetrics::default();
802 assert_eq!(default_metrics.mse, 0.0);
803 assert_eq!(default_metrics.psnr, 0.0);
804 assert_eq!(default_metrics.snr, 0.0);
805 assert_eq!(default_metrics.mae, 0.0);
806 assert_eq!(default_metrics.max_error, 0.0);
807 assert_eq!(default_metrics.zero_error_percentage, 100.0);
808 assert_eq!(default_metrics.cosine_similarity, 1.0);
809 assert_eq!(default_metrics.compression_ratio, 1.0);
810 }
811}