Files
rustytorch/crates/training/rtx-compress/src/quantization/mixed_precision.rs
T
osobhandClaude Opus 4.6 02d382d5f6 style: apply rustfmt across all crates and demos
Consistent formatting pass: line wrapping, import sorting, trailing
whitespace removal, let-chain indentation, merged derive attributes,
and unsafe block reformatting.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-12 07:01:58 -07:00

282 lines
9.2 KiB
Rust

use crate::error::{CompressionError, Result};
use rtx_tensor::Tensor;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct PrecisionConfig {
pub precision_bits: Vec<u8>,
pub sensitivity_threshold: f64,
pub performance_weight: f64,
pub quality_weight: f64,
}
#[derive(Debug, Clone)]
pub struct OptimizationObjective {
pub target_compression_ratio: f64,
pub max_quality_loss: f64,
pub memory_constraint_mb: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct LayerSensitivity {
pub error_impact: f64,
pub parameter_count: usize,
pub access_frequency: f64,
}
#[derive(Debug, Clone)]
pub struct OptimalConfiguration {
pub layer_precisions: HashMap<String, u8>,
pub estimated_quality_loss: f64,
pub compression_ratio: f64,
}
pub struct MixedPrecisionOptimizer {
config: PrecisionConfig,
hardware_constraints: HashMap<String, f64>,
quantization_aware_mode: bool,
}
impl MixedPrecisionOptimizer {
pub fn new(config: PrecisionConfig) -> Self {
Self {
config,
hardware_constraints: HashMap::new(),
quantization_aware_mode: false,
}
}
pub fn analyze_sensitivity(
&mut self,
layers: &HashMap<String, Tensor>,
_calibration_data: &Tensor,
) -> Result<HashMap<String, LayerSensitivity>> {
let mut sensitivities = HashMap::new();
for (layer_name, tensor) in layers {
// Simplified sensitivity analysis
let parameter_count = tensor.numel();
// Heuristic: smaller tensors often more sensitive
let size_factor = 1.0 / (parameter_count as f64).log10();
// Layer type heuristics
let type_factor = if layer_name.contains("layer_norm") || layer_name.contains("bias") {
2.0 // More sensitive
} else if layer_name.contains("embeddings") {
0.3 // Less sensitive
} else {
1.0 // Default
};
let error_impact = size_factor * type_factor * self.config.sensitivity_threshold;
sensitivities.insert(
layer_name.clone(),
LayerSensitivity {
error_impact,
parameter_count,
access_frequency: 1.0, // Placeholder
},
);
}
Ok(sensitivities)
}
pub fn optimize(
&mut self,
layers: &HashMap<String, Tensor>,
calibration_data: &Tensor,
_objective: OptimizationObjective,
) -> Result<OptimalConfiguration> {
let sensitivities = self.analyze_sensitivity(layers, calibration_data)?;
let mut layer_precisions = HashMap::new();
let mut total_bits = 0usize;
let mut quality_loss = 0.0f64;
// Sort layers by sensitivity (most sensitive first)
let mut sorted_layers: Vec<_> = sensitivities.iter().collect();
sorted_layers.sort_by(|a, b| b.1.error_impact.total_cmp(&a.1.error_impact));
// Assign precisions based on sensitivity and constraints
for (layer_name, sensitivity) in sorted_layers {
let precision = if sensitivity.error_impact > self.config.sensitivity_threshold * 1.5 {
16 // High precision for sensitive layers
} else if sensitivity.error_impact > self.config.sensitivity_threshold {
12 // Medium precision
} else if sensitivity.error_impact > self.config.sensitivity_threshold * 0.5 {
8 // Standard precision
} else {
4 // Low precision for insensitive layers
};
layer_precisions.insert(layer_name.clone(), precision);
total_bits += sensitivity.parameter_count * precision as usize;
quality_loss += sensitivity.error_impact * (16.0 - precision as f64) / 16.0;
}
// Calculate compression ratio
let original_bits = layers
.values()
.map(rtx_tensor::Tensor::numel)
.sum::<usize>()
* 32; // fp32
let compression_ratio = original_bits as f64 / total_bits as f64;
// Normalize quality loss
quality_loss /= sensitivities.len() as f64;
Ok(OptimalConfiguration {
layer_precisions,
estimated_quality_loss: quality_loss,
compression_ratio,
})
}
pub fn adjust_precisions_runtime(
&self,
sensitivities: &HashMap<String, LayerSensitivity>,
performance_feedback: &HashMap<String, f64>,
) -> Result<OptimalConfiguration> {
let mut layer_precisions = HashMap::new();
for (layer_name, sensitivity) in sensitivities {
let feedback = performance_feedback.get(layer_name).unwrap_or(&0.9);
// Adjust precision based on runtime feedback
let base_precision = if sensitivity.error_impact > self.config.sensitivity_threshold {
12
} else {
8
};
let precision = if *feedback < 0.9 {
(base_precision + 4).min(16) // Increase precision if poor performance
} else {
base_precision.max(4) // Keep base precision
};
layer_precisions.insert(layer_name.clone(), precision);
}
Ok(OptimalConfiguration {
layer_precisions,
estimated_quality_loss: 0.02, // Placeholder
compression_ratio: 3.5, // Placeholder
})
}
pub fn enable_quantization_aware_mode(&mut self, enable: bool) {
self.quantization_aware_mode = enable;
}
pub fn simulate_quantization(&self, tensor: &Tensor, bits: u8) -> Result<Tensor> {
// Simplified quantization simulation - just add some noise
let _noise_scale = match bits {
4 => 0.1,
8 => 0.01,
12 => 0.001,
16 => 0.0001,
_ => 0.01,
};
// Create noise tensor
let device = tensor.device();
let noise = Tensor::zeros(tensor.shape().clone(), device)?;
// Add noise to simulate quantization error
let result = tensor.add(&noise)?;
Ok(result)
}
pub fn set_hardware_constraints(&mut self, constraints: &[(&str, f64)]) -> Result<()> {
self.hardware_constraints.clear();
for (key, value) in constraints {
self.hardware_constraints.insert(key.to_string(), *value);
}
Ok(())
}
pub fn serialize_sensitivities(
&self,
sensitivities: &HashMap<String, LayerSensitivity>,
) -> Result<Vec<u8>> {
// Simplified serialization
let mut data = Vec::new();
data.extend_from_slice(&sensitivities.len().to_le_bytes());
for (name, sensitivity) in sensitivities {
let name_bytes = name.as_bytes();
data.extend_from_slice(&name_bytes.len().to_le_bytes());
data.extend_from_slice(name_bytes);
data.extend_from_slice(&sensitivity.error_impact.to_le_bytes());
data.extend_from_slice(&sensitivity.parameter_count.to_le_bytes());
data.extend_from_slice(&sensitivity.access_frequency.to_le_bytes());
}
Ok(data)
}
pub fn deserialize_sensitivities(
&self,
data: &[u8],
) -> Result<HashMap<String, LayerSensitivity>> {
let mut result = HashMap::new();
let mut offset = 0;
let count = usize::from_le_bytes(
data[offset..offset + 8]
.try_into()
.map_err(|_| CompressionError::MixedPrecisionError("Invalid count".to_string()))?,
);
offset += 8;
for _ in 0..count {
// Read name
let name_len =
usize::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| {
CompressionError::MixedPrecisionError("Invalid name length".to_string())
})?);
offset += 8;
let name =
String::from_utf8(data[offset..offset + name_len].to_vec()).map_err(|_| {
CompressionError::MixedPrecisionError("Invalid name encoding".to_string())
})?;
offset += name_len;
// Read sensitivity data
let error_impact =
f64::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| {
CompressionError::MixedPrecisionError("Invalid error impact".to_string())
})?);
offset += 8;
let parameter_count =
usize::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| {
CompressionError::MixedPrecisionError("Invalid parameter count".to_string())
})?);
offset += 8;
let access_frequency =
f64::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| {
CompressionError::MixedPrecisionError("Invalid access frequency".to_string())
})?);
offset += 8;
result.insert(
name,
LayerSensitivity {
error_impact,
parameter_count,
access_frequency,
},
);
}
Ok(result)
}
}