1use std::collections::HashMap;
6
7use anyhow::{Context, Result};
8
9use super::cpu_weights::{
10 DenseMlp, K3CpuLayer, K3CpuModel, KdaWeights, MixerW, MlaWeights, MlpW, MoeWeights,
11};
12use super::kda::KdaConfig;
13use super::latent_moe::LatentMoeConfig;
14use super::layer::{K3Graph, K3LayerSpec, MixerKind, MlpKind};
15use super::mla::MlaConfig;
16use crate::config::ModelConfig;
17
18pub fn text_key(prefix: &str, rest: &str) -> String {
19 let p = prefix.trim_end_matches('.');
20 if p.is_empty() {
21 rest.to_string()
22 } else {
23 format!("{p}.{rest}")
24 }
25}
26
27pub(super) fn inventory(
28 c: &ModelConfig,
29 graph: &K3Graph,
30 kda: &KdaConfig,
31 mla: &MlaConfig,
32 moe: &LatentMoeConfig,
33) -> Vec<(String, usize)> {
34 let p = c.weight_prefix.as_str();
35 let h = c.hidden_size;
36 let mut v = vec![
37 (text_key(p, "model.embed_tokens.weight"), c.vocab_size * h),
38 (text_key(p, "model.norm.weight"), h),
39 (text_key(p, "model.output_attn_res_proj.weight"), h),
40 (text_key(p, "model.output_attn_res_norm.weight"), h),
41 ];
42 if !c.tie_word_embeddings {
43 v.push((text_key(p, "lm_head.weight"), c.vocab_size * h));
44 }
45 for spec in &graph.layers {
46 v.extend(layer_inventory(p, spec, c, kda, mla, moe));
47 }
48 v
49}
50
51fn layer_inventory(
52 prefix: &str,
53 spec: &K3LayerSpec,
54 c: &ModelConfig,
55 kda: &KdaConfig,
56 mla: &MlaConfig,
57 moe: &LatentMoeConfig,
58) -> Vec<(String, usize)> {
59 let lp = text_key(prefix, &format!("model.layers.{}", spec.index));
60 let h = c.hidden_size;
61 let mut k = vec![
62 (format!("{lp}.input_layernorm.weight"), h),
63 (format!("{lp}.post_attention_layernorm.weight"), h),
64 (format!("{lp}.mlp_res_norm.weight"), h),
65 (format!("{lp}.mlp_res_proj.weight"), h),
66 (format!("{lp}.self_attention_res_norm.weight"), h),
67 (format!("{lp}.self_attention_res_proj.weight"), h),
68 ];
69 match spec.mixer {
70 MixerKind::Kda => {
71 let q = kda.qkv_dim();
72 k.extend([
73 (format!("{lp}.self_attn.g_proj.weight"), q * h),
74 (format!("{lp}.self_attn.o_proj.weight"), h * q),
75 (format!("{lp}.self_attn.A_log"), kda.heads),
76 (format!("{lp}.self_attn.b_proj.weight"), kda.heads * h),
77 (format!("{lp}.self_attn.dt_bias"), q),
78 (format!("{lp}.self_attn.f_a_proj.weight"), kda.head_dim * h),
79 (format!("{lp}.self_attn.f_b_proj.weight"), q * kda.head_dim),
80 (
81 format!("{lp}.self_attn.k_conv1d.weight"),
82 q * kda.conv_kernel,
83 ),
84 (format!("{lp}.self_attn.k_proj.weight"), q * h),
85 (format!("{lp}.self_attn.o_norm.weight"), kda.head_dim),
86 (
87 format!("{lp}.self_attn.q_conv1d.weight"),
88 q * kda.conv_kernel,
89 ),
90 (format!("{lp}.self_attn.q_proj.weight"), q * h),
91 (
92 format!("{lp}.self_attn.v_conv1d.weight"),
93 q * kda.conv_kernel,
94 ),
95 (format!("{lp}.self_attn.v_proj.weight"), q * h),
96 ]);
97 }
98 MixerKind::Mla => {
99 let qk = mla.heads * mla.qk_head_dim();
100 let dv = mla.heads * mla.v_head_dim;
101 let kv_in = mla.kv_lora_rank + mla.qk_rope_head_dim;
102 let kv_b = mla.heads * (mla.qk_nope_head_dim + mla.v_head_dim);
103 k.extend([
104 (format!("{lp}.self_attn.g_proj.weight"), dv * h),
105 (format!("{lp}.self_attn.o_proj.weight"), h * dv),
106 (
107 format!("{lp}.self_attn.kv_a_layernorm.weight"),
108 mla.kv_lora_rank,
109 ),
110 (
111 format!("{lp}.self_attn.kv_a_proj_with_mqa.weight"),
112 kv_in * h,
113 ),
114 (
115 format!("{lp}.self_attn.kv_b_proj.weight"),
116 kv_b * mla.kv_lora_rank,
117 ),
118 (
119 format!("{lp}.self_attn.q_a_layernorm.weight"),
120 mla.q_lora_rank,
121 ),
122 (
123 format!("{lp}.self_attn.q_a_proj.weight"),
124 mla.q_lora_rank * h,
125 ),
126 (
127 format!("{lp}.self_attn.q_b_proj.weight"),
128 qk * mla.q_lora_rank,
129 ),
130 ]);
131 }
132 }
133 match spec.mlp {
134 MlpKind::Dense => {
135 let inter = c.intermediate_size;
136 k.extend([
137 (format!("{lp}.mlp.down_proj.weight"), h * inter),
138 (format!("{lp}.mlp.gate_proj.weight"), inter * h),
139 (format!("{lp}.mlp.up_proj.weight"), inter * h),
140 ]);
141 }
142 MlpKind::LatentMoe => moe_inventory(&mut k, &lp, c, moe, h),
143 }
144 k
145}
146
147fn moe_inventory(
148 k: &mut Vec<(String, usize)>,
149 lp: &str,
150 c: &ModelConfig,
151 moe: &LatentMoeConfig,
152 h: usize,
153) {
154 let eh = moe.expert_hidden;
155 let lat = moe.latent;
156 let shared = if c.shared_expert_intermediate_size > 0 {
157 c.shared_expert_intermediate_size
158 } else {
159 eh
160 };
161 k.extend([
162 (
163 format!("{lp}.block_sparse_moe.gate.weight"),
164 moe.n_routed * h,
165 ),
166 (
167 format!("{lp}.block_sparse_moe.routed_expert_down_proj.weight"),
168 lat * h,
169 ),
170 (
171 format!("{lp}.block_sparse_moe.routed_expert_norm.weight"),
172 lat,
173 ),
174 (
175 format!("{lp}.block_sparse_moe.routed_expert_up_proj.weight"),
176 h * lat,
177 ),
178 ]);
179 if c.use_routing_bias {
180 k.push((
181 format!("{lp}.block_sparse_moe.gate.e_score_correction_bias"),
182 moe.n_routed,
183 ));
184 }
185 if moe.n_shared > 0 {
186 k.extend([
187 (
188 format!("{lp}.block_sparse_moe.shared_experts.down_proj.weight"),
189 h * shared,
190 ),
191 (
192 format!("{lp}.block_sparse_moe.shared_experts.gate_proj.weight"),
193 shared * h,
194 ),
195 (
196 format!("{lp}.block_sparse_moe.shared_experts.up_proj.weight"),
197 shared * h,
198 ),
199 ]);
200 }
201 for e in 0..moe.n_routed {
202 k.extend([
203 (
204 format!("{lp}.block_sparse_moe.experts.{e}.w1.weight"),
205 eh * lat,
206 ),
207 (
208 format!("{lp}.block_sparse_moe.experts.{e}.w2.weight"),
209 lat * eh,
210 ),
211 (
212 format!("{lp}.block_sparse_moe.experts.{e}.w3.weight"),
213 eh * lat,
214 ),
215 ]);
216 }
217}
218
219fn pull(got: &mut HashMap<String, Vec<f32>>, prefix: &str, rest: &str) -> Result<Vec<f32>> {
220 let k = text_key(prefix, rest);
221 got.remove(&k)
222 .with_context(|| format!("internal: missing {k}"))
223}
224
225pub(super) fn assemble(
226 c: &ModelConfig,
227 graph: K3Graph,
228 kda: KdaConfig,
229 mla: MlaConfig,
230 moe: LatentMoeConfig,
231 got: &mut HashMap<String, Vec<f32>>,
232) -> Result<K3CpuModel> {
233 let p = c.weight_prefix.as_str();
234 let mut layers = Vec::with_capacity(graph.layers.len());
235 for spec in &graph.layers {
236 layers.push(assemble_layer(p, spec, c, &moe, got)?);
237 }
238 let lm_head = match pull(got, p, "lm_head.weight") {
239 Ok(w) => w,
240 Err(_) if c.tie_word_embeddings => pull(got, p, "model.embed_tokens.weight")?,
241 Err(e) => return Err(e),
242 };
243 let embed = if c.tie_word_embeddings {
244 lm_head.clone()
245 } else {
246 pull(got, p, "model.embed_tokens.weight")?
247 };
248 Ok(K3CpuModel {
249 graph,
250 kda,
251 mla,
252 moe,
253 dense_intermediate: c.intermediate_size,
254 eps: c.rms_norm_eps as f32,
255 rope_theta: c.rope_theta as f32,
256 vocab: c.vocab_size,
257 embed,
258 lm_head,
259 final_norm: pull(got, p, "model.norm.weight")?,
260 output_res_proj: pull(got, p, "model.output_attn_res_proj.weight")?,
261 output_res_norm: pull(got, p, "model.output_attn_res_norm.weight")?,
262 layers,
263 })
264}
265
266pub fn assemble_layer(
267 prefix: &str,
268 spec: &K3LayerSpec,
269 c: &ModelConfig,
270 moe: &LatentMoeConfig,
271 got: &mut HashMap<String, Vec<f32>>,
272) -> Result<K3CpuLayer> {
273 let lp = format!("model.layers.{}", spec.index);
274 let mixer = match spec.mixer {
275 MixerKind::Kda => assemble_kda(prefix, &lp, got)?,
276 MixerKind::Mla => assemble_mla(prefix, &lp, got)?,
277 };
278 let mlp = match spec.mlp {
279 MlpKind::Dense => MlpW::Dense(DenseMlp {
280 gate: pull(got, prefix, &format!("{lp}.mlp.gate_proj.weight"))?,
281 up: pull(got, prefix, &format!("{lp}.mlp.up_proj.weight"))?,
282 down: pull(got, prefix, &format!("{lp}.mlp.down_proj.weight"))?,
283 }),
284 MlpKind::LatentMoe => assemble_moe(prefix, &lp, c, moe, got)?,
285 };
286 Ok(K3CpuLayer {
287 spec: *spec,
288 input_norm: pull(got, prefix, &format!("{lp}.input_layernorm.weight"))?,
289 post_norm: pull(
290 got,
291 prefix,
292 &format!("{lp}.post_attention_layernorm.weight"),
293 )?,
294 attn_res_proj: pull(got, prefix, &format!("{lp}.self_attention_res_proj.weight"))?,
295 attn_res_norm: pull(got, prefix, &format!("{lp}.self_attention_res_norm.weight"))?,
296 mlp_res_proj: pull(got, prefix, &format!("{lp}.mlp_res_proj.weight"))?,
297 mlp_res_norm: pull(got, prefix, &format!("{lp}.mlp_res_norm.weight"))?,
298 mixer,
299 mlp,
300 })
301}
302
303fn assemble_kda(prefix: &str, lp: &str, got: &mut HashMap<String, Vec<f32>>) -> Result<MixerW> {
304 let q = pull(got, prefix, &format!("{lp}.self_attn.q_conv1d.weight"))?;
305 let k = pull(got, prefix, &format!("{lp}.self_attn.k_conv1d.weight"))?;
306 let v = pull(got, prefix, &format!("{lp}.self_attn.v_conv1d.weight"))?;
307 let mut conv = q;
308 conv.extend(k);
309 conv.extend(v);
310 Ok(MixerW::Kda(KdaWeights {
311 q_proj: pull(got, prefix, &format!("{lp}.self_attn.q_proj.weight"))?,
312 k_proj: pull(got, prefix, &format!("{lp}.self_attn.k_proj.weight"))?,
313 v_proj: pull(got, prefix, &format!("{lp}.self_attn.v_proj.weight"))?,
314 conv,
315 f_a: pull(got, prefix, &format!("{lp}.self_attn.f_a_proj.weight"))?,
316 f_b: pull(got, prefix, &format!("{lp}.self_attn.f_b_proj.weight"))?,
317 dt_bias: pull(got, prefix, &format!("{lp}.self_attn.dt_bias"))?,
318 a_log: pull(got, prefix, &format!("{lp}.self_attn.A_log"))?,
319 b_proj: pull(got, prefix, &format!("{lp}.self_attn.b_proj.weight"))?,
320 g_proj: pull(got, prefix, &format!("{lp}.self_attn.g_proj.weight"))?,
321 o_norm: pull(got, prefix, &format!("{lp}.self_attn.o_norm.weight"))?,
322 o_proj: pull(got, prefix, &format!("{lp}.self_attn.o_proj.weight"))?,
323 }))
324}
325
326fn assemble_mla(prefix: &str, lp: &str, got: &mut HashMap<String, Vec<f32>>) -> Result<MixerW> {
327 Ok(MixerW::Mla(MlaWeights {
328 q_a: pull(got, prefix, &format!("{lp}.self_attn.q_a_proj.weight"))?,
329 q_a_ln: pull(got, prefix, &format!("{lp}.self_attn.q_a_layernorm.weight"))?,
330 q_b: pull(got, prefix, &format!("{lp}.self_attn.q_b_proj.weight"))?,
331 kv_a: pull(
332 got,
333 prefix,
334 &format!("{lp}.self_attn.kv_a_proj_with_mqa.weight"),
335 )?,
336 kv_a_ln: pull(
337 got,
338 prefix,
339 &format!("{lp}.self_attn.kv_a_layernorm.weight"),
340 )?,
341 kv_b: pull(got, prefix, &format!("{lp}.self_attn.kv_b_proj.weight"))?,
342 g_proj: pull(got, prefix, &format!("{lp}.self_attn.g_proj.weight"))?,
343 o_proj: pull(got, prefix, &format!("{lp}.self_attn.o_proj.weight"))?,
344 }))
345}
346
347fn assemble_moe(
348 prefix: &str,
349 lp: &str,
350 c: &ModelConfig,
351 moe: &LatentMoeConfig,
352 got: &mut HashMap<String, Vec<f32>>,
353) -> Result<MlpW> {
354 let bias = if c.use_routing_bias {
355 pull(
356 got,
357 prefix,
358 &format!("{lp}.block_sparse_moe.gate.e_score_correction_bias"),
359 )?
360 } else {
361 vec![0.0; moe.n_routed]
362 };
363 let shared = if moe.n_shared > 0 {
364 Some(DenseMlp {
365 gate: pull(
366 got,
367 prefix,
368 &format!("{lp}.block_sparse_moe.shared_experts.gate_proj.weight"),
369 )?,
370 up: pull(
371 got,
372 prefix,
373 &format!("{lp}.block_sparse_moe.shared_experts.up_proj.weight"),
374 )?,
375 down: pull(
376 got,
377 prefix,
378 &format!("{lp}.block_sparse_moe.shared_experts.down_proj.weight"),
379 )?,
380 })
381 } else {
382 None
383 };
384 let mut experts = Vec::with_capacity(moe.n_routed);
385 for e in 0..moe.n_routed {
386 let w1k = format!("{lp}.block_sparse_moe.experts.{e}.w1.weight");
387 if !got.contains_key(&text_key(prefix, &w1k)) {
388 experts.push((Vec::new(), Vec::new(), Vec::new()));
390 continue;
391 }
392 experts.push((
393 pull(got, prefix, &w1k)?,
394 pull(
395 got,
396 prefix,
397 &format!("{lp}.block_sparse_moe.experts.{e}.w2.weight"),
398 )?,
399 pull(
400 got,
401 prefix,
402 &format!("{lp}.block_sparse_moe.experts.{e}.w3.weight"),
403 )?,
404 ));
405 }
406 Ok(MlpW::Moe(MoeWeights {
407 down: pull(
408 got,
409 prefix,
410 &format!("{lp}.block_sparse_moe.routed_expert_down_proj.weight"),
411 )?,
412 up: pull(
413 got,
414 prefix,
415 &format!("{lp}.block_sparse_moe.routed_expert_up_proj.weight"),
416 )?,
417 norm: pull(
418 got,
419 prefix,
420 &format!("{lp}.block_sparse_moe.routed_expert_norm.weight"),
421 )?,
422 router: pull(got, prefix, &format!("{lp}.block_sparse_moe.gate.weight"))?,
423 bias,
424 experts,
425 shared,
426 }))
427}