zsfm_lag_llama/
convert.rs1use std::fs::File;
2use std::io::BufWriter;
3use std::path::Path;
4
5use anyhow::Context;
6use indicatif::{ProgressBar, ProgressStyle};
7
8use zsfm_checkpoint::cast::decode_to_f32;
9use zsfm_gguf::{GGMLType, GGUFMetaValue, GGUFWriter};
10
11use crate::config::LagLlamaConfig;
12use crate::tensor_map::map_tensor_name;
13
14pub struct ConvertOptions {
15 pub output_dtype: GGMLType,
16}
17
18pub fn convert(
20 ckpt_path: &Path,
21 config: &LagLlamaConfig,
22 opts: &ConvertOptions,
23 output_path: &Path,
24) -> anyhow::Result<()> {
25 let mut writer = GGUFWriter::new();
26 write_metadata(&mut writer, config);
27
28 println!("Reading checkpoint {} …", ckpt_path.display());
29 let ckpt = zsfm_checkpoint::load_checkpoint(
31 &[ckpt_path.to_path_buf()],
32 &zsfm_checkpoint::LoadOptions {
33 pickle_key: Some("state_dict".to_string()),
34 ..Default::default()
35 },
36 )
37 .with_context(|| format!("read checkpoint {}", ckpt_path.display()))?;
38
39 let total = ckpt.tensors.len();
40 let pb = ProgressBar::new(total as u64);
41 pb.set_style(
42 ProgressStyle::with_template(
43 "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} {msg}",
44 )
45 .unwrap()
46 .progress_chars("=>-"),
47 );
48
49 let mut mapped = 0usize;
50 let mut skipped: Vec<String> = Vec::new();
51 let mut fallback_count = 0usize;
52
53 for raw in &ckpt.tensors {
54 let hf_name = raw.name.clone();
55 pb.set_message(hf_name.clone());
56
57 let gguf_name = match map_tensor_name(&hf_name) {
58 Some(n) => n,
59 None => {
60 skipped.push(hf_name.clone());
61 pb.inc(1);
62 continue;
63 }
64 };
65
66 let py_shape: Vec<usize> = raw.shape.iter().map(|&d| d as usize).collect();
68 let n_elems: usize = py_shape.iter().product();
69 let innermost = py_shape.last().copied().unwrap_or(1);
70
71 let f32_vals: Vec<f32> = decode_to_f32(&raw.data, raw.dtype)
73 .with_context(|| format!("tensor {hf_name}: decode failed"))?;
74
75 let (dst_dtype, gguf_shape, tensor_data) =
76 if opts.output_dtype == GGMLType::Q8_0 && (innermost % 32 != 0 || n_elems % 32 != 0) {
77 fallback_count += 1;
78 let gs = py_shape.iter().rev().map(|&d| d as u64).collect();
79 (GGMLType::F32, gs, f32_to_bytes(&f32_vals))
80 } else {
81 let (dst, data) = apply_dtype(&f32_vals, opts.output_dtype)?;
82 let gs = py_shape.iter().rev().map(|&d| d as u64).collect();
83 (dst, gs, data)
84 };
85
86 writer.add_tensor(gguf_name, gguf_shape, dst_dtype, tensor_data);
87 mapped += 1;
88 pb.inc(1);
89 }
90
91 pb.finish_with_message("tensors processed");
92
93 if !skipped.is_empty() {
94 eprintln!(
95 "\nWarning: {} tensor(s) skipped (unrecognised names):",
96 skipped.len()
97 );
98 for name in &skipped {
99 eprintln!(" {name}");
100 }
101 }
102 if fallback_count > 0 {
103 eprintln!("\nNote: {fallback_count} tensor(s) fell back to F32.");
104 }
105
106 println!("Writing {mapped} tensors to {} …", output_path.display());
107 let out_file =
108 File::create(output_path).with_context(|| format!("create {}", output_path.display()))?;
109 let mut buf_writer = BufWriter::new(out_file);
110 writer.write_to(&mut buf_writer)?;
111 println!("Done.");
112 Ok(())
113}
114
115fn write_metadata(writer: &mut GGUFWriter, config: &LagLlamaConfig) {
116 writer.add_metadata(
117 "general.architecture",
118 GGUFMetaValue::String("lag_llama".into()),
119 );
120 writer.add_metadata("general.name", GGUFMetaValue::String("Lag-Llama".into()));
121 writer.add_metadata(
122 "lag_llama.n_layer",
123 GGUFMetaValue::Uint32(config.n_layer as u32),
124 );
125 writer.add_metadata(
126 "lag_llama.n_head",
127 GGUFMetaValue::Uint32(config.n_head as u32),
128 );
129 writer.add_metadata(
130 "lag_llama.n_embd_per_head",
131 GGUFMetaValue::Uint32(config.n_embd_per_head as u32),
132 );
133 writer.add_metadata(
134 "lag_llama.n_embd",
135 GGUFMetaValue::Uint32(config.n_embd as u32),
136 );
137 writer.add_metadata(
138 "lag_llama.mlp_hidden",
139 GGUFMetaValue::Uint32(config.mlp_hidden as u32),
140 );
141 writer.add_metadata(
142 "lag_llama.feature_size",
143 GGUFMetaValue::Uint32(config.feature_size as u32),
144 );
145 writer.add_metadata(
146 "lag_llama.n_lags",
147 GGUFMetaValue::Uint32(config.n_lags as u32),
148 );
149 writer.add_metadata(
150 "lag_llama.n_time_feat",
151 GGUFMetaValue::Uint32(config.n_time_feat as u32),
152 );
153 writer.add_metadata(
154 "lag_llama.max_context_length",
155 GGUFMetaValue::Uint32(config.max_context_length as u32),
156 );
157}
158
159fn f32_to_bytes(vals: &[f32]) -> Vec<u8> {
160 vals.iter().flat_map(|v| v.to_le_bytes()).collect()
161}
162
163fn apply_dtype(f32_vals: &[f32], dst: GGMLType) -> anyhow::Result<(GGMLType, Vec<u8>)> {
164 match dst {
165 GGMLType::F32 => Ok((GGMLType::F32, f32_to_bytes(f32_vals))),
166 GGMLType::F16 => {
167 let bytes: Vec<u8> = f32_vals
168 .iter()
169 .flat_map(|&v| f32_to_f16_bits(v).to_le_bytes())
170 .collect();
171 Ok((GGMLType::F16, bytes))
172 }
173 GGMLType::BF16 => {
174 let bytes: Vec<u8> = f32_vals
175 .iter()
176 .flat_map(|&v| ((v.to_bits() >> 16) as u16).to_le_bytes())
177 .collect();
178 Ok((GGMLType::BF16, bytes))
179 }
180 GGMLType::Q8_0 => Ok((GGMLType::Q8_0, quantize_q8_0(f32_vals)?)),
181 }
182}
183
184fn quantize_q8_0(values: &[f32]) -> anyhow::Result<Vec<u8>> {
185 const BLOCK: usize = 32;
186 if values.len() % BLOCK != 0 {
187 anyhow::bail!(
188 "Q8_0 requires count divisible by {BLOCK}, got {}",
189 values.len()
190 );
191 }
192 let n_blocks = values.len() / BLOCK;
193 let mut out = vec![0u8; n_blocks * 34];
194 for b in 0..n_blocks {
195 let blk = &values[b * BLOCK..(b + 1) * BLOCK];
196 let amax = blk.iter().copied().map(f32::abs).fold(0.0f32, f32::max);
197 let d = if amax == 0.0 { 0.0f32 } else { amax / 127.0 };
198 let d_inv = if d == 0.0 { 0.0f32 } else { 1.0 / d };
199 let base = b * 34;
200 out[base..base + 2].copy_from_slice(&f32_to_f16_bits(d).to_le_bytes());
201 for i in 0..BLOCK {
202 out[base + 2 + i] = (blk[i] * d_inv).round().clamp(-127.0, 127.0) as i8 as u8;
203 }
204 }
205 Ok(out)
206}
207
208fn f32_to_f16_bits(v: f32) -> u16 {
209 let bits = v.to_bits();
210 let sign = ((bits >> 16) & 0x8000) as u16;
211 let exp = ((bits >> 23) & 0xFF) as i32;
212 let mantissa = bits & 0x007F_FFFF;
213 if exp == 0xFF {
214 return sign | 0x7C00 | if mantissa != 0 { 0x0200 } else { 0 };
215 }
216 let new_exp = exp - 127 + 15;
217 if new_exp >= 31 {
218 return sign | 0x7C00;
219 }
220 if new_exp <= 0 {
221 if new_exp < -10 {
222 return sign;
223 }
224 let m = (mantissa | 0x0080_0000) >> (1 - new_exp);
225 return sign | (m >> 13) as u16;
226 }
227 sign | ((new_exp as u16) << 10) | (mantissa >> 13) as u16
228}