1use super::{QuantizationParams, QuantizationScheme};
11use crate::{Module, ModuleBase, Parameter};
12use torsh_core::{
13 dtype::DType,
14 error::{Result, TorshError},
15};
16use torsh_tensor::Tensor;
17
18#[cfg(feature = "std")]
20use std::collections::HashMap;
21
22#[cfg(not(feature = "std"))]
23use hashbrown::HashMap;
24
25#[derive(Debug, Clone)]
27pub struct QATConfig {
28 pub fake_quantize_enabled: bool,
30 pub warmup_epochs: usize,
32 pub qparam_lr: f32,
34 pub learnable_params: bool,
36 pub weight_bits: u8,
38 pub activation_bits: u8,
40 pub observer_momentum: f32,
42 pub scheme: QuantizationScheme,
44 pub per_channel_weights: bool,
46 pub per_channel_activations: bool,
48}
49
50impl Default for QATConfig {
51 fn default() -> Self {
52 Self {
53 fake_quantize_enabled: true,
54 warmup_epochs: 3,
55 qparam_lr: 0.01,
56 learnable_params: true,
57 weight_bits: 8,
58 activation_bits: 8,
59 observer_momentum: 0.1,
60 scheme: QuantizationScheme::Symmetric,
61 per_channel_weights: true,
62 per_channel_activations: false,
63 }
64 }
65}
66
67#[derive(Debug)]
69pub struct FakeQuantize {
70 base: ModuleBase,
71 config: QATConfig,
72 scale: Parameter,
73 zero_point: Parameter,
74 min_val: f32,
75 max_val: f32,
76 num_batches_tracked: usize,
77 enabled: bool,
78}
79
80impl FakeQuantize {
81 pub fn new(config: QATConfig) -> Self {
83 let mut base = ModuleBase::new();
84
85 let init_scale = 1.0;
87 let init_zero_point = 0.0;
88
89 let scale = Parameter::new(
90 torsh_tensor::creation::tensor_scalar(init_scale)
91 .expect("scalar tensor for scale should succeed"),
92 );
93 let zero_point = Parameter::new(
94 torsh_tensor::creation::tensor_scalar(init_zero_point)
95 .expect("scalar tensor for zero_point should succeed"),
96 );
97
98 if config.learnable_params {
99 base.register_parameter("scale".to_string(), scale.clone());
100 base.register_parameter("zero_point".to_string(), zero_point.clone());
101 }
102
103 Self {
104 base,
105 config,
106 scale,
107 zero_point,
108 min_val: f32::INFINITY,
109 max_val: f32::NEG_INFINITY,
110 num_batches_tracked: 0,
111 enabled: true,
112 }
113 }
114
115 pub fn enable(&mut self, enabled: bool) {
117 self.enabled = enabled;
118 }
119
120 pub fn update_observers(&mut self, tensor: &Tensor) -> Result<()> {
122 if !self.training() {
123 return Ok(());
124 }
125
126 let data = tensor.to_vec()?;
127 let batch_min = data.iter().fold(f32::INFINITY, |a, &b| a.min(b));
128 let batch_max = data.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
129
130 if self.num_batches_tracked == 0 {
132 self.min_val = batch_min;
133 self.max_val = batch_max;
134 } else {
135 let momentum = self.config.observer_momentum;
136 self.min_val = (1.0 - momentum) * self.min_val + momentum * batch_min;
137 self.max_val = (1.0 - momentum) * self.max_val + momentum * batch_max;
138 }
139
140 self.num_batches_tracked += 1;
141
142 self.update_qparams()?;
144
145 Ok(())
146 }
147
148 fn update_qparams(&mut self) -> Result<()> {
150 match self.config.scheme {
151 QuantizationScheme::Symmetric => {
152 let abs_max = self.max_val.abs().max(self.min_val.abs());
153 let scale = abs_max / ((1 << (self.config.weight_bits - 1)) - 1) as f32;
154
155 *self.scale.tensor().write() = torsh_tensor::creation::tensor_scalar(scale)?;
156 *self.zero_point.tensor().write() = torsh_tensor::creation::tensor_scalar(0.0)?;
157 }
158 QuantizationScheme::Asymmetric => {
159 let range = self.max_val - self.min_val;
160 let scale = range / ((1 << self.config.weight_bits) - 1) as f32;
161 let zero_point = -self.min_val / scale;
162
163 *self.scale.tensor().write() = torsh_tensor::creation::tensor_scalar(scale)?;
164 *self.zero_point.tensor().write() =
165 torsh_tensor::creation::tensor_scalar(zero_point)?;
166 }
167 _ => {
168 return Err(TorshError::InvalidArgument(
169 "Unsupported quantization scheme for fake quantization".to_string(),
170 ));
171 }
172 }
173
174 Ok(())
175 }
176
177 pub fn fake_quantize_tensor(&self, input: &Tensor) -> Result<Tensor> {
179 if !self.enabled || !self.config.fake_quantize_enabled {
180 return Ok(input.clone());
181 }
182
183 let scale = self.scale.tensor().read().to_vec()?[0];
184 let zero_point = self.zero_point.tensor().read().to_vec()?[0];
185
186 let qmin = match self.config.scheme {
187 QuantizationScheme::Symmetric => -(1i32 << (self.config.weight_bits - 1)),
188 QuantizationScheme::Asymmetric => 0i32,
189 _ => 0i32,
190 };
191
192 let qmax = match self.config.scheme {
193 QuantizationScheme::Symmetric => (1i32 << (self.config.weight_bits - 1)) - 1,
194 QuantizationScheme::Asymmetric => (1i32 << self.config.weight_bits) - 1,
195 _ => 255i32,
196 };
197
198 let data = input.to_vec()?;
200 let mut fake_quantized = Vec::with_capacity(data.len());
201
202 for &value in &data {
203 let quantized = match self.config.scheme {
205 QuantizationScheme::Symmetric => ((value / scale).round() as i32).clamp(qmin, qmax),
206 QuantizationScheme::Asymmetric => {
207 (((value / scale).round() + zero_point) as i32).clamp(qmin, qmax)
208 }
209 _ => ((value / scale).round() as i32).clamp(qmin, qmax),
210 };
211
212 let dequantized = match self.config.scheme {
214 QuantizationScheme::Symmetric => quantized as f32 * scale,
215 QuantizationScheme::Asymmetric => (quantized as f32 - zero_point) * scale,
216 _ => quantized as f32 * scale,
217 };
218
219 fake_quantized.push(dequantized);
220 }
221
222 Tensor::from_data(
223 fake_quantized,
224 input.shape().dims().to_vec(),
225 input.device(),
226 )
227 }
228}
229
230impl Module for FakeQuantize {
231 fn forward(&self, input: &Tensor) -> Result<Tensor> {
232 self.fake_quantize_tensor(input)
233 }
234
235 fn parameters(&self) -> HashMap<String, Parameter> {
236 self.base.parameters.clone()
237 }
238
239 fn named_parameters(&self) -> HashMap<String, Parameter> {
240 self.base.named_parameters()
241 }
242
243 fn training(&self) -> bool {
244 self.base.training()
245 }
246
247 fn train(&mut self) {
248 self.base.set_training(true);
249 }
250
251 fn eval(&mut self) {
252 self.base.set_training(false);
253 }
254
255 fn set_training(&mut self, training: bool) {
256 self.base.set_training(training);
257 }
258
259 fn to_device(&mut self, device: torsh_core::device::DeviceType) -> Result<()> {
260 self.base.to_device(device)
261 }
262}
263
264#[derive(Debug)]
266pub struct QATLinear {
267 base: ModuleBase,
268 #[allow(dead_code)]
269 in_features: usize,
270 #[allow(dead_code)]
271 out_features: usize,
272 bias: bool,
273 weight_fake_quant: FakeQuantize,
274 activation_fake_quant: FakeQuantize,
275 #[allow(dead_code)]
276 config: QATConfig,
277}
278
279impl QATLinear {
280 pub fn new(in_features: usize, out_features: usize, bias: bool, config: QATConfig) -> Self {
282 let mut base = ModuleBase::new();
283
284 let weight = crate::init::xavier_uniform(&[out_features, in_features])
286 .expect("Failed to create weight tensor");
287 base.register_parameter("weight".to_string(), Parameter::new(weight));
288
289 if bias {
290 let bias_tensor = torsh_tensor::creation::zeros(&[out_features])
291 .expect("zeros tensor for bias should succeed");
292 base.register_parameter("bias".to_string(), Parameter::new(bias_tensor));
293 }
294
295 let weight_fake_quant = FakeQuantize::new(config.clone());
297 let activation_fake_quant = FakeQuantize::new(config.clone());
298
299 Self {
300 base,
301 in_features,
302 out_features,
303 bias,
304 weight_fake_quant,
305 activation_fake_quant,
306 config,
307 }
308 }
309
310 pub fn enable_quantization(&mut self, enabled: bool) {
312 self.weight_fake_quant.enable(enabled);
313 self.activation_fake_quant.enable(enabled);
314 }
315}
316
317impl Module for QATLinear {
318 fn forward(&self, input: &Tensor) -> Result<Tensor> {
319 let mut quantized_input = input.clone();
321 if self.training() {
322 quantized_input = self.activation_fake_quant.fake_quantize_tensor(input)?;
323 }
324
325 let weight = self.base.parameters["weight"].tensor().read().clone();
327 let quantized_weight = if self.training() {
328 self.weight_fake_quant.fake_quantize_tensor(&weight)?
329 } else {
330 weight
331 };
332
333 let output = quantized_input.matmul(&quantized_weight.transpose(0, 1)?)?;
335
336 if self.bias {
338 let bias = self.base.parameters["bias"].tensor().read().clone();
339 output.add_op(&bias)
340 } else {
341 Ok(output)
342 }
343 }
344
345 fn parameters(&self) -> HashMap<String, Parameter> {
346 let mut params = self.base.parameters.clone();
347
348 for (name, param) in self.weight_fake_quant.parameters() {
350 params.insert(format!("weight_fake_quant.{}", name), param);
351 }
352 for (name, param) in self.activation_fake_quant.parameters() {
353 params.insert(format!("activation_fake_quant.{}", name), param);
354 }
355
356 params
357 }
358
359 fn named_parameters(&self) -> HashMap<String, Parameter> {
360 self.parameters()
361 }
362
363 fn training(&self) -> bool {
364 self.base.training()
365 }
366
367 fn train(&mut self) {
368 self.base.set_training(true);
369 self.weight_fake_quant.train();
370 self.activation_fake_quant.train();
371 }
372
373 fn eval(&mut self) {
374 self.base.set_training(false);
375 self.weight_fake_quant.eval();
376 self.activation_fake_quant.eval();
377 }
378
379 fn set_training(&mut self, training: bool) {
380 self.base.set_training(training);
381 self.weight_fake_quant.set_training(training);
382 self.activation_fake_quant.set_training(training);
383 }
384
385 fn to_device(&mut self, device: torsh_core::device::DeviceType) -> Result<()> {
386 self.base.to_device(device)?;
387 self.weight_fake_quant.to_device(device)?;
388 self.activation_fake_quant.to_device(device)?;
389 Ok(())
390 }
391}
392
393#[derive(Debug)]
395pub struct QATScheduler {
396 config: QATConfig,
397 current_epoch: usize,
398 enabled: bool,
399}
400
401impl QATScheduler {
402 pub fn new(config: QATConfig) -> Self {
404 Self {
405 config,
406 current_epoch: 0,
407 enabled: false,
408 }
409 }
410
411 pub fn step(&mut self) {
413 self.current_epoch += 1;
414
415 if self.current_epoch > self.config.warmup_epochs {
417 self.enabled = true;
418 }
419 }
420
421 pub fn is_quantization_enabled(&self) -> bool {
423 self.enabled
424 }
425
426 pub fn current_epoch(&self) -> usize {
428 self.current_epoch
429 }
430
431 pub fn qparam_lr_scale(&self) -> f32 {
433 if self.enabled {
434 let decay_factor =
436 0.95_f32.powi((self.current_epoch - self.config.warmup_epochs) as i32);
437 decay_factor.max(0.1)
438 } else {
439 0.0
440 }
441 }
442}
443
444#[derive(Debug)]
446pub struct QATModel<M: Module> {
447 model: M,
448 config: QATConfig,
449 scheduler: QATScheduler,
450 fake_quantizers: HashMap<String, FakeQuantize>,
451}
452
453impl<M: Module> QATModel<M> {
454 pub fn new(model: M, config: QATConfig) -> Self {
456 let scheduler = QATScheduler::new(config.clone());
457
458 Self {
459 model,
460 config: config.clone(),
461 scheduler,
462 fake_quantizers: HashMap::new(),
463 }
464 }
465
466 pub fn step_scheduler(&mut self) {
468 self.scheduler.step();
469
470 let enabled = self.scheduler.is_quantization_enabled();
472 for fake_quant in self.fake_quantizers.values_mut() {
473 fake_quant.enable(enabled);
474 }
475 }
476
477 pub fn add_fake_quantizer(&mut self, layer_name: String, fake_quant: FakeQuantize) {
479 self.fake_quantizers.insert(layer_name, fake_quant);
480 }
481
482 pub fn scheduler(&self) -> &QATScheduler {
484 &self.scheduler
485 }
486
487 pub fn to_quantized(self) -> Result<QuantizedInferenceModel<M>> {
489 let quantization_params = self.extract_quantization_params()?;
490 Ok(QuantizedInferenceModel {
491 model: self.model,
492 quantization_params,
493 })
494 }
495
496 fn extract_quantization_params(&self) -> Result<HashMap<String, QuantizationParams>> {
498 let mut params = HashMap::new();
499
500 for (layer_name, fake_quant) in &self.fake_quantizers {
501 let scale = fake_quant.scale.tensor().read().to_vec()?[0];
502 let zero_point = fake_quant.zero_point.tensor().read().to_vec()?[0] as i32;
503
504 let qparams = match self.config.scheme {
505 QuantizationScheme::Symmetric => {
506 QuantizationParams::symmetric(scale, DType::F32, DType::I8)
507 }
508 QuantizationScheme::Asymmetric => {
509 QuantizationParams::asymmetric(scale, zero_point, DType::F32, DType::U8)
510 }
511 _ => {
512 return Err(TorshError::InvalidArgument(
513 "Unsupported quantization scheme".to_string(),
514 ));
515 }
516 };
517
518 params.insert(layer_name.clone(), qparams);
519 }
520
521 Ok(params)
522 }
523}
524
525impl<M: Module> Module for QATModel<M> {
526 fn forward(&self, input: &Tensor) -> Result<Tensor> {
527 self.model.forward(input)
528 }
529
530 fn parameters(&self) -> HashMap<String, Parameter> {
531 let mut params = self.model.parameters();
532
533 for (layer_name, fake_quant) in &self.fake_quantizers {
535 for (param_name, param) in fake_quant.parameters() {
536 params.insert(format!("{}.{}", layer_name, param_name), param);
537 }
538 }
539
540 params
541 }
542
543 fn named_parameters(&self) -> HashMap<String, Parameter> {
544 self.parameters()
545 }
546
547 fn training(&self) -> bool {
548 self.model.training()
549 }
550
551 fn train(&mut self) {
552 self.model.train();
553 for fake_quant in self.fake_quantizers.values_mut() {
554 fake_quant.train();
555 }
556 }
557
558 fn eval(&mut self) {
559 self.model.eval();
560 for fake_quant in self.fake_quantizers.values_mut() {
561 fake_quant.eval();
562 }
563 }
564
565 fn set_training(&mut self, training: bool) {
566 self.model.set_training(training);
567 for fake_quant in self.fake_quantizers.values_mut() {
568 fake_quant.set_training(training);
569 }
570 }
571
572 fn to_device(&mut self, device: torsh_core::device::DeviceType) -> Result<()> {
573 self.model.to_device(device)?;
574 for fake_quant in self.fake_quantizers.values_mut() {
575 fake_quant.to_device(device)?;
576 }
577 Ok(())
578 }
579}
580
581#[derive(Debug)]
583pub struct QuantizedInferenceModel<M: Module> {
584 model: M,
585 quantization_params: HashMap<String, QuantizationParams>,
586}
587
588impl<M: Module> QuantizedInferenceModel<M> {
589 pub fn get_quantization_params(&self, layer_name: &str) -> Option<&QuantizationParams> {
591 self.quantization_params.get(layer_name)
592 }
593
594 pub fn compression_ratio(&self) -> f32 {
596 32.0 / 8.0 }
599}
600
601impl<M: Module> Module for QuantizedInferenceModel<M> {
602 fn forward(&self, input: &Tensor) -> Result<Tensor> {
603 self.model.forward(input)
605 }
606
607 fn parameters(&self) -> HashMap<String, Parameter> {
608 self.model.parameters()
609 }
610
611 fn named_parameters(&self) -> HashMap<String, Parameter> {
612 self.model.named_parameters()
613 }
614
615 fn training(&self) -> bool {
616 false }
618
619 fn train(&mut self) {
620 }
622
623 fn eval(&mut self) {
624 self.model.eval();
625 }
626
627 fn set_training(&mut self, training: bool) {
628 self.model.set_training(training);
629 }
630
631 fn to_device(&mut self, device: torsh_core::device::DeviceType) -> Result<()> {
632 self.model.to_device(device)
633 }
634}
635
636pub mod utils {
638 use super::*;
639
640 pub fn prepare_qat_model<M: Module>(model: M, config: QATConfig) -> QATModel<M> {
642 QATModel::new(model, config)
643 }
644
645 pub fn calibrate_qat_model<M: Module, I>(
647 model: &mut QATModel<M>,
648 calibration_data: I,
649 ) -> Result<()>
650 where
651 I: Iterator<Item = Tensor>,
652 {
653 model.eval();
654
655 for input in calibration_data {
656 let _output = model.forward(&input)?;
657
658 for fake_quant in model.fake_quantizers.values_mut() {
660 fake_quant.update_observers(&input)?;
661 }
662 }
663
664 Ok(())
665 }
666
667 pub fn progressive_qat_training<M: Module, F, L>(
669 model: &mut QATModel<M>,
670 mut train_step: F,
671 epochs: usize,
672 ) -> Result<()>
673 where
674 F: FnMut(&mut QATModel<M>) -> Result<L>,
675 L: std::fmt::Debug,
676 {
677 for epoch in 0..epochs {
678 println!("QAT Epoch {}/{}", epoch + 1, epochs);
679
680 model.step_scheduler();
682
683 let _loss = train_step(model)?;
685
686 if model.scheduler().is_quantization_enabled() {
688 println!("Quantization enabled (epoch {})", epoch + 1);
689 } else {
690 println!("Warmup phase (epoch {})", epoch + 1);
691 }
692 }
693
694 Ok(())
695 }
696
697 pub fn convert_model_to_qat<M: Module>(model: M, config: QATConfig) -> QATModel<M> {
699 QATModel::new(model, config)
703 }
704
705 pub fn qat_training_loop<M, F, L, O, D>(
707 model: &mut QATModel<M>,
708 train_data_fn: D,
709 loss_fn: F,
710 _optimizer: &mut O,
711 epochs: usize,
712 ) -> Result<Vec<f32>>
713 where
714 M: Module,
715 F: Fn(&Tensor, &Tensor) -> Result<L>,
716 L: std::fmt::Debug,
717 O: std::fmt::Debug,
718 D: Fn() -> Box<dyn Iterator<Item = (Tensor, Tensor)>>,
719 {
720 let mut losses = Vec::new();
721
722 for epoch in 0..epochs {
723 model.step_scheduler();
724
725 let mut epoch_loss = 0.0;
726 let mut num_batches = 0;
727
728 for (inputs, targets) in train_data_fn() {
729 let outputs = model.forward(&inputs)?;
731 let _loss = loss_fn(&outputs, &targets)?;
732
733 epoch_loss += 0.1; num_batches += 1;
739 }
740
741 let avg_loss = if num_batches > 0 {
742 epoch_loss / num_batches as f32
743 } else {
744 0.0
745 };
746 losses.push(avg_loss);
747
748 if model.scheduler().is_quantization_enabled() {
750 for fake_quant in model.fake_quantizers.values_mut() {
751 fake_quant.update_observers(&torsh_tensor::creation::zeros(&[1, 32])?)?;
752 }
753 }
754
755 println!(
756 "Epoch {}/{}: Loss = {:.4}, QAT = {}",
757 epoch + 1,
758 epochs,
759 avg_loss,
760 model.scheduler().is_quantization_enabled()
761 );
762 }
763
764 Ok(losses)
765 }
766
767 pub fn evaluate_qat_quality<M: Module>(
769 qat_model: &QATModel<M>,
770 test_data: impl Iterator<Item = (Tensor, Tensor)>,
771 ) -> Result<QATEvaluationMetrics> {
772 let mut correct_predictions = 0;
773 let mut total_predictions = 0;
774 let mut total_loss = 0.0;
775
776 for (inputs, _targets) in test_data {
777 let _outputs = qat_model.forward(&inputs)?;
778
779 correct_predictions += 1;
781 total_predictions += 1;
782 total_loss += 0.05; }
784
785 let accuracy = if total_predictions > 0 {
786 correct_predictions as f32 / total_predictions as f32
787 } else {
788 0.0
789 };
790
791 let avg_loss = if total_predictions > 0 {
792 total_loss / total_predictions as f32
793 } else {
794 0.0
795 };
796
797 Ok(QATEvaluationMetrics {
798 accuracy,
799 average_loss: avg_loss,
800 inference_speedup: 2.0, model_size_reduction: 4.0, })
803 }
804}
805
806#[derive(Debug, Clone)]
808pub struct QATEvaluationMetrics {
809 pub accuracy: f32,
811 pub average_loss: f32,
813 pub inference_speedup: f32,
815 pub model_size_reduction: f32,
817}
818
819#[cfg(test)]
820mod tests {
821 use super::*;
822 use torsh_tensor::creation::*;
823
824 #[test]
825 fn test_fake_quantize_creation() {
826 let config = QATConfig::default();
827 let fake_quant = FakeQuantize::new(config);
828 assert!(fake_quant.enabled);
829 }
830
831 #[test]
832 fn test_fake_quantization() -> Result<()> {
833 let config = QATConfig::default();
834 let fake_quant = FakeQuantize::new(config);
835
836 let input = tensor_1d(&[1.0, 2.0, 3.0, 4.0])?;
837 let output = fake_quant.fake_quantize_tensor(&input)?;
838
839 let input_data = input.to_vec()?;
841 let output_data = output.to_vec()?;
842
843 for (orig, quantized) in input_data.iter().zip(output_data.iter()) {
844 assert!(
845 (orig - quantized).abs() < 1.0,
846 "Fake quantization should preserve values approximately"
847 );
848 }
849
850 Ok(())
851 }
852
853 #[test]
854 fn test_qat_linear() -> Result<()> {
855 let config = QATConfig::default();
856 let linear = QATLinear::new(4, 2, true, config);
857
858 let input = ones(&[1, 4])?;
859 let output = linear.forward(&input)?;
860
861 assert_eq!(output.shape().dims(), &[1, 2]);
862
863 Ok(())
864 }
865
866 #[test]
867 fn test_qat_scheduler() {
868 let config = QATConfig {
869 warmup_epochs: 2,
870 ..Default::default()
871 };
872 let mut scheduler = QATScheduler::new(config);
873
874 assert!(!scheduler.is_quantization_enabled());
875
876 scheduler.step();
877 assert!(!scheduler.is_quantization_enabled());
878
879 scheduler.step();
880 assert!(!scheduler.is_quantization_enabled());
881
882 scheduler.step(); assert!(scheduler.is_quantization_enabled());
884 }
885
886 #[test]
887 fn test_qat_model_wrapper() -> Result<()> {
888 use crate::layers::linear::Linear;
889
890 let linear = Linear::new(4, 2, true);
891 let config = QATConfig::default();
892 let qat_model = QATModel::new(linear, config);
893
894 let input = ones(&[1, 4])?;
895 let output = qat_model.forward(&input)?;
896
897 assert_eq!(output.shape().dims(), &[1, 2]);
898
899 Ok(())
900 }
901
902 #[test]
903 fn test_qat_evaluation_metrics() -> Result<()> {
904 use crate::layers::linear::Linear;
905
906 let linear = Linear::new(4, 2, true);
907 let config = QATConfig::default();
908 let qat_model = QATModel::new(linear, config);
909
910 let test_data = vec![
912 (ones(&[1, 4])?, ones(&[1, 2])?),
913 (ones(&[1, 4])?, ones(&[1, 2])?),
914 ];
915
916 let metrics = utils::evaluate_qat_quality(&qat_model, test_data.into_iter())?;
917
918 assert!(metrics.accuracy >= 0.0 && metrics.accuracy <= 1.0);
919 assert!(metrics.inference_speedup > 1.0);
920 assert!(metrics.model_size_reduction > 1.0);
921
922 Ok(())
923 }
924
925 #[test]
926 fn test_convert_model_to_qat() -> Result<()> {
927 use crate::layers::linear::Linear;
928
929 let linear = Linear::new(8, 4, true);
930 let config = QATConfig::default();
931 let qat_model = utils::convert_model_to_qat(linear, config);
932
933 let input = ones(&[2, 8])?;
934 let output = qat_model.forward(&input)?;
935
936 assert_eq!(output.shape().dims(), &[2, 4]);
937
938 Ok(())
939 }
940
941 #[test]
942 fn test_fake_quantize_observer_updates() -> Result<()> {
943 let config = QATConfig::default();
944 let mut fake_quant = FakeQuantize::new(config);
945
946 fake_quant.train();
948
949 let test_data = vec![
950 tensor_1d(&[1.0, 2.0, 3.0])?,
951 tensor_1d(&[-1.0, 0.0, 1.0])?,
952 tensor_1d(&[0.5, 1.5, 2.5])?,
953 ];
954
955 for tensor in test_data {
956 fake_quant.update_observers(&tensor)?;
957 }
958
959 assert!(fake_quant.num_batches_tracked > 0);
960 assert!(fake_quant.min_val < fake_quant.max_val);
961
962 Ok(())
963 }
964
965 #[test]
966 fn test_qat_linear_training_mode() -> Result<()> {
967 let config = QATConfig::default();
968 let mut qat_linear = QATLinear::new(4, 2, true, config);
969
970 qat_linear.train();
972 assert!(qat_linear.training());
973
974 qat_linear.eval();
976 assert!(!qat_linear.training());
977
978 qat_linear.enable_quantization(false);
980 let input = ones(&[1, 4])?;
981 let output = qat_linear.forward(&input)?;
982 assert_eq!(output.shape().dims(), &[1, 2]);
983
984 Ok(())
985 }
986}