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