use cubecl::prelude::*;
use cubecl_common::quant::scheme::*;
use cubecl_common::{e2m1x2, e4m3, e5m2};
use cubecl_core as cubecl;
#[cube]
pub fn dequantize_aligned<Q: Scalar, S: CubePrimitive, F: Numeric, NQ: Size, NF: Size>(
value: Vector<Q, NQ>,
scale: S,
table: ComptimeOption<Box<[f32]>>,
#[comptime] scheme: QuantScheme,
) -> Vector<F, NF> {
comptime!(crate::quant::check_table_bindings(&scheme, table.is_some()));
let q_values = match scheme.store {
QuantStore::Native | QuantStore::PackedNative(_) => Vector::<F, NF>::cast_from(value),
QuantStore::PackedU32(_) => {
unpack_cast_u32::<F, NQ, NF>(Vector::cast_from(value), table.clone(), scheme)
}
};
match scheme.mode {
QuantMode::Symmetric | QuantMode::Lookup => q_values * Vector::<F, NF>::cast_from(scale),
}
}
#[cube]
pub fn dequantize_aligned_wide<Q: Scalar, F: Numeric, NQ: Size, NF: Size>(
value: Vector<Q, NQ>,
scale: f32,
table: ComptimeOption<Box<[f32]>>,
#[comptime] scheme: QuantScheme,
) -> Vector<F, NF> {
Vector::<F, NF>::cast_from(dequantize_aligned::<Q, f32, f32, NQ, NF>(
value, scale, table, scheme,
))
}
#[cube]
pub fn multiply_global_scale<S: CubePrimitive>(global_scale: f32, scale: S) -> f32 {
global_scale * f32::cast_from(scale)
}
#[cube]
pub fn unpack_cast_u32<F: Numeric, NQ: Size, NF: Size>(
value: Vector<u32, NQ>,
table: ComptimeOption<Box<[f32]>>,
#[comptime] scheme: QuantScheme,
) -> Vector<F, NF> {
let num_quants = scheme.num_quants();
let native_packing = scheme.native_packing();
let size_bits = scheme.size_bits_value();
let mask = comptime![packing_mask(scheme)];
let size!(NP) = native_packing;
let mut out = Vector::<F, NF>::empty();
#[unroll]
for vector_idx in 0..value.vector_size() {
let packed_val = value.extract(vector_idx);
let out_offset = vector_idx * num_quants;
#[unroll]
for packed_idx in range_stepped(0, num_quants, native_packing) {
let shift = packed_idx * size_bits;
let value = (packed_val >> shift as u32) & mask;
let float_value = cast_masked::<F, NP>(value, table.clone(), scheme);
#[unroll]
for native_idx in 0..native_packing {
let out_offset = out_offset + packed_idx + native_idx;
out.insert(out_offset, float_value.extract(native_idx));
}
}
}
out
}
#[cube]
pub fn unpack_fields<F: Numeric, NF: Size>(
word: u32,
first: u32,
table: ComptimeOption<Box<[f32]>>,
#[comptime] scheme: QuantScheme,
) -> Vector<F, NF> {
comptime!(assert!(
!matches!(scheme.value, QuantValue::E2M1),
"unpack_fields: e2m1 decodes in native pairs, which a sub-word line would split"
));
let size_bits = scheme.size_bits_value();
let mask = comptime![packing_mask(scheme)];
let size!(N1) = 1usize;
let mut out = Vector::<F, NF>::empty();
#[unroll]
for j in 0..NF::value() {
let shift = (first + j as u32) * size_bits as u32;
let field = (word >> shift) & mask;
let value = cast_masked::<F, N1>(field, table.clone(), scheme);
out.insert(j, value.extract(0usize));
}
out
}
fn packing_mask(scheme: QuantScheme) -> u32 {
let bits = match scheme.value {
QuantValue::E2M1 => 8, other => other.size_bits(),
};
(1u32 << bits) - 1
}
#[cube]
fn cast_masked<F: Numeric, N: Size>(
value: u32,
table: ComptimeOption<Box<[f32]>>,
#[comptime] scheme: QuantScheme,
) -> Vector<F, N> {
#[comptime]
match table {
ComptimeOption::Some(t) => Vector::<F, N>::cast_from(t[value as usize]),
ComptimeOption::None => cast_masked_plain::<F, N>(value, scheme),
}
}
#[cube]
fn cast_masked_plain<F: Numeric, N: Size>(
value: u32,
#[comptime] scheme: QuantScheme,
) -> Vector<F, N> {
match scheme.value {
QuantValue::E5M2 => Vector::<F, N>::cast_from(e5m2::from_bits(value as u8)),
QuantValue::E4M3 => Vector::<F, N>::cast_from(e4m3::from_bits(value as u8)),
QuantValue::E2M1 => Vector::<F, N>::cast_from(e2m1x2::from_bits(value as u8)),
QuantValue::Q8F
| QuantValue::Q4F
| QuantValue::Q2F
| QuantValue::Q8S
| QuantValue::Q4S
| QuantValue::Q2S => {
let size_quant = scheme.size_bits_value() as u32;
let sign_bit = 1u32 << (size_quant - 1);
let signed_value = (value ^ sign_bit) as i32 - sign_bit as i32;
Vector::<F, N>::cast_from(signed_value)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use cubecl_core::ir::{ElemType, Scope, UIntKind};
use cubecl_core::{define_size, ir::settings::Dim3};
define_size!(N1);
fn test_scope() -> Scope {
let scope = Scope::root(KernelSettings::new(
Dim3::new_single(),
ExecutionMode::Checked,
AddressType::U32,
));
scope.register_size::<N1>(1);
scope.register_type::<usize>(ElemType::UInt(UIntKind::U32));
scope
}
#[test]
fn expanding_takes_one_scale_whatever_the_levels() {
let scope = test_scope();
let one = f32::__expand_new(&scope, 1.0);
let value = Vector::<f32, N1>::__expand_new(&scope, one);
for scheme in [
QuantScheme::default(),
QuantScheme::default().per_block([32], ScaleDtype::F32),
QuantScheme::default()
.per_block([32], ScaleDtype::F32)
.per_tensor(ScaleDtype::F32),
] {
dequantize_aligned::expand::<f32, f32, f32, N1, N1>(
&scope,
value,
one,
ComptimeOptionExpand::None,
scheme,
);
}
}
}