zsfm_checkpoint/
recast.rs1use std::fs::File;
8use std::io::BufWriter;
9use std::path::Path;
10
11use anyhow::{Context, Result};
12
13use zsfm_gguf::{GGMLType, GGUFWriter};
14
15use crate::cast;
16use crate::read::{load_checkpoint, LoadOptions};
17
18pub fn recast(input: &Path, output: &Path, dtype: GGMLType) -> Result<()> {
19 let ckpt = load_checkpoint(&[input.to_path_buf()], &LoadOptions::default())
20 .with_context(|| format!("load checkpoint {}", input.display()))?;
21 anyhow::ensure!(!ckpt.tensors.is_empty(), "checkpoint {} contains no tensors", input.display());
22
23 let mut writer = GGUFWriter::new();
24 for (k, v) in ckpt.metadata {
25 writer.add_metadata(k, v);
26 }
27
28 let mut fallback_count = 0usize;
29 for t in &ckpt.tensors {
30 let n_elems: u64 = t.shape.iter().product();
31 let innermost = t.shape.last().copied().unwrap_or(1);
32 let dst = if dtype == GGMLType::Q8_0 && (innermost % 32 != 0 || n_elems % 32 != 0) {
35 fallback_count += 1;
36 GGMLType::F32
37 } else {
38 dtype
39 };
40 let data =
41 cast::cast_data(&t.data, t.dtype, dst).with_context(|| format!("tensor {}: cast failed", t.name))?;
42 let gguf_shape: Vec<u64> = t.shape.iter().rev().copied().collect();
43 writer.add_tensor(t.name.clone(), gguf_shape, dst, data);
44 }
45 if fallback_count > 0 {
46 eprintln!("note: {fallback_count} tensor(s) fell back to F32 (too small for Q8_0 blocks)");
47 }
48
49 if let Some(parent) = output.parent() {
50 if !parent.as_os_str().is_empty() {
51 std::fs::create_dir_all(parent)?;
52 }
53 }
54 let out_file = File::create(output).with_context(|| format!("create {}", output.display()))?;
55 writer.write_to(&mut BufWriter::new(out_file))?;
56 Ok(())
57}