use crate::k_quants::{GgmlType, QK_K};
use fuel_ir::quantized::GgmlDType;
use half::f16;
pub const QK_MXFP4: usize = 32;
pub const QK_NVFP4: usize = 64;
const QK_NVFP4_SUB: usize = 16;
pub const KVALUES_FP4: [i8; 16] = [0, 1, 2, 3, 4, 6, 8, 12, 0, -1, -2, -3, -4, -6, -8, -12];
#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct BlockTq1_0 {
pub(crate) qs: [u8; (QK_K - 4 * QK_K / 64) / 5],
pub(crate) qh: [u8; QK_K / 64],
pub(crate) d: f16,
}
const _: () = assert!(std::mem::size_of::<BlockTq1_0>() == 54);
#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct BlockTq2_0 {
pub(crate) qs: [u8; QK_K / 4],
pub(crate) d: f16,
}
const _: () = assert!(std::mem::size_of::<BlockTq2_0>() == 66);
#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct BlockMxfp4 {
pub(crate) e: u8,
pub(crate) qs: [u8; QK_MXFP4 / 2],
}
const _: () = assert!(std::mem::size_of::<BlockMxfp4>() == 17);
#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct BlockNvfp4 {
pub(crate) d: [u8; QK_NVFP4 / QK_NVFP4_SUB],
pub(crate) qs: [u8; QK_NVFP4 / 2],
}
const _: () = assert!(std::mem::size_of::<BlockNvfp4>() == 36);
fn e8m0_to_f32_half(x: u8) -> f32 {
let bits: u32 = if x < 2 {
0x0020_0000u32 << x
} else {
u32::from(x - 1) << 23
};
f32::from_bits(bits)
}
fn ldexp(x: f32, e: i32) -> f32 {
x * f32::from_bits(((e + 127) as u32) << 23)
}
fn ue4m3_to_f32(x: u8) -> f32 {
if x == 0 || x == 0x7F {
return 0.0;
}
let exp = i32::from((x >> 3) & 0xF);
let man = f32::from(x & 0x7);
let raw = if exp == 0 {
ldexp(man, -9)
} else {
ldexp(1.0 + man / 8.0, exp - 7)
};
raw * 0.5
}
fn tq1_digit(byte: u8, pow3: u8) -> f32 {
let q = byte.wrapping_mul(pow3);
let xi = ((u16::from(q) * 3) >> 8) as i16;
f32::from(xi - 1)
}
impl BlockTq1_0 {
fn decode(&self, y: &mut [f32]) {
const POW3: [u8; 5] = [1, 3, 9, 27, 81];
let d = self.d.to_f32();
let mut out = 0;
let (head, tail) = self.qs.split_at(self.qs.len() - self.qs.len() % 32);
let runs32 = head.as_chunks::<32>().0.iter().map(|c| &c[..]);
let runs16 = tail.as_chunks::<16>().0.iter().map(|c| &c[..]);
for chunk in runs32.chain(runs16) {
for p in POW3 {
for q in chunk {
y[out] = tq1_digit(*q, p) * d;
out += 1;
}
}
}
for p in &POW3[..4] {
for q in &self.qh {
y[out] = tq1_digit(*q, *p) * d;
out += 1;
}
}
debug_assert_eq!(out, QK_K);
}
}
impl BlockTq2_0 {
fn decode(&self, y: &mut [f32]) {
let d = self.d.to_f32();
let mut out = 0;
for chunk in self.qs.as_chunks::<32>().0 {
for l in 0..4 {
for q in chunk {
let v = ((q >> (l * 2)) & 3) as i8;
y[out] = f32::from(v - 1) * d;
out += 1;
}
}
}
}
}
impl BlockMxfp4 {
fn decode(&self, y: &mut [f32]) {
let d = e8m0_to_f32_half(self.e);
for (j, q) in self.qs.iter().enumerate() {
y[j] = f32::from(KVALUES_FP4[usize::from(q & 0x0F)]) * d;
y[j + QK_MXFP4 / 2] = f32::from(KVALUES_FP4[usize::from(q >> 4)]) * d;
}
}
}
impl BlockNvfp4 {
fn decode(&self, y: &mut [f32]) {
for (s, scale) in self.d.iter().enumerate() {
let d = ue4m3_to_f32(*scale);
let yb = &mut y[s * QK_NVFP4_SUB..(s + 1) * QK_NVFP4_SUB];
let qs = &self.qs[s * QK_NVFP4_SUB / 2..(s + 1) * QK_NVFP4_SUB / 2];
for (j, q) in qs.iter().enumerate() {
yb[j] = f32::from(KVALUES_FP4[usize::from(q & 0x0F)]) * d;
yb[j + QK_NVFP4_SUB / 2] = f32::from(KVALUES_FP4[usize::from(q >> 4)]) * d;
}
}
}
}
macro_rules! decode_only_type {
($blk:ty, $dtype:expr, $qk:expr, $vecdot:ty) => {
impl GgmlType for $blk {
const DTYPE: GgmlDType = $dtype;
const BLCK_SIZE: usize = $qk;
fn to_float(xs: &[Self], ys: &mut [f32]) {
for (b, y) in xs.iter().zip(ys.chunks_exact_mut($qk)) {
b.decode(y);
}
}
fn from_float(_xs: &[f32], _ys: &mut [Self]) {
panic!(
"{:?} is decode-only: Fuel cannot quantize to it (GAP-211 C)",
$dtype
);
}
}
};
}
decode_only_type!(BlockTq1_0, GgmlDType::Tq1_0, QK_K, BlockQ8K);
decode_only_type!(BlockTq2_0, GgmlDType::Tq2_0, QK_K, BlockQ8K);
decode_only_type!(BlockMxfp4, GgmlDType::Mxfp4, QK_MXFP4, BlockQ8_0);
decode_only_type!(BlockNvfp4, GgmlDType::Nvfp4, QK_NVFP4, BlockQ8_0);
#[cfg(test)]
mod tests {
use super::*;
fn one<T: GgmlType>(b: T) -> Vec<f32> {
let mut y = vec![0f32; T::BLCK_SIZE];
T::to_float(&[b], &mut y);
y
}
#[test]
fn geometry_agrees_with_ggml_dtype() {
fn check<T: GgmlType>() {
assert_eq!(
std::mem::size_of::<T>(),
T::DTYPE.type_size(),
"{:?}",
T::DTYPE
);
assert_eq!(T::BLCK_SIZE, T::DTYPE.block_size(), "{:?}", T::DTYPE);
assert!(!T::DTYPE.supports_quantize());
assert!(T::DTYPE.check_quantize().is_err());
}
check::<BlockTq1_0>();
check::<BlockTq2_0>();
check::<BlockMxfp4>();
check::<BlockNvfp4>();
assert!(GgmlDType::Q4K.supports_quantize());
}
#[test]
fn e8m0_half_scale_cases() {
assert_eq!(e8m0_to_f32_half(127), 0.5);
assert_eq!(e8m0_to_f32_half(128), 1.0);
assert_eq!(e8m0_to_f32_half(1), f32::from_bits(0x0040_0000));
assert_eq!(e8m0_to_f32_half(0), f32::from_bits(0x0020_0000));
}
#[test]
fn ue4m3_scale_cases() {
assert_eq!(ue4m3_to_f32(0x38), 0.5);
assert_eq!(ue4m3_to_f32(0x01), 2f32.powi(-10));
assert_eq!(ue4m3_to_f32(0x7E), 224.0);
assert_eq!(ue4m3_to_f32(0x00), 0.0);
assert_eq!(ue4m3_to_f32(0x7F), 0.0);
}
#[test]
fn mxfp4_dequantizes_as_the_ggml_formula() {
let mut b = BlockMxfp4::zeros();
b.e = 128; b.qs[0] = 0x21; b.qs[1] = 0xF8; let y = one(b);
assert_eq!(y[0], 1.0);
assert_eq!(y[16], 2.0);
assert_eq!(y[1], 0.0);
assert_eq!(y[17], -12.0);
assert_eq!(y[2], 0.0); }
#[test]
fn nvfp4_each_subblock_uses_its_own_scale() {
let mut b = BlockNvfp4::zeros();
b.d = [0x38, 0x38 + 8, 0x7F, 0x38]; b.qs[0] = 0x42; b.qs[8] = 0x42; b.qs[16] = 0x42; let y = one(b);
assert_eq!(y[0], 1.0); assert_eq!(y[8], 2.0); assert_eq!(y[16], 2.0); assert_eq!(y[24], 4.0);
assert_eq!(y[32], 0.0);
assert_eq!(y[40], 0.0);
}
#[test]
fn tq2_0_dequantizes_as_the_ggml_formula() {
let mut b = BlockTq2_0::zeros();
b.d = f16::from_f32(2.0);
b.qs[0] = 0b11_10_01_00;
let y = one(b);
assert_eq!(y[0], -2.0); assert_eq!(y[32], 0.0); assert_eq!(y[64], 2.0); assert_eq!(y[96], 4.0); assert_eq!(y[1], -2.0); }
#[test]
fn tq1_0_dequantizes_as_the_ggml_formula() {
let mut b = BlockTq1_0::zeros();
b.d = f16::from_f32(2.0);
b.qs[0] = 171; b.qs[32] = 171; b.qh[0] = 171;
let y = one(b);
assert_eq!(y[0], 2.0);
assert_eq!(y[32], -2.0);
assert_eq!(y[160], 2.0); assert_eq!(y[160 + 16], -2.0);
assert_eq!(y[240], 2.0); assert_eq!(y[244], -2.0);
assert_eq!(y[1], -2.0); }
}