1use std::collections::BTreeMap;
11use std::io::{Read, Seek, SeekFrom};
12
13use super::quant::{f16_to_f32, GgmlType};
14
15#[derive(Clone, Debug, PartialEq)]
17pub enum MetaValue {
18 U8(u8),
19 I8(i8),
20 U16(u16),
21 I16(i16),
22 U32(u32),
23 I32(i32),
24 U64(u64),
25 I64(i64),
26 F32(f32),
27 F64(f64),
28 Bool(bool),
29 Str(Vec<u8>),
31 Array { ty: u32, items: Vec<MetaValue> },
33}
34
35impl MetaValue {
36 pub fn as_u64(&self) -> Option<u64> {
38 match *self {
39 MetaValue::U8(v) => Some(v as u64),
40 MetaValue::U16(v) => Some(v as u64),
41 MetaValue::U32(v) => Some(v as u64),
42 MetaValue::U64(v) => Some(v),
43 MetaValue::I8(v) if v >= 0 => Some(v as u64),
44 MetaValue::I16(v) if v >= 0 => Some(v as u64),
45 MetaValue::I32(v) if v >= 0 => Some(v as u64),
46 MetaValue::I64(v) if v >= 0 => Some(v as u64),
47 _ => None,
48 }
49 }
50
51 pub fn as_i64(&self) -> Option<i64> {
53 match *self {
54 MetaValue::I8(v) => Some(v as i64),
55 MetaValue::I16(v) => Some(v as i64),
56 MetaValue::I32(v) => Some(v as i64),
57 MetaValue::I64(v) => Some(v),
58 MetaValue::U8(v) => Some(v as i64),
59 MetaValue::U16(v) => Some(v as i64),
60 MetaValue::U32(v) => Some(v as i64),
61 MetaValue::U64(v) => i64::try_from(v).ok(),
62 _ => None,
63 }
64 }
65
66 pub fn as_f64(&self) -> Option<f64> {
68 match *self {
69 MetaValue::F32(v) => Some(v as f64),
70 MetaValue::F64(v) => Some(v),
71 _ => None,
72 }
73 }
74
75 pub fn as_str(&self) -> Option<&str> {
77 match self {
78 MetaValue::Str(s) => std::str::from_utf8(s).ok(),
79 _ => None,
80 }
81 }
82
83 pub fn as_bytes(&self) -> Option<&[u8]> {
85 match self {
86 MetaValue::Str(s) => Some(s),
87 _ => None,
88 }
89 }
90
91 pub fn as_bool(&self) -> Option<bool> {
92 match *self {
93 MetaValue::Bool(b) => Some(b),
94 _ => None,
95 }
96 }
97
98 pub fn as_array(&self) -> Option<&[MetaValue]> {
99 match self {
100 MetaValue::Array { items, .. } => Some(items),
101 _ => None,
102 }
103 }
104}
105
106#[derive(Debug, Clone)]
109pub struct Arch {
110 pub arch: String,
111 pub block_count: u64,
112 pub context_length: u64,
114 pub embedding_length: u64,
115 pub feed_forward_length: u64,
116 pub head_count: u64,
117 pub head_count_kv: u64,
118 pub head_dim: u64,
119 pub rms_eps: f32,
120 pub rope_freq_base: f32,
121 pub value_length: u64,
123 pub rope_dimension_count: u64,
126 pub full_attention_interval: u64,
129 pub nextn_predict_layers: u64,
132 pub ssm_conv_kernel: u64,
134 pub ssm_state_size: u64,
136 pub ssm_group_count: u64,
138 pub ssm_time_step_rank: u64,
140 pub ssm_inner_size: u64,
142}
143
144impl Arch {
145 pub fn model_layers(&self) -> u64 {
147 self.block_count.saturating_sub(self.nextn_predict_layers)
148 }
149
150 pub fn is_full_attention_layer(&self, l: u64) -> bool {
153 self.full_attention_interval == 0 || (l + 1) % self.full_attention_interval == 0
154 }
155
156 fn from_meta(meta: &BTreeMap<String, MetaValue>) -> Arch {
157 let arch = meta
158 .get("general.architecture")
159 .and_then(MetaValue::as_str)
160 .unwrap_or("")
161 .to_string();
162 let g = |suffix: &str| -> u64 {
163 meta.get(&format!("{arch}.{suffix}"))
164 .and_then(MetaValue::as_u64)
165 .unwrap_or(0)
166 };
167 let gf = |suffix: &str, d: f32| -> f32 {
168 meta.get(&format!("{arch}.{suffix}"))
169 .and_then(MetaValue::as_f64)
170 .map_or(d, |v| v as f32)
171 };
172 Arch {
173 block_count: g("block_count"),
174 context_length: g("context_length"),
175 embedding_length: g("embedding_length"),
176 feed_forward_length: g("feed_forward_length"),
177 head_count: g("attention.head_count"),
178 head_count_kv: g("attention.head_count_kv"),
179 head_dim: g("attention.key_length"),
180 rms_eps: gf("attention.layer_norm_rms_epsilon", 1e-6),
181 rope_freq_base: gf("rope.freq_base", 10000.0),
182 value_length: g("attention.value_length"),
183 rope_dimension_count: g("rope.dimension_count"),
184 full_attention_interval: g("full_attention_interval"),
185 nextn_predict_layers: g("nextn_predict_layers"),
186 ssm_conv_kernel: g("ssm.conv_kernel"),
187 ssm_state_size: g("ssm.state_size"),
188 ssm_group_count: g("ssm.group_count"),
189 ssm_time_step_rank: g("ssm.time_step_rank"),
190 ssm_inner_size: g("ssm.inner_size"),
191 arch,
192 }
193 }
194}
195
196#[derive(Debug, Clone)]
198pub struct TensorInfo {
199 pub name: String,
200 pub dims: Vec<u64>,
202 pub ggml_type: u32,
204 pub offset: u64,
206}
207
208impl TensorInfo {
209 pub fn elements(&self) -> usize {
210 self.dims.iter().map(|&d| d as usize).product()
211 }
212}
213
214pub struct Gguf {
216 file: std::fs::File,
217 pub version: u32,
218 pub meta: BTreeMap<String, MetaValue>,
219 pub order: Vec<String>,
221 pub arch: Arch,
222 tensors: Vec<TensorInfo>,
223 data_start: u64,
224}
225
226pub fn fnv1a64(bytes: &[u8]) -> u64 {
228 bytes.iter().fold(0xcbf2_9ce4_8422_2325u64, |h, &b| (h ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01b3))
229}
230
231fn invalid(msg: String) -> std::io::Error {
232 std::io::Error::new(std::io::ErrorKind::InvalidData, msg)
233}
234
235fn not_found(name: &str) -> std::io::Error {
236 std::io::Error::new(std::io::ErrorKind::NotFound, format!("no tensor {name}"))
237}
238
239fn ru8(f: &mut impl Read) -> std::io::Result<u8> {
240 let mut b = [0u8; 1];
241 f.read_exact(&mut b)?;
242 Ok(b[0])
243}
244fn ru16(f: &mut impl Read) -> std::io::Result<u16> {
245 let mut b = [0u8; 2];
246 f.read_exact(&mut b)?;
247 Ok(u16::from_le_bytes(b))
248}
249fn ru32(f: &mut impl Read) -> std::io::Result<u32> {
250 let mut b = [0u8; 4];
251 f.read_exact(&mut b)?;
252 Ok(u32::from_le_bytes(b))
253}
254fn ru64(f: &mut impl Read) -> std::io::Result<u64> {
255 let mut b = [0u8; 8];
256 f.read_exact(&mut b)?;
257 Ok(u64::from_le_bytes(b))
258}
259fn rbytes(f: &mut impl Read) -> std::io::Result<Vec<u8>> {
260 let n = ru64(f)? as usize;
261 let mut b = vec![0u8; n];
262 f.read_exact(&mut b)?;
263 Ok(b)
264}
265
266fn rstr(f: &mut impl Read) -> std::io::Result<String> {
268 String::from_utf8(rbytes(f)?).map_err(|e| invalid(format!("a metadata key or tensor name is not UTF-8: {e}")))
269}
270
271fn read_value(f: &mut impl Read, t: u32) -> std::io::Result<MetaValue> {
273 Ok(match t {
274 0 => MetaValue::U8(ru8(f)?),
275 1 => MetaValue::I8(ru8(f)? as i8),
276 2 => MetaValue::U16(ru16(f)?),
277 3 => MetaValue::I16(ru16(f)? as i16),
278 4 => MetaValue::U32(ru32(f)?),
279 5 => MetaValue::I32(ru32(f)? as i32),
280 6 => MetaValue::F32(f32::from_bits(ru32(f)?)),
281 7 => MetaValue::Bool(ru8(f)? != 0),
282 8 => MetaValue::Str(rbytes(f)?),
283 9 => {
284 let et = ru32(f)?;
285 let n = ru64(f)? as usize;
286 let mut items = Vec::with_capacity(n.min(1 << 24));
287 for _ in 0..n {
288 items.push(read_value(f, et)?);
289 }
290 MetaValue::Array { ty: et, items }
291 }
292 10 => MetaValue::U64(ru64(f)?),
293 11 => MetaValue::I64(ru64(f)? as i64),
294 12 => MetaValue::F64(f64::from_bits(ru64(f)?)),
295 other => return Err(invalid(format!("unknown GGUF metadata type {other}"))),
296 })
297}
298
299impl Gguf {
300 pub fn open(path: &std::path::Path) -> std::io::Result<Gguf> {
302 let file = std::fs::File::open(path)?;
303 let mut r = std::io::BufReader::new(file);
304 let mut magic = [0u8; 4];
305 r.read_exact(&mut magic)?;
306 if &magic != b"GGUF" {
307 return Err(invalid(format!("{} is not a GGUF file", path.display())));
308 }
309 let version = ru32(&mut r)?;
310 if version < 2 {
311 return Err(invalid(format!(
312 "GGUF version {version} (32-bit counts) is not read; versions 2 and 3 are"
313 )));
314 }
315 let n_tensors = ru64(&mut r)?;
316 let n_kv = ru64(&mut r)?;
317 let mut meta = BTreeMap::new();
318 let mut order = Vec::with_capacity(n_kv as usize);
319 for _ in 0..n_kv {
320 let key = rstr(&mut r)?;
321 let t = ru32(&mut r)?;
322 order.push(key.clone());
323 meta.insert(key, read_value(&mut r, t)?);
324 }
325 let mut tensors = Vec::with_capacity(n_tensors as usize);
326 for _ in 0..n_tensors {
327 let name = rstr(&mut r)?;
328 let ndims = ru32(&mut r)? as usize;
329 let dims = (0..ndims)
330 .map(|_| ru64(&mut r))
331 .collect::<std::io::Result<Vec<u64>>>()?;
332 let ggml_type = ru32(&mut r)?;
333 let offset = ru64(&mut r)?;
334 tensors.push(TensorInfo {
335 name,
336 dims,
337 ggml_type,
338 offset,
339 });
340 }
341 let align = meta
342 .get("general.alignment")
343 .and_then(MetaValue::as_u64)
344 .unwrap_or(32);
345 let pos = r.stream_position()?;
346 let data_start = pos.div_ceil(align) * align;
347 let arch = Arch::from_meta(&meta);
348 Ok(Gguf {
349 file: r.into_inner(),
350 version,
351 meta,
352 order,
353 arch,
354 tensors,
355 data_start,
356 })
357 }
358
359 pub fn data_start(&self) -> u64 {
361 self.data_start
362 }
363
364 pub fn header_digest(&mut self) -> std::io::Result<u64> {
367 let mut head = vec![0u8; self.data_start as usize];
368 self.file.seek(SeekFrom::Start(0))?;
369 self.file.read_exact(&mut head)?;
370 Ok(fnv1a64(&head))
371 }
372
373 pub fn meta(&self, key: &str) -> Option<&MetaValue> {
375 self.meta.get(key)
376 }
377
378 pub fn arch_meta(&self, suffix: &str) -> Option<&MetaValue> {
380 self.meta.get(&format!("{}.{suffix}", self.arch.arch))
381 }
382
383 pub fn decode_read_bytes(&self) -> std::io::Result<u64> {
391 let untied = self.info("output.weight").is_some();
392 let experts = self.arch_meta("expert_count").and_then(MetaValue::as_u64).unwrap_or(0);
393 let routed = self.arch_meta("expert_used_count").and_then(MetaValue::as_u64).unwrap_or(0);
394 let layers = self.arch.model_layers();
395 let mut total = 0u128;
396 for t in &self.tensors {
397 if untied && t.name == "token_embd.weight" {
398 continue;
399 }
400 let block = t.name.strip_prefix("blk.").and_then(|r| r.split('.').next()).and_then(|b| b.parse::<u64>().ok());
401 if block.is_some_and(|b| b >= layers) {
402 continue;
403 }
404 let ty = self.tensor_type(&t.name)?;
405 let bytes = ty.bytes_for(t.elements()) as u128;
406 total += if t.name.contains("_exps.") && experts > 0 {
407 bytes * routed as u128 / experts as u128
408 } else {
409 bytes
410 };
411 }
412 Ok(total as u64)
413 }
414
415 pub fn tensors(&self) -> &[TensorInfo] {
417 &self.tensors
418 }
419
420 pub fn info(&self, name: &str) -> Option<&TensorInfo> {
422 self.tensors.iter().find(|t| t.name == name)
423 }
424
425 pub fn tensor_type(&self, name: &str) -> std::io::Result<GgmlType> {
427 let t = self.info(name).ok_or_else(|| not_found(name))?;
428 GgmlType::from_id(t.ggml_type).map_err(|id| {
429 invalid(format!(
430 "{name} is ggml type {id}, a format this crate does not read"
431 ))
432 })
433 }
434
435 pub fn row_bytes(&mut self, name: &str, first: usize, count: usize) -> std::io::Result<Vec<u8>> {
437 let ty = self.tensor_type(name)?;
438 let t = self.info(name).ok_or_else(|| not_found(name))?.clone();
439 let row = t.dims.first().copied().unwrap_or(1) as usize;
440 let rows = t.elements() / row.max(1);
441 if first + count > rows {
442 return Err(invalid(format!(
443 "{name} has {rows} rows; rows {first}..{} were asked for",
444 first + count
445 )));
446 }
447 let row_bytes = ty.bytes_for(row);
448 self.file.seek(SeekFrom::Start(
449 self.data_start + t.offset + (first * row_bytes) as u64,
450 ))?;
451 let mut raw = vec![0u8; count * row_bytes];
452 self.file.read_exact(&mut raw)?;
453 Ok(raw)
454 }
455
456 pub fn dequant_rows(&mut self, name: &str, first: usize, count: usize) -> std::io::Result<Vec<f32>> {
458 let ty = self.tensor_type(name)?;
459 let raw = self.row_bytes(name, first, count)?;
460 let mut out = Vec::new();
461 ty.dequantize(&raw, &mut out);
462 Ok(out)
463 }
464
465 pub fn dequant_tensor(&mut self, name: &str) -> std::io::Result<Vec<f32>> {
467 let t = self.info(name).ok_or_else(|| not_found(name))?.clone();
468 let row = t.dims.first().copied().unwrap_or(1) as usize;
469 self.dequant_rows(name, 0, t.elements() / row.max(1))
470 }
471
472 pub fn dims2(&self, name: &str) -> Option<(usize, usize)> {
474 self.info(name).and_then(|t| match t.dims.as_slice() {
475 [ne0, ne1] => Some((*ne0 as usize, *ne1 as usize)),
476 _ => None,
477 })
478 }
479
480 pub fn dims(&self, name: &str) -> Option<&[u64]> {
482 self.info(name).map(|t| t.dims.as_slice())
483 }
484
485 pub fn tensor_names(&self) -> Vec<(String, Vec<u64>, u32)> {
487 self.tensors
488 .iter()
489 .map(|t| (t.name.clone(), t.dims.clone(), t.ggml_type))
490 .collect()
491 }
492
493 pub fn has(&self, name: &str) -> bool {
495 self.info(name).is_some()
496 }
497
498 fn exactly(&self, name: &str, want: GgmlType) -> std::io::Result<TensorInfo> {
499 let ty = self.tensor_type(name)?;
500 if ty != want {
501 return Err(invalid(format!("{name} is {}, not {}", ty.name(), want.name())));
502 }
503 Ok(self.info(name).ok_or_else(|| not_found(name))?.clone())
504 }
505
506 pub fn f32_tensor(&mut self, name: &str) -> std::io::Result<Vec<f32>> {
508 self.exactly(name, GgmlType::F32)?;
509 self.dequant_tensor(name)
510 }
511
512 pub fn f16_tensor(&mut self, name: &str) -> std::io::Result<Vec<f32>> {
515 self.exactly(name, GgmlType::F16)?;
516 self.dequant_tensor(name)
517 }
518
519 pub fn float_tensor(&mut self, name: &str) -> std::io::Result<Vec<f32>> {
521 match self.tensor_type(name)? {
522 GgmlType::F32 | GgmlType::F16 => self.dequant_tensor(name),
523 other => Err(invalid(format!("{name} is {}, not F32/F16", other.name()))),
524 }
525 }
526
527 pub fn q8_0_matrix(&mut self, name: &str) -> std::io::Result<(Vec<i8>, Vec<f32>)> {
533 let t = self.exactly(name, GgmlType::Q8_0)?;
534 let in_f = t.dims[0] as usize;
535 let out_f = t.elements() / in_f;
536 let raw = self.row_bytes(name, 0, out_f)?;
537 let nb = in_f / 32;
538 let mut quants = Vec::with_capacity(out_f * in_f);
539 let mut scales = Vec::with_capacity(out_f * nb);
540 for block in raw.chunks_exact(34) {
541 scales.push(f16_to_f32(u16::from_le_bytes([block[0], block[1]])));
542 quants.extend(block[2..].iter().map(|&q| q as i8));
543 }
544 Ok((quants, scales))
545 }
546
547 pub fn q8_0_dequant_row(&mut self, name: &str, row: usize) -> std::io::Result<Vec<f32>> {
549 self.exactly(name, GgmlType::Q8_0)?;
550 self.dequant_rows(name, row, 1)
551 }
552
553 pub fn q8_0_tile_blocks(
557 &mut self,
558 name: &str,
559 rows: usize,
560 cols: usize,
561 ) -> std::io::Result<(Vec<i32>, Vec<f32>)> {
562 let t = self.exactly(name, GgmlType::Q8_0)?;
563 let ne0 = t.dims[0] as usize;
564 assert!(cols % 32 == 0, "cols must be a multiple of the Q8_0 block (32)");
565 assert!(
566 cols <= ne0 && rows <= t.dims.get(1).copied().unwrap_or(1) as usize,
567 "tile exceeds tensor"
568 );
569 let raw = self.row_bytes(name, 0, rows)?;
570 let row_bytes = ne0 / 32 * 34;
571 let mut quants = Vec::with_capacity(rows * cols);
572 let mut scales = Vec::with_capacity(rows * cols / 32);
573 for r in 0..rows {
574 for block in raw[r * row_bytes..].chunks_exact(34).take(cols / 32) {
575 scales.push(f16_to_f32(u16::from_le_bytes([block[0], block[1]])));
576 quants.extend(block[2..].iter().map(|&q| q as i8 as i32));
577 }
578 }
579 Ok((quants, scales))
580 }
581
582 pub fn q8_0_tile(&mut self, name: &str, rows: usize, cols: usize) -> std::io::Result<Vec<i32>> {
585 Ok(self.q8_0_tile_blocks(name, rows, cols)?.0)
586 }
587}
588
589impl MetaValue {
590 pub fn gguf_type(&self) -> u32 {
592 match self {
593 MetaValue::U8(_) => 0,
594 MetaValue::I8(_) => 1,
595 MetaValue::U16(_) => 2,
596 MetaValue::I16(_) => 3,
597 MetaValue::U32(_) => 4,
598 MetaValue::I32(_) => 5,
599 MetaValue::F32(_) => 6,
600 MetaValue::Bool(_) => 7,
601 MetaValue::Str(_) => 8,
602 MetaValue::Array { .. } => 9,
603 MetaValue::U64(_) => 10,
604 MetaValue::I64(_) => 11,
605 MetaValue::F64(_) => 12,
606 }
607 }
608
609 fn write_to(&self, out: &mut impl std::io::Write) -> std::io::Result<()> {
610 match self {
611 MetaValue::U8(v) => out.write_all(&[*v]),
612 MetaValue::I8(v) => out.write_all(&[*v as u8]),
613 MetaValue::U16(v) => out.write_all(&v.to_le_bytes()),
614 MetaValue::I16(v) => out.write_all(&v.to_le_bytes()),
615 MetaValue::U32(v) => out.write_all(&v.to_le_bytes()),
616 MetaValue::I32(v) => out.write_all(&v.to_le_bytes()),
617 MetaValue::F32(v) => out.write_all(&v.to_bits().to_le_bytes()),
618 MetaValue::Bool(v) => out.write_all(&[*v as u8]),
619 MetaValue::Str(b) => {
620 out.write_all(&(b.len() as u64).to_le_bytes())?;
621 out.write_all(b)
622 }
623 MetaValue::Array { ty, items } => {
624 out.write_all(&ty.to_le_bytes())?;
625 out.write_all(&(items.len() as u64).to_le_bytes())?;
626 items.iter().try_for_each(|v| v.write_to(out))
627 }
628 MetaValue::U64(v) => out.write_all(&v.to_le_bytes()),
629 MetaValue::I64(v) => out.write_all(&v.to_le_bytes()),
630 MetaValue::F64(v) => out.write_all(&v.to_bits().to_le_bytes()),
631 }
632 }
633}
634
635#[derive(Clone, Debug)]
637pub struct TensorOut {
638 pub name: String,
639 pub dims: Vec<u64>,
640 pub ty: GgmlType,
641}
642
643pub fn write(
647 path: &std::path::Path,
648 meta: &[(String, MetaValue)],
649 tensors: &[TensorOut],
650 mut data: impl FnMut(usize) -> std::io::Result<Vec<u8>>,
651) -> std::io::Result<()> {
652 use std::io::Write;
653 let align = meta
654 .iter()
655 .find(|(k, _)| k == "general.alignment")
656 .and_then(|(_, v)| v.as_u64())
657 .unwrap_or(32);
658 let mut out = std::io::BufWriter::new(std::fs::File::create(path)?);
659 let put_str = |out: &mut std::io::BufWriter<std::fs::File>, s: &[u8]| -> std::io::Result<()> {
660 out.write_all(&(s.len() as u64).to_le_bytes())?;
661 out.write_all(s)
662 };
663 out.write_all(b"GGUF")?;
664 out.write_all(&3u32.to_le_bytes())?;
665 out.write_all(&(tensors.len() as u64).to_le_bytes())?;
666 out.write_all(&(meta.len() as u64).to_le_bytes())?;
667 for (k, v) in meta {
668 put_str(&mut out, k.as_bytes())?;
669 out.write_all(&v.gguf_type().to_le_bytes())?;
670 v.write_to(&mut out)?;
671 }
672 let mut offset = 0u64;
673 let mut offsets = Vec::with_capacity(tensors.len());
674 for t in tensors {
675 put_str(&mut out, t.name.as_bytes())?;
676 out.write_all(&(t.dims.len() as u32).to_le_bytes())?;
677 for d in &t.dims {
678 out.write_all(&d.to_le_bytes())?;
679 }
680 out.write_all(&t.ty.id().to_le_bytes())?;
681 out.write_all(&offset.to_le_bytes())?;
682 offsets.push(offset);
683 let n = t.dims.iter().product::<u64>() as usize;
684 offset = (offset + t.ty.bytes_for(n) as u64).div_ceil(align) * align;
685 }
686 let mut written = 4 + 4 + 8 + 8
687 + meta.iter().map(|(k, v)| 8 + k.len() as u64 + 4 + encoded_len(v)).sum::<u64>()
688 + tensors.iter().map(|t| 8 + t.name.len() as u64 + 4 + 8 * t.dims.len() as u64 + 4 + 8).sum::<u64>();
689 let pad = |out: &mut std::io::BufWriter<std::fs::File>, written: &mut u64| -> std::io::Result<()> {
690 let to = written.div_ceil(align) * align;
691 out.write_all(&vec![0u8; (to - *written) as usize])?;
692 *written = to;
693 Ok(())
694 };
695 pad(&mut out, &mut written)?;
696 let data_start = written;
697 for (i, t) in tensors.iter().enumerate() {
698 debug_assert_eq!(written - data_start, offsets[i], "{} lands where its directory entry says", t.name);
699 let bytes = data(i)?;
700 let n = t.dims.iter().product::<u64>() as usize;
701 if bytes.len() != t.ty.bytes_for(n) {
702 return Err(invalid(format!("{}: {} bytes for {n} {} weights", t.name, bytes.len(), t.ty.name())));
703 }
704 out.write_all(&bytes)?;
705 written += bytes.len() as u64;
706 if i + 1 < tensors.len() {
707 pad(&mut out, &mut written)?;
708 }
709 }
710 out.flush()
711}
712
713fn encoded_len(v: &MetaValue) -> u64 {
715 match v {
716 MetaValue::U8(_) | MetaValue::I8(_) | MetaValue::Bool(_) => 1,
717 MetaValue::U16(_) | MetaValue::I16(_) => 2,
718 MetaValue::U32(_) | MetaValue::I32(_) | MetaValue::F32(_) => 4,
719 MetaValue::U64(_) | MetaValue::I64(_) | MetaValue::F64(_) => 8,
720 MetaValue::Str(b) => 8 + b.len() as u64,
721 MetaValue::Array { items, .. } => 4 + 8 + items.iter().map(encoded_len).sum::<u64>(),
722 }
723}