1use crate::config::{MixedPrecisionConfig, QuantConfig};
16
17#[cfg(feature = "std")]
18use std::collections::HashMap;
19
20#[cfg(not(feature = "std"))]
21extern crate alloc;
22
23#[cfg(not(feature = "std"))]
24use alloc::{collections::BTreeMap as HashMap, string::String, vec::Vec};
25
26use torsh_core::{
27 dtype::DType,
28 error::{Result as TorshResult, TorshError},
29};
30use torsh_tensor::Tensor;
31
32pub fn quantize_int4_per_tensor(
34 tensor: &Tensor,
35 _config: &QuantConfig,
36) -> TorshResult<(Tensor, f32, i32)> {
37 let data = tensor.data()?;
38 let min_val = data.iter().fold(f32::INFINITY, |a, &b| a.min(b)).min(0.0);
39 let max_val = data
40 .iter()
41 .fold(f32::NEG_INFINITY, |a, &b| a.max(b))
42 .max(0.0);
43
44 let scale = (max_val - min_val) / 15.0; let scale = if scale == 0.0 { 1.0 } else { scale };
47
48 let zero_point = (-8.0 - min_val / scale).round().clamp(-8.0, 7.0) as i32;
49
50 let quantized_data: Vec<f32> = data
51 .iter()
52 .map(|&x| {
53 let quantized = (x / scale).round() + zero_point as f32;
54 quantized.clamp(-8.0, 7.0) })
56 .collect();
57
58 let quantized_tensor = Tensor::from_data(
59 quantized_data,
60 tensor.shape().dims().to_vec(),
61 tensor.device(),
62 )?;
63
64 Ok((quantized_tensor, scale, zero_point))
65}
66
67pub fn quantize_int4_per_channel_full(
73 tensor: &Tensor,
74 axis: usize,
75 _config: &QuantConfig,
76) -> TorshResult<(Tensor, Vec<f32>, Vec<i32>)> {
77 let binding = tensor.shape();
78 let shape = binding.dims();
79
80 if axis >= shape.len() {
81 return Err(TorshError::InvalidArgument(
82 "Axis out of bounds".to_string(),
83 ));
84 }
85
86 let num_channels = shape[axis];
87 let data = tensor.data()?;
88
89 let mut strides = vec![1; shape.len()];
91 for i in (0..shape.len().saturating_sub(1)).rev() {
92 strides[i] = strides[i + 1] * shape[i + 1];
93 }
94
95 let mut channel_mins = vec![f32::INFINITY; num_channels];
97 let mut channel_maxs = vec![f32::NEG_INFINITY; num_channels];
98 for (i, &val) in data.iter().enumerate() {
99 let ch = (i / strides[axis]) % num_channels;
100 channel_mins[ch] = channel_mins[ch].min(val);
101 channel_maxs[ch] = channel_maxs[ch].max(val);
102 }
103
104 let mut scales = Vec::with_capacity(num_channels);
105 let mut zero_points = Vec::with_capacity(num_channels);
106
107 for ch in 0..num_channels {
108 let channel_min = channel_mins[ch].min(0.0);
110 let channel_max = channel_maxs[ch].max(0.0);
111
112 let scale = (channel_max - channel_min) / 15.0; let scale = if scale == 0.0 { 1.0 } else { scale };
115 let zero_point = (-8.0 - channel_min / scale).round().clamp(-8.0, 7.0) as i32;
116
117 scales.push(scale);
118 zero_points.push(zero_point);
119 }
120
121 let quantized_data: Vec<f32> = data
123 .iter()
124 .enumerate()
125 .map(|(i, &val)| {
126 let ch = (i / strides[axis]) % num_channels;
127 let quantized = (val / scales[ch]).round() + zero_points[ch] as f32;
128 quantized.clamp(-8.0, 7.0)
129 })
130 .collect();
131
132 let quantized_tensor = Tensor::from_data(quantized_data, shape.to_vec(), tensor.device())?;
133
134 Ok((quantized_tensor, scales, zero_points))
135}
136
137pub fn quantize_int4_per_channel(
145 tensor: &Tensor,
146 axis: usize,
147 config: &QuantConfig,
148) -> TorshResult<(Tensor, f32, i32)> {
149 let (quantized_tensor, scales, zero_points) =
150 quantize_int4_per_channel_full(tensor, axis, config)?;
151
152 let avg_scale = scales.iter().sum::<f32>() / scales.len() as f32;
154 let avg_zero_point =
155 (zero_points.iter().sum::<i32>() as f32 / zero_points.len() as f32).round() as i32;
156
157 Ok((quantized_tensor, avg_scale, avg_zero_point))
158}
159
160pub fn quantize_binary(tensor: &Tensor) -> TorshResult<(Tensor, f32, i32)> {
162 let data = tensor.data()?;
163
164 if data.is_empty() {
165 return Err(TorshError::InvalidArgument(
166 "Cannot quantize empty tensor".to_string(),
167 ));
168 }
169
170 let scale = data.iter().map(|&x| x.abs()).sum::<f32>() / data.len() as f32;
172 let scale = if scale == 0.0 { 1.0 } else { scale };
173
174 let quantized_data: Vec<f32> = data
175 .iter()
176 .map(|&x| if x >= 0.0 { 1.0 } else { -1.0 })
177 .collect();
178
179 let quantized_tensor = Tensor::from_data(
180 quantized_data,
181 tensor.shape().dims().to_vec(),
182 tensor.device(),
183 )?;
184
185 Ok((quantized_tensor, scale, 0)) }
187
188pub fn quantize_ternary(tensor: &Tensor) -> TorshResult<(Tensor, f32, i32)> {
190 let data = tensor.data()?;
191
192 if data.is_empty() {
193 return Err(TorshError::InvalidArgument(
194 "Cannot quantize empty tensor".to_string(),
195 ));
196 }
197
198 let max_abs = data.iter().map(|&x| x.abs()).fold(0.0f32, f32::max);
200 let threshold = max_abs * 0.7; let non_zero_sum: f32 = data
204 .iter()
205 .filter(|&&x| x.abs() > threshold)
206 .map(|&x| x.abs())
207 .sum();
208 let non_zero_count = data.iter().filter(|&&x| x.abs() > threshold).count();
209
210 let scale = if non_zero_count > 0 {
211 non_zero_sum / non_zero_count as f32
212 } else {
213 1.0
214 };
215
216 let quantized_data: Vec<f32> = data
217 .iter()
218 .map(|&x| {
219 if x.abs() <= threshold {
220 0.0
221 } else if x > 0.0 {
222 1.0
223 } else {
224 -1.0
225 }
226 })
227 .collect();
228
229 let quantized_tensor = Tensor::from_data(
230 quantized_data,
231 tensor.shape().dims().to_vec(),
232 tensor.device(),
233 )?;
234
235 Ok((quantized_tensor, scale, 0)) }
237
238pub fn quantize_group_wise_full(
245 tensor: &Tensor,
246 axis: usize,
247 group_size: usize,
248 config: &QuantConfig,
249) -> TorshResult<(Tensor, Vec<f32>, Vec<i32>)> {
250 let binding = tensor.shape();
251 let shape = binding.dims();
252
253 if axis >= shape.len() {
254 return Err(TorshError::InvalidArgument(
255 "Axis out of bounds".to_string(),
256 ));
257 }
258
259 if group_size == 0 {
260 return Err(TorshError::InvalidArgument(
261 "Group size must be greater than 0".to_string(),
262 ));
263 }
264
265 let num_channels = shape[axis];
266 let num_groups = num_channels.div_ceil(group_size); let data = tensor.data()?;
269
270 let mut strides = vec![1; shape.len()];
272 for i in (0..shape.len().saturating_sub(1)).rev() {
273 strides[i] = strides[i + 1] * shape[i + 1];
274 }
275
276 let mut group_mins = vec![f32::INFINITY; num_groups];
278 let mut group_maxs = vec![f32::NEG_INFINITY; num_groups];
279 for (i, &val) in data.iter().enumerate() {
280 let group = ((i / strides[axis]) % num_channels) / group_size;
281 group_mins[group] = group_mins[group].min(val);
282 group_maxs[group] = group_maxs[group].max(val);
283 }
284
285 let (qmin, qmax) = config.get_qint_range();
286 let mut group_scales = Vec::with_capacity(num_groups);
287 let mut group_zero_points = Vec::with_capacity(num_groups);
288
289 for group in 0..num_groups {
290 let (min_val, max_val) = if group_mins[group].is_finite() {
292 (group_mins[group].min(0.0), group_maxs[group].max(0.0))
293 } else {
294 (0.0, 0.0)
295 };
296
297 let scale = (max_val - min_val) / (qmax - qmin) as f32;
298 let scale = if scale == 0.0 { 1.0 } else { scale };
299
300 let zero_point = (qmin as f32 - min_val / scale)
301 .round()
302 .max(qmin as f32)
303 .min(qmax as f32) as i32;
304
305 group_scales.push(scale);
306 group_zero_points.push(zero_point);
307 }
308
309 let quantized_data: Vec<f32> = data
311 .iter()
312 .enumerate()
313 .map(|(i, &val)| {
314 let group = ((i / strides[axis]) % num_channels) / group_size;
315 let quantized = (val / group_scales[group]).round() + group_zero_points[group] as f32;
316 quantized.clamp(qmin as f32, qmax as f32)
317 })
318 .collect();
319
320 let quantized_tensor = Tensor::from_data(quantized_data, shape.to_vec(), tensor.device())?;
321
322 Ok((quantized_tensor, group_scales, group_zero_points))
323}
324
325pub fn quantize_group_wise(
333 tensor: &Tensor,
334 axis: usize,
335 group_size: usize,
336 config: &QuantConfig,
337) -> TorshResult<(Tensor, f32, i32)> {
338 let (quantized_tensor, group_scales, group_zero_points) =
339 quantize_group_wise_full(tensor, axis, group_size, config)?;
340
341 let avg_scale = if group_scales.is_empty() {
343 1.0
344 } else {
345 group_scales.iter().sum::<f32>() / group_scales.len() as f32
346 };
347 let avg_zero_point = if group_zero_points.is_empty() {
348 0
349 } else {
350 (group_zero_points.iter().sum::<i32>() as f32 / group_zero_points.len() as f32).round()
351 as i32
352 };
353
354 Ok((quantized_tensor, avg_scale, avg_zero_point))
355}
356
357pub fn quantize_mixed_precision(
359 tensors: &HashMap<String, Tensor>,
360 config: &MixedPrecisionConfig,
361) -> TorshResult<HashMap<String, (Tensor, f32, i32)>> {
362 let mut results = HashMap::new();
363
364 for (layer_name, tensor) in tensors {
365 let precision = determine_layer_precision(layer_name, config);
367
368 let layer_config = create_precision_config(precision);
370
371 let result = crate::algorithms::quantize_with_config(tensor, &layer_config)?;
373 results.insert(layer_name.clone(), result);
374 }
375
376 Ok(results)
377}
378
379pub fn determine_layer_precision(layer_name: &str, config: &MixedPrecisionConfig) -> DType {
381 for (pattern, precision) in &config.layer_precision {
383 if layer_name.contains(pattern) {
384 return *precision;
385 }
386 }
387
388 config.default_precision
390}
391
392pub fn create_precision_config(precision: DType) -> QuantConfig {
394 match precision {
395 DType::I8 => QuantConfig::int8(),
396 DType::U8 => QuantConfig::uint8(),
397 DType::F16 => {
398 QuantConfig {
400 dtype: DType::F16,
401 enable_fake_quant: false,
402 ..Default::default()
403 }
404 }
405 DType::F32 => {
406 QuantConfig {
408 dtype: DType::F32,
409 enable_fake_quant: false,
410 ..Default::default()
411 }
412 }
413 _ => QuantConfig::int8(), }
415}
416
417pub fn quantize_binary_learned_threshold(
419 tensor: &Tensor,
420 threshold: Option<f32>,
421) -> TorshResult<(Tensor, f32, i32, f32)> {
422 let data = tensor.data()?;
423
424 if data.is_empty() {
425 return Err(TorshError::InvalidArgument(
426 "Cannot quantize empty tensor".to_string(),
427 ));
428 }
429
430 let threshold = threshold.unwrap_or_else(|| {
432 let abs_sum: f32 = data.iter().map(|&x| x.abs()).sum();
434 abs_sum / data.len() as f32
435 });
436
437 let above_threshold: Vec<f32> = data
439 .iter()
440 .filter(|&&x| x.abs() > threshold)
441 .cloned()
442 .collect();
443
444 let scale = if above_threshold.is_empty() {
445 1.0
446 } else {
447 above_threshold.iter().map(|&x| x.abs()).sum::<f32>() / above_threshold.len() as f32
448 };
449
450 let quantized_data: Vec<f32> = data
451 .iter()
452 .map(|&x| {
453 if x.abs() <= threshold {
454 0.0
455 } else if x >= 0.0 {
456 1.0
457 } else {
458 -1.0
459 }
460 })
461 .collect();
462
463 let quantized_tensor = Tensor::from_data(
464 quantized_data,
465 tensor.shape().dims().to_vec(),
466 tensor.device(),
467 )?;
468
469 Ok((quantized_tensor, scale, 0, threshold))
470}
471
472pub fn quantize_ternary_adaptive(tensor: &Tensor) -> TorshResult<(Tensor, f32, i32, f32)> {
474 let data = tensor.data()?;
475
476 if data.is_empty() {
477 return Err(TorshError::InvalidArgument(
478 "Cannot quantize empty tensor".to_string(),
479 ));
480 }
481
482 let max_abs = data.iter().map(|&x| x.abs()).fold(0.0f32, f32::max);
484 let mut best_threshold = 0.0;
485 let mut best_error = f32::INFINITY;
486
487 for i in 1..=10 {
489 let threshold = max_abs * (i as f32 * 0.1);
490 let error = calculate_ternary_error(&data, threshold);
491 if error < best_error {
492 best_error = error;
493 best_threshold = threshold;
494 }
495 }
496
497 let non_zero_sum: f32 = data
499 .iter()
500 .filter(|&&x| x.abs() > best_threshold)
501 .map(|&x| x.abs())
502 .sum();
503 let non_zero_count = data.iter().filter(|&&x| x.abs() > best_threshold).count();
504
505 let scale = if non_zero_count > 0 {
506 non_zero_sum / non_zero_count as f32
507 } else {
508 1.0
509 };
510
511 let quantized_data: Vec<f32> = data
512 .iter()
513 .map(|&x| {
514 if x.abs() <= best_threshold {
515 0.0
516 } else if x > 0.0 {
517 1.0
518 } else {
519 -1.0
520 }
521 })
522 .collect();
523
524 let quantized_tensor = Tensor::from_data(
525 quantized_data,
526 tensor.shape().dims().to_vec(),
527 tensor.device(),
528 )?;
529
530 Ok((quantized_tensor, scale, 0, best_threshold))
531}
532
533fn calculate_ternary_error(data: &[f32], threshold: f32) -> f32 {
535 data.iter()
536 .map(|&x| {
537 let quantized = if x.abs() <= threshold {
538 0.0
539 } else if x > 0.0 {
540 1.0
541 } else {
542 -1.0
543 };
544 (x - quantized).powi(2)
545 })
546 .sum::<f32>()
547 / data.len() as f32
548}
549
550#[cfg(test)]
551mod tests {
552 use super::*;
553 use torsh_core::device::DeviceType;
554 use torsh_tensor::creation::tensor_1d;
555
556 #[test]
557 fn test_quantize_int4_per_tensor() {
558 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
559 let tensor = tensor_1d(&data).unwrap();
560 let config = QuantConfig::int4();
561
562 let result = quantize_int4_per_tensor(&tensor, &config);
563 assert!(result.is_ok());
564
565 let (quantized, scale, zero_point) = result.unwrap();
566 assert!(scale > 0.0);
567 assert!(zero_point >= -8 && zero_point <= 7);
568
569 let quantized_data = quantized.data().unwrap();
570 assert_eq!(quantized_data.len(), data.len());
571
572 for &val in &quantized_data {
574 assert!(val >= -8.0 && val <= 7.0);
575 }
576 }
577
578 #[test]
579 fn test_quantize_binary() {
580 let data = vec![-2.0, -1.0, 0.0, 1.0, 2.0];
581 let tensor = tensor_1d(&data).unwrap();
582
583 let result = quantize_binary(&tensor);
584 assert!(result.is_ok());
585
586 let (quantized, scale, zero_point) = result.unwrap();
587 assert!(scale > 0.0);
588 assert_eq!(zero_point, 0); let quantized_data = quantized.data().unwrap();
591 assert_eq!(quantized_data.len(), data.len());
592
593 for &val in &quantized_data {
595 assert!(val == -1.0 || val == 1.0);
596 }
597 }
598
599 #[test]
600 fn test_quantize_ternary() {
601 let data = vec![-3.0, -1.0, 0.1, 1.0, 3.0];
602 let tensor = tensor_1d(&data).unwrap();
603
604 let result = quantize_ternary(&tensor);
605 assert!(result.is_ok());
606
607 let (quantized, scale, zero_point) = result.unwrap();
608 assert!(scale > 0.0);
609 assert_eq!(zero_point, 0); let quantized_data = quantized.data().unwrap();
612 assert_eq!(quantized_data.len(), data.len());
613
614 for &val in &quantized_data {
616 assert!(val == -1.0 || val == 0.0 || val == 1.0);
617 }
618 }
619
620 #[test]
621 fn test_quantize_group_wise() {
622 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
623 let tensor = Tensor::from_data(data, vec![2, 3], DeviceType::Cpu).unwrap();
624 let config = QuantConfig::group_wise(1, 2);
625
626 let result = quantize_group_wise(&tensor, 1, 2, &config);
627 assert!(result.is_ok());
628
629 let (quantized, scale, _zero_point) = result.unwrap();
630 assert!(scale > 0.0);
631 assert_eq!(quantized.shape().dims(), tensor.shape().dims());
632 }
633
634 #[test]
635 fn test_mixed_precision() {
636 let mut tensors = HashMap::new();
637 tensors.insert(
638 "embedding".to_string(),
639 tensor_1d(&[1.0, 2.0, 3.0]).unwrap(),
640 );
641 tensors.insert(
642 "attention".to_string(),
643 tensor_1d(&[4.0, 5.0, 6.0]).unwrap(),
644 );
645
646 let config = MixedPrecisionConfig::default();
647
648 let result = quantize_mixed_precision(&tensors, &config);
649 assert!(result.is_ok());
650
651 let results = result.unwrap();
652 assert_eq!(results.len(), 2);
653 assert!(results.contains_key("embedding"));
654 assert!(results.contains_key("attention"));
655 }
656
657 #[test]
658 fn test_determine_layer_precision() {
659 let config = MixedPrecisionConfig::default();
660
661 let embedding_precision = determine_layer_precision("layer.embedding.weight", &config);
662 assert_eq!(embedding_precision, DType::I8);
663
664 let attention_precision = determine_layer_precision("layer.attention.query", &config);
665 assert_eq!(attention_precision, DType::F16);
666
667 let unknown_precision = determine_layer_precision("layer.unknown.weight", &config);
668 assert_eq!(unknown_precision, DType::I8); }
670
671 #[test]
672 fn test_binary_learned_threshold() {
673 let data = vec![-2.0, -0.1, 0.1, 0.5, 2.0];
674 let tensor = tensor_1d(&data).unwrap();
675
676 let result = quantize_binary_learned_threshold(&tensor, Some(0.3));
677 assert!(result.is_ok());
678
679 let (quantized, scale, zero_point, threshold) = result.unwrap();
680 assert!(scale > 0.0);
681 assert_eq!(zero_point, 0);
682 assert_eq!(threshold, 0.3);
683
684 let quantized_data = quantized.data().unwrap();
685
686 for (i, &original) in data.iter().enumerate() {
688 let expected = if original.abs() <= 0.3 {
689 0.0
690 } else if original >= 0.0 {
691 1.0
692 } else {
693 -1.0
694 };
695 assert_eq!(quantized_data[i], expected);
696 }
697 }
698
699 #[test]
700 fn test_ternary_adaptive() {
701 let data = vec![-3.0, -0.5, 0.0, 0.5, 3.0];
702 let tensor = tensor_1d(&data).unwrap();
703
704 let result = quantize_ternary_adaptive(&tensor);
705 assert!(result.is_ok());
706
707 let (quantized, scale, zero_point, threshold) = result.unwrap();
708 assert!(scale > 0.0);
709 assert_eq!(zero_point, 0);
710 assert!(threshold > 0.0);
711
712 let quantized_data = quantized.data().unwrap();
713 assert_eq!(quantized_data.len(), data.len());
714
715 for &val in &quantized_data {
717 assert!(val == -1.0 || val == 0.0 || val == 1.0);
718 }
719 }
720
721 #[test]
722 fn test_error_cases() {
723 let empty_data: Vec<f32> = vec![];
725 let empty_tensor = tensor_1d(&empty_data).unwrap();
726
727 assert!(quantize_binary(&empty_tensor).is_err());
728 assert!(quantize_ternary(&empty_tensor).is_err());
729
730 let data = vec![1.0, 2.0, 3.0];
732 let tensor = tensor_1d(&data).unwrap();
733 let config = QuantConfig::group_wise(0, 2);
734
735 let result = quantize_group_wise(&tensor, 5, 2, &config);
736 assert!(result.is_err());
737
738 let result = quantize_group_wise(&tensor, 0, 0, &config);
740 assert!(result.is_err());
741 }
742}