1mod ngram;
30mod prep;
31
32use anyhow::{Context, Result};
33use atlas_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::qwen3_attention::MlaWeights;
41use crate::layers::vision_encoder::VisionEncoder;
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("ATLAS_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: ATLAS_NVFP4_MLA=0 — MLA projections stay BF16");
118 }
119 if super::longcat::ffn::bf16_dense_ffn() {
120 tracing::info!("LongCat: ATLAS_LONGCAT_BF16_FFN=1 — per-sublayer dense FFN stays BF16");
121 }
122 let absmax_k = gpu.kernel("quantize_nvfp4", "nvfp4_global_absmax")?;
123 let quantize_k = gpu.kernel("quantize_nvfp4", "quantize_bf16_to_nvfp4")?;
124 let stream = gpu.default_stream();
125 let mut yarn_shared = DevicePtr::NULL;
126 let mut layers: Vec<Box<dyn TransformerLayer>> =
127 Vec::with_capacity(config.num_hidden_layers);
128
129 for l in 0..ckpt_layers {
130 let lp = format!("model.layers.{l}");
131 let carry = gpu.alloc(CARRY_TOKENS * h * 2)?;
135
136 for s in 0..2usize {
137 let ap = format!("{lp}.self_attn.{s}");
138 let global_idx = l * 2 + s;
139
140 let wq_a = dense(store, &format!("{ap}.q_a_proj.weight"))?;
142 let wq_b = prep::prep_q_b(
143 store,
144 &format!("{ap}.q_b_proj.weight"),
145 n_heads,
146 nope,
147 rope,
148 q_lora,
149 scale_q,
150 padded_hd,
151 gpu,
152 )?;
153 let q_a_norm = dense(store, &format!("{ap}.q_a_layernorm.weight"))?;
154 let wkv_a = prep::prep_kv_a(
155 store,
156 &format!("{ap}.kv_a_proj_with_mqa.weight"),
157 kv_lora,
158 rope,
159 h,
160 gpu,
161 )?;
162 let kv_a_norm = prep::prep_kv_a_norm(
163 store,
164 &format!("{ap}.kv_a_layernorm.weight"),
165 kv_lora,
166 scale_kv,
167 gpu,
168 )?;
169 let wkv_b = prep::prep_kv_b(
170 store,
171 &format!("{ap}.kv_b_proj.weight"),
172 n_heads,
173 nope,
174 config.v_head_dim,
175 rope,
176 kv_lora,
177 padded_hd,
178 gpu,
179 )?;
180 let wo = prep::prep_o_proj(
181 store,
182 &format!("{ap}.o_proj.weight"),
183 h,
184 n_heads,
185 config.v_head_dim,
186 padded_hd,
187 gpu,
188 )?;
189
190 let mut c = mctx::MistralLayerCtx::new(
193 store, config, gpu, absmax_k, quantize_k, stream, global_idx,
194 );
195 c.hd = padded_hd;
197 c.nope = padded_nope;
198 c.v_dim = padded_hd;
199 c.wq_a_dense = Some(wq_a);
200 c.wq_b = Some(wq_b);
201 c.q_a_norm = Some(q_a_norm);
202 c.wkv_a_dense = Some(wkv_a);
203 c.wkv_a_rope_dense = Some(DenseWeight {
204 weight: wkv_a.weight.offset(kv_lora * h * 2),
205 });
206 c.wkv_b = Some(wkv_b);
207 c.kv_a_norm = Some(kv_a_norm);
208 c.wq_a_nvfp4 = Some(quantize_to_nvfp4(
209 &wq_a, q_lora, h, gpu, absmax_k, quantize_k, stream,
210 )?);
211 c.wq_b_nvfp4 = Some(quantize_to_nvfp4(
212 &wq_b,
213 n_heads * padded_hd,
214 q_lora,
215 gpu,
216 absmax_k,
217 quantize_k,
218 stream,
219 )?);
220 c.wkv_a_nvfp4 = Some(quantize_to_nvfp4(
221 &wkv_a,
222 kv_lora + rope,
223 h,
224 gpu,
225 absmax_k,
226 quantize_k,
227 stream,
228 )?);
229 phase_per_head::build_per_head_views(&mut c)?;
230 phase_qk_absorbed::build_w_qk_absorbed(&mut c)?;
231 phase_block_diag::build_block_diagonals(&mut c)?;
232 let o_nvfp4 = quantize_to_nvfp4(
233 &wo,
234 h,
235 n_heads * padded_hd,
236 gpu,
237 absmax_k,
238 quantize_k,
239 stream,
240 )?;
241 let yarn = mctx::ensure_yarn_inv_freq(&mut yarn_shared, config, rope, gpu)?;
242
243 let null = DenseWeight {
244 weight: DevicePtr::NULL,
245 };
246 let mla = MlaWeights {
247 wq_a,
248 wq_a_fp8: None,
249 wq_a_nvfp4: if disable_nvfp4_mla {
250 None
251 } else {
252 c.wq_a_nvfp4
253 },
254 wq_b,
255 wq_b_fp8: None,
256 wq_b_nvfp4: if disable_nvfp4_mla {
257 None
258 } else {
259 c.wq_b_nvfp4
260 },
261 q_a_norm,
262 wkv_a,
263 wkv_a_nvfp4: if disable_nvfp4_mla {
264 None
265 } else {
266 c.wkv_a_nvfp4
267 },
268 wkv_b,
269 kv_a_norm,
270 wkv_a_rope: c.wkv_a_rope_dense.expect("set above"),
271 wkv_a_merged: DenseWeight {
272 weight: wkv_a.weight,
273 },
274 wo,
275 wo_nvfp4: if disable_nvfp4_mla {
276 None
277 } else {
278 Some(o_nvfp4)
279 },
280 wo_a: null,
281 wo_a_nvfp4: None,
282 wo_b: null,
283 wo_b_nvfp4: None,
284 wo_b_fp8: None,
285 wo_a_fp8: None,
286 wkv_a_fp8: None,
287 wq_b_rope: c.wq_b_rope.context("longcat: wq_b_rope")?,
288 w_uk_t: c.w_uk_t.context("longcat: w_uk_t")?,
289 w_uv: c.w_uv.context("longcat: w_uv")?,
290 w_qk_absorbed: c.w_qk_absorbed.context("longcat: w_qk_absorbed")?,
291 w_uk_block_diag: c.w_uk_block_diag.context("longcat: w_uk_bd")?,
292 w_uv_block_diag: c.w_uv_block_diag.context("longcat: w_uv_bd")?,
293 yarn_inv_freq: yarn,
294 main_inv_freq: yarn,
295 q_lora_rank: q_lora,
296 kv_lora_rank: kv_lora,
297 o_lora_rank: 0,
298 nope: padded_nope,
299 rope,
300 v_dim: padded_hd,
301 compressor: None,
302 attn_sink: DevicePtr::NULL,
303 };
304
305 let o_dummy = QuantizedWeight {
307 weight: DevicePtr::NULL,
308 weight_scale: DevicePtr::NULL,
309 weight_scale_2: 0.0,
310 input_scale: DevicePtr::NULL,
311 weight_scale_2_vec: DevicePtr::NULL,
312 };
313 let attn = AttentionWeights {
314 q_proj: null,
315 k_proj: null,
316 v_proj: null,
317 o_proj: o_dummy,
318 q_norm: null,
319 k_norm: null,
320 q_norm_full: None,
321 k_norm_full: None,
322 k_scale: 1.0,
323 v_scale: 1.0,
324 };
325
326 let ffn = build_dense_ffn(store, &format!("{lp}.mlps.{s}"), config, gpu)?;
328 let input_norm = dense(store, &format!("{lp}.input_layernorm.{s}.weight"))?;
329 let post_norm = dense(store, &format!("{lp}.post_attention_layernorm.{s}.weight"))?;
330 let kv_dtype = layer_kv_dtypes
331 .get(global_idx)
332 .copied()
333 .unwrap_or(KvCacheDtype::Bf16);
334
335 let mut layer = Qwen3AttentionLayer::new_ungated(
336 input_norm, attn, post_norm, ffn, global_idx, None, None, None, gpu, kv_dtype,
337 0, config,
338 )?;
339 layer.set_mla_weights(mla);
340 layer.set_attn_scale_override(attn_scale);
343 layer.set_dimension_overrides(padded_hd, n_heads, n_heads);
347
348 if s == 0 {
349 let moe = build_shortcut_moe(store, &lp, config, gpu)?;
352 layer.set_shortcut_moe(moe, carry, CARRY_TOKENS);
353 } else {
354 layer.set_shortcut_carry_in(carry, CARRY_TOKENS);
355 }
356 layers.push(Box::new(layer));
357 }
358
359 if (l + 1) % 4 == 0 || l == ckpt_layers - 1 {
360 let free = gpu.free_memory().unwrap_or(0);
361 tracing::info!(
362 "LongCat L{}/{ckpt_layers} — {:.1} GB free",
363 l + 1,
364 free as f64 / 1e9
365 );
366 }
367 }
368 Ok(layers)
369 }
370
371 fn load_embedding(
372 &self,
373 store: &WeightStore,
374 _config: &ModelConfig,
375 _gpu: &dyn GpuBackend,
376 ) -> Result<DenseWeight> {
377 dense(store, "model.embed_tokens.weight").context("longcat: embedding")
378 }
379
380 fn load_ngram_embedding(
381 &self,
382 store: &WeightStore,
383 config: &ModelConfig,
384 gpu: &dyn GpuBackend,
385 max_tokens: usize,
386 ) -> Result<Option<crate::layers::ngram_embed::NgramEmbedding>> {
387 ngram::build(store, config, gpu, max_tokens)
388 }
389
390 fn load_final_norm(
391 &self,
392 store: &WeightStore,
393 _config: &ModelConfig,
394 _gpu: &dyn GpuBackend,
395 ) -> Result<DenseWeight> {
396 dense(store, "model.norm.weight").context("longcat: final norm")
397 }
398
399 fn load_lm_head(
400 &self,
401 store: &WeightStore,
402 config: &ModelConfig,
403 _gpu: &dyn GpuBackend,
404 ) -> Result<DenseWeight> {
405 if store.contains("lm_head.weight") {
406 dense(store, "lm_head.weight")
407 } else if config.tie_word_embeddings {
408 dense(store, "model.embed_tokens.weight")
409 } else {
410 anyhow::bail!("longcat: lm_head.weight not found")
411 }
412 }
413
414 fn load_mtp_weights(
415 &self,
416 _store: &WeightStore,
417 _config: &ModelConfig,
418 _gpu: &dyn GpuBackend,
419 ) -> Result<Option<MtpWeights>> {
420 Ok(None)
424 }
425
426 fn load_vision_encoder(
427 &self,
428 _store: &WeightStore,
429 _config: &ModelConfig,
430 _gpu: &dyn GpuBackend,
431 ) -> Result<Option<VisionEncoder>> {
432 Ok(None)
433 }
434}
435
436mod ffn;
438use ffn::{build_dense_ffn, build_shortcut_moe};