1use super::{TensorIndexEntry, V2FormatError, ALIGNMENT, MAX_TENSOR_NAME_LEN};
5
6impl TensorIndexEntry {
7 #[must_use]
9 pub fn new(
10 name: impl Into<String>,
11 dtype: TensorDType,
12 shape: Vec<usize>,
13 offset: u64,
14 size: u64,
15 ) -> Self {
16 Self {
17 name: name.into(),
18 dtype,
19 shape,
20 offset,
21 size,
22 }
23 }
24
25 #[must_use]
27 pub fn element_count(&self) -> usize {
28 self.shape.iter().product()
29 }
30
31 #[must_use]
33 pub fn to_bytes(&self) -> Vec<u8> {
34 let mut buf = Vec::new();
35
36 let name_bytes = self.name.as_bytes();
38 let name_len = name_bytes.len().min(MAX_TENSOR_NAME_LEN) as u16;
39 buf.extend_from_slice(&name_len.to_le_bytes());
40 buf.extend_from_slice(&name_bytes[..name_len as usize]);
41
42 buf.push(self.dtype as u8);
44
45 let ndim = self.shape.len().min(8) as u8;
47 buf.push(ndim);
48 for &dim in self.shape.iter().take(8) {
49 buf.extend_from_slice(&(dim as u64).to_le_bytes());
50 }
51
52 buf.extend_from_slice(&self.offset.to_le_bytes());
54
55 buf.extend_from_slice(&self.size.to_le_bytes());
57
58 buf
59 }
60
61 pub fn from_bytes(buf: &[u8]) -> Result<(Self, usize), V2FormatError> {
66 if buf.len() < 4 {
67 return Err(V2FormatError::InvalidTensorIndex(
68 "buffer too small".to_string(),
69 ));
70 }
71
72 let mut pos = 0;
73
74 let name_len = u16::from_le_bytes([buf[pos], buf[pos + 1]]) as usize;
76 pos += 2;
77
78 if buf.len() < pos + name_len + 18 {
79 return Err(V2FormatError::InvalidTensorIndex(
80 "buffer too small for name".to_string(),
81 ));
82 }
83
84 let name = String::from_utf8_lossy(&buf[pos..pos + name_len]).to_string();
85 pos += name_len;
86
87 let dtype = TensorDType::from_u8(buf[pos])
89 .ok_or_else(|| V2FormatError::InvalidTensorIndex("invalid dtype".to_string()))?;
90 pos += 1;
91
92 let ndim = buf[pos] as usize;
94 pos += 1;
95
96 let mut shape = Vec::with_capacity(ndim);
97 for _ in 0..ndim {
98 if buf.len() < pos + 8 {
99 return Err(V2FormatError::InvalidTensorIndex(
100 "buffer too small for shape".to_string(),
101 ));
102 }
103 let dim = u64::from_le_bytes(buf[pos..pos + 8].try_into().unwrap_or([0; 8])) as usize;
104 shape.push(dim);
105 pos += 8;
106 }
107
108 if buf.len() < pos + 16 {
110 return Err(V2FormatError::InvalidTensorIndex(
111 "buffer too small for offset/size".to_string(),
112 ));
113 }
114 let offset = u64::from_le_bytes(buf[pos..pos + 8].try_into().unwrap_or([0; 8]));
115 pos += 8;
116
117 let size = u64::from_le_bytes(buf[pos..pos + 8].try_into().unwrap_or([0; 8]));
119 pos += 8;
120
121 Ok((
122 Self {
123 name,
124 dtype,
125 shape,
126 offset,
127 size,
128 },
129 pos,
130 ))
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154#[repr(u8)]
155pub enum TensorDType {
156 F32 = 0,
158 F16 = 1,
160 BF16 = 30,
162 F64 = 3,
164 I32 = 4,
166 I64 = 5,
168 I8 = 6,
170 U8 = 7,
172 AprQ4 = 128,
177 AprQ8 = 129,
182 Q4K = 12,
185 Q6K = 14,
187}
188
189impl std::fmt::Display for TensorDType {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 let name = match self {
192 Self::F32 => "F32",
193 Self::F16 => "F16",
194 Self::BF16 => "BF16",
195 Self::F64 => "F64",
196 Self::I32 => "I32",
197 Self::I64 => "I64",
198 Self::I8 => "I8",
199 Self::U8 => "U8",
200 Self::AprQ4 => "APR_Q4",
201 Self::AprQ8 => "APR_Q8",
202 Self::Q4K => "Q4_K",
203 Self::Q6K => "Q6_K",
204 };
205 f.write_str(name)
206 }
207}
208
209const _: () = assert!(TensorDType::F32 as u8 == 0, "F32 must be GGML type 0");
213const _: () = assert!(TensorDType::F16 as u8 == 1, "F16 must be GGML type 1");
214const _: () = assert!(TensorDType::BF16 as u8 == 30, "BF16 must be GGML type 30");
215const _: () = assert!(
216 TensorDType::Q4K as u8 == 12,
217 "Q4K must be GGML type 12 (Q4_K)"
218);
219const _: () = assert!(
220 TensorDType::Q6K as u8 == 14,
221 "Q6K must be GGML type 14 (Q6_K)"
222);
223const _: () = assert!(
225 TensorDType::AprQ4 as u8 >= 128,
226 "AprQ4 must be outside GGML range"
227);
228const _: () = assert!(
229 TensorDType::AprQ8 as u8 >= 128,
230 "AprQ8 must be outside GGML range"
231);
232
233impl TensorDType {
234 #[must_use]
239 pub fn from_u8(value: u8) -> Option<Self> {
240 match value {
241 0 => Some(Self::F32),
242 1 => Some(Self::F16),
243 30 => Some(Self::BF16),
244 3 => Some(Self::F64),
245 4 => Some(Self::I32),
246 5 => Some(Self::I64),
247 6 => Some(Self::I8),
248 7 => Some(Self::U8),
249 8 | 128 => Some(Self::AprQ4),
251 9 | 129 => Some(Self::AprQ8),
252 12 => Some(Self::Q4K),
253 14 => Some(Self::Q6K),
254 _ => None,
255 }
256 }
257
258 #[must_use]
260 pub const fn bytes_per_element(self) -> usize {
261 match self {
262 Self::F32 | Self::I32 => 4,
263 Self::F16 | Self::BF16 => 2,
264 Self::F64 | Self::I64 => 8,
265 Self::I8 | Self::U8 | Self::AprQ8 => 1,
266 Self::AprQ4 | Self::Q4K | Self::Q6K => 0, }
268 }
269
270 #[must_use]
272 pub const fn name(self) -> &'static str {
273 match self {
274 Self::F32 => "f32",
275 Self::F16 => "f16",
276 Self::BF16 => "bf16",
277 Self::F64 => "f64",
278 Self::I32 => "i32",
279 Self::I64 => "i64",
280 Self::I8 => "i8",
281 Self::U8 => "u8",
282 Self::AprQ4 => "q4",
283 Self::AprQ8 => "q8",
284 Self::Q4K => "q4_k",
285 Self::Q6K => "q6_k",
286 }
287 }
288}
289
290#[must_use]
296pub const fn align_up(value: usize, alignment: usize) -> usize {
297 (value + alignment - 1) & !(alignment - 1)
298}
299
300#[must_use]
302pub const fn align_64(value: usize) -> usize {
303 align_up(value, ALIGNMENT)
304}
305
306#[must_use]
308pub const fn padding_to_align(value: usize, alignment: usize) -> usize {
309 let aligned = align_up(value, alignment);
310 aligned - value
311}
312
313#[must_use]
315pub const fn is_aligned_64(value: usize) -> bool {
316 value.is_multiple_of(ALIGNMENT)
317}
318
319