1#![allow(unused_variables)] use crate::errors::Result;
10use crate::quantization::base::QuantizationScheme;
11use crate::tensor::Tensor;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct MixedBitConfig {
18 pub layer_configs: HashMap<String, LayerQuantConfig>,
20 pub default_config: LayerQuantConfig,
22 pub sensitivity_config: SensitivityConfig,
24 pub auto_bit_allocation: Option<AutoBitAllocationStrategy>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct LayerQuantConfig {
31 pub weight_bits: u8,
33 pub activation_bits: u8,
35 pub scheme: QuantizationScheme,
37 pub symmetric: bool,
39 pub group_size: Option<usize>,
41 pub channel_bits: Option<Vec<u8>>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct SensitivityConfig {
48 pub calibration_samples: usize,
50 pub sensitivity_threshold: f32,
52 pub metrics: Vec<SensitivityMetric>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub enum SensitivityMetric {
59 GradientMagnitude,
61 HessianDiagonal,
63 ActivationVariance,
65 WeightVariance,
67 OutputSensitivity,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub enum AutoBitAllocationStrategy {
74 SensitivityBased {
76 target_compression: f32,
78 min_bits: u8,
80 max_bits: u8,
82 },
83 AdaptiveUniform {
85 base_bits: u8,
87 adjustment_range: u8,
89 },
90 PerformanceDriven {
92 target_latency: f32,
94 accuracy_tolerance: f32,
96 },
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct MixedBitQuantizedTensor {
102 pub layer_name: String,
104 pub quantized_data: Vec<QuantizedBlock>,
106 pub shape: Vec<usize>,
108 pub config: LayerQuantConfig,
110 pub sensitivity_scores: Vec<f32>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct QuantizedBlock {
117 pub data: Vec<u8>,
119 pub scale: f32,
121 pub zero_point: i32,
123 pub bit_width: u8,
125 pub block_shape: Vec<usize>,
127 pub block_offset: Vec<usize>,
129}
130
131pub struct MixedBitQuantizer {
133 config: MixedBitConfig,
134 sensitivity_analyzer: SensitivityAnalyzer,
135}
136
137struct SensitivityAnalyzer {
139 config: SensitivityConfig,
140 sensitivity_cache: HashMap<String, Vec<f32>>,
141}
142
143impl Default for MixedBitConfig {
144 fn default() -> Self {
145 Self {
146 layer_configs: HashMap::new(),
147 default_config: LayerQuantConfig::default(),
148 sensitivity_config: SensitivityConfig::default(),
149 auto_bit_allocation: Some(AutoBitAllocationStrategy::SensitivityBased {
150 target_compression: 0.25, min_bits: 2,
152 max_bits: 8,
153 }),
154 }
155 }
156}
157
158impl Default for LayerQuantConfig {
159 fn default() -> Self {
160 Self {
161 weight_bits: 4,
162 activation_bits: 8,
163 scheme: QuantizationScheme::Int4,
164 symmetric: true,
165 group_size: Some(128),
166 channel_bits: None,
167 }
168 }
169}
170
171impl Default for SensitivityConfig {
172 fn default() -> Self {
173 Self {
174 calibration_samples: 128,
175 sensitivity_threshold: 0.01,
176 metrics: vec![
177 SensitivityMetric::GradientMagnitude,
178 SensitivityMetric::ActivationVariance,
179 SensitivityMetric::WeightVariance,
180 ],
181 }
182 }
183}
184
185impl MixedBitQuantizer {
186 pub fn new(config: MixedBitConfig) -> Self {
188 let sensitivity_analyzer = SensitivityAnalyzer::new(config.sensitivity_config.clone());
189 Self {
190 config,
191 sensitivity_analyzer,
192 }
193 }
194
195 pub fn quantize(
197 &mut self,
198 tensor: &Tensor,
199 layer_name: &str,
200 ) -> Result<MixedBitQuantizedTensor> {
201 let layer_config = self
203 .config
204 .layer_configs
205 .get(layer_name)
206 .cloned()
207 .unwrap_or_else(|| self.config.default_config.clone());
208
209 let sensitivity_scores = if let Some(ref auto_strategy) = self.config.auto_bit_allocation {
211 self.sensitivity_analyzer
212 .analyze_sensitivity(tensor, layer_name, &layer_config)?
213 } else {
214 vec![1.0; tensor.shape().iter().product()]
215 };
216
217 let bit_allocation = self.allocate_bits(&sensitivity_scores, &layer_config)?;
219
220 let quantized_blocks = self.quantize_blocks(tensor, &bit_allocation, &layer_config)?;
222
223 Ok(MixedBitQuantizedTensor {
224 layer_name: layer_name.to_string(),
225 quantized_data: quantized_blocks,
226 shape: tensor.shape(),
227 config: layer_config,
228 sensitivity_scores,
229 })
230 }
231
232 fn allocate_bits(
234 &self,
235 sensitivity_scores: &[f32],
236 config: &LayerQuantConfig,
237 ) -> Result<Vec<u8>> {
238 let mut bit_allocation = vec![config.weight_bits; sensitivity_scores.len()];
239
240 if let Some(ref strategy) = self.config.auto_bit_allocation {
241 match strategy {
242 AutoBitAllocationStrategy::SensitivityBased {
243 target_compression,
244 min_bits,
245 max_bits,
246 } => {
247 let mut indexed_scores: Vec<(usize, f32)> = sensitivity_scores
249 .iter()
250 .enumerate()
251 .map(|(i, &score)| (i, score))
252 .collect();
253 indexed_scores.sort_by(|a, b| {
254 b.1.partial_cmp(&a.1).unwrap_or(::std::cmp::Ordering::Equal)
255 });
256
257 let total_elements = sensitivity_scores.len();
259 let target_total_bits = (total_elements as f32
260 * config.weight_bits as f32
261 * target_compression) as usize;
262 let mut allocated_bits = 0;
263
264 for (idx, _) in indexed_scores {
266 let remaining_elements =
267 total_elements - allocated_bits / (*max_bits as usize);
268 let remaining_budget = target_total_bits.saturating_sub(allocated_bits);
269
270 let avg_bits_remaining =
271 remaining_budget.checked_div(remaining_elements).unwrap_or(0);
272 if avg_bits_remaining > 0 {
273 let bits = (avg_bits_remaining as u8).clamp(*min_bits, *max_bits);
274 bit_allocation[idx] = bits;
275 allocated_bits += bits as usize;
276 }
277 }
278 },
279 AutoBitAllocationStrategy::AdaptiveUniform {
280 base_bits,
281 adjustment_range,
282 } => {
283 let mean_sensitivity =
285 sensitivity_scores.iter().sum::<f32>() / sensitivity_scores.len() as f32;
286
287 for (i, &score) in sensitivity_scores.iter().enumerate() {
288 let normalized_score = score / mean_sensitivity;
289 let adjustment = (normalized_score * *adjustment_range as f32) as i8;
290 let bits = (*base_bits as i8 + adjustment).clamp(1, 8) as u8;
291 bit_allocation[i] = bits;
292 }
293 },
294 AutoBitAllocationStrategy::PerformanceDriven {
295 target_latency,
296 accuracy_tolerance,
297 } => {
298 return self.allocate_bits_performance_driven(
300 sensitivity_scores,
301 config,
302 *target_latency,
303 *accuracy_tolerance,
304 );
305 },
306 }
307 }
308
309 Ok(bit_allocation)
310 }
311
312 #[allow(dead_code)]
314 fn allocate_bits_sensitivity_based(
315 &self,
316 sensitivity_scores: &[f32],
317 config: &LayerQuantConfig,
318 ) -> Result<Vec<u8>> {
319 let mut bit_allocation = vec![config.weight_bits; sensitivity_scores.len()];
320
321 let mut sorted_scores = sensitivity_scores.to_vec();
323 sorted_scores.sort_by(|a, b| a.partial_cmp(b).unwrap_or(::std::cmp::Ordering::Equal));
324
325 let high_sensitivity_threshold =
326 sorted_scores[(sorted_scores.len() * 90 / 100).min(sorted_scores.len() - 1)];
327 let low_sensitivity_threshold = sorted_scores[sorted_scores.len() * 10 / 100];
328
329 for (i, &score) in sensitivity_scores.iter().enumerate() {
330 if score >= high_sensitivity_threshold {
331 bit_allocation[i] = 8; } else if score <= low_sensitivity_threshold {
333 bit_allocation[i] = 2; } else {
335 bit_allocation[i] = 4; }
337 }
338
339 Ok(bit_allocation)
340 }
341
342 fn allocate_bits_performance_driven(
344 &self,
345 sensitivity_scores: &[f32],
346 config: &LayerQuantConfig,
347 target_latency: f32,
348 accuracy_tolerance: f32,
349 ) -> Result<Vec<u8>> {
350 let total_elements = sensitivity_scores.len();
351
352 let performance_factor = |bits: u8| -> f32 {
355 match bits {
356 1 => 0.1, 2 => 0.25, 3 => 0.4, 4 => 0.6, 5 => 0.75, 6 => 0.85, 7 => 0.92, 8 => 1.0, _ => 1.0,
365 }
366 };
367
368 let accuracy_impact = |sensitivity: f32, bits: u8| -> f32 {
370 let base_impact = sensitivity / 100.0; let bit_factor = (8.0 - bits as f32) / 7.0; base_impact * bit_factor
373 };
374
375 let mut indexed_scores: Vec<(usize, f32)> =
377 sensitivity_scores.iter().enumerate().map(|(i, &score)| (i, score)).collect();
378 indexed_scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(::std::cmp::Ordering::Equal));
379
380 let mut current_bits = vec![2u8; total_elements];
382 let mut current_latency = 0.0;
383 let mut current_accuracy_loss = 0.0;
384
385 for (i, &score) in sensitivity_scores.iter().enumerate() {
387 current_latency += performance_factor(2);
388 current_accuracy_loss += accuracy_impact(score, 2);
389 }
390
391 for (idx, sensitivity) in indexed_scores {
393 let current_element_bits = current_bits[idx];
394
395 for new_bits in (current_element_bits + 1)..=8 {
397 let latency_change =
398 performance_factor(new_bits) - performance_factor(current_element_bits);
399 let accuracy_change = accuracy_impact(sensitivity, current_element_bits)
400 - accuracy_impact(sensitivity, new_bits);
401
402 let new_latency = current_latency + latency_change;
403 let new_accuracy_loss = current_accuracy_loss - accuracy_change;
404
405 let normalized_latency = new_latency / total_elements as f32;
407 if normalized_latency <= target_latency && new_accuracy_loss <= accuracy_tolerance {
408 current_bits[idx] = new_bits;
410 current_latency = new_latency;
411 current_accuracy_loss = new_accuracy_loss;
412 } else {
413 break;
415 }
416 }
417 }
418
419 let bit_allocation = current_bits;
421
422 Ok(bit_allocation)
423 }
424
425 fn quantize_blocks(
427 &self,
428 tensor: &Tensor,
429 bit_allocation: &[u8],
430 config: &LayerQuantConfig,
431 ) -> Result<Vec<QuantizedBlock>> {
432 let data = tensor.data()?;
433 let shape = tensor.shape();
434 let mut blocks = Vec::new();
435
436 let mut bit_groups: HashMap<u8, Vec<(usize, f32)>> = HashMap::new();
438 for (i, (&bits, &value)) in bit_allocation.iter().zip(data.iter()).enumerate() {
439 bit_groups.entry(bits).or_default().push((i, value));
440 }
441
442 for (bit_width, elements) in bit_groups {
444 let values: Vec<f32> = elements.iter().map(|(_, v)| *v).collect();
445 let indices: Vec<usize> = elements.iter().map(|(i, _)| *i).collect();
446
447 let (quantized_data, scale, zero_point) =
448 self.quantize_group(&values, bit_width, config)?;
449
450 blocks.push(QuantizedBlock {
451 data: quantized_data,
452 scale,
453 zero_point,
454 bit_width,
455 block_shape: vec![values.len()],
456 block_offset: vec![indices[0]], });
458 }
459
460 Ok(blocks)
461 }
462
463 fn quantize_group(
465 &self,
466 values: &[f32],
467 bit_width: u8,
468 config: &LayerQuantConfig,
469 ) -> Result<(Vec<u8>, f32, i32)> {
470 if values.is_empty() {
471 return Ok((Vec::new(), 1.0, 0));
472 }
473
474 let min_val = values.iter().fold(f32::INFINITY, |a, &b| a.min(b));
475 let max_val = values.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
476
477 let qmin = 0;
478 let qmax = (1 << bit_width) - 1;
479
480 let (scale, zero_point) = if config.symmetric {
481 let max_abs = max_val.abs().max(min_val.abs());
482 let scale = max_abs / (qmax as f32 / 2.0);
483 (scale, qmax / 2)
484 } else {
485 let scale = (max_val - min_val) / (qmax - qmin) as f32;
486 let zero_point = qmin as f32 - min_val / scale;
487 (scale, zero_point.round() as i32)
488 };
489
490 let mut quantized = Vec::with_capacity(values.len());
491 for &value in values {
492 let q_val = (value / scale + zero_point as f32).round() as i32;
493 let clamped = q_val.clamp(qmin, qmax) as u8;
494 quantized.push(clamped);
495 }
496
497 Ok((quantized, scale, zero_point))
498 }
499
500 pub fn compression_ratio(
502 &self,
503 original_size: usize,
504 quantized_tensor: &MixedBitQuantizedTensor,
505 ) -> f32 {
506 let compressed_size: usize =
507 quantized_tensor.quantized_data.iter().map(|block| block.data.len()).sum();
508
509 original_size as f32 / compressed_size as f32
510 }
511
512 pub fn memory_savings(
514 &self,
515 original_tensor: &Tensor,
516 quantized_tensor: &MixedBitQuantizedTensor,
517 ) -> f32 {
518 let original_bytes = original_tensor.size() * std::mem::size_of::<f32>();
519 let quantized_bytes: usize =
520 quantized_tensor.quantized_data.iter().map(|block| block.data.len()).sum();
521
522 1.0 - (quantized_bytes as f32 / original_bytes as f32)
523 }
524}
525
526impl MixedBitQuantizedTensor {
527 pub fn dequantize(&self) -> Result<Tensor> {
529 let total_elements: usize = self.shape.iter().product();
530 let mut result = vec![0.0f32; total_elements];
531
532 for block in &self.quantized_data {
533 for (i, &quantized_val) in block.data.iter().enumerate() {
534 let dequantized = (quantized_val as i32 - block.zero_point) as f32 * block.scale;
535 if i < result.len() {
537 result[i] = dequantized;
538 }
539 }
540 }
541
542 Tensor::from_vec(result, &self.shape)
543 }
544
545 pub fn average_bit_width(&self) -> f32 {
547 let total_elements: usize = self.quantized_data.iter().map(|b| b.data.len()).sum();
548 if total_elements == 0 {
549 return 0.0;
550 }
551
552 let total_bits: f32 = self
553 .quantized_data
554 .iter()
555 .map(|block| block.data.len() as f32 * block.bit_width as f32)
556 .sum();
557
558 total_bits / total_elements as f32
559 }
560
561 pub fn memory_footprint(&self) -> usize {
563 self.quantized_data.iter().map(|block| block.data.len()).sum()
564 }
565}
566
567impl SensitivityAnalyzer {
568 fn new(config: SensitivityConfig) -> Self {
569 Self {
570 config,
571 sensitivity_cache: HashMap::new(),
572 }
573 }
574
575 fn analyze_sensitivity(
577 &mut self,
578 tensor: &Tensor,
579 layer_name: &str,
580 _config: &LayerQuantConfig,
581 ) -> Result<Vec<f32>> {
582 if let Some(cached_scores) = self.sensitivity_cache.get(layer_name) {
584 return Ok(cached_scores.clone());
585 }
586
587 let data = tensor.data()?;
588 let mut sensitivity_scores = vec![0.0; data.len()];
589
590 for metric in &self.config.metrics {
592 let metric_scores = self.compute_metric_scores(tensor, metric)?;
593
594 for (i, score) in metric_scores.iter().enumerate() {
596 sensitivity_scores[i] += score / self.config.metrics.len() as f32;
597 }
598 }
599
600 self.sensitivity_cache
602 .insert(layer_name.to_string(), sensitivity_scores.clone());
603
604 Ok(sensitivity_scores)
605 }
606
607 fn compute_metric_scores(
609 &self,
610 tensor: &Tensor,
611 metric: &SensitivityMetric,
612 ) -> Result<Vec<f32>> {
613 let data = tensor.data()?;
614
615 match metric {
616 SensitivityMetric::WeightVariance => {
617 let mean = data.iter().sum::<f32>() / data.len() as f32;
619 let variance: Vec<f32> = data.iter().map(|&x| (x - mean).powi(2)).collect();
620 Ok(variance)
621 },
622 SensitivityMetric::GradientMagnitude => {
623 Ok(data.iter().map(|&x| x.abs()).collect())
625 },
626 SensitivityMetric::ActivationVariance => {
627 Ok(data.iter().map(|&x| x.abs()).collect())
629 },
630 SensitivityMetric::HessianDiagonal => {
631 let hessian_approx: Vec<f32> = data.iter().map(|&x| x.powi(2)).collect();
633 Ok(hessian_approx)
634 },
635 SensitivityMetric::OutputSensitivity => {
636 Ok(data.iter().map(|&x| x.abs()).collect())
638 },
639 }
640 }
641}
642
643#[cfg(test)]
644mod tests {
645 use super::*;
646 use crate::tensor::Tensor;
647
648 #[test]
649 fn test_mixed_bit_quantizer_creation() {
650 let config = MixedBitConfig::default();
651 let quantizer = MixedBitQuantizer::new(config);
652 assert!(quantizer.config.auto_bit_allocation.is_some());
653 }
654
655 #[test]
656 fn test_mixed_bit_quantization() -> Result<()> {
657 let mut quantizer = MixedBitQuantizer::new(MixedBitConfig::default());
658 let tensor = Tensor::randn(&[4, 4])?;
659
660 let quantized = quantizer.quantize(&tensor, "test_layer")?;
661 assert_eq!(quantized.shape, vec![4, 4]);
662 assert!(!quantized.quantized_data.is_empty());
663
664 Ok(())
665 }
666
667 #[test]
668 fn test_mixed_bit_dequantization() -> Result<()> {
669 let mut quantizer = MixedBitQuantizer::new(MixedBitConfig::default());
670 let tensor = Tensor::randn(&[2, 2])?;
671
672 let quantized = quantizer.quantize(&tensor, "test_layer")?;
673 let dequantized = quantized.dequantize()?;
674
675 assert_eq!(dequantized.shape(), tensor.shape());
676 Ok(())
677 }
678
679 #[test]
680 fn test_average_bit_width() -> Result<()> {
681 let mut quantizer = MixedBitQuantizer::new(MixedBitConfig::default());
682 let tensor = Tensor::randn(&[8])?;
683
684 let quantized = quantizer.quantize(&tensor, "test_layer")?;
685 let avg_bits = quantized.average_bit_width();
686
687 assert!(avg_bits > 0.0);
688 assert!(avg_bits <= 8.0);
689 Ok(())
690 }
691
692 #[test]
693 fn test_compression_ratio() -> Result<()> {
694 let mut quantizer = MixedBitQuantizer::new(MixedBitConfig::default());
695 let tensor = Tensor::randn(&[1024])?; let quantized = quantizer.quantize(&tensor, "test_layer")?;
698 let ratio = quantizer.compression_ratio(tensor.size(), &quantized);
699
700 assert!(ratio >= 1.0); Ok(())
702 }
703
704 #[test]
705 fn test_sensitivity_analysis() -> Result<()> {
706 let config = SensitivityConfig::default();
707 let mut analyzer = SensitivityAnalyzer::new(config);
708 let tensor = Tensor::randn(&[4, 4])?;
709
710 let layer_config = LayerQuantConfig::default();
711 let scores = analyzer.analyze_sensitivity(&tensor, "test_layer", &layer_config)?;
712
713 assert_eq!(scores.len(), 16);
714 assert!(scores.iter().all(|&score| score >= 0.0));
715 Ok(())
716 }
717}