use std::collections::HashMap;
#[cfg(feature = "mmap")]
use std::fs::File;
use std::io::{Cursor, Read};
#[cfg(feature = "mmap")]
use std::path::Path;
use std::ptr::NonNull;
use std::sync::Arc;
use anyhow::{Context, Result, bail, ensure};
#[cfg(feature = "mmap")]
use memmap2::Mmap;
use crate::tensor::{DType, Tensor};
const GGUF_MAGIC: u32 = 0x46554747;
const GGUF_TYPE_UINT8: u32 = 0;
const GGUF_TYPE_INT8: u32 = 1;
const GGUF_TYPE_UINT16: u32 = 2;
const GGUF_TYPE_INT16: u32 = 3;
const GGUF_TYPE_UINT32: u32 = 4;
const GGUF_TYPE_INT32: u32 = 5;
const GGUF_TYPE_FLOAT32: u32 = 6;
const GGUF_TYPE_BOOL: u32 = 7;
const GGUF_TYPE_STRING: u32 = 8;
const GGUF_TYPE_ARRAY: u32 = 9;
const GGUF_TYPE_UINT64: u32 = 10;
const GGUF_TYPE_INT64: u32 = 11;
const GGUF_TYPE_FLOAT64: u32 = 12;
const GGML_TYPE_F32: u32 = 0;
const GGML_TYPE_F16: u32 = 1;
const GGML_TYPE_Q4_0: u32 = 2;
const GGML_TYPE_Q4_1: u32 = 3;
const GGML_TYPE_Q5_0: u32 = 6;
const GGML_TYPE_Q5_1: u32 = 7;
const GGML_TYPE_Q8_0: u32 = 8;
const GGML_TYPE_Q8_1: u32 = 9;
const GGML_TYPE_Q2_K: u32 = 10;
const GGML_TYPE_Q3_K: u32 = 11;
const GGML_TYPE_Q4_K: u32 = 12;
const GGML_TYPE_Q5_K: u32 = 13;
const GGML_TYPE_Q6_K: u32 = 14;
const GGML_TYPE_Q8_K: u32 = 15;
const GGML_TYPE_BF16: u32 = 30;
const GGML_TYPE_I32: u32 = 26;
#[derive(Debug, Clone)]
pub enum GgufValue {
U8(u8),
I8(i8),
U16(u16),
I16(i16),
U32(u32),
I32(i32),
U64(u64),
I64(i64),
F32(f32),
F64(f64),
Bool(bool),
String(String),
Array(Vec<GgufValue>),
}
#[derive(Debug, Clone)]
pub struct TensorInfo {
pub name: String,
pub shape: Vec<usize>,
pub dtype: DType,
pub offset: u64,
pub size_bytes: usize,
pub ggml_type_id: u32,
}
#[derive(Clone)]
enum Backing {
#[cfg(feature = "mmap")]
Mmap(Arc<Mmap>),
Owned(Arc<[u8]>),
}
struct SafeDataPtr {
ptr: NonNull<u8>,
len: usize,
}
unsafe impl Send for SafeDataPtr {}
unsafe impl Sync for SafeDataPtr {}
impl SafeDataPtr {
fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
fn len(&self) -> usize {
self.len
}
}
pub struct GgufFile {
pub metadata: HashMap<String, GgufValue>,
pub tensors: HashMap<String, TensorInfo>,
data: SafeDataPtr,
_backing: Backing,
data_offset: usize,
}
impl Clone for GgufFile {
fn clone(&self) -> Self {
Self {
metadata: self.metadata.clone(),
tensors: self.tensors.clone(),
data: SafeDataPtr {
ptr: self.data.ptr,
len: self.data.len,
},
_backing: self._backing.clone(),
data_offset: self.data_offset,
}
}
}
struct GgufReader<R: Read> {
reader: R,
pos: u64,
}
impl<R: Read> GgufReader<R> {
fn new(reader: R) -> Self {
Self { reader, pos: 0 }
}
fn read_u8(&mut self) -> Result<u8> {
let mut buf = [0u8; 1];
self.reader.read_exact(&mut buf).context("read u8")?;
self.pos += 1;
Ok(buf[0])
}
fn read_i8(&mut self) -> Result<i8> {
Ok(self.read_u8()? as i8)
}
fn read_u16(&mut self) -> Result<u16> {
let mut buf = [0u8; 2];
self.reader.read_exact(&mut buf).context("read u16")?;
self.pos += 2;
Ok(u16::from_le_bytes(buf))
}
fn read_i16(&mut self) -> Result<i16> {
Ok(self.read_u16()? as i16)
}
fn read_u32(&mut self) -> Result<u32> {
let mut buf = [0u8; 4];
self.reader.read_exact(&mut buf).context("read u32")?;
self.pos += 4;
Ok(u32::from_le_bytes(buf))
}
fn read_i32(&mut self) -> Result<i32> {
Ok(self.read_u32()? as i32)
}
fn read_u64(&mut self) -> Result<u64> {
let mut buf = [0u8; 8];
self.reader.read_exact(&mut buf).context("read u64")?;
self.pos += 8;
Ok(u64::from_le_bytes(buf))
}
fn read_i64(&mut self) -> Result<i64> {
Ok(self.read_u64()? as i64)
}
fn read_f32(&mut self) -> Result<f32> {
let mut buf = [0u8; 4];
self.reader.read_exact(&mut buf).context("read f32")?;
self.pos += 4;
Ok(f32::from_le_bytes(buf))
}
fn read_f64(&mut self) -> Result<f64> {
let mut buf = [0u8; 8];
self.reader.read_exact(&mut buf).context("read f64")?;
self.pos += 8;
Ok(f64::from_le_bytes(buf))
}
fn read_bool(&mut self) -> Result<bool> {
Ok(self.read_u8()? != 0)
}
fn read_string(&mut self) -> Result<String> {
let len = self.read_u64()? as usize;
ensure!(len < 1_000_000, "string too long: {len}");
let mut buf = vec![0u8; len];
self.reader.read_exact(&mut buf).context("read string")?;
self.pos += len as u64;
String::from_utf8(buf).context("invalid UTF-8 in string")
}
fn read_value(&mut self, type_id: u32) -> Result<GgufValue> {
match type_id {
GGUF_TYPE_UINT8 => Ok(GgufValue::U8(self.read_u8()?)),
GGUF_TYPE_INT8 => Ok(GgufValue::I8(self.read_i8()?)),
GGUF_TYPE_UINT16 => Ok(GgufValue::U16(self.read_u16()?)),
GGUF_TYPE_INT16 => Ok(GgufValue::I16(self.read_i16()?)),
GGUF_TYPE_UINT32 => Ok(GgufValue::U32(self.read_u32()?)),
GGUF_TYPE_INT32 => Ok(GgufValue::I32(self.read_i32()?)),
GGUF_TYPE_UINT64 => Ok(GgufValue::U64(self.read_u64()?)),
GGUF_TYPE_INT64 => Ok(GgufValue::I64(self.read_i64()?)),
GGUF_TYPE_FLOAT32 => Ok(GgufValue::F32(self.read_f32()?)),
GGUF_TYPE_FLOAT64 => Ok(GgufValue::F64(self.read_f64()?)),
GGUF_TYPE_BOOL => Ok(GgufValue::Bool(self.read_bool()?)),
GGUF_TYPE_STRING => Ok(GgufValue::String(self.read_string()?)),
GGUF_TYPE_ARRAY => {
let elem_type = self.read_u32()?;
let count = self.read_u64()? as usize;
ensure!(count < 10_000_000, "array too long: {count}");
let mut arr = Vec::with_capacity(count);
for _ in 0..count {
arr.push(self.read_value(elem_type)?);
}
Ok(GgufValue::Array(arr))
}
_ => bail!("unknown GGUF value type: {type_id}"),
}
}
}
fn ggml_type_to_dtype(type_id: u32) -> Result<DType> {
match type_id {
GGML_TYPE_F32 => Ok(DType::F32),
GGML_TYPE_F16 => Ok(DType::F16),
GGML_TYPE_BF16 => Ok(DType::BF16),
GGML_TYPE_Q8_0 => Ok(DType::Q8_0),
GGML_TYPE_Q4_0 => Ok(DType::Q4_0),
GGML_TYPE_Q4_1 => Ok(DType::Q4_1),
GGML_TYPE_Q4_K => Ok(DType::Q4KM),
GGML_TYPE_Q5_K => Ok(DType::Q5KM),
GGML_TYPE_I32 => Ok(DType::I32),
GGML_TYPE_Q6_K => Ok(DType::Q6K),
GGML_TYPE_Q5_0 | GGML_TYPE_Q5_1 | GGML_TYPE_Q8_1 | GGML_TYPE_Q2_K | GGML_TYPE_Q3_K
| GGML_TYPE_Q8_K => {
bail!("quantization type {type_id} not yet supported")
}
_ => bail!("unknown GGML type: {type_id}"),
}
}
pub fn ggml_type_name(type_id: u32) -> &'static str {
match type_id {
GGML_TYPE_F32 => "F32",
GGML_TYPE_F16 => "F16",
GGML_TYPE_BF16 => "BF16",
GGML_TYPE_Q4_0 => "Q4_0",
GGML_TYPE_Q4_1 => "Q4_1",
GGML_TYPE_Q5_0 => "Q5_0",
GGML_TYPE_Q5_1 => "Q5_1",
GGML_TYPE_Q8_0 => "Q8_0",
GGML_TYPE_Q8_1 => "Q8_1",
GGML_TYPE_Q2_K => "Q2_K",
GGML_TYPE_Q3_K => "Q3_K",
GGML_TYPE_Q4_K => "Q4_K",
GGML_TYPE_Q5_K => "Q5_K",
GGML_TYPE_Q6_K => "Q6_K",
GGML_TYPE_Q8_K => "Q8_K",
GGML_TYPE_I32 => "I32",
_ => "???",
}
}
fn tensor_data_size(shape: &[usize], dtype: DType) -> Result<usize> {
let numel: usize = shape
.iter()
.try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
.context("tensor numel overflow")?;
let block_size = dtype.block_size();
let block_bytes = dtype.block_bytes();
if block_size == 1 {
numel
.checked_mul(block_bytes)
.context("tensor size overflow")
} else {
ensure!(
numel.is_multiple_of(block_size),
"tensor element count {numel} is not divisible by block size {block_size}"
);
(numel / block_size)
.checked_mul(block_bytes)
.context("tensor size overflow")
}
}
impl GgufFile {
#[cfg(feature = "mmap")]
pub fn open(path: &Path) -> Result<Self> {
let file = File::open(path).with_context(|| format!("opening {}", path.display()))?;
let mmap = unsafe { Mmap::map(&file)? };
Self::from_backing(Backing::Mmap(Arc::new(mmap)))
}
#[cfg(feature = "mmap")]
pub fn open_arc(path: &Path) -> Result<Arc<Self>> {
Self::open(path).map(Arc::new)
}
pub fn from_bytes(bytes: Arc<[u8]>) -> Result<Self> {
Self::from_backing(Backing::Owned(bytes))
}
pub fn from_reader<R: Read>(reader: R) -> Result<Self> {
Self::from_reader_with_limit(reader, Self::DEFAULT_READER_MAX_BYTES)
}
pub const DEFAULT_READER_MAX_BYTES: u64 = 64 * 1024 * 1024 * 1024;
pub fn from_reader_with_limit<R: Read>(reader: R, max_bytes: u64) -> Result<Self> {
let mut buf: Vec<u8> = Vec::new();
let probe_limit = max_bytes.saturating_add(1);
let mut bounded = reader.take(probe_limit);
let read = bounded
.read_to_end(&mut buf)
.context("reading GGUF stream")?;
ensure!(
(read as u64) <= max_bytes,
"GGUF stream exceeded {max_bytes} byte limit — pass a larger ceiling to `from_reader_with_limit` if legitimate"
);
Self::from_bytes(Arc::from(buf.into_boxed_slice()))
}
pub fn from_header_bytes(header_bytes: Arc<[u8]>, file_size: u64) -> Result<Self> {
Self::from_backing_with_file_size(Backing::Owned(header_bytes), file_size)
}
fn from_backing(backing: Backing) -> Result<Self> {
let file_size = match &backing {
#[cfg(feature = "mmap")]
Backing::Mmap(m) => m.len() as u64,
Backing::Owned(a) => a.len() as u64,
};
Self::from_backing_with_file_size(backing, file_size)
}
fn from_backing_with_file_size(backing: Backing, file_size: u64) -> Result<Self> {
#[allow(clippy::infallible_destructuring_match)]
let data_slice: &[u8] = match &backing {
#[cfg(feature = "mmap")]
Backing::Mmap(m) => m.as_ref(),
Backing::Owned(a) => a,
};
ensure!(
data_slice.len() >= 24,
"GGUF buffer too small ({} bytes; need at least 24 for the header)",
data_slice.len()
);
let ptr = NonNull::new(data_slice.as_ptr() as *mut u8)
.expect("non-empty slice always yields non-null pointer");
let mut reader = GgufReader::new(Cursor::new(data_slice));
let magic = reader.read_u32()?;
ensure!(
magic == GGUF_MAGIC,
"not a GGUF file (magic: 0x{magic:08X}, expected 0x{GGUF_MAGIC:08X})"
);
let version = reader.read_u32()?;
ensure!(
version == 3,
"unsupported GGUF version {version} (expected 3)"
);
let tensor_count = reader.read_u64()? as usize;
let kv_count = reader.read_u64()? as usize;
let mut metadata = HashMap::with_capacity(kv_count);
for _ in 0..kv_count {
let key = reader.read_string()?;
let type_id = reader.read_u32()?;
let value = reader.read_value(type_id)?;
metadata.insert(key, value);
}
let mut tensors = HashMap::with_capacity(tensor_count);
let mut tensor_infos_raw: Vec<(String, Vec<usize>, u32, u64)> =
Vec::with_capacity(tensor_count);
for _ in 0..tensor_count {
let name = reader.read_string()?;
let n_dims = reader.read_u32()? as usize;
ensure!(
n_dims <= 8,
"tensor {name} has too many dimensions: {n_dims}"
);
let mut shape = Vec::with_capacity(n_dims);
for _ in 0..n_dims {
shape.push(reader.read_u64()? as usize);
}
let type_id = reader.read_u32()?;
let offset = reader.read_u64()?;
tensor_infos_raw.push((name, shape, type_id, offset));
}
let alignment = match metadata.get("general.alignment") {
Some(GgufValue::U32(a)) => *a as usize,
_ => 32,
};
let header_end = reader.pos as usize;
ensure!(
alignment > 0 && alignment <= 1024 * 1024,
"invalid GGUF alignment: {alignment}"
);
let data_offset = header_end
.checked_add(alignment - 1)
.and_then(|v| v.checked_div(alignment))
.and_then(|v| v.checked_mul(alignment))
.with_context(|| {
format!(
"GGUF data offset overflow (header_end={header_end}, alignment={alignment})"
)
})?;
for (name, shape, type_id, offset) in tensor_infos_raw {
let (dtype, size_bytes) = match ggml_type_to_dtype(type_id) {
Ok(dt) => (dt, tensor_data_size(&shape, dt)?),
Err(_) => (DType::F32, 0),
};
let abs_offset = (data_offset as u64).checked_add(offset).with_context(|| {
format!(
"tensor {name} offset overflow (data_offset={data_offset}, offset={offset})"
)
})?;
if size_bytes > 0 {
let end = abs_offset.checked_add(size_bytes as u64).with_context(|| {
format!(
"tensor {name} end offset overflow (offset={abs_offset}, size={size_bytes})"
)
})?;
ensure!(
end <= file_size,
"tensor {name} extends beyond file (offset={abs_offset}, size={size_bytes}, file_size={file_size})"
);
}
tensors.insert(
name.clone(),
TensorInfo {
name,
shape,
dtype,
offset: abs_offset,
size_bytes,
ggml_type_id: type_id,
},
);
}
Ok(GgufFile {
metadata,
tensors,
data: SafeDataPtr {
ptr,
len: data_slice.len(),
},
_backing: backing,
data_offset,
})
}
pub fn architecture(&self) -> Option<&str> {
match self.metadata.get("general.architecture") {
Some(GgufValue::String(s)) => Some(s.as_str()),
_ => None,
}
}
pub fn get_u32(&self, key: &str) -> Option<u32> {
match self.metadata.get(key) {
Some(GgufValue::U32(v)) => Some(*v),
_ => None,
}
}
pub fn get_f32(&self, key: &str) -> Option<f32> {
match self.metadata.get(key) {
Some(GgufValue::F32(v)) => Some(*v),
_ => None,
}
}
pub fn get_str(&self, key: &str) -> Option<&str> {
match self.metadata.get(key) {
Some(GgufValue::String(s)) => Some(s.as_str()),
_ => None,
}
}
pub fn get_string_array(&self, key: &str) -> Option<Vec<&str>> {
match self.metadata.get(key) {
Some(GgufValue::Array(arr)) => {
let strings: Vec<&str> = arr
.iter()
.filter_map(|v| match v {
GgufValue::String(s) => Some(s.as_str()),
_ => None,
})
.collect();
if strings.len() == arr.len() {
Some(strings)
} else {
None
}
}
_ => None,
}
}
pub fn get_f32_array(&self, key: &str) -> Option<Vec<f32>> {
match self.metadata.get(key) {
Some(GgufValue::Array(arr)) => {
let floats: Vec<f32> = arr
.iter()
.filter_map(|v| match v {
GgufValue::F32(f) => Some(*f),
_ => None,
})
.collect();
if floats.len() == arr.len() {
Some(floats)
} else {
None
}
}
_ => None,
}
}
pub fn get_i32_array(&self, key: &str) -> Option<Vec<i32>> {
match self.metadata.get(key) {
Some(GgufValue::Array(arr)) => {
let ints: Vec<i32> = arr
.iter()
.filter_map(|v| match v {
GgufValue::I32(i) => Some(*i),
_ => None,
})
.collect();
if ints.len() == arr.len() {
Some(ints)
} else {
None
}
}
_ => None,
}
}
pub fn get_bool(&self, key: &str) -> Option<bool> {
match self.metadata.get(key) {
Some(GgufValue::Bool(v)) => Some(*v),
_ => None,
}
}
pub fn mmap_data(&self) -> &[u8] {
self.data.as_slice()
}
pub fn data_offset(&self) -> usize {
self.data_offset
}
fn tensor_range(&self, name: &str) -> Result<(&TensorInfo, std::ops::Range<usize>)> {
let info = self
.tensors
.get(name)
.with_context(|| format!("tensor not found: {name}"))?;
ensure!(
info.size_bytes > 0,
"tensor {name} has unsupported GGML type {} ({})",
info.ggml_type_id,
ggml_type_name(info.ggml_type_id)
);
let start = usize::try_from(info.offset)
.with_context(|| format!("tensor {name} offset {} exceeds usize range", info.offset))?;
let end = start
.checked_add(info.size_bytes)
.with_context(|| format!("tensor {name} end offset overflow"))?;
ensure!(
end <= self.data.len(),
"tensor {name} data extends beyond GGUF buffer"
);
Ok((info, start..end))
}
pub fn get_tensor(&self, name: &str) -> Result<Tensor> {
let (info, range) = self.tensor_range(name)?;
let data = self.data.as_slice()[range].to_vec();
Ok(Tensor::new(data, info.shape.clone(), info.dtype))
}
pub fn tensor_data(&self, name: &str) -> Result<&[u8]> {
let (_info, range) = self.tensor_range(name)?;
Ok(&self.data.as_slice()[range])
}
pub fn tensor_offset_len(&self, name: &str) -> Result<(usize, usize)> {
let (_info, range) = self.tensor_range(name)?;
Ok((range.start, range.end - range.start))
}
pub fn tensor_meta(&self, name: &str) -> Result<(usize, usize, usize, DType)> {
let (info, range) = self.tensor_range(name)?;
let (rows, cols) = match info.shape.len() {
1 => (1, info.shape[0]),
2 => (info.shape[1], info.shape[0]),
_ => anyhow::bail!(
"tensor_meta: unexpected rank for {name}: {}",
info.shape.len()
),
};
Ok((range.start, rows, cols, info.dtype))
}
pub fn tensor_meta_expert(
&self,
name: &str,
expert: usize,
) -> Result<(usize, usize, usize, usize, DType)> {
let (info, range) = self.tensor_range(name)?;
let [ne0, ne1, n_expert] = match info.shape[..] {
[ne0, ne1, n_expert] => [ne0, ne1, n_expert],
_ => bail!(
"tensor_meta_expert: {name} must be rank 3, got rank {}",
info.shape.len()
),
};
ensure!(
expert < n_expert,
"tensor_meta_expert: expert {expert} out of range for {name} ({n_expert} experts)"
);
let slice_bytes = tensor_data_size(&[ne0, ne1], info.dtype)?;
let start = expert
.checked_mul(slice_bytes)
.and_then(|off| range.start.checked_add(off))
.with_context(|| format!("tensor {name} expert {expert} offset overflow"))?;
let end = start
.checked_add(slice_bytes)
.with_context(|| format!("tensor {name} expert {expert} end offset overflow"))?;
ensure!(
end <= range.end,
"tensor {name} expert {expert} slice extends past the tensor"
);
Ok((start, slice_bytes, ne1, ne0, info.dtype))
}
pub fn print_inspect(&self) {
println!("=== GGUF File ===");
println!(
"Tensors: {}, Metadata keys: {}",
self.tensors.len(),
self.metadata.len()
);
println!("Data offset: {}", self.data_offset);
println!();
println!("--- Metadata ---");
let mut keys: Vec<&String> = self.metadata.keys().collect();
keys.sort();
for key in keys {
let value = &self.metadata[key];
let display = format_gguf_value(value);
println!(" {key} = {display}");
}
println!();
println!("--- Tensors ---");
let mut tensor_list: Vec<&TensorInfo> = self.tensors.values().collect();
tensor_list.sort_by_key(|t| &t.name);
for t in tensor_list {
println!(
" {} | {:?} | {} | {:.2} MB",
t.name,
t.shape,
ggml_type_name(t.ggml_type_id),
t.size_bytes as f64 / (1024.0 * 1024.0)
);
}
}
}
fn format_gguf_value(value: &GgufValue) -> String {
match value {
GgufValue::U8(v) => format!("{v}"),
GgufValue::I8(v) => format!("{v}"),
GgufValue::U16(v) => format!("{v}"),
GgufValue::I16(v) => format!("{v}"),
GgufValue::U32(v) => format!("{v}"),
GgufValue::I32(v) => format!("{v}"),
GgufValue::U64(v) => format!("{v}"),
GgufValue::I64(v) => format!("{v}"),
GgufValue::F32(v) => format!("{v}"),
GgufValue::F64(v) => format!("{v}"),
GgufValue::Bool(v) => format!("{v}"),
GgufValue::String(s) => {
if s.len() > 100 {
format!("\"{}...\" ({} chars)", &s[..100], s.len())
} else {
format!("\"{s}\"")
}
}
GgufValue::Array(arr) => {
if arr.is_empty() {
"[]".to_string()
} else {
let type_name = match &arr[0] {
GgufValue::String(_) => "string",
GgufValue::F32(_) => "f32",
GgufValue::U32(_) => "u32",
GgufValue::I32(_) => "i32",
_ => "mixed",
};
format!("[{type_name}; {}]", arr.len())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gguf_magic() {
assert_eq!(GGUF_MAGIC, 0x46554747);
let bytes = b"GGUF";
let magic = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
assert_eq!(magic, GGUF_MAGIC);
}
#[test]
fn test_tensor_data_size() {
assert_eq!(
tensor_data_size(&[10, 20], DType::F32).unwrap(),
10 * 20 * 4
);
assert_eq!(
tensor_data_size(&[10, 20], DType::F16).unwrap(),
10 * 20 * 2
);
assert_eq!(tensor_data_size(&[256], DType::Q8_0).unwrap(), 8 * 34);
assert_eq!(tensor_data_size(&[512], DType::Q4KM).unwrap(), 2 * 144); }
#[test]
fn test_tensor_data_size_overflow() {
assert!(tensor_data_size(&[usize::MAX, 2], DType::F32).is_err());
}
#[test]
fn test_tensor_data_size_bad_block_alignment() {
assert!(tensor_data_size(&[100], DType::Q8_0).is_err());
}
#[test]
fn test_tensor_range_unsupported_type() {
let bytes: Arc<[u8]> = Arc::from(vec![0u8; 256].into_boxed_slice());
let ptr = NonNull::new(bytes.as_ptr() as *mut u8).unwrap();
let len = bytes.len();
let mut tensors = HashMap::new();
tensors.insert(
"test_unsupported".to_string(),
TensorInfo {
name: "test_unsupported".to_string(),
shape: vec![32],
dtype: DType::F32,
offset: 0,
size_bytes: 0, ggml_type_id: 99,
},
);
tensors.insert(
"test_supported".to_string(),
TensorInfo {
name: "test_supported".to_string(),
shape: vec![4],
dtype: DType::F32,
offset: 0,
size_bytes: 16,
ggml_type_id: GGML_TYPE_F32,
},
);
let gguf = GgufFile {
metadata: HashMap::new(),
tensors,
data: SafeDataPtr { ptr, len },
_backing: Backing::Owned(bytes),
data_offset: 0,
};
match gguf.get_tensor("test_unsupported") {
Err(e) => assert!(e.to_string().contains("unsupported GGML type")),
Ok(_) => panic!("get_tensor should reject unsupported type"),
}
match gguf.tensor_data("test_unsupported") {
Err(e) => assert!(e.to_string().contains("unsupported GGML type")),
Ok(_) => panic!("tensor_data should reject unsupported type"),
}
assert!(gguf.tensor_data("test_supported").is_ok());
assert!(gguf.get_tensor("test_supported").is_ok());
}
#[test]
fn test_format_gguf_value() {
assert_eq!(format_gguf_value(&GgufValue::U32(42)), "42");
assert_eq!(format_gguf_value(&GgufValue::Bool(true)), "true");
assert_eq!(
format_gguf_value(&GgufValue::String("hello".to_string())),
"\"hello\""
);
assert_eq!(
format_gguf_value(&GgufValue::Array(vec![
GgufValue::F32(1.0),
GgufValue::F32(2.0)
])),
"[f32; 2]"
);
}
#[test]
fn test_reader_primitives() {
let data: Vec<u8> = vec![
0x47, 0x47, 0x55, 0x46, 0x03, 0x00, 0x00, 0x00, ];
let mut reader = GgufReader::new(std::io::Cursor::new(data));
assert_eq!(reader.read_u32().unwrap(), GGUF_MAGIC);
assert_eq!(reader.read_u32().unwrap(), 3);
assert_eq!(reader.pos, 8);
}
#[test]
fn test_reader_string() {
let mut data: Vec<u8> = Vec::new();
data.extend_from_slice(&5u64.to_le_bytes()); data.extend_from_slice(b"hello");
let mut reader = GgufReader::new(std::io::Cursor::new(data));
assert_eq!(reader.read_string().unwrap(), "hello");
}
fn minimal_gguf_bytes() -> Vec<u8> {
let mut data: Vec<u8> = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes()); data.extend_from_slice(&3u32.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes()); data
}
#[test]
fn from_bytes_parses_minimal_header() {
let bytes: Arc<[u8]> = Arc::from(minimal_gguf_bytes().into_boxed_slice());
let gguf = match GgufFile::from_bytes(bytes) {
Ok(g) => g,
Err(e) => panic!("parse minimal GGUF: {e}"),
};
assert!(gguf.metadata.is_empty());
assert!(gguf.tensors.is_empty());
assert_eq!(gguf.mmap_data().len(), 24);
}
#[test]
fn from_reader_matches_from_bytes() {
let bytes = minimal_gguf_bytes();
let a = match GgufFile::from_bytes(Arc::from(bytes.clone().into_boxed_slice())) {
Ok(g) => g,
Err(e) => panic!("from_bytes: {e}"),
};
let b = match GgufFile::from_reader(std::io::Cursor::new(bytes)) {
Ok(g) => g,
Err(e) => panic!("from_reader: {e}"),
};
assert_eq!(a.metadata.len(), b.metadata.len());
assert_eq!(a.tensors.len(), b.tensors.len());
assert_eq!(a.mmap_data().len(), b.mmap_data().len());
}
#[test]
fn from_bytes_rejects_too_small_buffer() {
let bytes: Arc<[u8]> = Arc::from(vec![0u8; 10].into_boxed_slice());
match GgufFile::from_bytes(bytes) {
Ok(_) => panic!("expected error for tiny buffer"),
Err(e) => assert!(e.to_string().contains("too small"), "unexpected error: {e}"),
}
}
#[test]
fn from_reader_with_limit_rejects_oversize_stream() {
let bytes = minimal_gguf_bytes(); match GgufFile::from_reader_with_limit(std::io::Cursor::new(bytes), 16) {
Ok(_) => panic!("expected error when stream exceeds limit"),
Err(e) => assert!(
e.to_string().contains("exceeded") || e.to_string().contains("too small"),
"unexpected error: {e}"
),
}
}
#[test]
fn from_reader_with_limit_accepts_stream_exactly_at_limit() {
let bytes = minimal_gguf_bytes();
let exact = bytes.len() as u64;
match GgufFile::from_reader_with_limit(std::io::Cursor::new(bytes), exact) {
Ok(g) => assert_eq!(g.mmap_data().len() as u64, exact),
Err(e) => panic!("stream exactly at limit should succeed, got: {e}"),
}
}
#[test]
fn from_bytes_rejects_bad_magic() {
let mut bytes = minimal_gguf_bytes();
bytes[0..4].copy_from_slice(b"ABCD"); match GgufFile::from_bytes(Arc::from(bytes.into_boxed_slice())) {
Ok(_) => panic!("expected error for bad magic"),
Err(e) => assert!(
e.to_string().contains("not a GGUF file"),
"unexpected error: {e}"
),
}
}
}
#[cfg(test)]
mod expert_slice_tests {
use super::*;
use std::collections::HashMap;
use std::ptr::NonNull;
use std::sync::Arc;
fn gguf_with_expert_tensor(ne0: usize, ne1: usize, n_expert: usize, offset: usize) -> GgufFile {
let size_bytes = ne0 * ne1 * n_expert * 4;
let bytes: Arc<[u8]> = Arc::from(vec![0u8; offset + size_bytes].into_boxed_slice());
let ptr = NonNull::new(bytes.as_ptr() as *mut u8).unwrap();
let len = bytes.len();
let mut tensors = HashMap::new();
tensors.insert(
"exps".to_string(),
TensorInfo {
name: "exps".to_string(),
shape: vec![ne0, ne1, n_expert],
dtype: DType::F32,
offset: offset as u64,
size_bytes,
ggml_type_id: GGML_TYPE_F32,
},
);
GgufFile {
metadata: HashMap::new(),
tensors,
data: SafeDataPtr { ptr, len },
_backing: Backing::Owned(bytes),
data_offset: 0,
}
}
#[test]
fn expert_slices_are_contiguous_and_2d() {
let (ne0, ne1, n_expert, base) = (2048, 1792, 32, 4096);
let gguf = gguf_with_expert_tensor(ne0, ne1, n_expert, base);
let stride = ne0 * ne1 * 4;
for e in 0..n_expert {
let (start, size, rows, cols, dtype) = gguf.tensor_meta_expert("exps", e).unwrap();
assert_eq!(start, base + e * stride, "expert {e} offset");
assert_eq!(size, stride, "expert {e} size");
assert_eq!((rows, cols), (ne1, ne0));
assert_eq!(dtype, DType::F32);
}
}
#[test]
fn last_expert_ends_exactly_at_the_tensor_end() {
let (ne0, ne1, n_expert, base) = (64, 32, 8, 0);
let gguf = gguf_with_expert_tensor(ne0, ne1, n_expert, base);
let (start, size, ..) = gguf.tensor_meta_expert("exps", n_expert - 1).unwrap();
assert_eq!(start + size, base + ne0 * ne1 * n_expert * 4);
}
#[test]
fn rejects_out_of_range_expert() {
let gguf = gguf_with_expert_tensor(64, 32, 8, 0);
let err = gguf.tensor_meta_expert("exps", 8).unwrap_err().to_string();
assert!(err.contains("out of range"), "{err}");
}
#[test]
fn tensor_meta_still_rejects_rank_3() {
let gguf = gguf_with_expert_tensor(64, 32, 8, 0);
let err = gguf.tensor_meta("exps").unwrap_err().to_string();
assert!(err.contains("unexpected rank"), "{err}");
}
}