1pub 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#[cfg(feature = "std")]
25use std::collections::HashMap;
26
27#[cfg(not(feature = "std"))]
28use hashbrown::HashMap;
29
30#[derive(Debug, Clone)]
32pub struct QuantizationConfig {
33 pub dtype: DType,
35 pub scheme: QuantizationScheme,
37 pub backend_config: BackendQuantConfig,
39 pub calibration: CalibrationConfig,
41 pub per_channel: bool,
43 pub quantize_weights: bool,
45 pub quantize_activations: bool,
47}
48
49#[derive(Debug, Clone, PartialEq)]
51pub enum QuantizationScheme {
52 Symmetric,
54 Asymmetric,
56 Dynamic,
58 KLDivergence,
60 Percentile(f32),
62}
63
64#[derive(Debug, Clone)]
66pub struct BackendQuantConfig {
67 pub use_hardware_acceleration: bool,
69 pub enable_kernel_fusion: bool,
71 pub optimize_memory_layout: bool,
73 pub target_platform: DeploymentPlatform,
75}
76
77#[derive(Debug, Clone, PartialEq)]
79pub enum DeploymentPlatform {
80 CPU,
82 GPU,
84 Mobile,
86 Edge,
88 Server,
90 WASM,
92}
93
94#[derive(Debug, Clone)]
96pub struct CalibrationConfig {
97 pub num_samples: usize,
99 pub method: CalibrationMethod,
101 pub outlier_percentile: f32,
103 pub use_moving_average: bool,
105 pub momentum: f32,
107}
108
109#[derive(Debug, Clone, PartialEq)]
111pub enum CalibrationMethod {
112 MinMax,
114 Entropy,
116 MSE,
118 CosineSimilarity,
120}
121
122#[derive(Debug, Clone)]
124pub struct QuantizationParams {
125 pub scale: f32,
127 pub zero_point: i32,
129 pub qmin: i32,
131 pub qmax: i32,
133 pub src_dtype: DType,
135 pub dst_dtype: DType,
137}
138
139impl QuantizationParams {
140 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 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 pub fn quantize(&self, tensor: &Tensor) -> Result<Tensor> {
180 ops::quantize_tensor(tensor, self)
181 }
182
183 pub fn dequantize(&self, tensor: &Tensor) -> Result<Tensor> {
185 ops::dequantize_tensor(tensor, self)
186 }
187}
188
189#[derive(Debug)]
191pub struct QuantizedModel<M: Module> {
192 pub model: M,
194 pub config: QuantizationConfig,
196 pub layer_params: HashMap<String, QuantizationParams>,
198 pub calibration_stats: Option<CalibrationStats>,
200}
201
202impl<M: Module> QuantizedModel<M> {
203 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 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 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 for (layer_name, params) in &self.layer_params {
237 println!(
240 "Quantizing layer {} with scale={}, zero_point={}",
241 layer_name, params.scale, params.zero_point
242 );
243 }
244
245 Ok(())
246 }
247
248 pub fn compression_ratio(&self) -> f32 {
250 if self.layer_params.is_empty() {
251 return 1.0;
252 }
253
254 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 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#[derive(Debug, Clone)]
311pub struct CalibrationStats {
312 pub num_samples: usize,
314 pub activation_ranges: HashMap<String, (f32, f32)>,
316 pub weight_ranges: HashMap<String, (f32, f32)>,
318 pub metrics: CalibrationMetrics,
320}
321
322#[derive(Debug, Clone)]
324pub struct CalibrationMetrics {
325 pub mse: f32,
327 pub snr: f32,
329 pub cosine_similarity: f32,
331 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
372pub 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 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 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 pub fn dynamic_quantization() -> QuantizationConfig {
403 QuantizationConfig {
404 scheme: QuantizationScheme::Dynamic,
405 ..Default::default()
406 }
407 }
408
409 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 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 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}