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]>
282 lines
9.2 KiB
Rust
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)
|
|
}
|
|
}
|