#[inline]
fn f16_to_f32(bits: u16) -> f32 {
let sign = (bits >> 15) & 1;
let exp = (bits >> 10) & 0x1f;
let mant = bits & 0x3ff;
let sign_bit = (sign as u32) << 31;
if exp == 0 {
if mant == 0 {
f32::from_bits(sign_bit)
} else {
let val = (mant as f32) * 2f32.powi(-24);
if sign == 1 {
-val
} else {
val
}
}
} else if exp == 0x1f {
let m = (mant as u32) << 13;
f32::from_bits(sign_bit | 0x7f80_0000 | m)
} else {
let e = (exp as i32 + (127 - 15)) as u32;
let m = (mant as u32) << 13;
f32::from_bits(sign_bit | (e << 23) | m)
}
}
#[inline]
fn rd_u16(block: &[u8], off: usize) -> u16 {
u16::from_le_bytes([block[off], block[off + 1]])
}
#[inline]
fn rd_u32(block: &[u8], off: usize) -> u32 {
u32::from_le_bytes([block[off], block[off + 1], block[off + 2], block[off + 3]])
}
#[inline]
fn get_scale_min_k4(j: usize, scales: &[u8]) -> (u32, u32) {
if j < 4 {
let sc = (scales[j] & 63) as u32;
let m = (scales[j + 4] & 63) as u32;
(sc, m)
} else {
let sc = ((scales[j + 4] & 0x0f) as u32) | (((scales[j - 4] >> 6) & 3) as u32) << 4;
let m = ((scales[j + 4] >> 4) & 0x0f) as u32 | (((scales[j] >> 6) & 3) as u32) << 4;
(sc, m)
}
}
pub type DequantFn = fn(block: &[u8], elem_in_block: usize) -> f32;
pub struct QuantFormula {
pub type_id: u32,
pub name: &'static str,
pub block_elems: usize,
pub block_bytes: usize,
pub dequant: DequantFn,
}
fn dequant_f32(block: &[u8], elem: usize) -> f32 {
f32::from_le_bytes([
block[elem * 4],
block[elem * 4 + 1],
block[elem * 4 + 2],
block[elem * 4 + 3],
])
}
fn dequant_f16(block: &[u8], elem: usize) -> f32 {
f16_to_f32(rd_u16(block, elem * 2))
}
fn dequant_q4_0(block: &[u8], elem: usize) -> f32 {
let d = f16_to_f32(rd_u16(block, 0));
let qs_byte = block[2 + (elem % 16)];
let nib = if elem < 16 {
qs_byte & 0x0f
} else {
qs_byte >> 4
};
(nib as f32 - 8.0) * d
}
fn dequant_q5_0(block: &[u8], elem: usize) -> f32 {
let d = f16_to_f32(rd_u16(block, 0));
let qh = rd_u32(block, 2);
let high_bit = (qh >> elem) & 1;
let qs_byte = block[6 + (elem % 16)];
let low = if elem < 16 {
qs_byte & 0x0f
} else {
qs_byte >> 4
};
let val5 = low as u32 | (high_bit << 4);
(val5 as f32 - 16.0) * d
}
fn dequant_q8_0(block: &[u8], elem: usize) -> f32 {
let d = f16_to_f32(rd_u16(block, 0));
let raw = block[2 + elem] as i8;
d * raw as f32
}
fn dequant_q4_k(block: &[u8], elem: usize) -> f32 {
let d = f16_to_f32(rd_u16(block, 0));
let dmin = f16_to_f32(rd_u16(block, 2));
let scales = &block[4..16];
let qs = &block[16..144];
let group = elem / 64;
let in_grp = elem % 64;
let sub = in_grp / 32;
let l = in_grp % 32;
let is = group * 2 + sub;
let (sc, m) = get_scale_min_k4(is, scales);
let sc_val = d * sc as f32;
let m_val = dmin * m as f32;
let qs_byte = qs[group * 32 + l];
let nibble = if sub == 0 {
qs_byte & 0x0f
} else {
qs_byte >> 4
};
sc_val * nibble as f32 - m_val
}
fn dequant_q5_k(block: &[u8], elem: usize) -> f32 {
let d = f16_to_f32(rd_u16(block, 0));
let dmin = f16_to_f32(rd_u16(block, 2));
let scales = &block[4..16];
let qh = &block[16..48];
let qs = &block[48..176];
let group = elem / 64;
let in_grp = elem % 64;
let sub = in_grp / 32;
let l = in_grp % 32;
let is = group * 2 + sub;
let (sc, m) = get_scale_min_k4(is, scales);
let sc_val = d * sc as f32;
let m_val = dmin * m as f32;
let qs_byte = qs[group * 32 + l];
let nibble = if sub == 0 {
qs_byte & 0x0f
} else {
qs_byte >> 4
};
let high_bit = (qh[l] >> (elem / 32)) & 1;
let q5 = nibble as u32 | (high_bit as u32) << 4;
sc_val * q5 as f32 - m_val
}
fn dequant_q6_k(block: &[u8], elem: usize) -> f32 {
let d = f16_to_f32(rd_u16(block, 208));
let half = elem / 128; let half_e = elem % 128;
let l = half_e % 32;
let quarter = half_e / 32;
let ql_rel = if quarter == 0 || quarter == 2 {
half * 64 + l
} else {
half * 64 + l + 32
};
let ql_byte = block[ql_rel];
let lower4 = if quarter < 2 {
ql_byte & 0x0f
} else {
ql_byte >> 4
};
let qh_byte = block[128 + half * 32 + l];
let upper2 = (qh_byte >> (quarter * 2)) & 3;
let q6 = lower4 as i32 | ((upper2 as i32) << 4); let signed_q = q6 - 32;
let sc_idx = half * 8 + (l / 16) + quarter * 2;
let sc_raw = block[192 + sc_idx] as i8;
d * sc_raw as f32 * signed_q as f32
}
pub const QUANT_FORMULAS: &[QuantFormula] = &[
QuantFormula {
type_id: 0,
name: "F32",
block_elems: 1,
block_bytes: 4,
dequant: dequant_f32,
},
QuantFormula {
type_id: 1,
name: "F16",
block_elems: 1,
block_bytes: 2,
dequant: dequant_f16,
},
QuantFormula {
type_id: 2,
name: "Q4_0",
block_elems: 32,
block_bytes: 18,
dequant: dequant_q4_0,
},
QuantFormula {
type_id: 6,
name: "Q5_0",
block_elems: 32,
block_bytes: 22,
dequant: dequant_q5_0,
},
QuantFormula {
type_id: 8,
name: "Q8_0",
block_elems: 32,
block_bytes: 34,
dequant: dequant_q8_0,
},
QuantFormula {
type_id: 12,
name: "Q4_K",
block_elems: 256,
block_bytes: 144,
dequant: dequant_q4_k,
},
QuantFormula {
type_id: 13,
name: "Q5_K",
block_elems: 256,
block_bytes: 176,
dequant: dequant_q5_k,
},
QuantFormula {
type_id: 14,
name: "Q6_K",
block_elems: 256,
block_bytes: 210,
dequant: dequant_q6_k,
},
];
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum FormulaSlot {
F32 = 0,
F16 = 1,
Q4_0 = 2,
Q5_0 = 3,
Q8_0 = 4,
Q4_K = 5,
Q5_K = 6,
Q6_K = 7,
}
impl FormulaSlot {
pub fn as_u32(self) -> u32 {
self as u32
}
}
pub fn slot_for_type(type_id: u32) -> Option<FormulaSlot> {
match type_id {
0 => Some(FormulaSlot::F32),
1 => Some(FormulaSlot::F16),
2 => Some(FormulaSlot::Q4_0),
6 => Some(FormulaSlot::Q5_0),
8 => Some(FormulaSlot::Q8_0),
12 => Some(FormulaSlot::Q4_K),
13 => Some(FormulaSlot::Q5_K),
14 => Some(FormulaSlot::Q6_K),
_ => None,
}
}
pub fn formula_for_type(type_id: u32) -> Option<&'static QuantFormula> {
QUANT_FORMULAS.iter().find(|f| f.type_id == type_id)
}
pub fn block_elems(type_id: u32) -> Option<usize> {
formula_for_type(type_id).map(|f| f.block_elems)
}
pub fn block_bytes(type_id: u32) -> Option<usize> {
formula_for_type(type_id).map(|f| f.block_bytes)
}
pub fn dequant_elem(type_id: u32, block: &[u8], elem_in_block: usize) -> Option<f32> {
let f = formula_for_type(type_id)?;
Some((f.dequant)(block, elem_in_block))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn q6_k_hand_computed_block() {
let mut block = vec![0u8; 210];
block[208] = 0x00;
block[209] = 0x3c;
block[192..208].fill(1);
let f = formula_for_type(14).expect("Q6_K registered");
for e in 0..256 {
let v = (f.dequant)(&block, e);
assert!((v + 32.0).abs() < 1e-6, "elem {} = {}", e, v);
}
}
#[test]
fn q6_k_scale_index_selects_correct_subblock() {
let mut block = vec![0u8; 210];
block[208] = 0x00;
block[209] = 0x3c; block[192..208].fill(1);
block[192] = 2; block[0] = 0x01; let f = formula_for_type(14).expect("Q6_K registered");
assert!(
((f.dequant)(&block, 0) + 62.0).abs() < 1e-6,
"got {}",
(f.dequant)(&block, 0)
);
assert!(
((f.dequant)(&block, 1) + 64.0).abs() < 1e-6,
"got {}",
(f.dequant)(&block, 1)
);
}
#[test]
fn f32_roundtrip() {
let mut block = std::f32::consts::PI.to_le_bytes().to_vec();
block.resize(4, 0);
let v = dequant_elem(0, &block, 0).unwrap();
assert!((v - std::f32::consts::PI).abs() < 1e-6);
}
#[test]
fn q4_0_known_value() {
let mut block = vec![0u8; 18];
block[0] = 0x00;
block[1] = 0x3c;
block[2] = 0x0a;
let v = dequant_elem(2, &block, 0).unwrap();
assert!((v - 2.0).abs() < 1e-6, "got {}", v);
}
#[test]
fn slot_mapping_covers_all_eight() {
for (tid, slot) in [
(0u32, 0u32),
(1, 1),
(2, 2),
(6, 3),
(8, 4),
(12, 5),
(13, 6),
(14, 7),
] {
let s = slot_for_type(tid).expect("supported");
assert_eq!(s.as_u32(), slot);
assert_eq!(formula_for_type(tid).unwrap().type_id, tid);
}
assert!(slot_for_type(99).is_none());
}
#[test]
fn f16_helper_matches_reference() {
assert_eq!(f16_to_f32(0x3C00), 1.0);
assert_eq!(f16_to_f32(0xC000), -2.0);
assert_eq!(f16_to_f32(0x3E00), 1.5);
}
}