use anyhow::{anyhow, Result};
use memmap2::Mmap;
use std::fs::File;
use std::path::Path;
use crate::{ByteOrder, GGUFModel, FILE_MAGIC_GGUF_BE, FILE_MAGIC_GGUF_LE};
pub struct MmapGGUF {
#[allow(dead_code)]
mmap: Mmap,
model: GGUFModel,
}
impl MmapGGUF {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
if !path.exists() {
return Err(anyhow!("file not found: {}", path.display()));
}
let file = File::open(path)?;
let mmap = unsafe { Mmap::map(&file)? };
if mmap.len() < 4 {
return Err(anyhow!("file too small to be a valid GGUF file"));
}
let magic = i32::from_le_bytes([mmap[0], mmap[1], mmap[2], mmap[3]]);
let byte_order = match magic {
FILE_MAGIC_GGUF_LE => ByteOrder::LE,
FILE_MAGIC_GGUF_BE => ByteOrder::BE,
_ => return Err(anyhow!("invalid file magic: not a GGUF file")),
};
let data = mmap[4..].to_vec();
let cursor = std::io::Cursor::new(data);
let mut container = crate::GGUFContainer::new(byte_order, Box::new(cursor), u64::MAX);
let model = container.decode()?;
Ok(Self { mmap, model })
}
pub fn model(&self) -> &GGUFModel {
&self.model
}
pub fn as_slice(&self) -> &[u8] {
&self.mmap
}
pub fn len(&self) -> usize {
self.mmap.len()
}
pub fn is_empty(&self) -> bool {
self.mmap.is_empty()
}
}
impl std::ops::Deref for MmapGGUF {
type Target = GGUFModel;
fn deref(&self) -> &Self::Target {
&self.model
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mmap_open() {
let mmap = MmapGGUF::open("tests/test-le-v3.gguf").unwrap();
assert!(!mmap.is_empty());
}
#[test]
fn test_mmap_decode() {
let mmap = MmapGGUF::open("tests/test-le-v3.gguf").unwrap();
assert_eq!(mmap.model().get_version(), "v3");
assert_eq!(mmap.model().model_family(), "llama");
}
#[test]
fn test_mmap_deref() {
let mmap = MmapGGUF::open("tests/test-le-v3.gguf").unwrap();
assert_eq!(mmap.get_version(), "v3");
assert_eq!(mmap.model_family(), "llama");
}
#[test]
fn test_mmap_file_not_found() {
let result = MmapGGUF::open("nonexistent.gguf");
assert!(result.is_err());
}
#[test]
fn test_mmap_file_too_small() {
let path = std::env::temp_dir().join("mmap_too_small.bin");
std::fs::write(&path, [0xAAu8, 0xBB]).unwrap();
let err = MmapGGUF::open(&path).err().unwrap();
assert!(err.to_string().contains("file too small"));
let _ = std::fs::remove_file(&path);
}
#[test]
fn test_mmap_invalid_magic() {
let path = std::env::temp_dir().join("mmap_bad_magic.bin");
std::fs::write(&path, [0xDEu8, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00]).unwrap();
let err = MmapGGUF::open(&path).err().unwrap();
assert!(err.to_string().contains("invalid file magic"));
let _ = std::fs::remove_file(&path);
}
#[test]
fn test_mmap_accessors() {
let mmap = MmapGGUF::open("tests/test-le-v3.gguf").unwrap();
assert!(mmap.len() > 4);
assert!(!mmap.is_empty());
let slice = mmap.as_slice();
assert_eq!(slice.len(), mmap.len());
assert_eq!(&slice[..4], &(crate::FILE_MAGIC_GGUF_LE as u32).to_le_bytes());
}
}