ferrum_quantization/gguf/
names.rs1pub fn ferrum_to_gguf(name: &str) -> Option<String> {
38 if let Some(out) = map_top_level(name) {
40 return Some(out);
41 }
42
43 let rest = name.strip_prefix("model.layers.")?;
45 let (idx_str, after_idx) = rest.split_once('.')?;
46 let idx: usize = idx_str.parse().ok()?;
47 let mapped = map_layer_scoped(after_idx)?;
48 Some(format!("blk.{idx}.{mapped}"))
49}
50
51fn map_top_level(name: &str) -> Option<String> {
52 let mapped = match name {
53 "model.embed_tokens" => "token_embd",
54 "model.embed_tokens.weight" => "token_embd.weight",
55 "model.norm" => "output_norm",
56 "model.norm.weight" => "output_norm.weight",
57 "lm_head" => "output",
58 "lm_head.weight" => "output.weight",
59 _ => return None,
60 };
61 Some(mapped.to_string())
62}
63
64fn map_layer_scoped(rest: &str) -> Option<String> {
65 let (stem, suffix) = if let Some(s) = rest.strip_suffix(".weight") {
67 (s, ".weight")
68 } else if let Some(s) = rest.strip_suffix(".bias") {
69 (s, ".bias")
70 } else {
71 (rest, "")
72 };
73
74 let mapped_stem = match stem {
75 "input_layernorm" => "attn_norm",
77 "post_attention_layernorm" => "ffn_norm",
78 "self_attn.q_proj" => "attn_q",
80 "self_attn.k_proj" => "attn_k",
81 "self_attn.v_proj" => "attn_v",
82 "self_attn.o_proj" => "attn_output",
83 "self_attn.q_norm" => "attn_q_norm",
85 "self_attn.k_norm" => "attn_k_norm",
86 "mlp.gate_proj" => "ffn_gate",
88 "mlp.up_proj" => "ffn_up",
89 "mlp.down_proj" => "ffn_down",
90 "mlp.router" => "ffn_gate_inp",
98 "mlp.gate_exps" => "ffn_gate_exps",
99 "mlp.up_exps" => "ffn_up_exps",
100 "mlp.down_exps" => "ffn_down_exps",
101 _ => return None,
102 };
103
104 Some(format!("{mapped_stem}{suffix}"))
105}
106
107pub fn qkv_split_parts(layer_prefix: &str) -> [String; 3] {
110 [
111 format!("{layer_prefix}self_attn.q_proj"),
112 format!("{layer_prefix}self_attn.k_proj"),
113 format!("{layer_prefix}self_attn.v_proj"),
114 ]
115}
116
117pub fn gate_up_split_parts(layer_prefix: &str) -> [String; 2] {
120 [
121 format!("{layer_prefix}mlp.gate_proj"),
122 format!("{layer_prefix}mlp.up_proj"),
123 ]
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn maps_top_level_tensors() {
132 assert_eq!(
133 ferrum_to_gguf("model.embed_tokens.weight"),
134 Some("token_embd.weight".into())
135 );
136 assert_eq!(
137 ferrum_to_gguf("model.embed_tokens"),
138 Some("token_embd".into())
139 );
140 assert_eq!(
141 ferrum_to_gguf("model.norm.weight"),
142 Some("output_norm.weight".into())
143 );
144 assert_eq!(ferrum_to_gguf("lm_head"), Some("output".into()));
145 assert_eq!(
146 ferrum_to_gguf("lm_head.weight"),
147 Some("output.weight".into())
148 );
149 }
150
151 #[test]
152 fn maps_layer_attention_weights() {
153 assert_eq!(
154 ferrum_to_gguf("model.layers.0.self_attn.q_proj.weight"),
155 Some("blk.0.attn_q.weight".into())
156 );
157 assert_eq!(
158 ferrum_to_gguf("model.layers.27.self_attn.k_proj.weight"),
159 Some("blk.27.attn_k.weight".into())
160 );
161 assert_eq!(
162 ferrum_to_gguf("model.layers.5.self_attn.v_proj.weight"),
163 Some("blk.5.attn_v.weight".into())
164 );
165 assert_eq!(
166 ferrum_to_gguf("model.layers.0.self_attn.o_proj.weight"),
167 Some("blk.0.attn_output.weight".into())
168 );
169 assert_eq!(
171 ferrum_to_gguf("model.layers.0.self_attn.o_proj"),
172 Some("blk.0.attn_output".into())
173 );
174 }
175
176 #[test]
177 fn maps_qwen3_qk_norm() {
178 assert_eq!(
179 ferrum_to_gguf("model.layers.0.self_attn.q_norm.weight"),
180 Some("blk.0.attn_q_norm.weight".into())
181 );
182 assert_eq!(
183 ferrum_to_gguf("model.layers.0.self_attn.k_norm.weight"),
184 Some("blk.0.attn_k_norm.weight".into())
185 );
186 }
187
188 #[test]
189 fn maps_attention_bias() {
190 assert_eq!(
191 ferrum_to_gguf("model.layers.0.self_attn.q_proj.bias"),
192 Some("blk.0.attn_q.bias".into())
193 );
194 }
195
196 #[test]
197 fn maps_layer_norms() {
198 assert_eq!(
199 ferrum_to_gguf("model.layers.0.input_layernorm.weight"),
200 Some("blk.0.attn_norm.weight".into())
201 );
202 assert_eq!(
203 ferrum_to_gguf("model.layers.0.post_attention_layernorm.weight"),
204 Some("blk.0.ffn_norm.weight".into())
205 );
206 }
207
208 #[test]
209 fn maps_mlp_projections() {
210 assert_eq!(
211 ferrum_to_gguf("model.layers.0.mlp.gate_proj.weight"),
212 Some("blk.0.ffn_gate.weight".into())
213 );
214 assert_eq!(
215 ferrum_to_gguf("model.layers.0.mlp.up_proj.weight"),
216 Some("blk.0.ffn_up.weight".into())
217 );
218 assert_eq!(
219 ferrum_to_gguf("model.layers.0.mlp.down_proj.weight"),
220 Some("blk.0.ffn_down.weight".into())
221 );
222 }
223
224 #[test]
225 fn maps_moe_router_and_stacked_experts() {
226 assert_eq!(
228 ferrum_to_gguf("model.layers.0.mlp.router.weight"),
229 Some("blk.0.ffn_gate_inp.weight".into())
230 );
231 assert_eq!(
233 ferrum_to_gguf("model.layers.0.mlp.gate_exps.weight"),
234 Some("blk.0.ffn_gate_exps.weight".into())
235 );
236 assert_eq!(
237 ferrum_to_gguf("model.layers.27.mlp.up_exps.weight"),
238 Some("blk.27.ffn_up_exps.weight".into())
239 );
240 assert_eq!(
241 ferrum_to_gguf("model.layers.0.mlp.down_exps.weight"),
242 Some("blk.0.ffn_down_exps.weight".into())
243 );
244 assert_eq!(
246 ferrum_to_gguf("model.layers.0.mlp.router"),
247 Some("blk.0.ffn_gate_inp".into())
248 );
249 }
250
251 #[test]
252 fn rejects_unknown_names() {
253 assert_eq!(ferrum_to_gguf("totally_made_up"), None);
254 assert_eq!(ferrum_to_gguf("model.layers.0.unknown_part.weight"), None);
255 assert_eq!(
256 ferrum_to_gguf("model.layers.bad_idx.input_layernorm.weight"),
257 None
258 );
259 assert_eq!(
262 ferrum_to_gguf("model.layers.0.mlp.experts.0.gate_proj.weight"),
263 None
264 );
265 }
266
267 #[test]
268 fn split_parts_helpers() {
269 assert_eq!(
270 qkv_split_parts("model.layers.3."),
271 [
272 "model.layers.3.self_attn.q_proj".to_string(),
273 "model.layers.3.self_attn.k_proj".into(),
274 "model.layers.3.self_attn.v_proj".into(),
275 ]
276 );
277 assert_eq!(
278 gate_up_split_parts("model.layers.3."),
279 [
280 "model.layers.3.mlp.gate_proj".to_string(),
281 "model.layers.3.mlp.up_proj".into(),
282 ]
283 );
284 }
285}