use std::collections::BTreeMap;
use ztensor::catalog::{Entry, Location, PartEntry, Payload};
use ztensor::cbor::Value;
use ztensor::{Catalog, DType, Error, Result, Store, StoreId};
use crate::project::Projection;
const NEED_MORE: &str = "header extends past the bytes read so far";
fn bad(detail: impl Into<String>) -> Error {
Error::InvalidInput(format!("gguf: {}", detail.into()))
}
fn type_info(id: u32) -> Result<(u64, u64, &'static str)> {
Ok(match id {
0 => (1, 4, "f32"),
1 => (1, 2, "f16"),
2 => (32, 18, "q4_0"),
3 => (32, 20, "q4_1"),
6 => (32, 22, "q5_0"),
7 => (32, 24, "q5_1"),
8 => (32, 34, "q8_0"),
9 => (32, 36, "q8_1"),
10 => (256, 84, "q2_k"),
11 => (256, 110, "q3_k"),
12 => (256, 144, "q4_k"),
13 => (256, 176, "q5_k"),
14 => (256, 210, "q6_k"),
15 => (256, 292, "q8_k"),
16 => (256, 66, "iq2_xxs"),
17 => (256, 74, "iq2_xs"),
18 => (256, 98, "iq3_xxs"),
19 => (256, 50, "iq1_s"),
20 => (32, 18, "iq4_nl"),
21 => (256, 110, "iq3_s"),
22 => (256, 82, "iq2_s"),
23 => (256, 136, "iq4_xs"),
24 => (1, 1, "i8"),
25 => (1, 2, "i16"),
26 => (1, 4, "i32"),
27 => (1, 8, "i64"),
28 => (1, 8, "f64"),
29 => (256, 56, "iq1_m"),
30 => (1, 2, "bf16"),
39 => (32, 17, "mxfp4"),
other => {
return Err(Error::Unsupported(format!(
"gguf tensor type id {other} has no registered projection"
)))
}
})
}
fn element_dtype(id: u32) -> Option<DType> {
Some(match id {
0 => DType::F32,
1 => DType::F16,
24 => DType::I8,
25 => DType::I16,
26 => DType::I32,
27 => DType::I64,
28 => DType::F64,
30 => DType::BF16,
_ => return None,
})
}
struct Cursor<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Cursor<'a> {
fn take(&mut self, n: usize) -> Result<&'a [u8]> {
let end = self
.pos
.checked_add(n)
.filter(|&e| e <= self.data.len())
.ok_or_else(|| bad(NEED_MORE))?;
let s = &self.data[self.pos..end];
self.pos = end;
Ok(s)
}
fn u32(&mut self) -> Result<u32> {
Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
}
fn u64(&mut self) -> Result<u64> {
Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap()))
}
fn string(&mut self) -> Result<String> {
let len = self.u64()?;
if len > self.data.len() as u64 {
return Err(bad("string length exceeds file"));
}
String::from_utf8(self.take(len as usize)?.to_vec())
.map_err(|_| bad("invalid UTF-8 in string"))
}
fn meta_value(&mut self, vtype: u32, depth: u32) -> Result<Value> {
if depth > 32 {
return Err(bad("metadata nesting too deep"));
}
Ok(match vtype {
0 => Value::Uint(self.take(1)?[0] as u64),
1 => int_value(self.take(1)?[0] as i8 as i64),
2 => Value::Uint(u16::from_le_bytes(self.take(2)?.try_into().unwrap()) as u64),
3 => int_value(i16::from_le_bytes(self.take(2)?.try_into().unwrap()) as i64),
4 => Value::Uint(self.u32()? as u64),
5 => int_value(i32::from_le_bytes(self.take(4)?.try_into().unwrap()) as i64),
6 => Value::Float(f32::from_le_bytes(self.take(4)?.try_into().unwrap()) as f64),
7 => Value::Bool(self.take(1)?[0] != 0),
8 => Value::Text(self.string()?),
9 => {
let elem_type = self.u32()?;
let count = self.u64()?;
let remaining = (self.data.len() - self.pos) as u64;
if count > remaining {
return Err(bad("array length exceeds remaining bytes"));
}
let mut items = Vec::with_capacity(count.min(1 << 16) as usize);
for _ in 0..count {
items.push(self.meta_value(elem_type, depth + 1)?);
}
Value::Array(items)
}
10 => Value::Uint(self.u64()?),
11 => int_value(self.u64()? as i64),
12 => Value::Float(f64::from_le_bytes(self.take(8)?.try_into().unwrap())),
other => return Err(bad(format!("unknown metadata value type {other}"))),
})
}
}
fn int_value(v: i64) -> Value {
if v < 0 {
Value::Nint((-1 - v) as u64)
} else {
Value::Uint(v as u64)
}
}
pub(crate) fn project(store: &Store) -> Result<Projection> {
let file_len = store.len();
if let Some(mapped) = store.bytes() {
return project_bytes(mapped, file_len);
}
let mut window = (1u64 << 20).min(file_len);
loop {
let buf = store.read(0, window)?;
match project_bytes(&buf, file_len) {
Err(e) if window < file_len && format!("{e}").contains(NEED_MORE) => {
window = (window * 4).min(file_len);
}
other => return other,
}
}
}
fn project_bytes(buf: &[u8], file_len: u64) -> Result<Projection> {
let mut c = Cursor { data: buf, pos: 0 };
if c.take(4)? != b"GGUF" {
return Err(bad("bad magic"));
}
let version = c.u32()?;
if !(2..=3).contains(&version) {
return Err(Error::Unsupported(format!(
"gguf version {version} (little-endian v2/v3 only)"
)));
}
let tensor_count = c.u64()?;
let kv_count = c.u64()?;
if tensor_count > file_len || kv_count > file_len {
return Err(bad("header counts exceed file size"));
}
let mut alignment = 32u64;
let mut attributes: Vec<(Value, Value)> =
Vec::with_capacity(crate::safe::capacity(kv_count, 13, buf.len()));
for _ in 0..kv_count {
let key = c.string()?;
let vtype = c.u32()?;
let value = c.meta_value(vtype, 0)?;
if key == "general.alignment" {
alignment = value
.as_u64()
.filter(|a| a.is_power_of_two())
.ok_or_else(|| bad("general.alignment must be a power-of-two uint"))?;
}
attributes.push((Value::Text(key), value));
}
struct Info {
name: String,
shape: Vec<u64>,
type_id: u32,
offset: u64,
}
let mut infos = Vec::with_capacity(crate::safe::capacity(tensor_count, 24, buf.len()));
for _ in 0..tensor_count {
let name = c.string()?;
let n_dims = c.u32()?;
if n_dims > 64 {
return Err(bad(format!("tensor {name:?} has {n_dims} dims")));
}
let mut shape = Vec::with_capacity(n_dims as usize);
for _ in 0..n_dims {
shape.push(c.u64()?);
}
shape.reverse();
let type_id = c.u32()?;
let offset = c.u64()?;
infos.push(Info {
name,
shape,
type_id,
offset,
});
}
let data_start = crate::safe::mul(
"gguf data section",
(c.pos as u64).div_ceil(alignment),
alignment,
)?;
if data_start > file_len {
return Err(bad("aligned data section starts past end of file"));
}
let mut catalog = Catalog::new();
let mut ranges = vec![(0u64, data_start)]; for info in infos {
let (epb, block_bytes, type_name) = type_info(info.type_id)?;
let fastest = info.shape.last().copied().unwrap_or(1);
if fastest % epb != 0 {
return Err(bad(format!(
"tensor {:?}: fastest dim {fastest} not divisible by block size {epb}",
info.name
)));
}
let elems = crate::safe::product("gguf shape", &info.shape)?;
let byte_size = crate::safe::mul("gguf tensor size", elems / epb, block_bytes)?;
let abs = crate::safe::add("gguf tensor offset", data_start, info.offset)?;
if crate::safe::add("gguf tensor", abs, byte_size)? > file_len {
return Err(bad(format!(
"tensor {:?} extends past end of file",
info.name
)));
}
let (layout, attrs, dtype) = match element_dtype(info.type_id) {
Some(dt) => ("dense".to_string(), None, dt),
None => (
format!("gguf.{type_name}/1"),
Some(Value::Map(vec![
(Value::Text("elems_per_block".into()), Value::Uint(epb)),
(Value::Text("block_bytes".into()), Value::Uint(block_bytes)),
])),
DType::U8,
),
};
let mut parts = BTreeMap::new();
parts.insert(
"data".to_string(),
PartEntry {
dtype,
logical: None,
payload: Payload::At(Location {
store: StoreId(0),
offset: abs,
len: byte_size,
}),
digest: None, },
);
ranges.push((abs, byte_size));
if catalog
.insert(
info.name.clone(),
Entry {
shape: info.shape,
layout,
attributes: attrs,
parts,
},
)
.is_some()
{
return Err(bad(format!("duplicate tensor name {:?}", info.name)));
}
}
ranges.sort_unstable();
ranges.dedup();
for w in ranges.windows(2) {
if w[0].0 + w[0].1 > w[1].0 {
return Err(bad(format!(
"tensor ranges overlap at {} and {}",
w[0].0, w[1].0
)));
}
}
if !attributes.is_empty() {
catalog.set_attributes(Some(Value::Map(attributes)));
}
Ok(Projection::new(catalog).occupying(ranges))
}