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