use std::collections::HashMap;
use std::fmt;
use super::format::QuantFormat;
#[derive(Debug, Clone)]
pub struct QuantizedWeights {
pub format: QuantFormat,
pub data: Vec<u8>,
pub shape: (usize, usize),
pub layer_name: String,
}
impl QuantizedWeights {
pub fn new(format: QuantFormat, data: Vec<u8>, shape: (usize, usize), name: &str) -> Self {
Self {
format,
data,
shape,
layer_name: name.to_string(),
}
}
pub fn num_weights(&self) -> usize {
self.shape.0 * self.shape.1
}
pub fn memory_bytes(&self) -> usize {
self.data.len()
}
pub fn f16_memory_bytes(&self) -> usize {
self.num_weights() * 2
}
pub fn compression_ratio(&self) -> f64 {
self.f16_memory_bytes() as f64 / self.memory_bytes() as f64
}
pub fn actual_bits_per_weight(&self) -> f64 {
(self.data.len() * 8) as f64 / self.num_weights() as f64
}
}
#[derive(Debug, Clone)]
pub struct LayerQuantStats {
pub name: String,
pub format: QuantFormat,
pub weights: usize,
pub memory_bytes: usize,
pub compression_ratio: f64,
}
#[derive(Debug, Clone, Default)]
pub struct QuantStats {
pub total_weights: usize,
pub total_memory_bytes: usize,
pub f16_memory_bytes: usize,
pub weights_by_format: HashMap<QuantFormat, usize>,
pub memory_by_format: HashMap<QuantFormat, usize>,
pub layer_stats: Vec<LayerQuantStats>,
}
impl QuantStats {
pub fn new() -> Self {
Self::default()
}
pub fn add_layer(&mut self, weights: &QuantizedWeights) {
self.total_weights += weights.num_weights();
self.total_memory_bytes += weights.memory_bytes();
self.f16_memory_bytes += weights.f16_memory_bytes();
*self.weights_by_format.entry(weights.format).or_default() += weights.num_weights();
*self.memory_by_format.entry(weights.format).or_default() += weights.memory_bytes();
self.layer_stats.push(LayerQuantStats {
name: weights.layer_name.clone(),
format: weights.format,
weights: weights.num_weights(),
memory_bytes: weights.memory_bytes(),
compression_ratio: weights.compression_ratio(),
});
}
pub fn compression_ratio(&self) -> f64 {
if self.total_memory_bytes == 0 {
1.0
} else {
self.f16_memory_bytes as f64 / self.total_memory_bytes as f64
}
}
pub fn avg_bits_per_weight(&self) -> f64 {
if self.total_weights == 0 {
0.0
} else {
(self.total_memory_bytes * 8) as f64 / self.total_weights as f64
}
}
pub fn dominant_format(&self) -> Option<QuantFormat> {
self.weights_by_format
.iter()
.max_by_key(|(_, count)| *count)
.map(|(format, _)| *format)
}
}
impl fmt::Display for QuantStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Quantization Statistics")?;
writeln!(f, "======================")?;
writeln!(f, "Total Weights: {}", self.total_weights)?;
writeln!(
f,
"Total Memory: {:.2} MB (quantized)",
self.total_memory_bytes as f64 / 1_000_000.0
)?;
writeln!(
f,
"F16 Memory: {:.2} MB (baseline)",
self.f16_memory_bytes as f64 / 1_000_000.0
)?;
writeln!(f, "Compression: {:.2}x", self.compression_ratio())?;
writeln!(f, "Avg Bits/Weight: {:.2}", self.avg_bits_per_weight())?;
if !self.weights_by_format.is_empty() {
writeln!(f)?;
writeln!(f, "By Format:")?;
for (format, weights) in &self.weights_by_format {
let memory = self.memory_by_format.get(format).unwrap_or(&0);
writeln!(
f,
" {}: {} weights, {:.2} MB",
format,
weights,
*memory as f64 / 1_000_000.0
)?;
}
}
Ok(())
}
}