1mod ngram;
24mod prep;
25
26use anyhow::{Context, Result};
27use metrale_cache::kv_cache::KvCacheDtype;
28use metrale_config::ModelConfig;
29use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend};
30use metrale_model_weights::weights::WeightStore;
31
32use crate::mistral_loader::loader_impl::{
33 ctx as mctx, phase_block_diag, phase_per_head, phase_qk_absorbed,
34};
35use crate::weight_loader::ModelWeightLoader;
36use metrale_model_layers::layer::TransformerLayer;
37use metrale_model_layers::layers::Qwen3AttentionLayer;
38use metrale_model_layers::layers::VisionTower;
39use metrale_model_layers::layers::qwen3_attention::MlaWeights;
40use metrale_model_layers::weight_map::{
41 AttentionWeights, DenseWeight, MtpWeights, QuantizedWeight, dense, quantize_to_nvfp4,
42};
43
44pub struct LongcatWeightLoader;
45
46const CARRY_TOKENS: usize = 8192;
49
50impl ModelWeightLoader for LongcatWeightLoader {
51 fn supports_tp(&self) -> bool {
52 false
54 }
55
56 fn load_layers(
57 &self,
58 store: &WeightStore,
59 config: &ModelConfig,
60 gpu: &dyn GpuBackend,
61 layer_kv_dtypes: &[KvCacheDtype],
62 ) -> Result<Vec<Box<dyn TransformerLayer>>> {
63 anyhow::ensure!(
64 config.num_hidden_layers.is_multiple_of(2),
65 "longcat: num_hidden_layers ({}) must be even — each checkpoint \
66 layer is TWO engine sublayers",
67 config.num_hidden_layers
68 );
69 let ckpt_layers = config.num_hidden_layers / 2;
70 let h = config.hidden_size;
71 let nope = config.qk_nope_head_dim;
72 let rope = config.qk_rope_head_dim;
73 let q_lora = config.q_lora_rank;
74 let kv_lora = config.kv_lora_rank;
75 let n_heads = config.num_attention_heads;
76 let scale_q = (h as f32 / q_lora as f32).sqrt();
79 let scale_kv = (h as f32 / kv_lora as f32).sqrt();
80 let true_qk_hd = nope + rope;
83 let padded_hd = 256usize;
84 let padded_nope = padded_hd - rope;
85 let attn_scale = 1.0f32 / (true_qk_hd as f32).sqrt();
87
88 tracing::info!(
89 "LongCat: {ckpt_layers} checkpoint layers → {} engine sublayers \
90 (MLA q_lora={q_lora} kv_lora={kv_lora} nope={nope} rope={rope}; \
91 rope de-interleave + q/kv LoRA scale folded at load: \
92 q×{scale_q:.4}, kv-norm×{scale_kv:.4}); {} routed + {} zero experts",
93 config.num_hidden_layers,
94 config.num_experts,
95 config.zero_expert_num,
96 );
97
98 let disable_nvfp4_mla = std::env::var("METRALE_NVFP4_MLA")
101 .map(|v| {
102 let v = v.trim().to_ascii_lowercase();
103 matches!(v.as_str(), "0" | "false" | "no" | "off")
104 })
105 .unwrap_or(false);
106 if disable_nvfp4_mla {
107 tracing::info!("LongCat: METRALE_NVFP4_MLA=0 — MLA projections stay BF16");
108 }
109 if super::longcat::ffn::bf16_dense_ffn() {
110 tracing::info!(
111 "LongCat: METRALE_LONGCAT_BF16_FFN=1 — per-sublayer dense FFN stays BF16"
112 );
113 }
114 let absmax_k = gpu.kernel("quantize_nvfp4", "nvfp4_global_absmax")?;
115 let quantize_k = gpu.kernel("quantize_nvfp4", "quantize_bf16_to_nvfp4")?;
116 let stream = gpu.default_stream();
117 let mut yarn_shared = DevicePtr::NULL;
118 let mut layers: Vec<Box<dyn TransformerLayer>> =
119 Vec::with_capacity(config.num_hidden_layers);
120
121 for l in 0..ckpt_layers {
122 let lp = format!("model.layers.{l}");
123 let carry = gpu.alloc(CARRY_TOKENS * h * 2)?;
126
127 for s in 0..2usize {
128 let ap = format!("{lp}.self_attn.{s}");
129 let global_idx = l * 2 + s;
130
131 let wq_a = dense(store, &format!("{ap}.q_a_proj.weight"))?;
133 let wq_b = prep::prep_q_b(
134 store,
135 &format!("{ap}.q_b_proj.weight"),
136 n_heads,
137 nope,
138 rope,
139 q_lora,
140 scale_q,
141 padded_hd,
142 gpu,
143 )?;
144 let q_a_norm = dense(store, &format!("{ap}.q_a_layernorm.weight"))?;
145 let wkv_a = prep::prep_kv_a(
146 store,
147 &format!("{ap}.kv_a_proj_with_mqa.weight"),
148 kv_lora,
149 rope,
150 h,
151 gpu,
152 )?;
153 let kv_a_norm = prep::prep_kv_a_norm(
154 store,
155 &format!("{ap}.kv_a_layernorm.weight"),
156 kv_lora,
157 scale_kv,
158 gpu,
159 )?;
160 let wkv_b = prep::prep_kv_b(
161 store,
162 &format!("{ap}.kv_b_proj.weight"),
163 n_heads,
164 nope,
165 config.v_head_dim,
166 rope,
167 kv_lora,
168 padded_hd,
169 gpu,
170 )?;
171 let wo = prep::prep_o_proj(
172 store,
173 &format!("{ap}.o_proj.weight"),
174 h,
175 n_heads,
176 config.v_head_dim,
177 padded_hd,
178 gpu,
179 )?;
180
181 let mut c = mctx::MistralLayerCtx::new(
184 store, config, gpu, absmax_k, quantize_k, stream, global_idx,
185 );
186 c.hd = padded_hd;
188 c.nope = padded_nope;
189 c.v_dim = padded_hd;
190 c.wq_a_dense = Some(wq_a);
191 c.wq_b = Some(wq_b);
192 c.q_a_norm = Some(q_a_norm);
193 c.wkv_a_dense = Some(wkv_a);
194 c.wkv_a_rope_dense = Some(DenseWeight {
195 weight: wkv_a.weight.offset(kv_lora * h * 2),
196 });
197 c.wkv_b = Some(wkv_b);
198 c.kv_a_norm = Some(kv_a_norm);
199 c.wq_a_nvfp4 = Some(quantize_to_nvfp4(
200 &wq_a, q_lora, h, gpu, absmax_k, quantize_k, stream,
201 )?);
202 c.wq_b_nvfp4 = Some(quantize_to_nvfp4(
203 &wq_b,
204 n_heads * padded_hd,
205 q_lora,
206 gpu,
207 absmax_k,
208 quantize_k,
209 stream,
210 )?);
211 c.wkv_a_nvfp4 = Some(quantize_to_nvfp4(
212 &wkv_a,
213 kv_lora + rope,
214 h,
215 gpu,
216 absmax_k,
217 quantize_k,
218 stream,
219 )?);
220 phase_per_head::build_per_head_views(&mut c)?;
221 phase_qk_absorbed::build_w_qk_absorbed(&mut c)?;
222 phase_block_diag::build_block_diagonals(&mut c)?;
223 let o_nvfp4 = quantize_to_nvfp4(
224 &wo,
225 h,
226 n_heads * padded_hd,
227 gpu,
228 absmax_k,
229 quantize_k,
230 stream,
231 )?;
232 let yarn = mctx::ensure_yarn_inv_freq(&mut yarn_shared, config, rope, gpu)?;
233
234 let null = DenseWeight {
235 weight: DevicePtr::NULL,
236 };
237 let mla = MlaWeights {
238 wq_a,
239 wq_a_fp8: None,
240 wq_a_nvfp4: if disable_nvfp4_mla {
241 None
242 } else {
243 c.wq_a_nvfp4
244 },
245 wq_b,
246 wq_b_fp8: None,
247 wq_b_nvfp4: if disable_nvfp4_mla {
248 None
249 } else {
250 c.wq_b_nvfp4
251 },
252 q_a_norm,
253 wkv_a,
254 wkv_a_nvfp4: if disable_nvfp4_mla {
255 None
256 } else {
257 c.wkv_a_nvfp4
258 },
259 wkv_b,
260 kv_a_norm,
261 wkv_a_rope: c.wkv_a_rope_dense.expect("set above"),
262 wkv_a_merged: DenseWeight {
263 weight: wkv_a.weight,
264 },
265 wo,
266 wo_nvfp4: if disable_nvfp4_mla {
267 None
268 } else {
269 Some(o_nvfp4)
270 },
271 wo_a: null,
272 wo_a_nvfp4: None,
273 wo_b: null,
274 wo_b_nvfp4: None,
275 wo_b_fp8: None,
276 wo_a_fp8: None,
277 wkv_a_fp8: None,
278 wq_b_rope: c.wq_b_rope.context("longcat: wq_b_rope")?,
279 w_uk_t: c.w_uk_t.context("longcat: w_uk_t")?,
280 w_uv: c.w_uv.context("longcat: w_uv")?,
281 w_qk_absorbed: c.w_qk_absorbed.context("longcat: w_qk_absorbed")?,
282 w_uk_block_diag: c.w_uk_block_diag.context("longcat: w_uk_bd")?,
283 w_uv_block_diag: c.w_uv_block_diag.context("longcat: w_uv_bd")?,
284 yarn_inv_freq: yarn,
285 main_inv_freq: yarn,
286 q_lora_rank: q_lora,
287 kv_lora_rank: kv_lora,
288 o_lora_rank: 0,
289 nope: padded_nope,
290 rope,
291 v_dim: padded_hd,
292 compressor: None,
293 attn_sink: DevicePtr::NULL,
294 };
295
296 let o_dummy = QuantizedWeight {
299 weight: DevicePtr::NULL,
300 weight_scale: DevicePtr::NULL,
301 weight_scale_2: 0.0,
302 input_scale: DevicePtr::NULL,
303 weight_scale_2_vec: DevicePtr::NULL,
304 act: metrale_config::Nvfp4Act::Unstamped,
305 };
306 let attn = AttentionWeights {
307 q_proj: null,
308 k_proj: null,
309 v_proj: null,
310 o_proj: o_dummy,
311 q_norm: null,
312 k_norm: null,
313 q_norm_full: None,
314 k_norm_full: None,
315 k_scale: 1.0,
316 v_scale: 1.0,
317 };
318
319 let ffn = build_dense_ffn(store, &format!("{lp}.mlps.{s}"), config, gpu)?;
321 let input_norm = dense(store, &format!("{lp}.input_layernorm.{s}.weight"))?;
322 let post_norm = dense(store, &format!("{lp}.post_attention_layernorm.{s}.weight"))?;
323 let kv_dtype = layer_kv_dtypes
324 .get(global_idx)
325 .copied()
326 .unwrap_or(KvCacheDtype::Bf16);
327
328 let mut layer = Qwen3AttentionLayer::new_ungated(
329 input_norm, attn, post_norm, ffn, global_idx, None, None, None, gpu, kv_dtype,
330 0, config,
331 )?;
332 layer.set_mla_weights(mla);
333 layer.set_attn_scale_override(attn_scale);
336 layer.set_dimension_overrides(padded_hd, n_heads, n_heads);
339
340 if s == 0 {
341 let moe = build_shortcut_moe(store, &lp, config, gpu)?;
344 layer.set_shortcut_moe(moe, carry, CARRY_TOKENS);
345 } else {
346 layer.set_shortcut_carry_in(carry, CARRY_TOKENS);
347 }
348 layers.push(Box::new(layer));
349 }
350
351 if (l + 1) % 4 == 0 || l == ckpt_layers - 1 {
352 let free = gpu.free_memory().unwrap_or(0);
353 tracing::info!(
354 "LongCat L{}/{ckpt_layers} — {:.1} GB free",
355 l + 1,
356 free as f64 / 1e9
357 );
358 }
359 }
360 Ok(layers)
361 }
362
363 fn load_embedding(
364 &self,
365 store: &WeightStore,
366 _config: &ModelConfig,
367 _gpu: &dyn GpuBackend,
368 ) -> Result<DenseWeight> {
369 dense(store, "model.embed_tokens.weight").context("longcat: embedding")
370 }
371
372 fn load_ngram_embedding(
373 &self,
374 store: &WeightStore,
375 config: &ModelConfig,
376 gpu: &dyn GpuBackend,
377 max_tokens: usize,
378 ) -> Result<Option<metrale_model_layers::layers::ngram_embed::NgramEmbedding>> {
379 ngram::build(store, config, gpu, max_tokens)
380 }
381
382 fn load_final_norm(
383 &self,
384 store: &WeightStore,
385 _config: &ModelConfig,
386 _gpu: &dyn GpuBackend,
387 ) -> Result<DenseWeight> {
388 dense(store, "model.norm.weight").context("longcat: final norm")
389 }
390
391 fn load_lm_head(
392 &self,
393 store: &WeightStore,
394 config: &ModelConfig,
395 _gpu: &dyn GpuBackend,
396 ) -> Result<DenseWeight> {
397 if store.contains("lm_head.weight") {
398 dense(store, "lm_head.weight")
399 } else if config.tie_word_embeddings {
400 dense(store, "model.embed_tokens.weight")
401 } else {
402 anyhow::bail!("longcat: lm_head.weight not found")
403 }
404 }
405
406 fn load_mtp_weights(
407 &self,
408 _store: &WeightStore,
409 _config: &ModelConfig,
410 _gpu: &dyn GpuBackend,
411 ) -> Result<Option<MtpWeights>> {
412 Ok(None)
413 }
414
415 fn load_vision_encoder(
416 &self,
417 _store: &WeightStore,
418 _config: &ModelConfig,
419 _gpu: &dyn GpuBackend,
420 ) -> Result<Option<VisionTower>> {
421 Ok(None)
422 }
423}
424
425mod ffn;
426use ffn::{build_dense_ffn, build_shortcut_moe};