1use alloc::vec::Vec;
4#[cfg(not(feature = "std"))]
5#[allow(unused_imports)]
6use num_traits::Float;
7
8use burn_backend::{
9 DType, ExecutionError, FloatDType, TensorData, TensorMetadata,
10 ops::{IntTensorOps, QTensorOps},
11 quantization::{
12 BlockLayout, BlockSize, QuantScheme, QuantizationParametersPrimitive, QuantizedBytes,
13 ScaleDtype, global_scale_dtype, scale_to_dtype,
14 },
15 tensor::{Device, FloatTensor, IntTensor, QuantizedTensor},
16};
17use burn_std::{Bytes, Shape, Slice, bf16, f16};
18
19use super::float_storage_as_f32;
20use crate::{Flex, FlexQTensor, FlexTensor, Layout, quant_codes::QuantCodes};
21
22fn block_layout(shape: &Shape, block: &BlockSize) -> BlockLayout {
24 let blocks = BlockLayout::new(shape, block);
25 assert!(
26 blocks.divides(),
27 "tensor {shape:?} is not a whole number of {block:?} blocks"
28 );
29 blocks
30}
31
32fn block_max_abs(values: &[f32], blocks: &BlockLayout) -> Vec<f32> {
34 let mut peaks = alloc::vec![0.0f32; blocks.num_blocks()];
35 for (index, &x) in values.iter().enumerate() {
36 let peak = &mut peaks[blocks.block_of(index)];
37 *peak = peak.max(x.abs());
38 }
39 peaks
40}
41
42impl QTensorOps<Flex> for Flex {
43 fn q_from_data(data: TensorData, _device: &Device<Flex>) -> QuantizedTensor<Flex> {
44 let scheme = match data.dtype() {
45 DType::QFloat(scheme) => scheme,
46 _ => panic!("Expected quantized dtype, got {:?}", data.dtype()),
47 };
48
49 let shape = data.shape().clone();
50
51 let q_bytes = QuantizedBytes {
52 shape: shape.clone(),
53 bytes: data.into_bytes(),
54 scheme,
55 };
56
57 let (values, qparams) = q_bytes.into_vec_i8();
58 let tensor_data = TensorData::new(values, shape);
59 let tensor = FlexTensor::from_data(tensor_data);
60
61 FlexQTensor::new(tensor, scheme, qparams.block, qparams.global)
62 }
63
64 fn quantize_dynamic(tensor: FloatTensor<Flex>, scheme: &QuantScheme) -> QuantizedTensor<Flex> {
65 let shape = tensor.shape();
66 let tensor = tensor.to_contiguous();
67 let float_data = float_storage_as_f32(&tensor);
68 let (min, max) = scheme.value.range();
69 let range = max - min;
70
71 let (quantized, scales, global) = match (scheme.block_size(), global_scale_dtype(scheme)) {
72 (Some(block), Some(global_dtype)) => {
73 let blocks = block_layout(&shape, &block);
74 let raw: Vec<f32> = block_max_abs(&float_data, &blocks)
75 .into_iter()
76 .map(|alpha| 2.0 * alpha / range)
77 .collect();
78 let peak = raw.iter().copied().fold(0.0f32, f32::max);
79
80 let global = validated_scale(
81 peak / scheme.scale_dtype().max_representable(),
82 global_dtype,
83 );
84
85 let scales: Vec<f32> = raw
86 .iter()
87 .map(|&raw| validated_scale(raw / global, scheme.scale_dtype()))
88 .collect();
89 let quantized = scheme
90 .value
91 .encode_all(&float_data, |index| global * scales[blocks.block_of(index)]);
92
93 (quantized, scales, Some(global))
94 }
95 (None, _) => {
96 let scale = validated_scale(
97 block_max_abs_scale(&float_data, range),
98 scheme.scale_dtype(),
99 );
100 let quantized = scheme.value.encode_all(&float_data, move |_| scale);
101
102 (quantized, alloc::vec![scale], None)
103 }
104 (Some(block_size), None) => {
105 let blocks = block_layout(&shape, &block_size);
106 let scales: Vec<f32> = block_max_abs(&float_data, &blocks)
107 .into_iter()
108 .map(|alpha| validated_scale(2.0 * alpha / range, scheme.scale_dtype()))
109 .collect();
110 let quantized = scheme
111 .value
112 .encode_all(&float_data, |index| scales[blocks.block_of(index)]);
113
114 (quantized, scales, None)
115 }
116 };
117
118 let bytes = Bytes::from_elems(quantized);
119 let layout = Layout::contiguous(shape);
120 let qt = FlexTensor::new(bytes, layout, DType::I8);
121
122 FlexQTensor::new(qt, *scheme, scales, global)
123 }
124
125 fn quantize(
126 tensor: FloatTensor<Flex>,
127 scheme: &QuantScheme,
128 qparams: QuantizationParametersPrimitive<Flex>,
129 ) -> QuantizedTensor<Flex> {
130 let shape = tensor.shape();
131 let tensor = tensor.to_contiguous();
132 let float_data = float_storage_as_f32(&tensor);
133
134 let scales_tensor = qparams.scales.to_contiguous();
139 let scales_data = float_storage_as_f32(&scales_tensor);
140 let scales: Vec<f32> = scales_data
141 .iter()
142 .copied()
143 .map(|s| validated_scale(s, scheme.scale_dtype()))
144 .collect();
145
146 let global = qparams.global.map(|global| {
147 let dtype = global_scale_dtype(scheme)
148 .expect("a per-tensor scale should come with a two-level scheme");
149 let global = global.to_contiguous();
150 validated_scale(float_storage_as_f32(&global)[0], dtype)
151 });
152
153 let quantized = match scheme.block_size() {
154 None => {
155 let scale = scales[0];
156 scheme.value.encode_all(&float_data, move |_| scale)
157 }
158 Some(block_size) => {
159 let blocks = block_layout(&shape, &block_size);
160 let multiplier = global.unwrap_or(1.0);
161 scheme.value.encode_all(&float_data, |index| {
162 multiplier * scales[blocks.block_of(index)]
163 })
164 }
165 };
166
167 let bytes = Bytes::from_elems(quantized);
168 let layout = Layout::contiguous(shape);
169 let qt = FlexTensor::new(bytes, layout, DType::I8);
170
171 FlexQTensor::new(qt, *scheme, scales, global)
172 }
173
174 fn dequantize(tensor: QuantizedTensor<Flex>, dtype: FloatDType) -> FloatTensor<Flex> {
175 let shape = tensor.tensor.shape();
176 let qt = tensor.tensor.to_contiguous();
177 let q_data: &[i8] = qt.storage();
178
179 let dequantized = match tensor.scheme.block_size() {
180 None => {
181 let scale = tensor.scales[0];
182 tensor.scheme.value.decode_all(q_data, move |_| scale)
183 }
184 Some(block_size) => {
185 let blocks = BlockLayout::new(&shape, &block_size);
186 let multiplier = tensor.global.unwrap_or(1.0);
187 tensor.scheme.value.decode_all(q_data, |index| {
188 multiplier * tensor.scales[blocks.block_of(index)]
189 })
190 }
191 };
192
193 let layout = Layout::contiguous(shape);
194 match dtype {
195 FloatDType::F32 | FloatDType::Flex32 => {
196 FlexTensor::new(Bytes::from_elems(dequantized), layout, DType::F32)
197 }
198 FloatDType::F64 => {
199 let data: Vec<f64> = dequantized.iter().map(|&v| v as f64).collect();
200 FlexTensor::new(Bytes::from_elems(data), layout, DType::F64)
201 }
202 FloatDType::F16 => {
203 let data: Vec<f16> = dequantized.iter().map(|&v| f16::from_f32(v)).collect();
204 FlexTensor::new(Bytes::from_elems(data), layout, DType::F16)
205 }
206 FloatDType::BF16 => {
207 let data: Vec<bf16> = dequantized.iter().map(|&v| bf16::from_f32(v)).collect();
208 FlexTensor::new(Bytes::from_elems(data), layout, DType::BF16)
209 }
210 }
211 }
212
213 fn q_to_device(tensor: QuantizedTensor<Flex>, _device: &Device<Flex>) -> QuantizedTensor<Flex> {
214 tensor
215 }
216
217 fn q_reshape(tensor: QuantizedTensor<Flex>, shape: Shape) -> QuantizedTensor<Flex> {
218 let mut scheme = tensor.scheme;
219 scheme.reshape_packing_dim(shape.num_dims());
220 block_safe_layout_op(tensor, scheme, |t| t.reshape(shape))
221 }
222
223 async fn q_into_data(tensor: QuantizedTensor<Flex>) -> Result<TensorData, ExecutionError> {
224 let shape = tensor.tensor.shape();
225 let scheme = tensor.scheme;
226 let qt = tensor.tensor.to_contiguous();
227 let values: Vec<i8> = qt.storage::<i8>().to_vec();
228
229 Ok(TensorData::quantized(
230 values,
231 shape.to_vec(),
232 scheme,
233 &tensor.scales,
234 tensor.global,
235 ))
236 }
237
238 fn q_swap_dims(
239 tensor: QuantizedTensor<Flex>,
240 dim1: usize,
241 dim2: usize,
242 ) -> QuantizedTensor<Flex> {
243 let mut scheme = tensor.scheme;
244 scheme.swap_dims(tensor.tensor.shape().num_dims(), dim1, dim2);
245 block_safe_layout_op(tensor, scheme, |t| t.transpose(dim1, dim2))
246 }
247
248 fn q_permute(tensor: QuantizedTensor<Flex>, axes: &[usize]) -> QuantizedTensor<Flex> {
249 let mut scheme = tensor.scheme;
250 scheme.permute_dims(axes);
251 block_safe_layout_op(tensor, scheme, |t| t.permute(axes))
252 }
253
254 fn q_flip(tensor: QuantizedTensor<Flex>, axes: &[usize]) -> QuantizedTensor<Flex> {
255 let scheme = tensor.scheme;
256 block_safe_layout_op(tensor, scheme, |t| crate::ops::flip::flip(t, axes))
257 }
258
259 fn q_expand(tensor: QuantizedTensor<Flex>, shape: Shape) -> QuantizedTensor<Flex> {
260 let scheme = tensor.scheme;
261 block_safe_layout_op(tensor, scheme, |t| crate::ops::expand::expand(t, shape))
262 }
263
264 fn q_select(
265 tensor: QuantizedTensor<Flex>,
266 dim: usize,
267 indices: IntTensor<Flex>,
268 ) -> QuantizedTensor<Flex> {
269 match tensor.scheme.block_size() {
270 None => FlexQTensor::new(
271 crate::ops::gather_scatter::select::<i8>(tensor.tensor, dim, indices),
272 tensor.scheme,
273 tensor.scales,
274 tensor.global,
275 ),
276 Some(_) => {
277 let scheme = tensor.scheme;
278 let float_tensor = Flex::dequantize(tensor, FloatDType::F32);
279 let result = crate::ops::gather_scatter::select::<f32>(float_tensor, dim, indices);
280 Flex::quantize_dynamic(result, &scheme)
281 }
282 }
283 }
284
285 fn q_slice(tensor: QuantizedTensor<Flex>, slices: &[Slice]) -> QuantizedTensor<Flex> {
286 let scheme = tensor.scheme;
287 block_safe_layout_op(tensor, scheme, |t| crate::ops::slice::slice(t, slices))
288 }
289
290 fn q_argmax(
291 tensor: QuantizedTensor<Flex>,
292 dim: usize,
293 out_dtype: burn_std::IntDType,
294 ) -> IntTensor<Flex> {
295 let result = if codes_order_as_values(&tensor) {
296 crate::ops::reduce::argmax(tensor.tensor, dim)
297 } else {
298 crate::ops::reduce::argmax(Flex::dequantize(tensor, FloatDType::F32), dim)
299 };
300 if result.dtype() != DType::from(out_dtype) {
301 Flex::int_cast(result, out_dtype)
302 } else {
303 result
304 }
305 }
306
307 fn q_argmin(
308 tensor: QuantizedTensor<Flex>,
309 dim: usize,
310 out_dtype: burn_std::IntDType,
311 ) -> IntTensor<Flex> {
312 let result = if codes_order_as_values(&tensor) {
313 crate::ops::reduce::argmin(tensor.tensor, dim)
314 } else {
315 crate::ops::reduce::argmin(Flex::dequantize(tensor, FloatDType::F32), dim)
316 };
317 if result.dtype() != DType::from(out_dtype) {
318 Flex::int_cast(result, out_dtype)
319 } else {
320 result
321 }
322 }
323
324 fn q_gather(
325 dim: usize,
326 tensor: QuantizedTensor<Flex>,
327 indices: IntTensor<Flex>,
328 ) -> QuantizedTensor<Flex> {
329 match tensor.scheme.block_size() {
330 None => FlexQTensor::new(
331 crate::ops::gather_scatter::gather::<i8>(tensor.tensor, dim, indices),
332 tensor.scheme,
333 tensor.scales,
334 tensor.global,
335 ),
336 Some(_) => {
337 let scheme = tensor.scheme;
338 let float_tensor = Flex::dequantize(tensor, FloatDType::F32);
339 let result = crate::ops::gather_scatter::gather::<f32>(float_tensor, dim, indices);
340 Flex::quantize_dynamic(result, &scheme)
341 }
342 }
343 }
344}
345
346fn block_safe_layout_op(
350 qtensor: FlexQTensor,
351 scheme: QuantScheme,
352 op: impl FnOnce(FlexTensor) -> FlexTensor,
353) -> FlexQTensor {
354 match qtensor.scheme.block_size() {
355 None => FlexQTensor::new(op(qtensor.tensor), scheme, qtensor.scales, qtensor.global),
356 Some(_) => {
357 let float_tensor = Flex::dequantize(qtensor, FloatDType::F32);
358 let result = op(float_tensor);
359 Flex::quantize_dynamic(result, &scheme)
360 }
361 }
362}
363
364fn codes_order_as_values(tensor: &FlexQTensor) -> bool {
366 tensor.scheme.block_size().is_none() && !tensor.scheme.value.is_float()
367}
368
369fn block_max_abs_scale(block: &[f32], range: f32) -> f32 {
371 let alpha = block.iter().fold(0.0f32, |alpha, &x| alpha.max(x.abs()));
372 2.0 * alpha / range
373}
374
375fn validated_scale(scale: f32, dtype: ScaleDtype) -> f32 {
379 let scale = scale_to_dtype(scale, dtype);
380 if scale > 0.0 && scale.is_finite() {
381 scale
382 } else {
383 scale_to_dtype(f32::MIN_POSITIVE, dtype)
384 }
385}
386
387#[cfg(test)]
395mod tests {
396 use super::*;
397 use burn_backend::{
398 TensorMetadata,
399 quantization::{QuantStore, QuantValue},
400 };
401
402 fn data_of(tensor: QuantizedTensor<Flex>) -> TensorData {
403 burn_std::reader::try_read_sync(Flex::q_into_data(tensor))
404 .expect("flex reads synchronously")
405 .unwrap()
406 }
407
408 fn packed_q4() -> QuantScheme {
409 QuantScheme::default()
410 .with_value(QuantValue::Q4S)
411 .with_store(QuantStore::PackedU32(0))
412 .per_block([8], ScaleDtype::F32)
413 }
414
415 fn ramp(shape: [usize; 2]) -> FlexTensor {
416 let values = (0..shape[0] * shape[1])
417 .map(|i| (i as f32 * 0.37).sin())
418 .collect::<Vec<_>>();
419 FlexTensor::from_data(TensorData::new(values, shape))
420 }
421
422 #[test]
423 fn a_packed_tensor_keeps_its_scheme_through_its_data() {
424 let scheme = packed_q4();
425 let quantized = Flex::quantize_dynamic(ramp([4, 16]), &scheme);
426 assert_eq!(quantized.scheme, scheme);
427
428 let data = data_of(quantized.clone());
429 assert_eq!(data.dtype(), DType::QFloat(scheme));
430
431 let reloaded = Flex::q_from_data(data.clone(), &Default::default());
432 assert_eq!(reloaded.scheme, scheme);
433 assert_eq!(data_of(reloaded).as_bytes(), data.as_bytes());
434 }
435
436 #[test]
437 fn swapping_the_packed_axis_moves_the_store_with_it() {
438 let per_tensor = QuantScheme::default()
439 .with_value(QuantValue::Q4S)
440 .with_store(QuantStore::PackedU32(0));
441 for scheme in [per_tensor, packed_q4()] {
442 let quantized = Flex::quantize_dynamic(ramp([16, 8]), &scheme);
443
444 let swapped = Flex::q_swap_dims(quantized, 0, 1);
445 assert_eq!(swapped.scheme.store, QuantStore::PackedU32(1));
446
447 let direct = Flex::dequantize(swapped.clone(), FloatDType::F32);
448 let reloaded = Flex::q_from_data(data_of(swapped), &Default::default());
449 let reloaded = Flex::dequantize(reloaded, FloatDType::F32);
450 assert_eq!(
451 reloaded.to_contiguous().storage::<f32>(),
452 direct.to_contiguous().storage::<f32>(),
453 "{scheme:?}"
454 );
455 }
456 }
457
458 #[test]
459 fn a_reshape_that_drops_the_packed_axis_packs_innermost() {
460 let q8 = QuantScheme::default().with_value(QuantValue::Q8S);
461 for (scheme, flattened) in [
462 (
463 q8.with_store(QuantStore::PackedU32(0)),
464 QuantStore::PackedU32(0),
465 ),
466 (
467 q8.with_value(QuantValue::E2M1)
468 .with_store(QuantStore::PackedNative(0)),
469 QuantStore::PackedNative(0),
470 ),
471 ] {
472 let swapped = Flex::q_swap_dims(Flex::quantize_dynamic(ramp([4, 8]), &scheme), 0, 1);
473 let expected = Flex::dequantize(swapped.clone(), FloatDType::F32);
474
475 let reshaped = Flex::q_reshape(swapped, Shape::new([32]));
476
477 assert_eq!(reshaped.scheme.store, flattened);
478 let reloaded = Flex::q_from_data(data_of(reshaped), &Default::default());
479 assert_eq!(
480 Flex::dequantize(reloaded, FloatDType::F32).storage::<f32>(),
481 expected.to_contiguous().storage::<f32>(),
482 "{scheme:?}"
483 );
484 }
485 }
486
487 #[test]
488 fn global_reductions_follow_a_swap_of_the_packed_axis() {
489 let scheme = QuantScheme::default()
490 .with_value(QuantValue::Q8S)
491 .with_store(QuantStore::PackedU32(0));
492 let swapped = Flex::q_swap_dims(Flex::quantize_dynamic(ramp([4, 8]), &scheme), 0, 1);
493 let values = Flex::dequantize(swapped.clone(), FloatDType::F32)
494 .to_contiguous()
495 .storage::<f32>()
496 .to_vec();
497 let reduced = |tensor: QuantizedTensor<Flex>| {
498 Flex::dequantize(tensor, FloatDType::F32).storage::<f32>()[0]
499 };
500
501 assert_eq!(
502 reduced(Flex::q_max(swapped.clone())),
503 values.iter().copied().fold(f32::MIN, f32::max)
504 );
505 assert_eq!(
506 reduced(Flex::q_min(swapped.clone())),
507 values.iter().copied().fold(f32::MAX, f32::min)
508 );
509 assert_eq!(
510 reduced(Flex::q_max_abs(swapped)).abs(),
511 values.iter().map(|value| value.abs()).fold(0.0, f32::max)
512 );
513 }
514
515 #[test]
516 fn float_formats_reconstruct_values_on_their_grid() {
517 for (value, values) in [
518 (
519 QuantValue::E2M1,
520 [0.0, 0.5, -1.5, 6.0, -3.0, 2.0, 4.0, -6.0],
521 ),
522 (
523 QuantValue::E4M3,
524 [448.0, -2.5, 0.125, 1.0, -0.0, 3.5, -448.0, 64.0],
525 ),
526 ] {
527 let scheme = QuantScheme::default()
528 .with_value(value)
529 .with_store(QuantStore::Native);
530 let input = FlexTensor::from_data(TensorData::new(values.to_vec(), [8]));
531
532 let output = Flex::dequantize(Flex::quantize_dynamic(input, &scheme), FloatDType::F32);
533
534 assert_eq!(output.to_contiguous().storage::<f32>(), values, "{value:?}");
535 }
536 }
537
538 #[test]
539 fn argmax_over_float_codes_follows_the_values() {
540 let scheme = QuantScheme::default()
541 .with_value(QuantValue::E2M1)
542 .with_store(QuantStore::Native);
543 let input = FlexTensor::from_data(TensorData::new(vec![-6.0f32, -1.0, 0.5, -0.5], [1, 4]));
544 let quantized = Flex::quantize_dynamic(input, &scheme);
545
546 let argmax = Flex::q_argmax(quantized.clone(), 1, burn_std::IntDType::I64);
547 let argmin = Flex::q_argmin(quantized, 1, burn_std::IntDType::I64);
548
549 assert_eq!(argmax.to_contiguous().storage::<i64>(), [2]);
550 assert_eq!(argmin.to_contiguous().storage::<i64>(), [0]);
551 }
552
553 #[test]
554 fn float_schemes_keep_their_layout_through_their_data() {
555 let mxfp4 = QuantScheme::default()
556 .with_value(QuantValue::E2M1)
557 .per_block([32], ScaleDtype::UE8M0);
558 for scheme in [
559 QuantScheme::default().with_value(QuantValue::E4M3),
560 QuantScheme::default()
561 .with_value(QuantValue::E5M2)
562 .with_store(QuantStore::Native),
563 mxfp4,
564 mxfp4.with_store(QuantStore::PackedNative(0)),
565 ] {
566 let quantized = Flex::quantize_dynamic(ramp([4, 32]), &scheme);
567 let data = data_of(quantized.clone());
568 assert_eq!(data.dtype(), DType::QFloat(scheme));
569
570 let reloaded = Flex::q_from_data(data.clone(), &Default::default());
571 assert_eq!(
572 data_of(reloaded.clone()).as_bytes(),
573 data.as_bytes(),
574 "{scheme:?}"
575 );
576 assert_eq!(
577 Flex::dequantize(reloaded, FloatDType::F32)
578 .to_contiguous()
579 .storage::<f32>(),
580 Flex::dequantize(quantized, FloatDType::F32)
581 .to_contiguous()
582 .storage::<f32>(),
583 "{scheme:?}"
584 );
585 }
586 }
587
588 #[test]
589 fn test_quantize_dequantize_roundtrip() {
590 let values = vec![0.0f32, 1.0, 2.0, 3.0, 4.0, 5.0];
592 let tensor = FlexTensor::from_data(TensorData::new(values.clone(), [2, 3]));
593
594 let scheme = QuantScheme::default()
595 .with_value(QuantValue::Q8S)
596 .with_store(QuantStore::Native);
597
598 let scale: f32 = 2.0 * 5.0 / 254.0;
602 let scales_tensor = FlexTensor::from_data(TensorData::new(vec![scale], [1]));
603
604 let qparams = QuantizationParametersPrimitive {
605 scales: scales_tensor,
606 global: None,
607 };
608
609 let qtensor = Flex::quantize(tensor, &scheme, qparams);
611 assert_eq!(qtensor.tensor.shape().to_vec(), vec![2, 3]);
612 assert_eq!(qtensor.tensor.dtype(), DType::I8);
613
614 let q_vals: &[i8] = qtensor.tensor.storage();
616 assert_eq!(q_vals[0], 0);
618 assert_eq!(q_vals[1], 25);
619 assert_eq!(q_vals[5], 127);
620
621 let result = Flex::dequantize(qtensor, FloatDType::F32);
623 assert_eq!(result.shape().to_vec(), vec![2, 3]);
624 assert_eq!(result.dtype(), DType::F32);
625
626 let result_vals: &[f32] = result.storage();
627 for (orig, deq) in values.iter().zip(result_vals.iter()) {
629 assert!((orig - deq).abs() < 0.05, "orig={orig}, dequantized={deq}");
630 }
631 }
632
633 #[test]
634 fn test_quantize_dequantize_negative_values() {
635 let values = vec![-3.0f32, -1.5, 0.0, 1.5, 3.0];
636 let tensor = FlexTensor::from_data(TensorData::new(values.clone(), [5]));
637
638 let scheme = QuantScheme::default()
639 .with_value(QuantValue::Q8S)
640 .with_store(QuantStore::Native);
641
642 let scale: f32 = 2.0 * 3.0 / 254.0;
643 let scales_tensor = FlexTensor::from_data(TensorData::new(vec![scale], [1]));
644
645 let qparams = QuantizationParametersPrimitive {
646 scales: scales_tensor,
647 global: None,
648 };
649
650 let qtensor = Flex::quantize(tensor, &scheme, qparams);
651 let result = Flex::dequantize(qtensor, FloatDType::F32);
652 let result_vals: &[f32] = result.storage();
653
654 for (orig, deq) in values.iter().zip(result_vals.iter()) {
655 assert!((orig - deq).abs() < 0.05, "orig={orig}, dequantized={deq}");
656 }
657 }
658
659 #[test]
660 fn test_q_from_data_into_data_roundtrip() {
661 let values = vec![0i8, 25, 51, 76, 102, 127];
663 let scale = 0.03937008f32;
664 let scheme = QuantScheme::default()
665 .with_value(QuantValue::Q8S)
666 .with_store(QuantStore::Native);
667
668 let data = TensorData::quantized(values.clone(), [2, 3], scheme, &[scale], None);
669
670 let qtensor = Flex::q_from_data(data, &Default::default());
672 assert_eq!(qtensor.tensor.shape().to_vec(), vec![2, 3]);
673 assert_eq!(qtensor.scales, vec![scale]);
674
675 let float_tensor = Flex::dequantize(qtensor, FloatDType::F32);
677 let result: &[f32] = float_tensor.storage();
678 assert!((result[0]).abs() < 0.01); assert!((result[5] - 5.0).abs() < 0.05); }
681
682 #[test]
683 fn test_quantize_zero_tensor() {
684 let values = vec![0.0f32; 4];
685 let tensor = FlexTensor::from_data(TensorData::new(values, [4]));
686
687 let scheme = QuantScheme::default()
688 .with_value(QuantValue::Q8S)
689 .with_store(QuantStore::Native);
690
691 let scales_tensor = FlexTensor::from_data(TensorData::new(vec![0.0f32], [1]));
693 let qparams = QuantizationParametersPrimitive {
694 scales: scales_tensor,
695 global: None,
696 };
697
698 let qtensor = Flex::quantize(tensor, &scheme, qparams);
699 let q_vals: &[i8] = qtensor.tensor.storage();
700 assert_eq!(q_vals, &[0, 0, 0, 0]);
701 }
702
703 #[test]
707 fn test_quantize_dynamic_honors_scale_dtype_precision() {
708 let values = vec![-3.0f32, -1.5, 0.0, 1.5, 3.0, 4.5];
710 let scheme = QuantScheme::default()
711 .with_value(QuantValue::Q8S)
712 .with_store(QuantStore::Native);
713
714 let quantize_with = |dtype| {
715 let tensor = FlexTensor::from_data(TensorData::new(values.clone(), [2, 3]));
716 Flex::quantize_dynamic(tensor, &scheme.per_tensor(dtype)).scales[0]
717 };
718
719 let exact = quantize_with(ScaleDtype::F32);
720 let coarse = quantize_with(ScaleDtype::UE4M3);
721
722 assert_ne!(
723 exact, coarse,
724 "UE4M3 scale should differ from the exact f32 scale"
725 );
726 assert_eq!(
727 coarse,
728 scale_to_dtype(exact, ScaleDtype::UE4M3),
729 "stored scale should be the exact scale rounded to the scale dtype"
730 );
731 }
732
733 #[test]
734 fn test_quantize_dynamic_roundtrip() {
735 let values = vec![-3.0f32, -1.5, 0.0, 1.5, 3.0, 4.5];
736 let tensor = FlexTensor::from_data(TensorData::new(values.clone(), [2, 3]));
737
738 let scheme = QuantScheme::default()
739 .with_value(QuantValue::Q8S)
740 .with_store(QuantStore::Native);
741
742 let qtensor = Flex::quantize_dynamic(tensor, &scheme);
743 assert_eq!(qtensor.tensor.shape().to_vec(), vec![2, 3]);
744 assert_eq!(qtensor.scales.len(), 1);
745
746 let expected_scale: f32 = 2.0 * 4.5 / 254.0;
748 assert!(
749 (qtensor.scales[0] - expected_scale).abs() < 1e-6,
750 "scale={}, expected={}",
751 qtensor.scales[0],
752 expected_scale
753 );
754
755 let result = Flex::dequantize(qtensor, FloatDType::F32);
756 let result_vals: &[f32] = result.storage();
757 for (orig, deq) in values.iter().zip(result_vals.iter()) {
758 assert!((orig - deq).abs() < 0.1, "orig={orig}, dequantized={deq}");
759 }
760 }
761
762 #[test]
763 fn test_per_block_quantize_dequantize() {
764 use burn_std::quantization::BlockSize;
765
766 let values = vec![0.0f32, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
767 let tensor = FlexTensor::from_data(TensorData::new(values.clone(), [8]));
768
769 let block_size = BlockSize::new([4]);
770 let scheme = QuantScheme::default()
771 .with_value(QuantValue::Q8S)
772 .per_block(block_size.as_slice(), ScaleDtype::F32)
773 .with_store(QuantStore::Native);
774
775 let scale_1: f32 = 2.0 * 3.0 / 254.0;
778 let scale_2: f32 = 2.0 * 7.0 / 254.0;
779 let scales_tensor = FlexTensor::from_data(TensorData::new(vec![scale_1, scale_2], [2]));
780
781 let qparams = QuantizationParametersPrimitive {
782 scales: scales_tensor,
783 global: None,
784 };
785
786 let qtensor = Flex::quantize(tensor, &scheme, qparams);
787 assert_eq!(qtensor.scales.len(), 2);
788
789 let result = Flex::dequantize(qtensor, FloatDType::F32);
790 let result_vals: &[f32] = result.storage();
791
792 for (orig, deq) in values.iter().zip(result_vals.iter()) {
793 assert!((orig - deq).abs() < 0.1, "orig={orig}, dequantized={deq}");
794 }
795 }
796
797 #[test]
798 fn test_quantize_dynamic_block() {
799 use burn_std::quantization::BlockSize;
800
801 let values = vec![-2.0f32, -1.0, 0.0, 1.0, 4.0, 5.0, 6.0, 7.0];
802 let tensor = FlexTensor::from_data(TensorData::new(values.clone(), [8]));
803
804 let block_size = BlockSize::new([4]);
805 let scheme = QuantScheme::default()
806 .with_value(QuantValue::Q8S)
807 .per_block(block_size.as_slice(), ScaleDtype::F32)
808 .with_store(QuantStore::Native);
809
810 let qtensor = Flex::quantize_dynamic(tensor, &scheme);
811 assert_eq!(qtensor.scales.len(), 2);
812
813 let expected_scale_1: f32 = 2.0 * 2.0 / 254.0;
816 let expected_scale_2: f32 = 2.0 * 7.0 / 254.0;
817 assert!((qtensor.scales[0] - expected_scale_1).abs() < 1e-6);
818 assert!((qtensor.scales[1] - expected_scale_2).abs() < 1e-6);
819
820 let result = Flex::dequantize(qtensor, FloatDType::F32);
821 let result_vals: &[f32] = result.storage();
822 for (orig, deq) in values.iter().zip(result_vals.iter()) {
823 assert!((orig - deq).abs() < 0.1, "orig={orig}, dequantized={deq}");
824 }
825 }
826
827 #[test]
828 fn test_quantize_dynamic_q8f() {
829 let values = vec![-5.0f32, -2.5, 0.0, 2.5, 5.0, 7.5];
831 let tensor = FlexTensor::from_data(TensorData::new(values.clone(), [6]));
832
833 let scheme = QuantScheme::default()
834 .with_value(QuantValue::Q8F)
835 .with_store(QuantStore::Native);
836
837 let qtensor = Flex::quantize_dynamic(tensor, &scheme);
838
839 let expected_scale: f32 = 2.0 * 7.5 / 255.0;
842 assert!(
843 (qtensor.scales[0] - expected_scale).abs() < 1e-6,
844 "scale={}, expected={}",
845 qtensor.scales[0],
846 expected_scale
847 );
848
849 let result = Flex::dequantize(qtensor, FloatDType::F32);
850 let result_vals: &[f32] = result.storage();
851 for (orig, deq) in values.iter().zip(result_vals.iter()) {
852 assert!((orig - deq).abs() < 0.1, "orig={orig}, dequantized={deq}");
853 }
854 }
855
856 #[test]
857 fn test_block_quantized_transpose_dequantize() {
858 use burn_std::quantization::BlockSize;
859
860 let values = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
862 let tensor = FlexTensor::from_data(TensorData::new(values, [2, 4]));
863
864 let block_size = BlockSize::new([4]);
865 let scheme = QuantScheme::default()
866 .with_value(QuantValue::Q8S)
867 .per_block(block_size.as_slice(), ScaleDtype::F32)
868 .with_store(QuantStore::Native);
869
870 let qtensor = Flex::quantize_dynamic(tensor, &scheme);
871
872 let transposed = Flex::q_swap_dims(qtensor, 0, 1);
874 assert_eq!(transposed.tensor.shape().to_vec(), vec![4, 2]);
875
876 let result = Flex::dequantize(transposed, FloatDType::F32);
877 let result_vals: &[f32] = result.storage();
878
879 let expected = [1.0f32, 5.0, 2.0, 6.0, 3.0, 7.0, 4.0, 8.0];
881 for (exp, deq) in expected.iter().zip(result_vals.iter()) {
882 assert!(
883 (exp - deq).abs() < 0.15,
884 "expected={exp}, dequantized={deq}"
885 );
886 }
887 }
888
889 #[test]
890 fn test_block_quantized_select() {
891 use burn_std::quantization::BlockSize;
892
893 let values = vec![1.0f32, 2.0, 3.0, 4.0, 10.0, 20.0, 30.0, 40.0];
895 let tensor = FlexTensor::from_data(TensorData::new(values, [2, 4]));
896
897 let block_size = BlockSize::new([4]);
898 let scheme = QuantScheme::default()
899 .with_value(QuantValue::Q8S)
900 .per_block(block_size.as_slice(), ScaleDtype::F32)
901 .with_store(QuantStore::Native);
902
903 let qtensor = Flex::quantize_dynamic(tensor, &scheme);
904
905 let indices = FlexTensor::from_data(TensorData::new(vec![1i64], [1]));
907 let selected = Flex::q_select(qtensor, 0, indices);
908 assert_eq!(selected.tensor.shape().to_vec(), vec![1, 4]);
909
910 let result = Flex::dequantize(selected, FloatDType::F32);
911 let result_vals: &[f32] = result.storage();
912 let expected = [10.0f32, 20.0, 30.0, 40.0];
913 for (exp, deq) in expected.iter().zip(result_vals.iter()) {
914 assert!((exp - deq).abs() < 0.5, "expected={exp}, dequantized={deq}");
915 }
916 }
917
918 #[test]
919 fn test_block_quantized_flip_dequantize() {
920 use burn_std::quantization::BlockSize;
921
922 let values = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
923 let tensor = FlexTensor::from_data(TensorData::new(values, [2, 4]));
924
925 let block_size = BlockSize::new([4]);
926 let scheme = QuantScheme::default()
927 .with_value(QuantValue::Q8S)
928 .per_block(block_size.as_slice(), ScaleDtype::F32)
929 .with_store(QuantStore::Native);
930
931 let qtensor = Flex::quantize_dynamic(tensor, &scheme);
932
933 let flipped = Flex::q_flip(qtensor, &[0]);
935 assert_eq!(flipped.tensor.shape().to_vec(), vec![2, 4]);
936
937 let result = Flex::dequantize(flipped, FloatDType::F32);
938 let result_vals: &[f32] = result.storage();
939 let expected = [5.0f32, 6.0, 7.0, 8.0, 1.0, 2.0, 3.0, 4.0];
940 for (exp, deq) in expected.iter().zip(result_vals.iter()) {
941 assert!(
942 (exp - deq).abs() < 0.15,
943 "expected={exp}, dequantized={deq}"
944 );
945 }
946 }
947
948 #[test]
949 fn test_quantize_dynamic_f64_tensor() {
950 use burn_backend::quantization::QuantValue;
951
952 let values = vec![0.0f64, 1.0, 2.0, 3.0, 4.0, 5.0];
953 let tensor = FlexTensor::new(
954 Bytes::from_elems(values),
955 Layout::contiguous([6].into()),
956 DType::F64,
957 );
958 assert_eq!(tensor.dtype(), DType::F64);
959
960 let scheme = QuantScheme::default()
961 .with_value(QuantValue::Q8S)
962 .with_store(QuantStore::Native);
963
964 let qtensor = Flex::quantize_dynamic(tensor, &scheme);
965 assert_eq!(qtensor.tensor.dtype(), DType::I8);
966
967 let result = Flex::dequantize(qtensor, FloatDType::F32);
969 let result_vals: &[f32] = result.storage();
970 let expected = [0.0f32, 1.0, 2.0, 3.0, 4.0, 5.0];
971 for (exp, deq) in expected.iter().zip(result_vals.iter()) {
972 assert!(
973 (exp - deq).abs() < 0.15,
974 "expected={exp}, dequantized={deq}"
975 );
976 }
977 }
978
979 #[test]
980 fn test_dequantize_f64() {
981 let values = vec![0.0f32, 1.0, 2.0, 3.0];
982 let tensor = FlexTensor::from_data(TensorData::new(values.clone(), [4]));
983
984 let scheme = QuantScheme::default()
985 .with_value(QuantValue::Q8S)
986 .with_store(QuantStore::Native);
987
988 let qtensor = Flex::quantize_dynamic(tensor, &scheme);
989 let result = Flex::dequantize(qtensor, FloatDType::F64);
990 assert_eq!(result.dtype(), DType::F64);
991 let result_vals: &[f64] = result.storage();
992 for (orig, deq) in values.iter().zip(result_vals.iter()) {
993 assert!(
994 (*orig as f64 - deq).abs() < 0.05,
995 "orig={orig}, dequantized={deq}"
996 );
997 }
998 }
999
1000 #[test]
1001 fn test_dequantize_f16() {
1002 let values = vec![0.0f32, 1.0, 2.0, 3.0];
1003 let tensor = FlexTensor::from_data(TensorData::new(values.clone(), [4]));
1004
1005 let scheme = QuantScheme::default()
1006 .with_value(QuantValue::Q8S)
1007 .with_store(QuantStore::Native);
1008
1009 let qtensor = Flex::quantize_dynamic(tensor, &scheme);
1010 let result = Flex::dequantize(qtensor, FloatDType::F16);
1011 assert_eq!(result.dtype(), DType::F16);
1012 let result_vals: &[f16] = result.storage();
1013 for (orig, deq) in values.iter().zip(result_vals.iter()) {
1014 assert!(
1015 (*orig - f32::from(*deq)).abs() < 0.05,
1016 "orig={orig}, dequantized={deq}"
1017 );
1018 }
1019 }
1020}