use crate::{
error::{Error, Result},
loader::{LoadOptions, LoadedModel},
progress::ProgressEvent,
smart_mapping::SmartTensorNameMapper,
ModelConfig,
};
use candle_nn::VarBuilder;
use memmap2::Mmap;
use std::{
collections::HashMap,
fs::File,
path::{Path, PathBuf},
sync::Arc,
};
pub struct GGUFContent {
_mmap: Arc<Mmap>,
candle_content: candle_core::quantized::gguf_file::Content,
}
impl GGUFContent {
pub fn read<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
let file = File::open(path).map_err(|e| {
Error::model_loading(&format!(
"Failed to open GGUF file {}: {}",
path.display(),
e
))
})?;
let mmap = unsafe {
Mmap::map(&file).map_err(|e| {
Error::model_loading(&format!(
"Failed to mmap GGUF file {}: {}",
path.display(),
e
))
})?
};
let mmap = Arc::new(mmap);
let mut file = File::open(path).map_err(|e| {
Error::model_loading(&format!(
"Failed to reopen GGUF file {}: {}",
path.display(),
e
))
})?;
let candle_content = candle_core::quantized::gguf_file::Content::read(&mut file)
.map_err(|e| Error::model_loading(&format!("Failed to parse GGUF content: {}", e)))?;
Ok(Self {
_mmap: mmap,
candle_content,
})
}
pub fn tensor_names(&self) -> Vec<&str> {
self.candle_content
.tensor_infos
.keys()
.map(|s| s.as_str())
.collect()
}
pub fn get_qtensor(&self, name: &str) -> Result<candle_core::quantized::QTensor> {
let mut cursor = std::io::Cursor::new(&**self._mmap);
let device = candle_core::Device::Cpu;
self.candle_content
.tensor(&mut cursor, name, &device)
.map_err(|e| {
Error::model_loading(&format!("Failed to load GGUF tensor '{}': {}", name, e))
})
}
pub fn get_all_tensor_names(&self) -> Vec<String> {
self.candle_content.tensor_infos.keys().cloned().collect()
}
}
pub fn load_gguf(path: &Path, options: &LoadOptions) -> Result<LoadedModel> {
if let Some(callback) = &options.progress {
callback(ProgressEvent::LoadingFile {
file: path.to_path_buf(),
format: "GGUF".to_string(),
});
}
let content = GGUFContent::read(path)?;
let tensor_names: Vec<String> = content.get_all_tensor_names();
let name_mapper = SmartTensorNameMapper::from_tensor_names(&tensor_names)?;
if let Some(callback) = &options.progress {
callback(ProgressEvent::LoadingTensorsFromFiles {
count: tensor_names.len(),
format: "GGUF".to_string(),
});
}
let mut raw_tensors = HashMap::new();
let mut quantized_tensors = if options.preserve_quantization {
Some(HashMap::new())
} else {
None
};
let sample_tensor_names: Vec<_> = tensor_names.iter().take(10).collect();
for tensor_name in &sample_tensor_names {
match content.get_qtensor(tensor_name) {
Ok(qtensor) => {
if options.preserve_quantization {
match qtensor.dequantize(&options.device) {
Ok(tensor) => {
raw_tensors.insert(tensor_name.to_string(), tensor);
}
Err(e) => {
eprintln!(
"Warning: Failed to dequantize tensor '{}': {}",
tensor_name, e
);
}
}
if let Some(ref mut qtensors) = quantized_tensors {
qtensors.insert(tensor_name.to_string(), qtensor);
}
} else {
match qtensor.dequantize(&options.device) {
Ok(tensor) => {
raw_tensors.insert(tensor_name.to_string(), tensor);
}
Err(e) => {
eprintln!(
"Warning: Failed to dequantize tensor '{}': {}",
tensor_name, e
);
}
}
}
}
Err(e) => {
eprintln!("Warning: Failed to load tensor '{}': {}", tensor_name, e);
}
}
}
let config = ModelConfig {
vocab_size: 32000, hidden_size: 4096,
num_attention_heads: 32,
num_hidden_layers: 32,
intermediate_size: 11008,
max_position_embeddings: 4096,
layer_norm_eps: 1e-6,
dropout: 0.0,
attention_dropout: 0.0,
activation_function: "silu".to_string(),
rope_theta: 10000.0,
tie_word_embeddings: false,
architecture: name_mapper
.architecture()
.cloned()
.unwrap_or(crate::name_mapping::Architecture::LLaMA),
raw_config: serde_json::Value::Null,
};
let var_builder = if !raw_tensors.is_empty() {
VarBuilder::from_tensors(raw_tensors.clone(), options.dtype, &options.device)
} else {
let var_map = candle_nn::VarMap::new();
VarBuilder::from_varmap(&var_map, options.dtype, &options.device)
};
if let Some(callback) = &options.progress {
callback(ProgressEvent::Complete {
tensor_count: tensor_names.len(),
format: "GGUF".to_string(),
});
}
Ok(LoadedModel {
var_builder,
config,
name_mapper,
raw_tensors,
quantized_tensors,
metadata: crate::metadata::ModelMetadata::new(),
tensor_info: HashMap::new(),
quantization_info: None,
provenance: crate::metadata::ModelProvenance::new(),
})
}
pub fn find_gguf_files(model_dir: &Path) -> Result<Vec<PathBuf>> {
let mut gguf_files = Vec::new();
if !model_dir.is_dir() {
return Err(Error::model_loading(&format!(
"Model directory not found: {:?}",
model_dir
)));
}
let entries = std::fs::read_dir(model_dir).map_err(|e| {
Error::model_loading(&format!(
"Cannot read model directory {:?}: {}",
model_dir, e
))
})?;
for entry in entries {
let entry = entry
.map_err(|e| Error::model_loading(&format!("Error reading directory entry: {}", e)))?;
let path = entry.path();
if let Some(extension) = path.extension() {
if extension == "gguf" {
gguf_files.push(path);
}
}
}
gguf_files.sort();
Ok(gguf_files)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_find_gguf_files() {
let temp_dir = TempDir::new().unwrap();
std::fs::write(temp_dir.path().join("model.gguf"), b"dummy").unwrap();
std::fs::write(temp_dir.path().join("tokenizer.gguf"), b"dummy").unwrap();
std::fs::write(temp_dir.path().join("config.json"), b"{}").unwrap();
std::fs::write(temp_dir.path().join("not_gguf.bin"), b"dummy").unwrap();
let gguf_files = find_gguf_files(temp_dir.path()).unwrap();
assert_eq!(gguf_files.len(), 2);
let names: Vec<_> = gguf_files
.iter()
.map(|p| p.file_name().unwrap().to_str().unwrap())
.collect();
assert!(names.contains(&"model.gguf"));
assert!(names.contains(&"tokenizer.gguf"));
}
#[test]
fn test_find_gguf_files_empty_dir() {
let temp_dir = TempDir::new().unwrap();
let gguf_files = find_gguf_files(temp_dir.path()).unwrap();
assert_eq!(gguf_files.len(), 0);
}
}