spark_runtime/weights/gguf/
config.rs1use std::path::Path;
12
13use anyhow::{Context, Result};
14use avarok_core::config::{GgufConfigInputs, GgufMeta, ModelConfig, config_from_gguf};
15
16use super::container::GgufFile;
17use super::find_gguf;
18
19impl GgufMeta for GgufFile {
20 fn get_u64(&self, key: &str) -> Option<u64> {
21 GgufFile::get_u64(self, key)
22 }
23 fn get_f64(&self, key: &str) -> Option<f64> {
24 GgufFile::get_f64(self, key)
25 }
26 fn get_str(&self, key: &str) -> Option<&str> {
27 GgufFile::get_str(self, key)
28 }
29 fn get_arr_len(&self, key: &str) -> Option<usize> {
30 GgufFile::arr_len(self, key)
31 }
32 fn get_u64_arr(&self, key: &str) -> Option<Vec<u64>> {
41 let arr = GgufFile::get(self, key)?.as_array()?;
42 arr.iter()
43 .map(|v| {
44 v.as_u64()
45 .or_else(|| v.as_i64().and_then(|i| u64::try_from(i).ok()))
46 })
47 .collect()
48 }
49 fn get_f64_arr(&self, key: &str) -> Option<Vec<f64>> {
51 let arr = GgufFile::get(self, key)?.as_array()?;
52 arr.iter().map(|v| v.as_f64()).collect()
53 }
54}
55
56pub fn config_from_gguf_dir(model_dir: &Path) -> Result<ModelConfig> {
62 let path = find_gguf(model_dir)
63 .with_context(|| format!("no .gguf file in {}", model_dir.display()))?;
64 let file =
65 std::fs::File::open(&path).with_context(|| format!("failed to open {}", path.display()))?;
66 let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? };
69 let gguf = GgufFile::parse(&mmap)
70 .with_context(|| format!("failed to parse GGUF metadata: {}", path.display()))?;
71
72 let token_embd_vocab = gguf
73 .tensor("token_embd.weight")
74 .and_then(|t| t.dims.last().copied());
75 let has_output_weight = gguf.tensor("output.weight").is_some();
76
77 let inputs = GgufConfigInputs {
78 meta: &gguf,
79 token_embd_vocab,
80 has_output_weight,
81 };
82 config_from_gguf(&inputs).context("failed to build ModelConfig from GGUF metadata")
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
92 fn gguf_meta_bridge_forwards() {
93 let mut b: Vec<u8> = Vec::new();
95 let push_u32 = |b: &mut Vec<u8>, v: u32| b.extend_from_slice(&v.to_le_bytes());
96 let push_u64 = |b: &mut Vec<u8>, v: u64| b.extend_from_slice(&v.to_le_bytes());
97 let push_str = |b: &mut Vec<u8>, s: &str| {
98 b.extend_from_slice(&(s.len() as u64).to_le_bytes());
99 b.extend_from_slice(s.as_bytes());
100 };
101 push_u32(&mut b, 0x4655_4747); push_u32(&mut b, 3); push_u64(&mut b, 0); push_u64(&mut b, 2); push_str(&mut b, "qwen3.block_count");
107 push_u32(&mut b, 4); push_u32(&mut b, 28);
109 push_str(&mut b, "tokenizer.ggml.tokens");
111 push_u32(&mut b, 9); push_u32(&mut b, 8); push_u64(&mut b, 3); for s in ["a", "bb", "ccc"] {
115 push_str(&mut b, s);
116 }
117 while !b.len().is_multiple_of(32) {
120 b.push(0);
121 }
122 let gguf = GgufFile::parse(&b).unwrap();
123 let m: &dyn GgufMeta = &gguf;
124 assert_eq!(m.get_u64("qwen3.block_count"), Some(28));
125 assert_eq!(m.get_arr_len("tokenizer.ggml.tokens"), Some(3));
126 assert_eq!(m.get_str("nonexistent"), None);
127 }
128}