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,
9 },
10 tensor::{FloatTensor, IntTensor, QuantizedTensor},
11};
12use burn_std::{FloatDType, IntDType};
13
14use crate::{
15 NdArray, NdArrayDevice, NdArrayQTensor, NdArrayTensor, SharedArray, element::QuantElement,
16 execute_with_dtype, execute_with_int_dtype, execute_with_int_out_dtype,
17 execute_with_numeric_dtype, slice,
18};
19
20use super::quantization::{QuantizationStrategy, SymmetricQuantization};
21use super::{NdArrayMathOps, NdArrayOps};
22
23impl QTensorOps<Self> for NdArray {
24 fn q_from_data(data: TensorData, _device: &NdArrayDevice) -> QuantizedTensor<Self> {
25 match data.dtype {
26 DType::QFloat(scheme) => {
27 let shape = data.shape.clone();
28 let num_elements = data.num_elements();
29 let q_bytes = QuantizedBytes {
30 bytes: data.into_bytes(),
31 scheme,
32 num_elements,
33 };
34
35 match scheme {
36 QuantScheme {
37 level: QuantLevel::Tensor | QuantLevel::Block(_),
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 qparams = qparams
49 .scales
50 .into_iter()
51 .map(|scales| QParams { scales })
52 .collect();
53
54 NdArrayQTensor {
55 qtensor: NdArrayTensor::from_data(data),
56 scheme,
57 qparams,
58 }
59 }
60 QuantScheme {
61 value:
62 QuantValue::Q4F
63 | QuantValue::Q4S
64 | QuantValue::Q2F
65 | QuantValue::Q2S
66 | QuantValue::E2M1
67 | QuantValue::E4M3
68 | QuantValue::E5M2,
69 ..
70 } => unimplemented!("from_data not supported for scheme {scheme:?}"),
71 }
72 }
73 _ => panic!(
74 "Invalid dtype (expected DType::QFloat, got {:?})",
75 data.dtype
76 ),
77 }
78 }
79
80 fn quantize(
81 tensor: FloatTensor<Self>,
82 scheme: &QuantScheme,
83 qparams: QuantizationParametersPrimitive<Self>,
84 ) -> QuantizedTensor<Self> {
85 let shape = tensor.shape();
86 let data_f = tensor.into_data();
87 let scales = qparams.scales.into_data().convert::<f32>();
88
89 let (data, qparams) = match scheme {
91 QuantScheme {
92 level: QuantLevel::Tensor,
93 mode: QuantMode::Symmetric,
94 #[cfg(not(feature = "export_tests"))]
97 value: QuantValue::Q8F | QuantValue::Q8S | QuantValue::Q2S,
98 #[cfg(feature = "export_tests")]
101 value:
102 QuantValue::Q8F
103 | QuantValue::Q8S
104 | QuantValue::Q4F
105 | QuantValue::Q4S
106 | QuantValue::Q2F
107 | QuantValue::Q2S,
108 store: QuantStore::Native,
109 ..
110 } => {
111 let scales = scales.iter().next().unwrap();
112 let strategy = QuantizationStrategy::PerTensorSymmetric(
113 SymmetricQuantization::init(scales, scheme.value),
114 );
115 let values = strategy.quantize(data_f.as_slice().unwrap());
116 (
117 TensorData::quantized(values, shape.clone(), *scheme, &[scales]),
118 vec![QParams { scales }],
119 )
120 }
121 QuantScheme {
122 level: QuantLevel::Block(block_size),
123 mode: QuantMode::Symmetric,
124 #[cfg(not(feature = "export_tests"))]
125 value: QuantValue::Q8F | QuantValue::Q8S,
126 #[cfg(feature = "export_tests")]
127 value:
128 QuantValue::Q8F
129 | QuantValue::Q8S
130 | QuantValue::Q4F
131 | QuantValue::Q4S
132 | QuantValue::Q2F
133 | QuantValue::Q2S,
134 store: QuantStore::Native,
135 ..
136 } => {
137 let scales = scales.as_slice().unwrap();
138 let (strategy, qparams) = scales
139 .iter()
140 .map(|&s| {
141 (
142 SymmetricQuantization::init(s, scheme.value),
143 QParams { scales: s },
144 )
145 })
146 .unzip();
147 let strategy = QuantizationStrategy::PerBlockSymmetric(strategy, *block_size);
148 let values = strategy.quantize(data_f.as_slice().unwrap());
149 (
150 TensorData::quantized(values, shape.clone(), *scheme, scales),
151 qparams,
152 )
153 }
154 scheme => unimplemented!("Quantization not supported for scheme {scheme:?}"),
155 };
156
157 let num_elements = data.num_elements();
158 let q_bytes = QuantizedBytes {
159 bytes: data.into_bytes(),
160 scheme: *scheme,
161 num_elements,
162 };
163 let (values, _) = q_bytes.into_vec_i8();
164 let data = TensorData::new(values, shape);
165
166 NdArrayQTensor {
167 qtensor: NdArrayTensor::from_data(data),
168 scheme: *scheme,
169 qparams,
170 }
171 }
172
173 fn dequantize(tensor: QuantizedTensor<Self>, dtype: FloatDType) -> FloatTensor<Self> {
174 let strategy = tensor.strategy();
175 let scheme = tensor.scheme;
176 let shape = tensor.shape();
177 let data = match tensor.qtensor {
178 NdArrayTensor::I8(storage) => {
179 let data = storage.into_shared().into_iter().collect();
180 dequantize(data, shape, scheme, &strategy, dtype.into())
181 }
182 _ => unreachable!(),
183 };
184 NdArrayTensor::from_data(data)
185 }
186
187 fn q_matmul(lhs: TensorPrimitive<Self>, rhs: TensorPrimitive<Self>) -> TensorPrimitive<Self> {
199 if let (TensorPrimitive::Float(l), TensorPrimitive::QFloat(r)) = (&lhs, &rhs)
200 && let Some(out) = ternary_matmul(l, r)
201 {
202 return TensorPrimitive::Float(out);
203 }
204
205 let mut propagation = QuantPropagation::Inhibit;
208 let mut scheme = QuantScheme::default();
209 let target_dtype: Option<FloatDType> = match (&lhs, &rhs) {
210 (TensorPrimitive::Float(t), _) | (_, TensorPrimitive::Float(t)) => {
211 Some(t.dtype().into())
212 }
213 _ => None,
214 };
215 let lhs = match lhs {
216 TensorPrimitive::Float(lhs) => lhs,
217 TensorPrimitive::QFloat(lhs) => {
218 let settings = get_device_settings::<Self>(&lhs.device());
219 propagation = settings.quantization.propagation;
220 scheme = lhs.scheme;
221 let float_dtype = target_dtype.unwrap_or(settings.float_dtype);
222 Self::dequantize(lhs, float_dtype)
223 }
224 };
225 let rhs = match rhs {
226 TensorPrimitive::Float(rhs) => rhs,
227 TensorPrimitive::QFloat(rhs) => {
228 let settings = get_device_settings::<Self>(&rhs.device());
229 propagation = settings.quantization.propagation;
230 scheme = rhs.scheme;
231 let float_dtype = target_dtype.unwrap_or(settings.float_dtype);
232 Self::dequantize(rhs, float_dtype)
233 }
234 };
235 let out_f = <Self as FloatTensorOps<Self>>::float_matmul(lhs, rhs);
236 match propagation {
237 QuantPropagation::Propagate => {
238 TensorPrimitive::QFloat(Self::quantize_dynamic(out_f, &scheme))
239 }
240 QuantPropagation::Inhibit => TensorPrimitive::Float(out_f),
241 }
242 }
243
244 fn q_to_device(
245 tensor: QuantizedTensor<Self>,
246 _device: &NdArrayDevice,
247 ) -> QuantizedTensor<Self> {
248 tensor
249 }
250
251 fn q_reshape(tensor: QuantizedTensor<Self>, shape: Shape) -> QuantizedTensor<Self> {
252 NdArrayQTensor {
253 qtensor: execute_with_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
254 NdArrayOps::reshape(array, shape)
255 }),
256 scheme: tensor.scheme,
257 qparams: tensor.qparams,
258 }
259 }
260
261 async fn q_into_data(tensor: QuantizedTensor<Self>) -> Result<TensorData, ExecutionError> {
262 let shape = tensor.qtensor.shape();
263 let scales = tensor.qparams.iter().map(|q| q.scales).collect::<Vec<_>>();
264 Ok(execute_with_numeric_dtype!(
265 tensor.qtensor,
266 E,
267 |array: SharedArray<E>| {
268 let values = array.into_iter().collect();
269 TensorData::quantized(values, shape, tensor.scheme, &scales)
270 }
271 ))
272 }
273
274 fn q_swap_dims(
275 tensor: QuantizedTensor<Self>,
276 dim1: usize,
277 dim2: usize,
278 ) -> QuantizedTensor<Self> {
279 NdArrayQTensor {
280 qtensor: execute_with_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
281 NdArrayOps::swap_dims(array, dim1, dim2)
282 }),
283 scheme: tensor.scheme,
284 qparams: tensor.qparams,
285 }
286 }
287
288 fn q_permute(tensor: QuantizedTensor<Self>, axes: &[usize]) -> QuantizedTensor<Self> {
289 NdArrayQTensor {
290 qtensor: execute_with_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
291 NdArrayOps::permute(array, axes)
292 }),
293 scheme: tensor.scheme,
294 qparams: tensor.qparams,
295 }
296 }
297
298 fn q_flip(tensor: QuantizedTensor<Self>, axes: &[usize]) -> QuantizedTensor<Self> {
299 NdArrayQTensor {
300 qtensor: execute_with_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
301 NdArrayOps::flip(array, axes)
302 }),
303 scheme: tensor.scheme,
304 qparams: tensor.qparams,
305 }
306 }
307
308 fn q_gather(
309 dim: usize,
310 tensor: QuantizedTensor<Self>,
311 indices: IntTensor<Self>,
312 ) -> QuantizedTensor<Self> {
313 let qtensor = execute_with_int_dtype!(indices, IntElem, |idx_array: SharedArray<
314 IntElem,
315 >|
316 -> NdArrayTensor {
317 execute_with_numeric_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
318 NdArrayOps::gather(dim, array, idx_array)
319 })
320 });
321 NdArrayQTensor {
322 qtensor,
323 scheme: tensor.scheme,
324 qparams: tensor.qparams,
325 }
326 }
327
328 fn q_select(
329 tensor: QuantizedTensor<Self>,
330 dim: usize,
331 indices: IntTensor<Self>,
332 ) -> QuantizedTensor<Self> {
333 let qtensor = execute_with_int_dtype!(indices, IntElem, |idx_array: SharedArray<
334 IntElem,
335 >|
336 -> NdArrayTensor {
337 execute_with_numeric_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
338 NdArrayMathOps::select(array, dim, idx_array)
339 })
340 });
341 NdArrayQTensor {
342 qtensor,
343 scheme: tensor.scheme,
344 qparams: tensor.qparams,
345 }
346 }
347
348 fn q_slice(
349 tensor: QuantizedTensor<Self>,
350 slices: &[burn_backend::Slice],
351 ) -> QuantizedTensor<Self> {
352 NdArrayQTensor {
353 qtensor: slice!(tensor.qtensor, slices),
354 scheme: tensor.scheme,
355 qparams: tensor.qparams,
356 }
357 }
358
359 fn q_argmax(tensor: QuantizedTensor<Self>, dim: usize, out_dtype: IntDType) -> IntTensor<Self> {
360 execute_with_int_out_dtype!(out_dtype, I, {
361 execute_with_numeric_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
362 NdArrayMathOps::argmax::<I>(array, dim)
363 })
364 })
365 }
366
367 fn q_argmin(tensor: QuantizedTensor<Self>, dim: usize, out_dtype: IntDType) -> IntTensor<Self> {
368 execute_with_int_out_dtype!(out_dtype, I, {
369 execute_with_numeric_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
370 NdArrayMathOps::argmin::<I>(array, dim)
371 })
372 })
373 }
374
375 fn q_expand(tensor: QuantizedTensor<Self>, shape: Shape) -> QuantizedTensor<Self> {
376 NdArrayQTensor {
377 qtensor: execute_with_dtype!(tensor.qtensor, E, |array: SharedArray<E>| {
378 NdArrayOps::expand(array, shape)
379 }),
380 scheme: tensor.scheme,
381 qparams: tensor.qparams,
382 }
383 }
384}
385
386fn ternary_matmul(
398 lhs: &FloatTensor<NdArray>,
399 rhs: &NdArrayQTensor,
400) -> Option<FloatTensor<NdArray>> {
401 if !matches!(
403 rhs.scheme,
404 QuantScheme {
405 value: QuantValue::Q2S,
406 mode: QuantMode::Symmetric,
407 level: QuantLevel::Tensor,
408 ..
409 }
410 ) {
411 return None;
412 }
413 if !matches!(lhs, NdArrayTensor::F32(_)) {
415 return None;
416 }
417
418 let wdims = rhs.qtensor.shape().to_vec();
420 if wdims.len() != 2 {
421 return None;
422 }
423 let (k, n) = (wdims[0], wdims[1]);
424
425 let ldims = lhs.shape().to_vec();
427 if ldims.len() < 2 || *ldims.last().unwrap() != k {
428 return None;
429 }
430 let m: usize = ldims[..ldims.len() - 1].iter().product();
431
432 let gamma = rhs.qparams.first()?.scales;
434
435 let a_data = lhs.clone().into_data();
437 let a = a_data.as_slice::<f32>().ok()?;
438 let w_data = rhs.qtensor.clone().into_data();
439 let w = w_data.as_slice::<i8>().ok()?;
440 if a.len() != m * k || w.len() != k * n {
441 return None;
442 }
443
444 let mut out = vec![0f32; m * n];
446 for i in 0..m {
447 let arow = &a[i * k..(i + 1) * k];
448 let orow = &mut out[i * n..(i + 1) * n];
449 for kk in 0..k {
450 let x = arow[kk];
451 let wrow = &w[kk * n..(kk + 1) * n];
452 for (o, &t) in orow.iter_mut().zip(wrow) {
453 match t {
454 1 => *o += x,
455 -1 => *o -= x,
456 _ => {} }
458 }
459 }
460 for o in orow.iter_mut() {
461 *o *= gamma;
462 }
463 }
464
465 let mut out_dims = ldims[..ldims.len() - 1].to_vec();
466 out_dims.push(n);
467 Some(NdArrayTensor::from_data(TensorData::new(
468 out,
469 Shape::from(out_dims),
470 )))
471}
472
473fn dequantize<Q: QuantElement>(
474 data: Vec<Q>,
475 shape: Shape,
476 scheme: QuantScheme,
477 strategy: &QuantizationStrategy,
478 dtype: DType,
479) -> TensorData {
480 let qparams = match strategy {
481 QuantizationStrategy::PerTensorSymmetric(quant) => vec![quant.scale],
482 QuantizationStrategy::PerBlockSymmetric(quant, _block_size) => {
483 quant.iter().map(|q| q.scale).collect()
484 }
485 };
486 let q_bytes = QuantizedBytes::new(data, scheme, &qparams);
487 let (values, _qparams) = q_bytes.into_vec_i8();
488 TensorData::new(strategy.dequantize(&values), shape).convert_dtype(dtype)
489}