use std::path::Path;
use indexmap::IndexMap;
use kopitiam_core::{DType, Error, Result, Shape};
use crate::byte_source::ByteSource;
use crate::metadata::{GgufMetadata, GgufValue, ModelMetadata};
use crate::model::{LoadedModel, ModelLoader, TensorEntry};
const FORMAT: &str = "gguf";
const MAGIC: [u8; 4] = *b"GGUF";
const DEFAULT_ALIGNMENT: u64 = 32;
const MAX_TENSOR_DIMS: u32 = 8;
const MAX_ARRAY_NESTING_DEPTH: u32 = 32;
fn malformed(reason: impl Into<String>) -> Error {
Error::MalformedModel { format: FORMAT, reason: reason.into() }
}
fn unsupported(feature: impl Into<String>) -> Error {
Error::UnsupportedModelFeature { format: FORMAT, feature: feature.into() }
}
struct Cursor<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> Cursor<'a> {
fn new(bytes: &'a [u8]) -> Self {
Self { bytes, pos: 0 }
}
fn position(&self) -> usize {
self.pos
}
fn take(&mut self, n: usize) -> Result<&'a [u8]> {
let end = self
.pos
.checked_add(n)
.ok_or_else(|| malformed("byte offset overflow while reading"))?;
let slice = self.bytes.get(self.pos..end).ok_or_else(|| {
malformed(format!(
"unexpected end of file: needed {n} bytes at offset {}, file has {} bytes",
self.pos,
self.bytes.len()
))
})?;
self.pos = end;
Ok(slice)
}
fn u8(&mut self) -> Result<u8> {
Ok(self.take(1)?[0])
}
fn i8(&mut self) -> Result<i8> {
Ok(self.take(1)?[0] as i8)
}
fn u16(&mut self) -> Result<u16> {
Ok(u16::from_le_bytes(self.take(2)?.try_into().expect("take(2) returns 2 bytes")))
}
fn i16(&mut self) -> Result<i16> {
Ok(i16::from_le_bytes(self.take(2)?.try_into().expect("take(2) returns 2 bytes")))
}
fn u32(&mut self) -> Result<u32> {
Ok(u32::from_le_bytes(self.take(4)?.try_into().expect("take(4) returns 4 bytes")))
}
fn i32(&mut self) -> Result<i32> {
Ok(i32::from_le_bytes(self.take(4)?.try_into().expect("take(4) returns 4 bytes")))
}
fn u64(&mut self) -> Result<u64> {
Ok(u64::from_le_bytes(self.take(8)?.try_into().expect("take(8) returns 8 bytes")))
}
fn i64(&mut self) -> Result<i64> {
Ok(i64::from_le_bytes(self.take(8)?.try_into().expect("take(8) returns 8 bytes")))
}
fn f32(&mut self) -> Result<f32> {
Ok(f32::from_le_bytes(self.take(4)?.try_into().expect("take(4) returns 4 bytes")))
}
fn f64(&mut self) -> Result<f64> {
Ok(f64::from_le_bytes(self.take(8)?.try_into().expect("take(8) returns 8 bytes")))
}
fn bool(&mut self) -> Result<bool> {
match self.u8()? {
0 => Ok(false),
1 => Ok(true),
other => Err(malformed(format!(
"invalid bool byte {other:#04x} (must be 0x00 or 0x01)"
))),
}
}
fn string(&mut self) -> Result<String> {
let len = self.u64()?;
let len = usize::try_from(len)
.map_err(|_| malformed(format!("string length {len} does not fit in memory")))?;
let bytes = self.take(len)?;
String::from_utf8(bytes.to_vec())
.map_err(|_| malformed("metadata string is not valid UTF-8"))
}
}
fn read_value(cursor: &mut Cursor, value_type: u32, depth: u32) -> Result<GgufValue> {
match value_type {
0 => Ok(GgufValue::U8(cursor.u8()?)),
1 => Ok(GgufValue::I8(cursor.i8()?)),
2 => Ok(GgufValue::U16(cursor.u16()?)),
3 => Ok(GgufValue::I16(cursor.i16()?)),
4 => Ok(GgufValue::U32(cursor.u32()?)),
5 => Ok(GgufValue::I32(cursor.i32()?)),
6 => Ok(GgufValue::F32(cursor.f32()?)),
7 => Ok(GgufValue::Bool(cursor.bool()?)),
8 => Ok(GgufValue::String(cursor.string()?)),
9 => {
if depth >= MAX_ARRAY_NESTING_DEPTH {
return Err(malformed("array nesting exceeds sanity limit"));
}
let elem_type = cursor.u32()?;
let len = cursor.u64()?;
let len = usize::try_from(len)
.map_err(|_| malformed(format!("array length {len} does not fit in memory")))?;
let mut values = Vec::with_capacity(len.min(1024));
for _ in 0..len {
values.push(read_value(cursor, elem_type, depth + 1)?);
}
Ok(GgufValue::Array(values))
}
10 => Ok(GgufValue::U64(cursor.u64()?)),
11 => Ok(GgufValue::I64(cursor.i64()?)),
12 => Ok(GgufValue::F64(cursor.f64()?)),
other => Err(unsupported(format!("metadata value type id {other}"))),
}
}
fn dtype_from_ggml_type(ggml_type: u32) -> Result<DType> {
match ggml_type {
0 => Ok(DType::F32),
1 => Ok(DType::F16),
2 => Ok(DType::Q4_0),
3 => Ok(DType::Q4_1),
6 => Ok(DType::Q5_0),
7 => Ok(DType::Q5_1),
8 => Ok(DType::Q8_0),
10 => Ok(DType::Q2_K),
11 => Ok(DType::Q3_K),
12 => Ok(DType::Q4_K),
13 => Ok(DType::Q5_K),
14 => Ok(DType::Q6_K),
15 => Ok(DType::Q8_K),
30 => Ok(DType::BF16),
other => Err(unsupported(format!("ggml tensor type id {other}"))),
}
}
fn align_up(offset: u64, alignment: u64) -> Result<u64> {
if alignment == 0 {
return Err(malformed("general.alignment must not be zero"));
}
let remainder = offset % alignment;
if remainder == 0 {
return Ok(offset);
}
offset
.checked_add(alignment - remainder)
.ok_or_else(|| malformed("alignment padding overflows a u64 offset"))
}
struct RawTensorInfo {
name: String,
dims: Vec<u64>,
ggml_type: u32,
relative_offset: u64,
}
fn parse(source: ByteSource) -> Result<LoadedModel> {
let bytes = source.as_slice();
let mut cursor = Cursor::new(bytes);
let magic = cursor.take(4)?;
if magic != MAGIC {
return Err(malformed(format!(
"bad magic {magic:02x?}, expected {MAGIC:02x?} (\"GGUF\")"
)));
}
let version = cursor.u32()?;
if version != 2 && version != 3 {
return Err(unsupported(format!(
"gguf version {version} (only v2 and v3 are supported; see module docs re: v1)"
)));
}
let tensor_count = cursor.u64()?;
let metadata_kv_count = cursor.u64()?;
let mut kv_map = IndexMap::new();
for _ in 0..metadata_kv_count {
let key = cursor.string()?;
let value_type = cursor.u32()?;
let value = read_value(&mut cursor, value_type, 0)?;
if kv_map.insert(key.clone(), value).is_some() {
return Err(malformed(format!("duplicate metadata key {key:?}")));
}
}
let kv = GgufMetadata(kv_map);
let mut raw_infos = Vec::new();
for _ in 0..tensor_count {
let name = cursor.string()?;
let n_dims = cursor.u32()?;
if n_dims > MAX_TENSOR_DIMS {
return Err(malformed(format!(
"tensor {name:?} declares {n_dims} dimensions, more than the {MAX_TENSOR_DIMS} this loader accepts"
)));
}
let mut dims = Vec::with_capacity(n_dims as usize);
for _ in 0..n_dims {
dims.push(cursor.u64()?);
}
let ggml_type = cursor.u32()?;
let relative_offset = cursor.u64()?;
raw_infos.push(RawTensorInfo { name, dims, ggml_type, relative_offset });
}
let alignment = kv
.get_u32("general.alignment")
.map(u64::from)
.unwrap_or(DEFAULT_ALIGNMENT);
if alignment == 0 || !alignment.is_multiple_of(8) {
return Err(malformed(format!(
"general.alignment {alignment} must be a positive multiple of 8"
)));
}
let tensor_data_start = align_up(cursor.position() as u64, alignment)?;
let file_len = bytes.len() as u64;
let mut tensors = IndexMap::new();
for info in raw_infos {
let dtype = dtype_from_ggml_type(info.ggml_type)?;
let mut dims = Vec::with_capacity(info.dims.len());
for &d in info.dims.iter().rev() {
let d = usize::try_from(d).map_err(|_| {
malformed(format!(
"tensor {:?} has a dimension ({d}) too large to represent",
info.name
))
})?;
dims.push(d);
}
let shape = Shape::new(dims);
let elem_count = shape.elem_count();
let byte_len = dtype.storage_bytes(elem_count).ok_or(Error::PartialQuantizedBlock {
dtype,
count: elem_count,
block_size: dtype.block_size(),
})?;
if !info.relative_offset.is_multiple_of(alignment) {
return Err(malformed(format!(
"tensor {:?} offset {} is not a multiple of alignment {alignment}",
info.name, info.relative_offset
)));
}
let abs_offset = tensor_data_start.checked_add(info.relative_offset).ok_or_else(|| {
malformed(format!("tensor {:?} data offset overflows a u64", info.name))
})?;
let abs_end = abs_offset.checked_add(byte_len as u64).ok_or_else(|| {
malformed(format!("tensor {:?} data end offset overflows a u64", info.name))
})?;
if abs_end > file_len {
return Err(malformed(format!(
"tensor {:?} data range [{abs_offset}, {abs_end}) extends past end of file ({file_len} bytes)",
info.name
)));
}
let abs_offset = usize::try_from(abs_offset)
.map_err(|_| malformed(format!("tensor {:?} offset does not fit in memory", info.name)))?;
let entry = TensorEntry {
name: info.name.clone(),
dtype,
shape,
offset: abs_offset,
len: byte_len,
};
if tensors.insert(info.name.clone(), entry).is_some() {
return Err(malformed(format!("duplicate tensor name {:?}", info.name)));
}
}
let metadata = build_metadata(kv);
Ok(LoadedModel { metadata, tensors, source, format: FORMAT })
}
fn build_metadata(kv: GgufMetadata) -> ModelMetadata {
let architecture = kv.get_str("general.architecture").map(str::to_owned);
let name = kv.get_str("general.name").map(str::to_owned);
let prefixed = |suffix: &str| architecture.as_deref().map(|arch| format!("{arch}.{suffix}"));
let n_layers = prefixed("block_count").and_then(|k| kv.get_u64(&k));
let n_heads = prefixed("attention.head_count").and_then(|k| kv.get_u64(&k));
let n_kv_heads = prefixed("attention.head_count_kv").and_then(|k| kv.get_u64(&k));
let embedding_length = prefixed("embedding_length").and_then(|k| kv.get_u64(&k));
let feed_forward_length = prefixed("feed_forward_length").and_then(|k| kv.get_u64(&k));
let context_length = prefixed("context_length").and_then(|k| kv.get_u64(&k));
let rope_theta = prefixed("rope.freq_base").and_then(|k| kv.get_f32(&k));
let rope_dimension_count = prefixed("rope.dimension_count").and_then(|k| kv.get_u64(&k));
let norm_epsilon = prefixed("attention.layer_norm_rms_epsilon")
.and_then(|k| kv.get_f32(&k))
.or_else(|| prefixed("attention.layer_norm_epsilon").and_then(|k| kv.get_f32(&k)));
let vocab_size = kv
.get_array("tokenizer.ggml.tokens")
.map(|tokens| tokens.len() as u64)
.or_else(|| prefixed("vocab_size").and_then(|k| kv.get_u64(&k)));
let quantization_version = kv.get_u32("general.quantization_version");
let file_type = kv.get_u32("general.file_type");
ModelMetadata {
architecture,
name,
n_layers,
n_heads,
n_kv_heads,
embedding_length,
feed_forward_length,
context_length,
vocab_size,
rope_theta,
rope_dimension_count,
norm_epsilon,
quantization_version,
file_type,
raw: kv,
}
}
pub struct GgufLoader;
impl ModelLoader for GgufLoader {
fn format_name(&self) -> &'static str {
FORMAT
}
fn probe(&self, bytes: &[u8]) -> bool {
bytes.len() >= MAGIC.len() && bytes[..MAGIC.len()] == MAGIC
}
fn load(&self, path: &Path) -> Result<LoadedModel> {
let source = ByteSource::open(path)?;
parse(source)
}
}