use crate::k_quants::{
BlockQ2K, BlockQ3K, BlockQ4_0, BlockQ4_1, BlockQ4K, BlockQ5_0, BlockQ5_1, BlockQ5K, BlockQ6K,
BlockQ8_0, BlockQ8K, GgmlType,
};
use fuel_ir::Result;
use fuel_ir::quantized::GgmlDType;
use half::{bf16, f16};
fn cast_and_dequant<T: GgmlType>(bytes: &[u8]) -> Vec<f32> {
let blocks = crate::k_quants::blocks_from_bytes_cow::<T>(bytes);
let mut out = vec![0.0_f32; blocks.len() * T::BLCK_SIZE];
T::to_float(&blocks, &mut out);
out
}
pub fn dequant_ggml_bytes(bytes: &[u8], dtype: GgmlDType, name: &str) -> Result<Vec<f32>> {
Ok(match dtype {
GgmlDType::F32 => {
if !bytes.len().is_multiple_of(4) {
return Err(fuel_ir::Error::Msg(format!(
"gguf {name}: F32 byte count {} not a multiple of 4",
bytes.len(),
))
.bt());
}
bytes
.as_chunks::<4>()
.0
.iter()
.map(|c| f32::from_le_bytes(*c))
.collect()
}
GgmlDType::F16 => {
if !bytes.len().is_multiple_of(2) {
return Err(fuel_ir::Error::Msg(format!(
"gguf {name}: F16 byte count {} not a multiple of 2",
bytes.len(),
))
.bt());
}
bytes
.as_chunks::<2>()
.0
.iter()
.map(|c| f16::from_le_bytes(*c).to_f32())
.collect()
}
GgmlDType::BF16 => {
if !bytes.len().is_multiple_of(2) {
return Err(fuel_ir::Error::Msg(format!(
"gguf {name}: BF16 byte count {} not a multiple of 2",
bytes.len(),
))
.bt());
}
bytes
.as_chunks::<2>()
.0
.iter()
.map(|c| bf16::from_le_bytes(*c).to_f32())
.collect()
}
GgmlDType::Q4_0 => cast_and_dequant::<BlockQ4_0>(bytes),
GgmlDType::Q4_1 => cast_and_dequant::<BlockQ4_1>(bytes),
GgmlDType::Q5_0 => cast_and_dequant::<BlockQ5_0>(bytes),
GgmlDType::Q5_1 => cast_and_dequant::<BlockQ5_1>(bytes),
GgmlDType::Q8_0 => cast_and_dequant::<BlockQ8_0>(bytes),
GgmlDType::Q8_1 => {
return Err(fuel_ir::Error::Msg(format!(
"gguf {name}: Q8_1 dequantization is unimplemented upstream (GAP-125) -- \
BlockQ8_1::to_float has no body",
))
.bt());
}
GgmlDType::Q2K => cast_and_dequant::<BlockQ2K>(bytes),
GgmlDType::Q3K => cast_and_dequant::<BlockQ3K>(bytes),
GgmlDType::Q4K => cast_and_dequant::<BlockQ4K>(bytes),
GgmlDType::Q5K => cast_and_dequant::<BlockQ5K>(bytes),
GgmlDType::Q6K => cast_and_dequant::<BlockQ6K>(bytes),
GgmlDType::Q8K => cast_and_dequant::<BlockQ8K>(bytes),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn f32_round_trips() {
let original: Vec<f32> = vec![1.0, -2.5, 3.25, 0.0];
let bytes: Vec<u8> = original.iter().flat_map(|v| v.to_le_bytes()).collect();
let out = dequant_ggml_bytes(&bytes, GgmlDType::F32, "t").unwrap();
assert_eq!(out, original);
}
#[test]
fn f32_rejects_a_misaligned_byte_count() {
let bytes = vec![0_u8; 3];
let err = dequant_ggml_bytes(&bytes, GgmlDType::F32, "t").unwrap_err();
assert!(err.to_string().contains("not a multiple of 4"));
}
#[test]
fn every_wired_block_quant_variant_dequantizes_without_error() {
for dtype in [
GgmlDType::Q4_0,
GgmlDType::Q4_1,
GgmlDType::Q5_0,
GgmlDType::Q5_1,
GgmlDType::Q8_0,
GgmlDType::Q2K,
GgmlDType::Q3K,
GgmlDType::Q4K,
GgmlDType::Q5K,
GgmlDType::Q6K,
GgmlDType::Q8K,
] {
let bytes = vec![0_u8; 4096];
let out = dequant_ggml_bytes(&bytes, dtype, "t").unwrap_or_else(|e| {
panic!("{dtype:?} must dequantize a whole-zero block without error: {e}")
});
assert!(
!out.is_empty(),
"{dtype:?}: 4096 zeroed bytes must yield at least one dequantized block",
);
}
}
#[test]
fn cast_and_dequant_handles_a_misaligned_byte_slice() {
let align = std::mem::align_of::<crate::k_quants::BlockQ4_0>();
let block_size = std::mem::size_of::<crate::k_quants::BlockQ4_0>();
let n_blocks = 3;
let total = n_blocks * block_size;
let mut buf = vec![0_u8; total + 1];
for (i, b) in buf.iter_mut().enumerate() {
*b = (i as u8).wrapping_mul(37).wrapping_add(11);
}
let offset0_aligned = buf[0..total].as_ptr().align_offset(align) == 0;
let (aligned_slice, misaligned_slice) = if offset0_aligned {
(&buf[0..total], &buf[1..1 + total])
} else {
(&buf[1..1 + total], &buf[0..total])
};
assert_eq!(
aligned_slice.as_ptr().align_offset(align),
0,
"test setup bug"
);
assert_ne!(
misaligned_slice.as_ptr().align_offset(align),
0,
"test setup bug: this slice must actually BE misaligned for the test to mean \
anything -- the two offsets in a 1-byte-larger buffer must have opposite parity \
relative to `align`, so exactly one of them is always misaligned",
);
let aligned_out = dequant_ggml_bytes(aligned_slice, GgmlDType::Q4_0, "t").unwrap();
let misaligned_out = dequant_ggml_bytes(misaligned_slice, GgmlDType::Q4_0, "t").unwrap();
let misaligned_bytes_copy: Vec<u8> = misaligned_slice.to_vec();
let reference_out =
dequant_ggml_bytes(&misaligned_bytes_copy, GgmlDType::Q4_0, "t").unwrap();
assert_eq!(
reference_out, misaligned_out,
"dequantizing a byte slice from a misaligned pointer must produce identical values \
to dequantizing the same bytes from an aligned one",
);
assert_eq!(aligned_out.len(), misaligned_out.len());
}
#[test]
fn q8_1_declines_with_a_typed_error_not_a_panic() {
let bytes = vec![0_u8; 4096];
let err = dequant_ggml_bytes(&bytes, GgmlDType::Q8_1, "t").unwrap_err();
assert!(err.to_string().contains("GAP-125"));
}
#[test]
fn distinct_dtypes_on_the_same_bytes_diverge() {
let bytes = vec![0xAB_u8; 4096];
let q4_0 = dequant_ggml_bytes(&bytes, GgmlDType::Q4_0, "t").unwrap();
let q6_k = dequant_ggml_bytes(&bytes, GgmlDType::Q6K, "t").unwrap();
assert_ne!(
q4_0.len(),
q6_k.len(),
"Q4_0 and Q6_K have different block sizes; equal output lengths would mean one \
arm is reaching the wrong to_float",
);
}
}