use crate::mul_mm::{f16_to_f32, Codebook, MulMmKind, SUB};
const KVALUES_IQ4NL: [f32; 16] = [
-127.0, -104.0, -83.0, -65.0, -49.0, -35.0, -22.0, -10.0, 1.0, 13.0, 25.0, 38.0, 53.0, 69.0,
89.0, 113.0,
];
const IQ4NL_CODEBOOK: Codebook = Codebook {
c_name: "ferrox_kvalues_iq4nl",
values: &KVALUES_IQ4NL,
};
const KVALUES_MXFP4: [f32; 16] = [
0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, -0.0, -0.5, -1.0, -1.5, -2.0, -3.0, -4.0, -6.0,
];
const MXFP4_CODEBOOK: Codebook = Codebook {
c_name: "ferrox_kvalues_mxfp4",
values: &KVALUES_MXFP4,
};
pub const IQ4_NL: MulMmKind = MulMmKind {
name: "IQ4_NL",
module_name: "ferrox_mul_mm_iq4_nl",
fn_name: "iq4_nl_mul_mm",
block_bytes: 18,
block_elems: 32,
dequant_src: r#"
__device__ __forceinline__ void ferrox_dequant_sub(
const unsigned char* xb, int il, float* reg
) {
const float d = ferrox_f16_to_f32(
(unsigned short)xb[0] | ((unsigned short)xb[1] << 8));
const unsigned char* qs = xb + 2;
const int shift = il ? 4 : 0;
#pragma unroll
for (int i = 0; i < 16; i++) {
reg[i] = d * ferrox_kvalues_iq4nl[(qs[i] >> shift) & 0xF];
}
}
"#,
dequant_twin: dequant_sub_iq4_nl,
codebook: Some(IQ4NL_CODEBOOK),
};
fn dequant_sub_iq4_nl(xb: &[u8], il: usize, reg: &mut [f32; SUB]) {
let d = f16_to_f32(u16::from(xb[0]) | (u16::from(xb[1]) << 8));
let qs = &xb[2..2 + 16];
let shift = if il != 0 { 4 } else { 0 };
for (r, q) in reg.iter_mut().zip(qs.iter()) {
*r = d * KVALUES_IQ4NL[usize::from((q >> shift) & 0xF)];
}
}
pub const IQ4_XS: MulMmKind = MulMmKind {
name: "IQ4_XS",
module_name: "ferrox_mul_mm_iq4_xs",
fn_name: "iq4_xs_mul_mm",
block_bytes: 136,
block_elems: 256,
dequant_src: r#"
__device__ __forceinline__ void ferrox_dequant_sub(
const unsigned char* xb, int il, float* reg
) {
const float d = ferrox_f16_to_f32(
(unsigned short)xb[0] | ((unsigned short)xb[1] << 8));
const unsigned int scales_h =
(unsigned int)xb[2] | ((unsigned int)xb[3] << 8);
const unsigned char* scales_l = xb + 4;
const int ib = il / 2;
const unsigned char* qs = xb + 8 + 16 * ib;
const int shift = (il & 1) ? 4 : 0;
const unsigned int ls =
((unsigned int)(scales_l[ib / 2] >> (4 * (ib & 1))) & 0xFu)
| (((scales_h >> (2 * ib)) & 3u) << 4);
const float dl = d * ((float)ls - 32.0f);
#pragma unroll
for (int i = 0; i < 16; i++) {
reg[i] = dl * ferrox_kvalues_iq4nl[(qs[i] >> shift) & 0xF];
}
}
"#,
dequant_twin: dequant_sub_iq4_xs,
codebook: Some(IQ4NL_CODEBOOK),
};
fn dequant_sub_iq4_xs(xb: &[u8], il: usize, reg: &mut [f32; SUB]) {
let d = f16_to_f32(u16::from(xb[0]) | (u16::from(xb[1]) << 8));
let scales_h = u32::from(xb[2]) | (u32::from(xb[3]) << 8);
let scales_l = &xb[4..8];
let ib = il / 2;
let qs = &xb[8 + 16 * ib..8 + 16 * ib + 16];
let shift = if il & 1 != 0 { 4 } else { 0 };
let ls =
(u32::from(scales_l[ib / 2] >> (4 * (ib & 1))) & 0xF) | (((scales_h >> (2 * ib)) & 3) << 4);
let dl = d * (ls as f32 - 32.0);
for (r, q) in reg.iter_mut().zip(qs.iter()) {
*r = dl * KVALUES_IQ4NL[usize::from((q >> shift) & 0xF)];
}
}
pub const MXFP4: MulMmKind = MulMmKind {
name: "MXFP4",
module_name: "ferrox_mul_mm_mxfp4",
fn_name: "mxfp4_mul_mm",
block_bytes: 17,
block_elems: 32,
dequant_src: r#"
__device__ __forceinline__ float ferrox_e8m0_to_f32(unsigned char e) {
// An E8M0 scale byte IS an f32 exponent field (bias 127), so
// placing it there is exact rather than an approximation. `e == 0`
// has to be special-cased: shifting it in would give 0.0, and the
// format means 2^-127, which is the subnormal bit pattern below.
// `e == 255` is reserved for NaN by the OCP spec and is not handled
// here, matching ggml's own documented limitation.
return e == 0 ? __int_as_float(0x00400000)
: __int_as_float((int)((unsigned int)e << 23));
}
__device__ __forceinline__ void ferrox_dequant_sub(
const unsigned char* xb, int il, float* reg
) {
const float d = ferrox_e8m0_to_f32(xb[0]);
const unsigned char* qs = xb + 1;
const int shift = il ? 4 : 0;
#pragma unroll
for (int i = 0; i < 16; i++) {
reg[i] = d * ferrox_kvalues_mxfp4[(qs[i] >> shift) & 0xF];
}
}
"#,
dequant_twin: dequant_sub_mxfp4,
codebook: Some(MXFP4_CODEBOOK),
};
fn e8m0_to_f32(e: u8) -> f32 {
if e == 0 {
f32::from_bits(0x0040_0000)
} else {
f32::from_bits(u32::from(e) << 23)
}
}
fn dequant_sub_mxfp4(xb: &[u8], il: usize, reg: &mut [f32; SUB]) {
let d = e8m0_to_f32(xb[0]);
let qs = &xb[1..1 + 16];
let shift = if il != 0 { 4 } else { 0 };
for (r, q) in reg.iter_mut().zip(qs.iter()) {
*r = d * KVALUES_MXFP4[usize::from((q >> shift) & 0xF)];
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_iq4_codebook_is_ferrox_quants() {
let mut block = vec![0u8; 18];
block[0] = 0x00;
block[1] = 0x3C;
for j in 0..16usize {
block[2 + j] = (j as u8) | ((j as u8) << 4);
}
let decoded = ferrox_quant::dequant_iq4_nl(&block).expect("iq4_nl dequant");
assert_eq!(decoded.len(), 32);
for (code, want) in KVALUES_IQ4NL.iter().enumerate() {
assert_eq!(decoded[code], *want, "low nibble, code {code}");
assert_eq!(decoded[16 + code], *want, "high nibble, code {code}");
}
}
#[test]
fn the_mxfp4_codebook_and_its_scale_bias_are_ferrox_quants() {
let mut block = vec![0u8; 17];
block[0] = 127;
for j in 0..16usize {
block[1 + j] = (j as u8) | ((j as u8) << 4);
}
let decoded = ferrox_quant::dequant_mxfp4_gguf(&block).expect("mxfp4 dequant");
assert_eq!(decoded.len(), 32);
assert_eq!(e8m0_to_f32(127), 1.0, "E8M0 bias is 127, not 128");
for (code, want) in KVALUES_MXFP4.iter().enumerate() {
assert_eq!(
decoded[code].to_bits(),
want.to_bits(),
"low nibble, code {code}: negative zero is a distinct value here"
);
assert_eq!(decoded[16 + code].to_bits(), want.to_bits(), "high, {code}");
}
}
#[test]
fn every_e8m0_byte_is_two_to_the_e_minus_127() {
for e in 0u32..=255 {
let got = e8m0_to_f32(e as u8);
let want = if e == 0 {
2f32.powi(-127)
} else {
2f32.powi(e as i32 - 127)
};
assert_eq!(got.to_bits(), want.to_bits(), "e = {e}");
}
}
}