use crate::tensor::JitTensor;
use crate::FloatElement;
use crate::{IntElement, JitElement, JitRuntime};
use burn_tensor::quantization::{QuantizationScheme, QuantizationType};
use cubecl::calculate_cube_count_elemwise;
use cubecl::prelude::*;
#[cube]
pub(crate) fn quantize_affine_int8<F: Float>(
value: Line<F>,
scale: f32,
offset: i32,
range_min: f32,
range_max: f32,
) -> Line<u32> {
Line::cast_from(
Line::clamp(
Line::round((value / Line::cast_from(scale)) + Line::cast_from(offset)),
Line::cast_from(range_min),
Line::cast_from(range_max),
) + Line::cast_from(comptime!(256f32)),
)
}
#[cube(launch_unchecked)]
pub(crate) fn quantize_per_tensor_affine_int8_kernel(
input: &Tensor<Line<f32>>,
scale: &Tensor<f32>,
offset: &Tensor<i32>,
range_min: f32,
range_max: f32,
output: &mut Array<u32>,
) {
if ABSOLUTE_POS >= output.len() {
return;
}
let scale = scale[0];
let offset = offset[0];
if ABSOLUTE_POS == output.len() - 1 {
output[ABSOLUTE_POS] = u32::bitcast_from(scale);
return;
}
if ABSOLUTE_POS == output.len() - 2 {
output[ABSOLUTE_POS] = u32::bitcast_from(offset);
return;
}
let line_size = comptime!(input.line_size());
if comptime!(line_size == 4) {
let value =
quantize_affine_int8::<f32>(input[ABSOLUTE_POS], scale, offset, range_min, range_max);
output[ABSOLUTE_POS] = pack_i8s_to_u32s(value);
} else {
let mut v_packed = 0;
let num_packed = comptime!(4);
#[unroll]
for i in 0..num_packed {
let v = quantize_affine_int8::<f32>(
input[ABSOLUTE_POS + i],
scale,
offset,
range_min,
range_max,
);
v_packed |= (v[0] & 0xFF) << (8 * i);
}
output[ABSOLUTE_POS] = v_packed;
}
}
#[cube]
pub(crate) fn quantize_symmetric_int8<F: Float>(
value: Line<F>,
scale: f32,
range_min: F,
range_max: F,
) -> Line<u32> {
Line::cast_from(
Line::clamp(
Line::round(value / Line::cast_from(scale)),
Line::new(range_min),
Line::new(range_max),
) + Line::cast_from(comptime!(256f32)),
)
}
#[cube]
pub(crate) fn pack_i8s_to_u32s(value: Line<u32>) -> u32 {
let line_size = value.size();
let mut v_packed = 0;
#[unroll]
for i in 0..line_size {
v_packed |= (value[i] & 0xFF) << (8 * i);
}
v_packed
}
#[cube(launch_unchecked)]
pub(crate) fn quantize_per_tensor_symmetric_int8_kernel(
input: &Tensor<Line<f32>>,
scale: &Tensor<f32>,
range_min: f32,
range_max: f32,
output: &mut Array<u32>,
) {
if ABSOLUTE_POS >= output.len() {
return;
}
let scale = scale[0];
if ABSOLUTE_POS == output.len() - 1 {
output[ABSOLUTE_POS] = u32::bitcast_from(scale);
return;
}
let line_size = comptime!(input.line_size());
if comptime!(line_size == 4) {
let value =
quantize_symmetric_int8::<f32>(input[ABSOLUTE_POS], scale, range_min, range_max);
output[ABSOLUTE_POS] = pack_i8s_to_u32s(value);
} else {
let num_packed = comptime!(4);
let mut v_packed = 0;
#[unroll]
for i in 0..num_packed {
let v = quantize_symmetric_int8::<f32>(
input[ABSOLUTE_POS + i],
scale,
range_min,
range_max,
);
v_packed |= (v[0] & 0xFF) << (8 * i);
}
output[ABSOLUTE_POS] = v_packed;
}
}
pub(crate) fn quantize_per_tensor<R, F, I>(
tensor: JitTensor<R>,
scale: JitTensor<R>,
offset: Option<JitTensor<R>>,
scheme: QuantizationScheme,
) -> JitTensor<R>
where
R: JitRuntime,
F: JitElement,
I: IntElement,
{
let ndims = tensor.shape.num_dims();
let num_elems = tensor.shape.num_elements();
let client = tensor.client.clone();
let output_num_elems = usize::div_ceil(num_elems, 4) * core::mem::size_of::<u32>();
let line_size: u8 = if num_elems < 4 { 1 } else { 4 };
let cube_dim = CubeDim::default();
let cube_count = calculate_cube_count_elemwise(num_elems / line_size as usize, cube_dim);
let dummy_array = vec![1; ndims];
if let Some(offset) = offset {
let handle = client
.empty(output_num_elems + core::mem::size_of::<f32>() + core::mem::size_of::<i32>());
let output = JitTensor::new_contiguous(
client.clone(),
tensor.device.clone(),
tensor.shape.clone(),
handle,
burn_tensor::DType::QFloat(scheme),
);
unsafe {
quantize_per_tensor_affine_int8_kernel::launch_unchecked::<R>(
&client,
cube_count,
cube_dim,
tensor.as_tensor_arg::<F>(line_size),
TensorArg::from_raw_parts::<F>(&scale.handle, &dummy_array, &dummy_array, 1),
TensorArg::from_raw_parts::<I>(&offset.handle, &dummy_array, &dummy_array, 1),
ScalarArg::new(i8::MIN as f32),
ScalarArg::new(i8::MAX as f32),
output.as_array_arg::<u32>(1),
)
};
output
} else {
let handle = client.empty(output_num_elems + core::mem::size_of::<f32>());
let output = JitTensor::new_contiguous(
client.clone(),
tensor.device.clone(),
tensor.shape.clone(),
handle,
burn_tensor::DType::QFloat(scheme),
);
unsafe {
quantize_per_tensor_symmetric_int8_kernel::launch_unchecked::<R>(
&client,
cube_count,
cube_dim,
tensor.as_tensor_arg::<F>(line_size),
TensorArg::from_raw_parts::<F>(&scale.handle, &dummy_array, &dummy_array, 1),
ScalarArg::new(-i8::MAX as f32),
ScalarArg::new(i8::MAX as f32),
output.as_array_arg::<u32>(1),
)
};
output
}
}
pub fn quantize<R, F, I>(
tensor: JitTensor<R>,
scheme: &QuantizationScheme,
scale: JitTensor<R>,
offset: Option<JitTensor<R>>,
) -> JitTensor<R>
where
R: JitRuntime,
F: FloatElement,
I: IntElement,
{
match scheme {
QuantizationScheme::PerTensorAffine(dtype)
| QuantizationScheme::PerTensorSymmetric(dtype) => match dtype {
QuantizationType::QInt8 => {
quantize_per_tensor::<R, F, I>(tensor, scale, offset, *scheme)
}
},
}
}