Skip to main content

torsh_nn/quantization/
mod.rs

1//! Quantization support for model compression and deployment optimization
2//!
3//! This module provides comprehensive quantization capabilities including:
4//! - INT8 quantization for inference acceleration
5//! - Quantization-aware training (QAT)
6//! - Post-training quantization (PTQ)
7//! - Dynamic quantization
8//! - Model compression utilities
9
10pub mod calibration;
11pub mod ops;
12pub mod qat;
13pub mod schemes;
14pub mod utils;
15
16use crate::{Module, Parameter};
17use torsh_core::{
18    dtype::DType,
19    error::{Result, TorshError},
20};
21use torsh_tensor::Tensor;
22
23// Conditional imports for std/no_std compatibility
24#[cfg(feature = "std")]
25use std::collections::HashMap;
26
27#[cfg(not(feature = "std"))]
28use hashbrown::HashMap;
29
30/// Quantization configuration
31#[derive(Debug, Clone)]
32pub struct QuantizationConfig {
33    /// Target data type for quantization
34    pub dtype: DType,
35    /// Quantization scheme
36    pub scheme: QuantizationScheme,
37    /// Backend-specific optimizations
38    pub backend_config: BackendQuantConfig,
39    /// Calibration configuration
40    pub calibration: CalibrationConfig,
41    /// Whether to use per-channel quantization
42    pub per_channel: bool,
43    /// Whether to quantize weights
44    pub quantize_weights: bool,
45    /// Whether to quantize activations
46    pub quantize_activations: bool,
47}
48
49/// Quantization schemes
50#[derive(Debug, Clone, PartialEq)]
51pub enum QuantizationScheme {
52    /// Symmetric quantization around zero
53    Symmetric,
54    /// Asymmetric quantization with zero-point
55    Asymmetric,
56    /// Dynamic quantization (runtime calibration)
57    Dynamic,
58    /// KL divergence-based quantization
59    KLDivergence,
60    /// Percentile-based quantization
61    Percentile(f32),
62}
63
64/// Backend-specific quantization configuration
65#[derive(Debug, Clone)]
66pub struct BackendQuantConfig {
67    /// Use hardware acceleration when available
68    pub use_hardware_acceleration: bool,
69    /// Kernel fusion optimizations
70    pub enable_kernel_fusion: bool,
71    /// Memory layout optimizations
72    pub optimize_memory_layout: bool,
73    /// Target deployment platform
74    pub target_platform: DeploymentPlatform,
75}
76
77/// Deployment platform targets
78#[derive(Debug, Clone, PartialEq)]
79pub enum DeploymentPlatform {
80    /// General CPU deployment
81    CPU,
82    /// GPU deployment
83    GPU,
84    /// Mobile devices (ARM, NEON)
85    Mobile,
86    /// Edge devices with specific constraints
87    Edge,
88    /// Server deployment
89    Server,
90    /// WebAssembly deployment
91    WASM,
92}
93
94/// Calibration configuration
95#[derive(Debug, Clone)]
96pub struct CalibrationConfig {
97    /// Number of calibration samples
98    pub num_samples: usize,
99    /// Calibration method
100    pub method: CalibrationMethod,
101    /// Percentile for outlier handling
102    pub outlier_percentile: f32,
103    /// Whether to use moving averages
104    pub use_moving_average: bool,
105    /// Moving average momentum
106    pub momentum: f32,
107}
108
109/// Calibration methods
110#[derive(Debug, Clone, PartialEq)]
111pub enum CalibrationMethod {
112    /// Min-max calibration
113    MinMax,
114    /// Entropy-based calibration
115    Entropy,
116    /// MSE-based calibration
117    MSE,
118    /// Cosine similarity-based calibration
119    CosineSimilarity,
120}
121
122/// Quantization parameters
123#[derive(Debug, Clone)]
124pub struct QuantizationParams {
125    /// Scale factor
126    pub scale: f32,
127    /// Zero point (for asymmetric quantization)
128    pub zero_point: i32,
129    /// Minimum quantized value
130    pub qmin: i32,
131    /// Maximum quantized value
132    pub qmax: i32,
133    /// Source dtype
134    pub src_dtype: DType,
135    /// Target dtype
136    pub dst_dtype: DType,
137}
138
139impl QuantizationParams {
140    /// Create symmetric quantization parameters
141    pub fn symmetric(scale: f32, src_dtype: DType, dst_dtype: DType) -> Self {
142        let (qmin, qmax) = match dst_dtype {
143            DType::I8 => (-128i32, 127i32),
144            DType::U8 => (0i32, 255i32),
145            DType::I16 => (-32768i32, 32767i32),
146            _ => panic!("Unsupported quantization dtype: {:?}", dst_dtype),
147        };
148
149        Self {
150            scale,
151            zero_point: 0,
152            qmin,
153            qmax,
154            src_dtype,
155            dst_dtype,
156        }
157    }
158
159    /// Create asymmetric quantization parameters
160    pub fn asymmetric(scale: f32, zero_point: i32, src_dtype: DType, dst_dtype: DType) -> Self {
161        let (qmin, qmax) = match dst_dtype {
162            DType::I8 => (-128i32, 127i32),
163            DType::U8 => (0i32, 255i32),
164            DType::I16 => (-32768i32, 32767i32),
165            _ => panic!("Unsupported quantization dtype: {:?}", dst_dtype),
166        };
167
168        Self {
169            scale,
170            zero_point,
171            qmin,
172            qmax,
173            src_dtype,
174            dst_dtype,
175        }
176    }
177
178    /// Quantize a tensor using these parameters
179    pub fn quantize(&self, tensor: &Tensor) -> Result<Tensor> {
180        ops::quantize_tensor(tensor, self)
181    }
182
183    /// Dequantize a tensor using these parameters
184    pub fn dequantize(&self, tensor: &Tensor) -> Result<Tensor> {
185        ops::dequantize_tensor(tensor, self)
186    }
187}
188
189/// Quantized model wrapper
190#[derive(Debug)]
191pub struct QuantizedModel<M: Module> {
192    /// Original model
193    pub model: M,
194    /// Quantization configuration
195    pub config: QuantizationConfig,
196    /// Per-layer quantization parameters
197    pub layer_params: HashMap<String, QuantizationParams>,
198    /// Calibration statistics
199    pub calibration_stats: Option<CalibrationStats>,
200}
201
202impl<M: Module> QuantizedModel<M> {
203    /// Create a new quantized model
204    pub fn new(model: M, config: QuantizationConfig) -> Self {
205        Self {
206            model,
207            config,
208            layer_params: HashMap::new(),
209            calibration_stats: None,
210        }
211    }
212
213    /// Calibrate the model using sample data
214    pub fn calibrate<I>(&mut self, calibration_data: I) -> Result<()>
215    where
216        I: Iterator<Item = Tensor>,
217    {
218        let mut calibrator = calibration::Calibrator::new(&self.config.calibration);
219        calibrator.calibrate(&mut self.model, calibration_data)?;
220
221        self.calibration_stats = Some(calibrator.stats());
222        self.layer_params = calibrator.quantization_params();
223
224        Ok(())
225    }
226
227    /// Convert model to quantized format
228    pub fn quantize(&mut self) -> Result<()> {
229        if self.layer_params.is_empty() {
230            return Err(TorshError::InvalidArgument(
231                "Model must be calibrated before quantization".to_string(),
232            ));
233        }
234
235        // Apply quantization to each layer
236        for (layer_name, params) in &self.layer_params {
237            // This would be implemented to actually quantize the model layers
238            // For now, we just store the parameters
239            println!(
240                "Quantizing layer {} with scale={}, zero_point={}",
241                layer_name, params.scale, params.zero_point
242            );
243        }
244
245        Ok(())
246    }
247
248    /// Get model size reduction
249    pub fn compression_ratio(&self) -> f32 {
250        if self.layer_params.is_empty() {
251            return 1.0;
252        }
253
254        // Calculate compression based on dtype size reduction
255        let original_bits = match DType::F32 {
256            DType::F32 => 32,
257            DType::F16 => 16,
258            _ => 32,
259        };
260
261        let quantized_bits = match self.config.dtype {
262            DType::I8 | DType::U8 => 8,
263            DType::I16 => 16,
264            _ => 32,
265        };
266
267        original_bits as f32 / quantized_bits as f32
268    }
269}
270
271impl<M: Module> Module for QuantizedModel<M> {
272    fn forward(&self, input: &Tensor) -> Result<Tensor> {
273        // In a full implementation, this would:
274        // 1. Quantize inputs if needed
275        // 2. Run quantized inference
276        // 3. Dequantize outputs if needed
277        self.model.forward(input)
278    }
279
280    fn parameters(&self) -> HashMap<String, Parameter> {
281        self.model.parameters()
282    }
283
284    fn named_parameters(&self) -> HashMap<String, Parameter> {
285        self.model.named_parameters()
286    }
287
288    fn training(&self) -> bool {
289        self.model.training()
290    }
291
292    fn train(&mut self) {
293        self.model.train()
294    }
295
296    fn eval(&mut self) {
297        self.model.eval()
298    }
299
300    fn set_training(&mut self, training: bool) {
301        self.model.set_training(training);
302    }
303
304    fn to_device(&mut self, device: torsh_core::device::DeviceType) -> Result<()> {
305        self.model.to_device(device)
306    }
307}
308
309/// Calibration statistics
310#[derive(Debug, Clone)]
311pub struct CalibrationStats {
312    /// Number of samples processed
313    pub num_samples: usize,
314    /// Per-layer activation ranges
315    pub activation_ranges: HashMap<String, (f32, f32)>,
316    /// Per-layer weight ranges
317    pub weight_ranges: HashMap<String, (f32, f32)>,
318    /// Calibration loss/error metrics
319    pub metrics: CalibrationMetrics,
320}
321
322/// Calibration metrics
323#[derive(Debug, Clone)]
324pub struct CalibrationMetrics {
325    /// Mean squared error
326    pub mse: f32,
327    /// Signal-to-noise ratio
328    pub snr: f32,
329    /// Cosine similarity
330    pub cosine_similarity: f32,
331    /// KL divergence
332    pub kl_divergence: f32,
333}
334
335impl Default for QuantizationConfig {
336    fn default() -> Self {
337        Self {
338            dtype: DType::I8,
339            scheme: QuantizationScheme::Symmetric,
340            backend_config: BackendQuantConfig::default(),
341            calibration: CalibrationConfig::default(),
342            per_channel: false,
343            quantize_weights: true,
344            quantize_activations: true,
345        }
346    }
347}
348
349impl Default for BackendQuantConfig {
350    fn default() -> Self {
351        Self {
352            use_hardware_acceleration: true,
353            enable_kernel_fusion: true,
354            optimize_memory_layout: true,
355            target_platform: DeploymentPlatform::CPU,
356        }
357    }
358}
359
360impl Default for CalibrationConfig {
361    fn default() -> Self {
362        Self {
363            num_samples: 100,
364            method: CalibrationMethod::MinMax,
365            outlier_percentile: 99.99,
366            use_moving_average: true,
367            momentum: 0.9,
368        }
369    }
370}
371
372/// Convenience functions for common quantization tasks
373pub mod prelude {
374    pub use super::qat::utils::{calibrate_qat_model, prepare_qat_model, progressive_qat_training};
375    pub use super::qat::{
376        FakeQuantize, QATConfig, QATLinear, QATModel, QATScheduler, QuantizedInferenceModel,
377    };
378    pub use super::{
379        BackendQuantConfig, CalibrationConfig, CalibrationMethod, DeploymentPlatform,
380        QuantizationConfig, QuantizationParams, QuantizationScheme, QuantizedModel,
381    };
382
383    /// Create INT8 symmetric quantization config
384    pub fn int8_symmetric() -> QuantizationConfig {
385        QuantizationConfig {
386            dtype: torsh_core::dtype::DType::I8,
387            scheme: QuantizationScheme::Symmetric,
388            ..Default::default()
389        }
390    }
391
392    /// Create INT8 asymmetric quantization config
393    pub fn int8_asymmetric() -> QuantizationConfig {
394        QuantizationConfig {
395            dtype: torsh_core::dtype::DType::I8,
396            scheme: QuantizationScheme::Asymmetric,
397            ..Default::default()
398        }
399    }
400
401    /// Create dynamic quantization config
402    pub fn dynamic_quantization() -> QuantizationConfig {
403        QuantizationConfig {
404            scheme: QuantizationScheme::Dynamic,
405            ..Default::default()
406        }
407    }
408
409    /// Create QAT config for INT8 training
410    pub fn qat_int8_config() -> QATConfig {
411        QATConfig {
412            weight_bits: 8,
413            activation_bits: 8,
414            scheme: QuantizationScheme::Symmetric,
415            ..Default::default()
416        }
417    }
418
419    /// Create conservative QAT config with longer warmup
420    pub fn qat_conservative_config() -> QATConfig {
421        QATConfig {
422            warmup_epochs: 5,
423            qparam_lr: 0.005,
424            observer_momentum: 0.05,
425            ..Default::default()
426        }
427    }
428
429    /// Create aggressive QAT config for fast convergence
430    pub fn qat_aggressive_config() -> QATConfig {
431        QATConfig {
432            warmup_epochs: 1,
433            qparam_lr: 0.02,
434            observer_momentum: 0.2,
435            ..Default::default()
436        }
437    }
438}