1use alloc::{vec, vec::Vec};
2
3use burn_backend::{
4 DType, ExecutionError, Shape, TensorData, TensorMetadata, TensorPrimitive, get_device_settings,
5 ops::{FloatTensorOps, QTensorOps},
6 quantization::{
7 QParams, QuantLevel, QuantMode, QuantPropagation, QuantScheme, QuantStore, QuantValue,
8 QuantizationParametersPrimitive, QuantizedBytes, params_shape, scale_to_param,
9 },
10 tensor::{FloatTensor, IntTensor, QuantizedTensor},
11};
12use burn_std::{FloatDType, IntDType};
13use ndarray::ArrayD;
14
15use crate::{
16 NdArray, NdArrayDevice, NdArrayQTensor, NdArrayTensor, SharedArray, element::QuantElement,
17 execute_with_dtype, execute_with_int_dtype, execute_with_int_out_dtype,
18 execute_with_numeric_dtype, slice,
19};
20
21use super::quantization::{Quantization, QuantizationStrategy, SymmetricQuantization};
22use super::{NdArrayMathOps, NdArrayOps};
23
24impl QTensorOps<Self> for NdArray {
25 fn q_from_data(data: TensorData, _device: &NdArrayDevice) -> QuantizedTensor<Self> {
26 match data.dtype {
27 DType::QFloat(scheme) => {
28 let shape = data.shape.clone();
29 let num_elements = data.num_elements();
30 let q_bytes = QuantizedBytes {
31 bytes: data.into_bytes(),
32 scheme,
33 num_elements,
34 };
35
36 match scheme {
37 QuantScheme {
38 level: QuantLevel::Tensor | QuantLevel::Block(_),
39 mode: QuantMode::Symmetric,
40 value: QuantValue::Q8F | QuantValue::Q8S,
41 ..
42 } => {
43 let (values, qparams) = q_bytes.into_vec_i8();
45 let data = TensorData::new(values, shape);
46 let scheme = scheme.with_store(QuantStore::Native);
48
49 let qparams = qparams
50 .scales
51 .into_iter()
52 .map(|scales| QParams { scales })
53 .collect();
54
55 NdArrayQTensor {
56 qtensor: NdArrayTensor::from_data(data),
57 scheme,
58 qparams,
59 }
60 }
61 QuantScheme {
62 value:
63 QuantValue::Q4F
64 | QuantValue::Q4S
65 | QuantValue::Q2F
66 | QuantValue::Q2S
67 | QuantValue::E2M1
68 | QuantValue::E4M3
69 | QuantValue::E5M2,
70 ..
71 } => unimplemented!("from_data not supported for scheme {scheme:?}"),
72 QuantScheme {
73 level: QuantLevel::BlockTensor { .. },
74 ..
75 } => unimplemented!("two-level quantization is not supported yet"),
76 }
77 }
78 _ => panic!(
79 "Invalid dtype (expected DType::QFloat, got {:?})",
80 data.dtype
81 ),
82 }
83 }
84
85 fn quantize(
86 tensor: FloatTensor<Self>,
87 scheme: &QuantScheme,
88 qparams: QuantizationParametersPrimitive<Self>,
89 ) -> QuantizedTensor<Self> {
90 let shape = tensor.shape();
91 let data_f = tensor.into_data();
92 let scales = qparams.scales.into_data().convert::<f32>();
93 let scales: Vec<f32> = scales
96 .iter::<f32>()
97 .map(|s| scale_to_param(s, scheme.param))
98 .collect();
99
100 let (data, qparams) = match scheme {
102 QuantScheme {
103 level: QuantLevel::Tensor,
104 mode: QuantMode::Symmetric,
105 #[cfg(not(feature = "export_tests"))]
108 value: QuantValue::Q8F | QuantValue::Q8S | QuantValue::Q2S,
109 #[cfg(feature = "export_tests")]
112 value:
113 QuantValue::Q8F
114 | QuantValue::Q8S
115 | QuantValue::Q4F
116 | QuantValue::Q4S
117 | QuantValue::Q2F
118 | QuantValue::Q2S,
119 store: QuantStore::Native,
120 ..
121 } => {
122 let scales = scales[0];
123 let strategy = QuantizationStrategy::PerTensorSymmetric(
124 SymmetricQuantization::init(scales, scheme.value),
125 );
126 let values = strategy.quantize(data_f.as_slice().unwrap());
127 (
128 TensorData::quantized(values, shape.clone(), *scheme, &[scales]),
129 vec![QParams { scales }],
130 )
131 }
132 QuantScheme {
133 level: QuantLevel::Block(block_size),
134 mode: QuantMode::Symmetric,
135 #[cfg(not(feature = "export_tests"))]
136 value: QuantValue::Q8F | QuantValue::Q8S,
137 #[cfg(feature = "export_tests")]
138 value:
139 QuantValue::Q8F
140 | QuantValue::Q8S
141 | QuantValue::Q4F
142 | QuantValue::Q4S
143 | QuantValue::Q2F
144 | QuantValue::Q2S,
145 store: QuantStore::Native,
146 ..
147 } => {
148 let scales = scales.as_slice();
149 let (strategy, qparams) = scales
150 .iter()
151 .map(|&s| {
152 (
153 SymmetricQuantization::init(s, scheme.value),
154 QParams { scales: s },
155 )
156 })
157 .unzip();
158 let strategy = QuantizationStrategy::PerBlockSymmetric(strategy, *block_size);
159 let values = strategy.quantize(data_f.as_slice().unwrap());
160 (
161 TensorData::quantized(values, shape.clone(), *scheme, scales),
162 qparams,
163 )
164 }
165 scheme => unimplemented!("Quantization not supported for scheme {scheme:?}"),
166 };
167
168 let num_elements = data.num_elements();
169 let q_bytes = QuantizedBytes {
170 bytes: data.into_bytes(),
171 scheme: *scheme,
172 num_elements,
173 };
174 let (values, _) = q_bytes.into_vec_i8();
175 let data = TensorData::new(values, shape);
176
177 NdArrayQTensor {
178 qtensor: NdArrayTensor::from_data(data),
179 scheme: *scheme,
180 qparams,
181 }
182 }
183
184 fn dequantize(tensor: QuantizedTensor<Self>, dtype: FloatDType) -> FloatTensor<Self> {
185 let strategy = tensor.strategy();
186 let scheme = tensor.scheme;
187 let shape = tensor.shape();
188 let data = match tensor.qtensor {
189 NdArrayTensor::I8(storage) => {
190 let data = storage.into_shared().into_iter().collect();
191 dequantize(data, shape, scheme, &strategy, dtype.into())
192 }
193 _ => unreachable!(),
194 };
195 NdArrayTensor::from_data(data)
196 }
197
198 fn q_matmul(lhs: TensorPrimitive<Self>, rhs: TensorPrimitive<Self>) -> TensorPrimitive<Self> {
210 if let (TensorPrimitive::Float(l), TensorPrimitive::QFloat(r)) = (&lhs, &rhs)
211 && let Some(out) = ternary_matmul(l, r)
212 {
213 return TensorPrimitive::Float(out);
214 }
215
216 let mut propagation = QuantPropagation::Inhibit;
219 let mut scheme = QuantScheme::default();
220 let target_dtype: Option<FloatDType> = match (&lhs, &rhs) {
221 (TensorPrimitive::Float(t), _) | (_, TensorPrimitive::Float(t)) => {
222 Some(t.dtype().into())
223 }
224 _ => None,
225 };
226 let lhs = match lhs {
227 TensorPrimitive::Float(lhs) => lhs,
228 TensorPrimitive::QFloat(lhs) => {
229 let settings = get_device_settings::<Self>(&lhs.device());
230 propagation = settings.quantization.propagation;
231 scheme = lhs.scheme;
232 let float_dtype = target_dtype.unwrap_or(settings.float_dtype);
233 Self::dequantize(lhs, float_dtype)
234 }
235 };
236 let rhs = match rhs {
237 TensorPrimitive::Float(rhs) => rhs,
238 TensorPrimitive::QFloat(rhs) => {
239 let settings = get_device_settings::<Self>(&rhs.device());
240 propagation = settings.quantization.propagation;
241 scheme = rhs.scheme;
242 let float_dtype = target_dtype.unwrap_or(settings.float_dtype);
243 Self::dequantize(rhs, float_dtype)
244 }
245 };
246 let out_f = <Self as FloatTensorOps<Self>>::float_matmul(lhs, rhs);
247 match propagation {
248 QuantPropagation::Propagate => {
249 TensorPrimitive::QFloat(Self::quantize_dynamic(out_f, &scheme))
250 }
251 QuantPropagation::Inhibit => TensorPrimitive::Float(out_f),
252 }
253 }
254
255 fn q_to_device(
256 tensor: QuantizedTensor<Self>,
257 _device: &NdArrayDevice,
258 ) -> QuantizedTensor<Self> {
259 tensor
260 }
261
262 fn q_reshape(tensor: QuantizedTensor<Self>, shape: Shape) -> QuantizedTensor<Self> {
263 NdArrayQTensor {
264 qtensor: execute_with_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
265 NdArrayOps::reshape(array, shape)
266 }),
267 scheme: tensor.scheme,
268 qparams: tensor.qparams,
269 }
270 }
271
272 async fn q_into_data(tensor: QuantizedTensor<Self>) -> Result<TensorData, ExecutionError> {
273 let shape = tensor.qtensor.shape();
274 let scales = tensor.qparams.iter().map(|q| q.scales).collect::<Vec<_>>();
275 Ok(execute_with_numeric_dtype!(
276 tensor.qtensor,
277 E,
278 |array: SharedArray<E>| {
279 let values = array.into_iter().collect();
280 TensorData::quantized(values, shape, tensor.scheme, &scales)
281 }
282 ))
283 }
284
285 fn q_swap_dims(
286 tensor: QuantizedTensor<Self>,
287 dim1: usize,
288 dim2: usize,
289 ) -> QuantizedTensor<Self> {
290 let mut axes = (0..tensor.qtensor.shape().num_dims()).collect::<Vec<_>>();
291 axes.swap(dim1, dim2);
292 Self::q_permute(tensor, &axes)
293 }
294
295 fn q_permute(tensor: QuantizedTensor<Self>, axes: &[usize]) -> QuantizedTensor<Self> {
296 let (scheme, qparams) = match tensor.scheme.level {
297 QuantLevel::Tensor => (tensor.scheme, tensor.qparams),
298 QuantLevel::Block(block_size) => {
299 let shape = tensor.qtensor.shape();
300 let qparams_shape = params_shape(&shape, tensor.scheme.level);
301 let scales = tensor
302 .qparams
303 .into_iter()
304 .map(|params| params.scales)
305 .collect();
306 let scales = ArrayD::from_shape_vec(qparams_shape.as_slice(), scales)
307 .unwrap()
308 .into_shared();
309 let scales = NdArrayOps::permute(scales, axes);
310 let qparams = scales
311 .into_iter()
312 .map(|scales| QParams { scales })
313 .collect();
314
315 let block_size = block_size.to_dim_vec(shape.num_dims());
316 let block_size = axes
317 .iter()
318 .map(|axis| block_size[*axis])
319 .collect::<Vec<_>>();
320 let scheme = tensor
321 .scheme
322 .with_level(QuantLevel::block(block_size.as_slice()));
323
324 (scheme, qparams)
325 }
326 QuantLevel::BlockTensor { .. } => {
327 unimplemented!("two-level quantization is not supported yet")
328 }
329 };
330
331 NdArrayQTensor {
332 qtensor: execute_with_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
333 NdArrayOps::permute(array, axes)
334 }),
335 scheme,
336 qparams,
337 }
338 }
339
340 fn q_flip(tensor: QuantizedTensor<Self>, axes: &[usize]) -> QuantizedTensor<Self> {
341 NdArrayQTensor {
342 qtensor: execute_with_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
343 NdArrayOps::flip(array, axes)
344 }),
345 scheme: tensor.scheme,
346 qparams: tensor.qparams,
347 }
348 }
349
350 fn q_gather(
351 dim: usize,
352 tensor: QuantizedTensor<Self>,
353 indices: IntTensor<Self>,
354 ) -> QuantizedTensor<Self> {
355 let qtensor = execute_with_int_dtype!(indices, IntElem, |idx_array: SharedArray<
356 IntElem,
357 >|
358 -> NdArrayTensor {
359 execute_with_numeric_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
360 NdArrayOps::gather(dim, array, idx_array)
361 })
362 });
363 NdArrayQTensor {
364 qtensor,
365 scheme: tensor.scheme,
366 qparams: tensor.qparams,
367 }
368 }
369
370 fn q_select(
371 tensor: QuantizedTensor<Self>,
372 dim: usize,
373 indices: IntTensor<Self>,
374 ) -> QuantizedTensor<Self> {
375 let qtensor = execute_with_int_dtype!(indices, IntElem, |idx_array: SharedArray<
376 IntElem,
377 >|
378 -> NdArrayTensor {
379 execute_with_numeric_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
380 NdArrayMathOps::select(array, dim, idx_array)
381 })
382 });
383 NdArrayQTensor {
384 qtensor,
385 scheme: tensor.scheme,
386 qparams: tensor.qparams,
387 }
388 }
389
390 fn q_slice(
391 tensor: QuantizedTensor<Self>,
392 slices: &[burn_backend::Slice],
393 ) -> QuantizedTensor<Self> {
394 NdArrayQTensor {
395 qtensor: slice!(tensor.qtensor, slices),
396 scheme: tensor.scheme,
397 qparams: tensor.qparams,
398 }
399 }
400
401 fn q_argmax(tensor: QuantizedTensor<Self>, dim: usize, out_dtype: IntDType) -> IntTensor<Self> {
402 execute_with_int_out_dtype!(out_dtype, I, {
403 execute_with_numeric_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
404 NdArrayMathOps::argmax::<I>(array, dim)
405 })
406 })
407 }
408
409 fn q_argmin(tensor: QuantizedTensor<Self>, dim: usize, out_dtype: IntDType) -> IntTensor<Self> {
410 execute_with_int_out_dtype!(out_dtype, I, {
411 execute_with_numeric_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
412 NdArrayMathOps::argmin::<I>(array, dim)
413 })
414 })
415 }
416
417 fn q_expand(tensor: QuantizedTensor<Self>, shape: Shape) -> QuantizedTensor<Self> {
418 NdArrayQTensor {
419 qtensor: execute_with_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
420 NdArrayOps::expand(array, shape)
421 }),
422 scheme: tensor.scheme,
423 qparams: tensor.qparams,
424 }
425 }
426}
427
428fn ternary_matmul(
440 lhs: &FloatTensor<NdArray>,
441 rhs: &NdArrayQTensor,
442) -> Option<FloatTensor<NdArray>> {
443 if !matches!(
445 rhs.scheme,
446 QuantScheme {
447 value: QuantValue::Q2S,
448 mode: QuantMode::Symmetric,
449 level: QuantLevel::Tensor,
450 ..
451 }
452 ) {
453 return None;
454 }
455 if !matches!(lhs, NdArrayTensor::F32(_)) {
457 return None;
458 }
459
460 let wdims = rhs.qtensor.shape().to_vec();
462 if wdims.len() != 2 {
463 return None;
464 }
465 let (k, n) = (wdims[0], wdims[1]);
466
467 let ldims = lhs.shape().to_vec();
469 if ldims.len() < 2 || *ldims.last().unwrap() != k {
470 return None;
471 }
472 let m: usize = ldims[..ldims.len() - 1].iter().product();
473
474 let gamma = rhs.qparams.first()?.scales;
476
477 let a_data = lhs.clone().into_data();
479 let a = a_data.as_slice::<f32>().ok()?;
480 let w_data = rhs.qtensor.clone().into_data();
481 let w = w_data.as_slice::<i8>().ok()?;
482 if a.len() != m * k || w.len() != k * n {
483 return None;
484 }
485
486 let mut out = vec![0f32; m * n];
488 for i in 0..m {
489 let arow = &a[i * k..(i + 1) * k];
490 let orow = &mut out[i * n..(i + 1) * n];
491 for kk in 0..k {
492 let x = arow[kk];
493 let wrow = &w[kk * n..(kk + 1) * n];
494 for (o, &t) in orow.iter_mut().zip(wrow) {
495 match t {
496 1 => *o += x,
497 -1 => *o -= x,
498 _ => {} }
500 }
501 }
502 for o in orow.iter_mut() {
503 *o *= gamma;
504 }
505 }
506
507 let mut out_dims = ldims[..ldims.len() - 1].to_vec();
508 out_dims.push(n);
509 Some(NdArrayTensor::from_data(TensorData::new(
510 out,
511 Shape::from(out_dims),
512 )))
513}
514
515fn dequantize<Q: QuantElement>(
516 data: Vec<Q>,
517 shape: Shape,
518 scheme: QuantScheme,
519 strategy: &QuantizationStrategy,
520 dtype: DType,
521) -> TensorData {
522 let qparams = match strategy {
523 QuantizationStrategy::PerTensorSymmetric(quant) => vec![quant.scale],
524 QuantizationStrategy::PerBlockSymmetric(quant, _block_size) => {
525 quant.iter().map(|q| q.scale).collect()
526 }
527 };
528 let q_bytes = QuantizedBytes::new(data, scheme, &qparams);
529 let (values, _qparams) = q_bytes.into_vec_i8();
530 let values = match strategy {
531 QuantizationStrategy::PerTensorSymmetric(quant) => quant.dequantize(&values),
532 QuantizationStrategy::PerBlockSymmetric(quant, block_size) => {
533 let block_size = block_size.to_dim_vec(shape.num_dims());
534 let qparams_shape = params_shape(&shape, scheme.level);
535
536 values
537 .into_iter()
538 .enumerate()
539 .map(|(index, value)| {
540 let mut index = index;
541 let mut qparam_index = 0;
542 let mut qparam_stride = 1;
543
544 for dim in (0..shape.num_dims()).rev() {
545 let coordinate = index % shape[dim];
546 index /= shape[dim];
547 qparam_index += coordinate / block_size[dim] as usize * qparam_stride;
548 qparam_stride *= qparams_shape[dim];
549 }
550
551 quant[qparam_index].dequantize_one(value)
552 })
553 .collect()
554 }
555 };
556 TensorData::new(values, shape).convert_dtype(dtype)
557}