use crate::error::{RealizarError, Result};
use crate::quantize::iq2_s::{dequantize_iq2_s_block, GGML_TYPE_IQ2_S, IQ2_S_BLOCK_BYTES};
use crate::quantize::iq2_xxs::{dequantize_iq2_xxs_block, GGML_TYPE_IQ2_XXS, IQ2_XXS_BLOCK_BYTES};
use crate::quantize::iq3_s::{dequantize_iq3_s_block, GGML_TYPE_IQ3_S, IQ3_S_BLOCK_BYTES};
use crate::quantize::iq3_xxs::{dequantize_iq3_xxs_block, GGML_TYPE_IQ3_XXS, IQ3_XXS_BLOCK_BYTES};
use crate::quantize::iq4_xs::{dequantize_iq4_xs_block, GGML_TYPE_IQ4_XS, IQ4_XS_BLOCK_BYTES};
pub const IQ_BLOCK_ELEMS: usize = 256;
#[must_use]
pub const fn iq_block_bytes(qtype: u32) -> Option<usize> {
match qtype {
GGML_TYPE_IQ2_XXS => Some(IQ2_XXS_BLOCK_BYTES),
GGML_TYPE_IQ2_S => Some(IQ2_S_BLOCK_BYTES),
GGML_TYPE_IQ3_XXS => Some(IQ3_XXS_BLOCK_BYTES),
GGML_TYPE_IQ3_S => Some(IQ3_S_BLOCK_BYTES),
GGML_TYPE_IQ4_XS => Some(IQ4_XS_BLOCK_BYTES),
_ => None,
}
}
pub fn dequantize_iq_block(qtype: u32, block: &[u8], out: &mut [f32]) -> Result<()> {
match qtype {
GGML_TYPE_IQ2_XXS => dequantize_iq2_xxs_block(block, out),
GGML_TYPE_IQ2_S => dequantize_iq2_s_block(block, out),
GGML_TYPE_IQ3_XXS => dequantize_iq3_xxs_block(block, out),
GGML_TYPE_IQ3_S => dequantize_iq3_s_block(block, out),
GGML_TYPE_IQ4_XS => dequantize_iq4_xs_block(block, out),
other => {
return Err(RealizarError::UnsupportedOperation {
operation: "dequantize_iq_block".to_string(),
reason: format!("ggml type {other} is not an IQ type with a dequantizer here"),
});
},
}
Ok(())
}
pub fn dequantize_iq_tensor(qtype: u32, data: &[u8]) -> Result<Vec<f32>> {
let block_bytes = iq_block_bytes(qtype).ok_or_else(|| RealizarError::UnsupportedOperation {
operation: "dequantize_iq_tensor".to_string(),
reason: format!("ggml type {qtype} is not an IQ type with a dequantizer here"),
})?;
if !data.len().is_multiple_of(block_bytes) {
return Err(RealizarError::InvalidShape {
reason: format!(
"IQ tensor length {} is not a multiple of block size {block_bytes}",
data.len()
),
});
}
let nb = data.len() / block_bytes;
let mut out = vec![0.0f32; nb * IQ_BLOCK_ELEMS];
for (i, block) in data.chunks_exact(block_bytes).enumerate() {
dequantize_iq_block(
qtype,
block,
&mut out[i * IQ_BLOCK_ELEMS..(i + 1) * IQ_BLOCK_ELEMS],
)?;
}
Ok(out)
}
pub fn iq_parallel_matvec_into(
qtype: u32,
data: &[u8],
x: &[f32],
in_dim: usize,
out_dim: usize,
output: &mut [f32],
) -> Result<()> {
use rayon::prelude::*;
let block_bytes = iq_block_bytes(qtype).ok_or_else(|| RealizarError::UnsupportedOperation {
operation: "iq_parallel_matvec_into".to_string(),
reason: format!("ggml type {qtype} is not an IQ type with a dequantizer here"),
})?;
if x.len() < in_dim || output.len() < out_dim {
return Err(RealizarError::InvalidShape {
reason: format!(
"IQ matvec needs x >= {in_dim} (got {}) and output >= {out_dim} (got {})",
x.len(),
output.len()
),
});
}
let blocks_per_row = in_dim.div_ceil(IQ_BLOCK_ELEMS);
let row_bytes = blocks_per_row * block_bytes;
let needed = row_bytes * out_dim;
if data.len() < needed {
return Err(RealizarError::InvalidShape {
reason: format!(
"IQ weight has {} bytes, needs {needed} for {out_dim}x{in_dim} (type {qtype})",
data.len()
),
});
}
output[..out_dim]
.par_iter_mut()
.enumerate()
.try_for_each(|(row, dst)| -> Result<()> {
let row_data = &data[row * row_bytes..row * row_bytes + row_bytes];
let mut scratch = [0.0f32; IQ_BLOCK_ELEMS];
let mut sum = 0.0f32;
for (b, block) in row_data.chunks_exact(block_bytes).enumerate() {
dequantize_iq_block(qtype, block, &mut scratch)?;
let col0 = b * IQ_BLOCK_ELEMS;
let n = IQ_BLOCK_ELEMS.min(in_dim - col0);
for (j, w) in scratch[..n].iter().enumerate() {
sum += w * x[col0 + j];
}
}
*dst = sum;
Ok(())
})?;
Ok(())
}
pub fn iq_parallel_matvec(
qtype: u32,
data: &[u8],
x: &[f32],
in_dim: usize,
out_dim: usize,
) -> Result<Vec<f32>> {
let mut out = vec![0.0f32; out_dim];
iq_parallel_matvec_into(qtype, data, x, in_dim, out_dim, &mut out)?;
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
const IQ_TYPES: [u32; 5] = [
GGML_TYPE_IQ2_XXS,
GGML_TYPE_IQ2_S,
GGML_TYPE_IQ3_XXS,
GGML_TYPE_IQ3_S,
GGML_TYPE_IQ4_XS,
];
fn block_of(qtype: u32, salt: u8) -> Vec<u8> {
let n = iq_block_bytes(qtype).expect("iq type");
let mut b = vec![0u8; n];
b[0] = 0x00;
b[1] = 0x26; let mut state = u32::from(salt).wrapping_mul(2_654_435_761).wrapping_add(1);
for byte in b.iter_mut().skip(2) {
state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
*byte = (state >> 16) as u8;
}
b
}
#[test]
fn iq_block_bytes_knows_the_five_types_and_refuses_others() {
assert_eq!(iq_block_bytes(GGML_TYPE_IQ2_XXS), Some(66));
assert_eq!(iq_block_bytes(GGML_TYPE_IQ2_S), Some(82));
assert_eq!(iq_block_bytes(GGML_TYPE_IQ3_XXS), Some(98));
assert_eq!(iq_block_bytes(GGML_TYPE_IQ3_S), Some(110));
assert_eq!(iq_block_bytes(GGML_TYPE_IQ4_XS), Some(136));
assert_eq!(iq_block_bytes(12), None);
assert_eq!(iq_block_bytes(0), None);
}
#[test]
fn matvec_with_a_one_hot_input_reads_the_dequantized_weight() {
for qtype in IQ_TYPES {
let data = block_of(qtype, 7);
let dequantized = dequantize_iq_tensor(qtype, &data).expect("dequant");
for col in [0usize, 1, 31, 130, 255] {
let mut x = vec![0.0f32; IQ_BLOCK_ELEMS];
x[col] = 1.0;
let got = iq_parallel_matvec(qtype, &data, &x, IQ_BLOCK_ELEMS, 1).expect("matvec");
assert!(
(got[0] - dequantized[col]).abs() <= 1e-6 * dequantized[col].abs().max(1.0),
"type {qtype} col {col}: matvec {} vs dequant {}",
got[0],
dequantized[col]
);
}
}
}
#[test]
fn rows_are_read_at_their_own_stride() {
for qtype in IQ_TYPES {
let mut data = block_of(qtype, 1);
let row1 = block_of(qtype, 2);
data.extend_from_slice(&row1);
let x = vec![1.0f32; IQ_BLOCK_ELEMS];
let got = iq_parallel_matvec(qtype, &data, &x, IQ_BLOCK_ELEMS, 2).expect("matvec");
let d0: f32 = dequantize_iq_tensor(qtype, &data[..data.len() / 2])
.expect("dequant")
.iter()
.sum();
let d1: f32 = dequantize_iq_tensor(qtype, &row1)
.expect("dequant")
.iter()
.sum();
assert!(
(got[0] - d0).abs() <= 1e-4 * d0.abs().max(1.0),
"type {qtype} row 0"
);
assert!(
(got[1] - d1).abs() <= 1e-4 * d1.abs().max(1.0),
"type {qtype} row 1"
);
}
}
#[test]
fn a_non_iq_type_is_refused_rather_than_guessed() {
let err = iq_parallel_matvec(12, &[0u8; 144], &[0.0; 256], 256, 1).expect_err("Q4_K");
assert!(format!("{err}").contains("not an IQ type"));
let err = dequantize_iq_tensor(0, &[0u8; 4]).expect_err("F32");
assert!(format!("{err}").contains("not an IQ type"));
}
#[test]
fn a_short_weight_is_refused_rather_than_read_out_of_bounds() {
let data = block_of(GGML_TYPE_IQ4_XS, 3);
let err = iq_parallel_matvec(GGML_TYPE_IQ4_XS, &data, &[0.0; 256], 256, 2)
.expect_err("one block cannot feed two rows");
assert!(format!("{err}").contains("needs"));
}
}