1use std::ffi::{CStr, CString};
2use std::fs::File;
3use std::io::{Read, Seek, SeekFrom};
4use std::path::{Path, PathBuf};
5use std::ptr::NonNull;
6
7use qts_ggml::sys;
8
9use super::paths::ModelPaths;
10use crate::Qwen3TtsError;
11
12pub struct GgufFile {
13 ctx: NonNull<sys::gguf_context>,
14 meta_ctx: Option<NonNull<sys::ggml_context>>,
15 path: PathBuf,
16}
17
18#[derive(Debug, Clone)]
19pub struct GgufTensorInfo {
20 pub name: String,
21 pub ty: sys::ggml_type,
22 pub dims: Vec<usize>,
23 pub offset: usize,
24 pub size: usize,
25}
26
27impl GgufFile {
28 pub fn open(path: impl AsRef<Path>) -> Result<Self, Qwen3TtsError> {
29 let path = path.as_ref().to_path_buf();
30 let path_str = path.to_str().ok_or(Qwen3TtsError::InvalidPath)?;
31 let path_c = CString::new(path_str)?;
32
33 let mut meta_ctx = std::ptr::null_mut();
34 let params = sys::gguf_init_params {
35 no_alloc: true,
36 ctx: &mut meta_ctx,
37 };
38
39 let ctx = unsafe { sys::gguf_init_from_file(path_c.as_ptr(), params) };
40 let ctx = NonNull::new(ctx).ok_or_else(|| Qwen3TtsError::InvalidGguf(path.clone()))?;
41
42 Ok(Self {
43 ctx,
44 meta_ctx: NonNull::new(meta_ctx),
45 path,
46 })
47 }
48
49 #[must_use]
50 pub fn ctx_ptr(&self) -> *mut sys::gguf_context {
51 self.ctx.as_ptr()
52 }
53
54 #[must_use]
55 pub fn path(&self) -> &Path {
56 &self.path
57 }
58
59 #[must_use]
60 pub fn meta_ctx_ptr(&self) -> Option<*mut sys::ggml_context> {
61 self.meta_ctx.map(NonNull::as_ptr)
62 }
63
64 #[must_use]
65 pub fn n_tensors(&self) -> i64 {
66 unsafe { sys::gguf_get_n_tensors(self.ctx.as_ptr()) }
67 }
68
69 #[must_use]
70 pub fn data_offset(&self) -> usize {
71 unsafe { sys::gguf_get_data_offset(self.ctx.as_ptr()) }
72 }
73
74 #[must_use]
75 pub fn key_index(&self, key: &str) -> Option<i64> {
76 let key = CString::new(key).ok()?;
77 let idx = unsafe { sys::gguf_find_key(self.ctx.as_ptr(), key.as_ptr()) };
78 (idx >= 0).then_some(idx)
79 }
80
81 #[must_use]
82 pub fn get_u32(&self, key: &str, default: u32) -> u32 {
83 self.key_index(key)
84 .map(|idx| unsafe { sys::gguf_get_val_u32(self.ctx.as_ptr(), idx) })
85 .unwrap_or(default)
86 }
87
88 #[must_use]
89 pub fn get_f32(&self, key: &str, default: f32) -> f32 {
90 self.key_index(key)
91 .map(|idx| unsafe { sys::gguf_get_val_f32(self.ctx.as_ptr(), idx) })
92 .unwrap_or(default)
93 }
94
95 #[must_use]
96 pub fn get_str(&self, key: &str) -> Option<String> {
97 let idx = self.key_index(key)?;
98 if unsafe { sys::gguf_get_kv_type(self.ctx.as_ptr(), idx) }
99 != sys::gguf_type_GGUF_TYPE_STRING
100 {
101 return None;
102 }
103 let value = unsafe { sys::gguf_get_val_str(self.ctx.as_ptr(), idx) };
104 if value.is_null() {
105 return None;
106 }
107 Some(
108 unsafe { CStr::from_ptr(value) }
109 .to_string_lossy()
110 .into_owned(),
111 )
112 }
113
114 #[must_use]
115 pub fn get_arr_str(&self, key: &str) -> Option<Vec<String>> {
116 let idx = self.key_index(key)?;
117 let n = unsafe { sys::gguf_get_arr_n(self.ctx.as_ptr(), idx) };
118 let mut out = Vec::with_capacity(n);
119 for i in 0..n {
120 let s = unsafe { sys::gguf_get_arr_str(self.ctx.as_ptr(), idx, i) };
121 if s.is_null() {
122 continue;
123 }
124 out.push(unsafe { CStr::from_ptr(s) }.to_string_lossy().into_owned());
125 }
126 Some(out)
127 }
128
129 pub fn tensor_info(&self, name: &str) -> Result<GgufTensorInfo, Qwen3TtsError> {
130 let meta_ctx = self
131 .meta_ctx_ptr()
132 .ok_or_else(|| Qwen3TtsError::InvalidGguf(self.path.clone()))?;
133 let name_c = CString::new(name)?;
134 let tensor = unsafe { sys::ggml_get_tensor(meta_ctx, name_c.as_ptr()) };
135 let tensor =
136 NonNull::new(tensor).ok_or_else(|| Qwen3TtsError::MissingTensor(name.into()))?;
137
138 let idx = self
139 .tensor_index(name)
140 .ok_or_else(|| Qwen3TtsError::MissingTensor(name.into()))?;
141 let n_dims = unsafe { sys::ggml_n_dims(tensor.as_ptr()) } as usize;
142 let dims = (0..n_dims)
143 .map(|dim| unsafe { (*tensor.as_ptr()).ne[dim] as usize })
144 .collect::<Vec<_>>();
145
146 Ok(GgufTensorInfo {
147 name: name.into(),
148 ty: unsafe { sys::gguf_get_tensor_type(self.ctx.as_ptr(), idx) },
149 dims,
150 offset: unsafe { sys::gguf_get_tensor_offset(self.ctx.as_ptr(), idx) },
151 size: unsafe { sys::gguf_get_tensor_size(self.ctx.as_ptr(), idx) },
152 })
153 }
154
155 pub fn read_tensor_f32(&self, name: &str) -> Result<(GgufTensorInfo, Vec<f32>), Qwen3TtsError> {
156 let info = self.tensor_info(name)?;
157 let raw = self.read_tensor_bytes_by_info(&info)?;
158
159 let element_count = info
160 .dims
161 .iter()
162 .copied()
163 .try_fold(1usize, |acc, dim| acc.checked_mul(dim))
164 .ok_or_else(|| Qwen3TtsError::InvalidTensor(name.into()))?;
165
166 let data = match info.ty {
167 sys::ggml_type_GGML_TYPE_F32 => {
168 if info.size != element_count * std::mem::size_of::<f32>() {
169 return Err(Qwen3TtsError::InvalidTensor(name.into()));
170 }
171 raw.as_chunks::<4>()
172 .0
173 .iter()
174 .map(|bytes| f32::from_le_bytes(*bytes))
175 .collect()
176 }
177 sys::ggml_type_GGML_TYPE_F16 => {
178 if info.size != element_count * std::mem::size_of::<sys::ggml_fp16_t>() {
179 return Err(Qwen3TtsError::InvalidTensor(name.into()));
180 }
181 let mut out = Vec::with_capacity(element_count);
182 for bytes in raw.as_chunks::<2>().0 {
183 let value = u16::from_le_bytes(*bytes);
184 out.push(unsafe { sys::ggml_fp16_to_fp32(value) });
185 }
186 out
187 }
188 _ => return Err(Qwen3TtsError::UnsupportedTensorType(name.into())),
189 };
190
191 Ok((info, data))
192 }
193
194 pub fn read_tensor_bytes(
195 &self,
196 name: &str,
197 ) -> Result<(GgufTensorInfo, Vec<u8>), Qwen3TtsError> {
198 let info = self.tensor_info(name)?;
199 let raw = self.read_tensor_bytes_by_info(&info)?;
200 Ok((info, raw))
201 }
202
203 fn tensor_index(&self, name: &str) -> Option<i64> {
204 for idx in 0..self.n_tensors() {
205 let tensor_name = unsafe { sys::gguf_get_tensor_name(self.ctx.as_ptr(), idx) };
206 if tensor_name.is_null() {
207 continue;
208 }
209 if unsafe { CStr::from_ptr(tensor_name) }.to_string_lossy() == name {
210 return Some(idx);
211 }
212 }
213 None
214 }
215
216 fn read_tensor_bytes_by_info(&self, info: &GgufTensorInfo) -> Result<Vec<u8>, Qwen3TtsError> {
217 let mut file = File::open(&self.path)?;
218 file.seek(SeekFrom::Start((self.data_offset() + info.offset) as u64))?;
219 let mut raw = vec![0u8; info.size];
220 file.read_exact(&mut raw)?;
221 Ok(raw)
222 }
223}
224
225impl Drop for GgufFile {
226 fn drop(&mut self) {
227 unsafe {
228 sys::gguf_free(self.ctx.as_ptr());
229 if let Some(meta_ctx) = self.meta_ctx {
230 sys::ggml_free(meta_ctx.as_ptr());
231 }
232 }
233 }
234}
235
236pub fn load_and_validate(paths: &ModelPaths) -> Result<(), Qwen3TtsError> {
238 if !paths.main_gguf.is_file() {
239 return Err(Qwen3TtsError::ModelFile(paths.main_gguf.clone()));
240 }
241 if !paths.vocoder_onnx.is_file() {
242 return Err(Qwen3TtsError::ModelFile(paths.vocoder_onnx.clone()));
243 }
244 validate_gguf_file(&paths.main_gguf)?;
245 Ok(())
246}
247
248fn validate_gguf_file(path: &Path) -> Result<(), Qwen3TtsError> {
249 GgufFile::open(path).map(|_| ())
250}