1use alloc::vec::Vec;
4#[cfg(not(feature = "std"))]
5#[allow(unused_imports)]
6use num_traits::Float;
7
8use ruda_tensor::{
9 DType, ExecutionError, FloatDType, TensorData, TensorMetadata,
10 ops::QTensorOps,
11 quantization::{
12 QuantLevel, QuantScheme, QuantStore, QuantizationParametersPrimitive,
13 },
14 tensor::{Device, FloatTensor, IntTensor, QuantizedTensor},
15};
16use ruda_tensor::{Bytes, Shape, Slice, bf16, f16};
17
18use crate::{Host, HostTensor, Layout};
19
20impl QTensorOps<Host> for Host {
21 fn q_from_data(data: TensorData, _device: &Device<Host>) -> QuantizedTensor<Host> {
22 ruprim_host::quantization::q_from_data(data)
23 }
24
25 fn quantize_dynamic(tensor: FloatTensor<Host>, scheme: &QuantScheme) -> QuantizedTensor<Host> {
26 ruprim_host::quantization::quantize_dynamic(tensor, scheme)
27 }
28
29 fn quantize(
30 tensor: FloatTensor<Host>,
31 scheme: &QuantScheme,
32 qparams: QuantizationParametersPrimitive<Host>,
33 ) -> QuantizedTensor<Host> {
34 ruprim_host::quantization::quantize(tensor, scheme, ruda_core::tensor::quantization::QParams { scales: qparams.scales })
35 }
36
37 fn dequantize(tensor: QuantizedTensor<Host>, dtype: FloatDType) -> FloatTensor<Host> {
38 ruprim_host::quantization::dequantize(tensor, dtype)
39 }
40
41 fn q_device(_tensor: &QuantizedTensor<Host>) -> Device<Host> {
42 Default::default()
43 }
44
45 fn q_to_device(tensor: QuantizedTensor<Host>, _device: &Device<Host>) -> QuantizedTensor<Host> {
46 tensor
47 }
48
49 fn q_reshape(tensor: QuantizedTensor<Host>, shape: Shape) -> QuantizedTensor<Host> {
50 ruprim_host::quantization::q_reshape(tensor, shape)
51 }
52
53 async fn q_into_data(tensor: QuantizedTensor<Host>) -> Result<TensorData, ExecutionError> {
54 ruprim_host::quantization::q_into_data(tensor).await
55 }
56
57 fn q_swap_dims(
58 tensor: QuantizedTensor<Host>,
59 dim1: usize,
60 dim2: usize,
61 ) -> QuantizedTensor<Host> {
62 ruprim_host::quantization::q_swap_dims(tensor, dim1, dim2)
63 }
64
65 fn q_permute(tensor: QuantizedTensor<Host>, axes: &[usize]) -> QuantizedTensor<Host> {
66 ruprim_host::quantization::q_permute(tensor, axes)
67 }
68
69 fn q_flip(tensor: QuantizedTensor<Host>, axes: &[usize]) -> QuantizedTensor<Host> {
70 ruprim_host::quantization::q_flip(tensor, axes)
71 }
72
73 fn q_expand(tensor: QuantizedTensor<Host>, shape: Shape) -> QuantizedTensor<Host> {
74 ruprim_host::quantization::q_expand(tensor, shape)
75 }
76
77 fn q_select(
78 tensor: QuantizedTensor<Host>,
79 dim: usize,
80 indices: IntTensor<Host>,
81 ) -> QuantizedTensor<Host> {
82 ruprim_host::quantization::q_select(tensor, dim, indices)
83 }
84
85 fn q_slice(tensor: QuantizedTensor<Host>, slices: &[Slice]) -> QuantizedTensor<Host> {
86 ruprim_host::quantization::q_slice(tensor, slices)
87 }
88
89 fn q_gather(
90 dim: usize,
91 tensor: QuantizedTensor<Host>,
92 indices: IntTensor<Host>,
93 ) -> QuantizedTensor<Host> {
94 ruprim_host::quantization::q_gather(dim, tensor, indices)
95 }
96}
97
98#[cfg(test)]
106mod tests {
107 use super::*;
108 use ruda_tensor::{TensorMetadata, quantization::QuantValue};
109
110 #[test]
111 fn test_quantize_dequantize_roundtrip() {
112 let values = vec![0.0f32, 1.0, 2.0, 3.0, 4.0, 5.0];
114 let tensor = HostTensor::from_data(TensorData::new(values.clone(), [2, 3]));
115
116 let scheme = QuantScheme::default()
117 .with_value(QuantValue::Q8S)
118 .with_store(QuantStore::Native);
119
120 let scale: f32 = 2.0 * 5.0 / 254.0;
124 let scales_tensor = HostTensor::from_data(TensorData::new(vec![scale], [1]));
125
126 let qparams = QuantizationParametersPrimitive {
127 scales: scales_tensor,
128 };
129
130 let qtensor = Host::quantize(tensor, &scheme, qparams);
132 assert_eq!(qtensor.tensor().shape().to_vec(), vec![2, 3]);
133 assert_eq!(qtensor.tensor().dtype(), DType::I8);
134
135 let q_vals: &[i8] = qtensor.tensor().storage();
137 assert_eq!(q_vals[0], 0);
139 assert_eq!(q_vals[1], 25);
140 assert_eq!(q_vals[5], 127);
141
142 let result = Host::dequantize(qtensor, FloatDType::F32);
144 assert_eq!(result.shape().to_vec(), vec![2, 3]);
145 assert_eq!(result.dtype(), DType::F32);
146
147 let result_vals: &[f32] = result.storage();
148 for (orig, deq) in values.iter().zip(result_vals.iter()) {
150 assert!((orig - deq).abs() < 0.05, "orig={orig}, dequantized={deq}");
151 }
152 }
153
154 #[test]
155 fn test_quantize_dequantize_negative_values() {
156 let values = vec![-3.0f32, -1.5, 0.0, 1.5, 3.0];
157 let tensor = HostTensor::from_data(TensorData::new(values.clone(), [5]));
158
159 let scheme = QuantScheme::default()
160 .with_value(QuantValue::Q8S)
161 .with_store(QuantStore::Native);
162
163 let scale: f32 = 2.0 * 3.0 / 254.0;
164 let scales_tensor = HostTensor::from_data(TensorData::new(vec![scale], [1]));
165
166 let qparams = QuantizationParametersPrimitive {
167 scales: scales_tensor,
168 };
169
170 let qtensor = Host::quantize(tensor, &scheme, qparams);
171 let result = Host::dequantize(qtensor, FloatDType::F32);
172 let result_vals: &[f32] = result.storage();
173
174 for (orig, deq) in values.iter().zip(result_vals.iter()) {
175 assert!((orig - deq).abs() < 0.05, "orig={orig}, dequantized={deq}");
176 }
177 }
178
179 #[test]
180 fn test_q_from_data_into_data_roundtrip() {
181 let values = vec![0i8, 25, 51, 76, 102, 127];
183 let scale = 0.03937008f32;
184 let scheme = QuantScheme::default()
185 .with_value(QuantValue::Q8S)
186 .with_store(QuantStore::Native);
187
188 let data = TensorData::quantized(values.clone(), [2, 3], scheme, &[scale]);
189
190 let qtensor = Host::q_from_data(data, &Default::default());
192 assert_eq!(qtensor.tensor().shape().to_vec(), vec![2, 3]);
193 assert_eq!(qtensor.scales(), vec![scale]);
194
195 let float_tensor = Host::dequantize(qtensor, FloatDType::F32);
197 let result: &[f32] = float_tensor.storage();
198 assert!((result[0]).abs() < 0.01); assert!((result[5] - 5.0).abs() < 0.05); }
201
202 #[test]
203 fn test_quantize_zero_tensor() {
204 let values = vec![0.0f32; 4];
205 let tensor = HostTensor::from_data(TensorData::new(values, [4]));
206
207 let scheme = QuantScheme::default()
208 .with_value(QuantValue::Q8S)
209 .with_store(QuantStore::Native);
210
211 let scales_tensor = HostTensor::from_data(TensorData::new(vec![0.0f32], [1]));
213 let qparams = QuantizationParametersPrimitive {
214 scales: scales_tensor,
215 };
216
217 let qtensor = Host::quantize(tensor, &scheme, qparams);
218 let q_vals: &[i8] = qtensor.tensor().storage();
219 assert_eq!(q_vals, &[0, 0, 0, 0]);
220 }
221
222 #[test]
223 fn test_quantize_dynamic_roundtrip() {
224 let values = vec![-3.0f32, -1.5, 0.0, 1.5, 3.0, 4.5];
225 let tensor = HostTensor::from_data(TensorData::new(values.clone(), [2, 3]));
226
227 let scheme = QuantScheme::default()
228 .with_value(QuantValue::Q8S)
229 .with_store(QuantStore::Native);
230
231 let qtensor = Host::quantize_dynamic(tensor, &scheme);
232 assert_eq!(qtensor.tensor().shape().to_vec(), vec![2, 3]);
233 assert_eq!(qtensor.scales().len(), 1);
234
235 let expected_scale: f32 = 2.0 * 4.5 / 254.0;
237 assert!(
238 (qtensor.scales()[0] - expected_scale).abs() < 1e-6,
239 "scale={}, expected={}",
240 qtensor.scales()[0],
241 expected_scale
242 );
243
244 let result = Host::dequantize(qtensor, FloatDType::F32);
245 let result_vals: &[f32] = result.storage();
246 for (orig, deq) in values.iter().zip(result_vals.iter()) {
247 assert!((orig - deq).abs() < 0.1, "orig={orig}, dequantized={deq}");
248 }
249 }
250
251 #[test]
252 fn test_per_block_quantize_dequantize() {
253 use ruda_tensor::quantization::BlockSize;
254
255 let values = vec![0.0f32, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
256 let tensor = HostTensor::from_data(TensorData::new(values.clone(), [8]));
257
258 let block_size = BlockSize::new([4]);
259 let scheme = QuantScheme::default()
260 .with_value(QuantValue::Q8S)
261 .with_level(QuantLevel::Block(block_size))
262 .with_store(QuantStore::Native);
263
264 let scale_1: f32 = 2.0 * 3.0 / 254.0;
267 let scale_2: f32 = 2.0 * 7.0 / 254.0;
268 let scales_tensor = HostTensor::from_data(TensorData::new(vec![scale_1, scale_2], [2]));
269
270 let qparams = QuantizationParametersPrimitive {
271 scales: scales_tensor,
272 };
273
274 let qtensor = Host::quantize(tensor, &scheme, qparams);
275 assert_eq!(qtensor.scales().len(), 2);
276
277 let result = Host::dequantize(qtensor, FloatDType::F32);
278 let result_vals: &[f32] = result.storage();
279
280 for (orig, deq) in values.iter().zip(result_vals.iter()) {
281 assert!((orig - deq).abs() < 0.1, "orig={orig}, dequantized={deq}");
282 }
283 }
284
285 #[test]
286 fn test_quantize_dynamic_block() {
287 use ruda_tensor::quantization::BlockSize;
288
289 let values = vec![-2.0f32, -1.0, 0.0, 1.0, 4.0, 5.0, 6.0, 7.0];
290 let tensor = HostTensor::from_data(TensorData::new(values.clone(), [8]));
291
292 let block_size = BlockSize::new([4]);
293 let scheme = QuantScheme::default()
294 .with_value(QuantValue::Q8S)
295 .with_level(QuantLevel::Block(block_size))
296 .with_store(QuantStore::Native);
297
298 let qtensor = Host::quantize_dynamic(tensor, &scheme);
299 assert_eq!(qtensor.scales().len(), 2);
300
301 let expected_scale_1: f32 = 2.0 * 2.0 / 254.0;
304 let expected_scale_2: f32 = 2.0 * 7.0 / 254.0;
305 assert!((qtensor.scales()[0] - expected_scale_1).abs() < 1e-6);
306 assert!((qtensor.scales()[1] - expected_scale_2).abs() < 1e-6);
307
308 let result = Host::dequantize(qtensor, FloatDType::F32);
309 let result_vals: &[f32] = result.storage();
310 for (orig, deq) in values.iter().zip(result_vals.iter()) {
311 assert!((orig - deq).abs() < 0.1, "orig={orig}, dequantized={deq}");
312 }
313 }
314
315 #[test]
316 fn test_quantize_dynamic_q8f() {
317 let values = vec![-5.0f32, -2.5, 0.0, 2.5, 5.0, 7.5];
319 let tensor = HostTensor::from_data(TensorData::new(values.clone(), [6]));
320
321 let scheme = QuantScheme::default()
322 .with_value(QuantValue::Q8F)
323 .with_store(QuantStore::Native);
324
325 let qtensor = Host::quantize_dynamic(tensor, &scheme);
326
327 let expected_scale: f32 = 2.0 * 7.5 / 255.0;
330 assert!(
331 (qtensor.scales()[0] - expected_scale).abs() < 1e-6,
332 "scale={}, expected={}",
333 qtensor.scales()[0],
334 expected_scale
335 );
336
337 let result = Host::dequantize(qtensor, FloatDType::F32);
338 let result_vals: &[f32] = result.storage();
339 for (orig, deq) in values.iter().zip(result_vals.iter()) {
340 assert!((orig - deq).abs() < 0.1, "orig={orig}, dequantized={deq}");
341 }
342 }
343
344 #[test]
345 fn test_block_quantized_transpose_dequantize() {
346 use ruda_tensor::quantization::BlockSize;
347
348 let values = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
350 let tensor = HostTensor::from_data(TensorData::new(values, [2, 4]));
351
352 let block_size = BlockSize::new([4]);
353 let scheme = QuantScheme::default()
354 .with_value(QuantValue::Q8S)
355 .with_level(QuantLevel::Block(block_size))
356 .with_store(QuantStore::Native);
357
358 let qtensor = Host::quantize_dynamic(tensor, &scheme);
359
360 let transposed = Host::q_swap_dims(qtensor, 0, 1);
362 assert_eq!(transposed.tensor().shape().to_vec(), vec![4, 2]);
363
364 let result = Host::dequantize(transposed, FloatDType::F32);
365 let result_vals: &[f32] = result.storage();
366
367 let expected = [1.0f32, 5.0, 2.0, 6.0, 3.0, 7.0, 4.0, 8.0];
369 for (exp, deq) in expected.iter().zip(result_vals.iter()) {
370 assert!(
371 (exp - deq).abs() < 0.15,
372 "expected={exp}, dequantized={deq}"
373 );
374 }
375 }
376
377 #[test]
378 fn test_block_quantized_select() {
379 use ruda_tensor::quantization::BlockSize;
380
381 let values = vec![1.0f32, 2.0, 3.0, 4.0, 10.0, 20.0, 30.0, 40.0];
383 let tensor = HostTensor::from_data(TensorData::new(values, [2, 4]));
384
385 let block_size = BlockSize::new([4]);
386 let scheme = QuantScheme::default()
387 .with_value(QuantValue::Q8S)
388 .with_level(QuantLevel::Block(block_size))
389 .with_store(QuantStore::Native);
390
391 let qtensor = Host::quantize_dynamic(tensor, &scheme);
392
393 let indices = HostTensor::from_data(TensorData::new(vec![1i64], [1]));
395 let selected = Host::q_select(qtensor, 0, indices);
396 assert_eq!(selected.tensor().shape().to_vec(), vec![1, 4]);
397
398 let result = Host::dequantize(selected, FloatDType::F32);
399 let result_vals: &[f32] = result.storage();
400 let expected = [10.0f32, 20.0, 30.0, 40.0];
401 for (exp, deq) in expected.iter().zip(result_vals.iter()) {
402 assert!((exp - deq).abs() < 0.5, "expected={exp}, dequantized={deq}");
403 }
404 }
405
406 #[test]
407 fn test_block_quantized_flip_dequantize() {
408 use ruda_tensor::quantization::BlockSize;
409
410 let values = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
411 let tensor = HostTensor::from_data(TensorData::new(values, [2, 4]));
412
413 let block_size = BlockSize::new([4]);
414 let scheme = QuantScheme::default()
415 .with_value(QuantValue::Q8S)
416 .with_level(QuantLevel::Block(block_size))
417 .with_store(QuantStore::Native);
418
419 let qtensor = Host::quantize_dynamic(tensor, &scheme);
420
421 let flipped = Host::q_flip(qtensor, &[0]);
423 assert_eq!(flipped.tensor().shape().to_vec(), vec![2, 4]);
424
425 let result = Host::dequantize(flipped, FloatDType::F32);
426 let result_vals: &[f32] = result.storage();
427 let expected = [5.0f32, 6.0, 7.0, 8.0, 1.0, 2.0, 3.0, 4.0];
428 for (exp, deq) in expected.iter().zip(result_vals.iter()) {
429 assert!(
430 (exp - deq).abs() < 0.15,
431 "expected={exp}, dequantized={deq}"
432 );
433 }
434 }
435
436 #[test]
437 fn test_quantize_dynamic_f64_tensor() {
438 use ruda_tensor::quantization::QuantValue;
439
440 let values = vec![0.0f64, 1.0, 2.0, 3.0, 4.0, 5.0];
441 let tensor = HostTensor::new(
442 Bytes::from_elems(values),
443 Layout::contiguous([6].into()),
444 DType::F64,
445 );
446 assert_eq!(tensor.dtype(), DType::F64);
447
448 let scheme = QuantScheme::default()
449 .with_value(QuantValue::Q8S)
450 .with_store(QuantStore::Native);
451
452 let qtensor = Host::quantize_dynamic(tensor, &scheme);
453 assert_eq!(qtensor.tensor().dtype(), DType::I8);
454
455 let result = Host::dequantize(qtensor, FloatDType::F32);
457 let result_vals: &[f32] = result.storage();
458 let expected = [0.0f32, 1.0, 2.0, 3.0, 4.0, 5.0];
459 for (exp, deq) in expected.iter().zip(result_vals.iter()) {
460 assert!(
461 (exp - deq).abs() < 0.15,
462 "expected={exp}, dequantized={deq}"
463 );
464 }
465 }
466
467 #[test]
468 fn test_dequantize_f64() {
469 let values = vec![0.0f32, 1.0, 2.0, 3.0];
470 let tensor = HostTensor::from_data(TensorData::new(values.clone(), [4]));
471
472 let scheme = QuantScheme::default()
473 .with_value(QuantValue::Q8S)
474 .with_store(QuantStore::Native);
475
476 let qtensor = Host::quantize_dynamic(tensor, &scheme);
477 let result = Host::dequantize(qtensor, FloatDType::F64);
478 assert_eq!(result.dtype(), DType::F64);
479 let result_vals: &[f64] = result.storage();
480 for (orig, deq) in values.iter().zip(result_vals.iter()) {
481 assert!(
482 (*orig as f64 - deq).abs() < 0.05,
483 "orig={orig}, dequantized={deq}"
484 );
485 }
486 }
487
488 #[test]
489 fn test_dequantize_f16() {
490 let values = vec![0.0f32, 1.0, 2.0, 3.0];
491 let tensor = HostTensor::from_data(TensorData::new(values.clone(), [4]));
492
493 let scheme = QuantScheme::default()
494 .with_value(QuantValue::Q8S)
495 .with_store(QuantStore::Native);
496
497 let qtensor = Host::quantize_dynamic(tensor, &scheme);
498 let result = Host::dequantize(qtensor, FloatDType::F16);
499 assert_eq!(result.dtype(), DType::F16);
500 let result_vals: &[f16] = result.storage();
501 for (orig, deq) in values.iter().zip(result_vals.iter()) {
502 assert!(
503 (*orig - f32::from(*deq)).abs() < 0.05,
504 "orig={orig}, dequantized={deq}"
505 );
506 }
507 }
508}