spark_model/weight_loader/qwen35_dense.rs
1// SPDX-License-Identifier: AGPL-3.0-only
2
3use anyhow::Result;
4use atlas_core::config::{LayerType, ModelConfig};
5use spark_runtime::gpu::GpuBackend;
6use spark_runtime::kv_cache::KvCacheDtype;
7use spark_runtime::weights::{WeightDtype, WeightStore};
8
9use super::{ModelWeightLoader, WeightFormat};
10use crate::layer::TransformerLayer;
11use crate::layers::{DenseFfnLayer, FfnComponent, Qwen3AttentionLayer, Qwen3SsmLayer};
12use crate::tp_shard::{
13 TpGdnDims, TpShardKind, load_qkvo_tp, shard_dense_bf16, shard_gdn_ba_rows, shard_gdn_conv_rows,
14 shard_gdn_out_proj_row_parallel, shard_gdn_qkvz_rows, shard_gdn_value_vector,
15 shard_quantized_nvfp4,
16};
17use crate::weight_map::{
18 AttentionWeights, DenseWeight, Fp8Weight, MtpWeights, Nvfp4Variant, PackedQ2Weight, SsmWeights,
19 dense, dense_auto, dense_f32_safe, dense_keep_f32, dequant_nvfp4_to_bf16, detect_nvfp4_variant,
20 gpu_concat_rows, interleave_ba, load_dense_ffn, load_fp8_block_scaled_as_fp8weight,
21 load_kv_scales, load_mtp, quantize_to_nvfp4, quantized_auto,
22};
23
24/// True when `{prefix}.weight` is FP8 E4M3 on disk with a 2D block scale
25/// (`weight_scale_inv` or 2D `weight_scale`) — i.e. a native FP8 checkpoint
26/// projection that should load as `Fp8Weight` rather than be requantized to
27/// NVFP4. Mirrors `qwen35::load_layers::proj_is_native_fp8`.
28/// The Q2_0 group size if `{prefix}.weight` is a keep-packed ternary tensor
29/// (`WeightDtype::PackedQ2_0`, produced by the GGUF loader under
30/// `ATLAS_GGUF_NATIVE_Q2=1`), else `None`. When `None` for every projection the
31/// FFN takes the unchanged BF16→NVFP4 path, so the default (flag-off) behavior
32/// is byte-identical.
33fn proj_q2_group(store: &WeightStore, prefix: &str) -> Option<u16> {
34 store
35 .get(&format!("{prefix}.weight"))
36 .ok()
37 .and_then(|w| w.q2_group())
38}
39
40/// Build a [`PackedQ2Weight`] borrowing the store's packed `block_q2_0` buffer.
41/// The buffer is owned by the `WeightStore` (freed with it), so this only wraps
42/// the pointer + `[n, k]` + group; no dequant, no allocation.
43fn packed_q2_from_store(store: &WeightStore, prefix: &str) -> Result<PackedQ2Weight> {
44 let w = store.get(&format!("{prefix}.weight"))?;
45 let group = w
46 .q2_group()
47 .ok_or_else(|| anyhow::anyhow!("{prefix}.weight is not keep-packed Q2_0"))?;
48 anyhow::ensure!(
49 w.shape.len() == 2,
50 "packed Q2_0 {prefix}.weight must be 2D, got {:?}",
51 w.shape
52 );
53 Ok(PackedQ2Weight {
54 weight: w.ptr,
55 n: w.shape[0] as u32,
56 k: w.shape[1] as u32,
57 group,
58 })
59}
60
61fn proj_is_native_fp8(store: &WeightStore, prefix: &str) -> bool {
62 let is_fp8_weight = store
63 .get(&format!("{prefix}.weight"))
64 .map(|w| w.dtype == WeightDtype::FP8E4M3)
65 .unwrap_or(false);
66 let has_block_scale = store.contains(&format!("{prefix}.weight_scale_inv"))
67 || store
68 .get(&format!("{prefix}.weight_scale"))
69 .map(|s| s.shape.len() == 2)
70 .unwrap_or(false);
71 is_fp8_weight && has_block_scale
72}
73
74/// True when `{prefix}.weight` is on-disk FP8 with a scale the native-FP8
75/// `w8a16` path can actually consume — i.e. one that is (or broadcasts to) the
76/// `[ceil(N/128), ceil(K/128)]` FP32 block grid the kernel indexes as
77/// `block_scale[n/128, k/128]`:
78///
79/// * `weight_scale_inv` / 2-D `weight_scale` shaped as that block grid
80/// (DeepSeek-V3 / Qwen-native convention), or
81/// * a per-tensor SCALAR `weight_scale` (ModelOpt; e.g. the nvidia
82/// Qwen3.6-27B-NVFP4 GDN projections) — `load_fp8_block_scaled_as_fp8weight`
83/// broadcasts it into a uniform grid, which is exact.
84///
85/// A **per-row** `weight_scale` (`[N,1]`, e.g. unsloth's re-quantized
86/// Qwen3.6-*-NVFP4, 2026-07-10) is deliberately REJECTED. It is not a block
87/// grid: the kernel would read row `n`'s multiplier from grid cell `n/128`, so
88/// 127 of every 128 rows get some other row's scale. That is in-bounds — the
89/// widened `[N]` buffer is *larger* than the `[N/128, K/128]` grid — so it does
90/// not fault; it silently produces garbage logits. Returning false here drops
91/// the projection to the default `dequant_fp8_blockscaled_to_bf16` →
92/// `quantize_to_nvfp4` path, which reads a `[N,1]` scale correctly
93/// (`block_n = N/N = 1`, i.e. one multiplier per row).
94fn proj_is_fp8_any_scale(store: &WeightStore, prefix: &str) -> bool {
95 let Ok(w) = store.get(&format!("{prefix}.weight")) else {
96 return false;
97 };
98 if w.dtype != WeightDtype::FP8E4M3 || w.shape.len() != 2 {
99 return false;
100 }
101 let (n, k) = (w.shape[0], w.shape[1]);
102
103 for key in [
104 format!("{prefix}.weight_scale_inv"),
105 format!("{prefix}.weight_scale"),
106 ] {
107 let Ok(s) = store.get(&key) else { continue };
108 // Per-tensor scalar → broadcast to a uniform grid: exact.
109 if s.num_elements() == 1 {
110 return true;
111 }
112 // 2-D scale: only a genuine 128×128 block grid is consumable.
113 if s.shape.len() == 2 && s.shape[0] == n.div_ceil(128) && s.shape[1] == k.div_ceil(128) {
114 return true;
115 }
116 }
117 false
118}
119
120/// Concatenate two block-scaled FP8 weights along rows (dim 0):
121/// `[n_a, k] ++ [n_b, k] -> [n_a+n_b, k]`. FP8 bytes (1 B/elem) and the
122/// `[n/128, k/128]` FP32 block-scale grids are contiguous row-major, so each is
123/// a straight device-to-device append. Requires `n_a % 128 == 0` so the
124/// scale-grid rows meet at a block boundary (GDN qkv rows are a multiple of
125/// 128). Produces the `[Q|K|V|Z]` sequential order the SSM layer expects.
126fn concat_fp8_block_scaled(
127 a: &Fp8Weight,
128 b: &Fp8Weight,
129 k: usize,
130 gpu: &dyn GpuBackend,
131) -> Result<Fp8Weight> {
132 let kb = k.div_ceil(128);
133 let a_w = a.n as usize * k;
134 let b_w = b.n as usize * k;
135 let weight = gpu.alloc(a_w + b_w)?;
136 gpu.copy_d2d(a.weight, weight, a_w)?;
137 gpu.copy_d2d(b.weight, weight.offset(a_w), b_w)?;
138 let a_s = (a.n as usize).div_ceil(128) * kb * 4;
139 let b_s = (b.n as usize).div_ceil(128) * kb * 4;
140 let row_scale = gpu.alloc(a_s + b_s)?;
141 gpu.copy_d2d(a.row_scale, row_scale, a_s)?;
142 gpu.copy_d2d(b.row_scale, row_scale.offset(a_s), b_s)?;
143 Ok(Fp8Weight {
144 weight,
145 row_scale,
146 n: a.n + b.n,
147 k: k as u32,
148 scale_format: crate::weight_map::WeightQuantFormat::Fp8BlockScaled,
149 })
150}
151
152/// Opt-in gate for native dense-FP8 attention + FFN dispatch (Qwythos / dense
153/// Ornith-FP8). Default OFF.
154///
155/// VERIFIED 2026-06-29 on Qwythos-9B-FP8 (gb10/ornith-1.0-9b): with the flag
156/// on, the FP8 arms fire for all 32 FFN + 8 full-attn layers and text is
157/// correct (coherence/fib/tools 3/3). BUT it is NOT a perf win — ~30 tok/s vs
158/// ~40 for the NVFP4 fallback — because this target's NVFP4 W4A16 kernels
159/// (fused dual-GEMV decode, transposed m128 prefill) are more optimized than
160/// its FP8 W8A16 kernels (unfused per-projection GEMV, non-transposed
161/// `w8a16_gemm` prefill; the attention FP8 prefill transpose also does not
162/// engage). Vision prefill additionally hits a CUDA-700. Making FP8 pay off
163/// here needs dedicated dense-FP8 kernels (fused FP8 dual-GEMV + fast
164/// transposed FP8 prefill GEMM), not loader wiring. Until then NVFP4 autoquant
165/// is the better dense runtime. `ATLAS_DENSE_FP8=1` opts in for that kernel work.
166fn dense_fp8_enabled() -> bool {
167 std::env::var("ATLAS_DENSE_FP8").as_deref() == Ok("1")
168}
169
170/// Whether the native block-scaled FP8 GDN arm runs for this SSM layer.
171///
172/// ONE predicate, called by `load_layers` and by `prune_after_load`: the
173/// second frees the store tensors the first copied into the fused `[QKV|Z]`
174/// weight, and a drift between the two is a use-after-free with no
175/// diagnostic. The keep-packed Q2 arm `continue`s ahead of this one, so it is
176/// part of the condition (#915).
177fn gdn_fp8_arm_selected(store: &WeightStore, la: &str, tp_size: usize) -> bool {
178 let q2 = tp_size.max(1) == 1
179 && std::env::var_os("ATLAS_NO_Q2_GDN").is_none()
180 && proj_q2_group(store, &format!("{la}.in_proj_qkv")).is_some()
181 && proj_q2_group(store, &format!("{la}.in_proj_z")).is_some();
182 !q2 && std::env::var_os("ATLAS_NO_GDN_FP8").is_none()
183 && proj_is_fp8_any_scale(store, &format!("{la}.in_proj_qkv"))
184 && proj_is_fp8_any_scale(store, &format!("{la}.in_proj_z"))
185 && proj_is_fp8_any_scale(store, &format!("{la}.out_proj"))
186}
187
188/// The dense-FFN width. `moe_intermediate_size` is the PER-EXPERT width and is
189/// unset (=0) on dense Qwen3.6/3.8-*-FP8, so reading it first would size a
190/// 0-byte allocation; `intermediate_size` is unset on the older MoE-style
191/// configs. Same fallback order as `weight_map/fp8_lut.rs::load_dense_ffn`.
192fn ffn_inter(config: &ModelConfig) -> usize {
193 if config.intermediate_size > 0 {
194 config.intermediate_size
195 } else {
196 config.moe_intermediate_size
197 }
198}
199
200/// Whether the native-FP8 dense-FFN overlay runs for THIS layer.
201///
202/// The condition `load_layers` applied inline before `prune_after_load` needed
203/// the same answer. Pure — a function of the store, the config and the
204/// detected variant — so the two sites cannot drift, which is the failure a
205/// prune predicate must not have: freeing a store tensor a layer still aliases
206/// is a use-after-free with no diagnostic.
207fn ffn_fp8_arm_selected(
208 store: &WeightStore,
209 config: &ModelConfig,
210 variant: Nvfp4Variant,
211 lp: &str,
212) -> bool {
213 dense_fp8_enabled()
214 && config.tp_world_size.max(1) == 1
215 && matches!(variant, Nvfp4Variant::Fp8Dequanted)
216 && proj_is_native_fp8(store, &format!("{lp}.mlp.gate_proj"))
217}
218
219/// Whether THIS layer's gate and up are fused into one `[2*inter, hidden]`
220/// block-scaled FP8 weight (#927).
221///
222/// Beyond the FP8 overlay itself: the target must arm the arm (`[defaults]
223/// ffn_gateup_fused`), the model must be DENSE (a MoE config has no
224/// `ffn_gate_up_fused` arena buffer and never reaches the dense-FFN path), and
225/// both extents must be whole 128-blocks — `concat_fp8_block_scaled` appends
226/// the `[N/128, K/128]` scale grids, which is only the fused grid when the
227/// seam falls on a block boundary (`ceil` of a sum is not the sum of `ceil`s).
228///
229/// The SECOND caller is `prune_after_load`, which releases exactly the store
230/// tensors this returned true for. Rule and the receipt:
231/// `layers/dense_ffn_gateup_fused.rs`.
232fn ffn_gateup_fused_selected(
233 store: &WeightStore,
234 config: &ModelConfig,
235 variant: Nvfp4Variant,
236 lp: &str,
237) -> bool {
238 let inter = ffn_inter(config);
239 let hidden = config.hidden_size;
240 // The CONFIG's widths are what the ledger terms, the prediction and the
241 // view offsets are all computed from, so a checkpoint whose tensors
242 // disagree with them must DECLINE rather than be concatenated at the wrong
243 // stride. Checked here and not at the call site because `prune_after_load`
244 // asks this same question and frees on the answer: a predicate the loader
245 // narrows locally is a store tensor freed out from under a live view.
246 let on_disk = |name: &str| {
247 store
248 .get(&format!("{lp}.mlp.{name}.weight"))
249 .is_ok_and(|w| w.shape == [inter, hidden])
250 };
251 ffn_fp8_arm_selected(store, config, variant, lp)
252 && crate::layers::dense_ffn::gateup_fused::ffn_gateup_fused()
253 && config.num_experts == 0
254 && inter > 0
255 && inter.is_multiple_of(128)
256 && hidden.is_multiple_of(128)
257 && on_disk("gate_proj")
258 && on_disk("up_proj")
259}
260
261// `pub` (re-exported from `weight_loader/mod.rs`): the pre-load residency
262// PREDICTION in `predicted_residency` is read by spark-server's preflight,
263// and it prices its terms with this module's shape helpers so the prediction
264// and the loader's own tally cannot be two different arithmetics (#915).
265pub mod fp8_residency;
266mod loaders_b;
267pub mod predicted_residency;
268mod rowwise_fp8;
269
270use crate::layers::qwen3_attention::Fp8TwinSet;
271use fp8_residency::{DerivedResidency, RouteEnv};
272
273pub struct Qwen35DenseWeightLoader;
274
275impl ModelWeightLoader for Qwen35DenseWeightLoader {
276 fn supports_tp(&self) -> bool {
277 // FullAttention layers are TP-sharded (NVFP4-from-disk and BF16
278 // → NVFP4 paths). LinearAttention (GDN SSM) layers run
279 // full-replica per rank — see qwen35.rs for the rationale.
280 true
281 }
282
283 fn load_layers(
284 &self,
285 store: &WeightStore,
286 config: &ModelConfig,
287 gpu: &dyn GpuBackend,
288 layer_kv_dtypes: &[KvCacheDtype],
289 ) -> Result<Vec<Box<dyn TransformerLayer>>> {
290 let layer_types = if config.layer_types.is_empty() {
291 (0..config.num_hidden_layers)
292 .map(|i| config.layer_type(i))
293 .collect::<Vec<_>>()
294 } else {
295 config.layer_types.clone()
296 };
297
298 let mut layers: Vec<Box<dyn TransformerLayer>> =
299 Vec::with_capacity(config.num_hidden_layers);
300 let mut attn_idx = 0usize;
301
302 let absmax_k = gpu.kernel("quantize_nvfp4", "nvfp4_global_absmax")?;
303 let quantize_k = gpu.kernel("quantize_nvfp4", "quantize_bf16_to_nvfp4")?;
304 let stream = gpu.default_stream();
305 let h = config.hidden_size;
306
307 let variant = detect_nvfp4_variant(store, config);
308 let weight_format = WeightFormat::detect(store, config);
309 tracing::info!(
310 "Weight format: {:?}, NVFP4 variant: {:?}",
311 weight_format,
312 variant
313 );
314
315 // Native FP8 SSM prefill GEMM (Qwen3.6-27B-FP8 root-cause fix,
316 // commit 3ebc08a). Atlas's prior SSM in_proj_qkv path was
317 // FP8 → BF16 → NVFP4 → BF16 (in `w4a16_gemm` dequant) → MMA — a
318 // double-quant chain whose NVFP4 hop's ~4-bit per-group precision
319 // is dominated by signal at q/v but attenuated into a k-direction
320 // error (HF conv-k ‖6.3‖ vs conv-v ‖117.2‖, ~18× smaller). For
321 // every FP8-on-disk checkpoint we install a single-scale FP8 copy
322 // of the stacked `[QKV|Z]` and `out_proj` weights for prefill,
323 // bypassing the NVFP4 intermediate. Prefill dispatches via the
324 // existing `fp8_gemm_n128` (BF16 act × FP8 weight) — same path
325 // the MoE shared-expert FP8 prefill uses. Decode/GEMV unchanged.
326 // Originally env-gated `ATLAS_FP8_SSM_PREFILL=1`; promoted to
327 // unconditional 2026-05-20 after live verification (commit
328 // dfb4e8a era, tokens_to_first_degeneration 1,196 → 16,968).
329 // 2026-07-03 precision policy: GDN projections stay ≥FP8 — the
330 // NVFP4 requant of qkvz/out_proj flattens tensors that both
331 // checkpoint toolchains deliberately keep high-precision (modelopt
332 // sensitivity analysis ships them FP8; unsloth ships BF16). Applies
333 // to ALL NVFP4 variants now, not just Fp8Dequanted. Kill-switch
334 // ATLAS_NO_GDN_FP8_PREFILL restores the pre-policy behavior for A/B.
335 let fp8_ssm_prefill = std::env::var_os("ATLAS_NO_GDN_FP8_PREFILL").is_none();
336 let bf16_to_fp8_k = if fp8_ssm_prefill {
337 tracing::info!(
338 // The old wording — "NVFP4 kept as structural fallback for
339 // decode batch paths" — was DISPROVEN by nsys at C=8
340 // (2026-08-17): the batched verify QKVZ was taking this FP8
341 // copy at every M>8, 26.3% of the step. It is true again only
342 // because that arm now reads NVFP4; say the actual rule so the
343 // log cannot re-drift from the dispatch.
344 "SSM in_proj_qkv + out_proj via native FP8 prefill GEMM \
345 (BF16 act × FP8 weight via fp8_gemm_n128). PREFILL ONLY: \
346 decode + batched verify read the NVFP4 copy — weight-streaming \
347 GEMV at M<=8, tile GEMM above it (trait_decode_batched.rs). \
348 The FP8 copy reaches decode only at M<=8 on a build whose \
349 batched NVFP4 GEMVs are absent"
350 );
351 Some(gpu.kernel("w4a16", "bf16_to_fp8")?)
352 } else {
353 None
354 };
355
356 // ATLAS_MEM_PROFILE: per-phase GPU-free trace to pin the strix/APU
357 // load-time footprint (FP8-source persistence vs NVFP4 steady-state vs
358 // BF16 requant transients). Gated env so it's a no-op in production.
359 let mem_profile = std::env::var("ATLAS_MEM_PROFILE").is_ok();
360 let log_free = |tag: &str| {
361 if mem_profile && let Ok(free) = gpu.free_memory() {
362 tracing::info!("MEM_PROFILE[{tag}]: {:.2} GB GPU-free", free as f64 / 1e9);
363 }
364 };
365 log_free("dense-load-start");
366
367 // #915: resolved ONCE, from the same resolvers the forward pass uses —
368 // the loader runs before `ForwardContext` exists. Contract and WHY:
369 // `fp8_residency.rs`.
370 let route_env = RouteEnv::from_env();
371 let mut residency = DerivedResidency::default();
372
373 for (i, lt) in layer_types.iter().enumerate() {
374 if i % 8 == 0 {
375 log_free(&format!("layer-{i}"));
376 }
377 let lp = config.layer_prefix(i);
378 let input_norm = dense(store, &format!("{lp}.input_layernorm.weight"))?;
379 let post_attn_norm = dense(store, &format!("{lp}.post_attention_layernorm.weight"))?;
380
381 // Dense FFN instead of MoE. Native FP8 checkpoints (single-GPU)
382 // load gate/up/down directly as block-scaled `Fp8Weight` and
383 // dispatch w8a16 — no NVFP4 requant. TP>1 still uses the NVFP4
384 // path (FP8 FFN sharding is a follow-up).
385 let ffn_fp8 = ffn_fp8_arm_selected(store, config, variant, &lp);
386 // 2026-09-11 (#915), replacing "always load the NVFP4 weights so
387 // every dispatch path has a valid weight to fall back to": there is
388 // no such path any more. `forward_k2`/`k3`/`km` redirect to
389 // `forward_prefill` whenever an FP8 overlay is installed
390 // (`native_small_batch_uses_prefill`), and both `forward` and
391 // `forward_prefill_inner` return from inside their `fp8_weights`
392 // arm. Building the fallback cost 18.4 GiB on Qwen3.8-27B-FP8 that
393 // nothing could read. The CUDA-700-at-concurrency hazard the old
394 // rule guarded is real and is why this is decided by
395 // `fp8_residency.rs` rather than inline: a NULL NVFP4 weight is
396 // only sound when the plan proves no w4a16 dispatch is reachable.
397 // Native-BF16 dense-FFN prefill (Holo/Ornith Bf16Raw): the fast
398 // tensor-core `dense_gemm_tc` path (dense_ffn::forward_prefill) needs
399 // LIVE BF16 gate/up/down. `load_dense_ffn`'s Bf16Raw arm runtime-
400 // quantizes each proj via `quantized_any`, whose Bf16Raw branch
401 // FREES the store's cached BF16 buffer (`gpu.free(w.ptr)` in
402 // nvfp4_detect.rs:288-309, the Bf16Raw arm). `dense_auto` returns that
403 // SAME (now-freed) store ptr for a BF16 tensor — quant_helpers.rs:290
404 // hands back `w.ptr` uncopied — so overlaying it AFTER load_dense_ffn hands
405 // `set_bf16_weights` freed GPU memory -> dense_gemm_tc CUDA-700 at
406 // grid=[div_ceil(intermediate_size,64),..] on the first prefill.
407 // Snapshot fresh D2D copies BEFORE the free (the clone is an
408 // independent allocation the layer owns for its lifetime).
409 // Native keep-packed ternary Q2_0 (ATLAS_GGUF_NATIVE_Q2=1): when the
410 // GGUF loader tagged gate/up/down as `PackedQ2_0`, DON'T requant to
411 // NVFP4 — install the raw 2-bit blocks and dispatch `q2_0_gemv` at
412 // decode. Requires tp_size=1 (packed-block sharding is unimplemented).
413 // Flag off → tensors are BF16, `ffn_q2` is false, path unchanged.
414 let ffn_q2 = config.tp_world_size.max(1) == 1
415 && proj_q2_group(store, &format!("{lp}.mlp.gate_proj")).is_some()
416 && proj_q2_group(store, &format!("{lp}.mlp.up_proj")).is_some()
417 && proj_q2_group(store, &format!("{lp}.mlp.down_proj")).is_some();
418 // Keep-packed projections are 2-bit blocks, not BF16 — there is
419 // nothing to snapshot (dense_auto has no PackedQ2_0 arm and would
420 // abort the load), and the ffn_q2 arm below owns their compute.
421 let ffn_bf16_snapshot = if !ffn_q2 && matches!(variant, Nvfp4Variant::Bf16Raw) {
422 let inter = if config.intermediate_size > 0 {
423 config.intermediate_size
424 } else {
425 config.moe_intermediate_size
426 };
427 let clone_bf16 = |name: &str, rows: usize, cols: usize| -> Result<DenseWeight> {
428 let src = dense_auto(store, &format!("{lp}.mlp.{name}.weight"), gpu)?;
429 let bytes = rows * cols * 2; // BF16 = 2 bytes/elem
430 let dst = gpu.alloc(bytes)?;
431 // The whole point of this snapshot is an allocation
432 // INDEPENDENT of the store's cached ptr (which load_dense_ffn
433 // frees below); an aliased dst would silently re-create the
434 // freed-memory CUDA-700 this helper exists to prevent.
435 debug_assert_ne!(
436 dst, src.weight,
437 "ffn_bf16_snapshot must be a fresh allocation, not an alias of the store ptr"
438 );
439 gpu.copy_d2d(src.weight, dst, bytes)?;
440 Ok(DenseWeight { weight: dst })
441 };
442 Some((
443 clone_bf16("gate_proj", inter, h)?,
444 clone_bf16("up_proj", inter, h)?,
445 clone_bf16("down_proj", h, inter)?,
446 ))
447 } else {
448 None
449 };
450
451 // #915: the NVFP4 gate/up/down fallback and its transposed twins
452 // are 18.4 GiB on Qwen3.8-27B-FP8 that NO native-FP8 dispatch can
453 // reach — every `DenseFfnLayer` entry point returns from inside its
454 // `self.fp8_weights` arm and `w8_gemm!` binds the transposed
455 // operand to a literal `None`. The comment above ("always load the
456 // NVFP4 weights ... the rare batched paths fall back to real
457 // NVFP4") described the pre-#927 dispatch; `forward_k2`/`k3`/`km`
458 // have redirected to `forward_prefill` since
459 // `native_small_batch_uses_prefill` landed. Decision table and the
460 // loader-runs-before-dispatch contract: `fp8_residency.rs`.
461 let plan = route_env.plan(ffn_fp8, false, false);
462 let ffn_nvfp4 = !ffn_q2 && plan.ffn_nvfp4;
463 let ffn_weights = if ffn_nvfp4 {
464 load_dense_ffn(
465 store, &lp, gpu, variant, absmax_k, quantize_k, stream, config,
466 )?
467 } else {
468 // NULL NVFP4 fallback. Packed-Q2 (Tier-1c): decode uses the
469 // packed weights; prefill / batched paths bail (Tier-2).
470 // Native FP8: every arm reads the E4M3 bytes installed by
471 // `set_fp8_weights` below. No NVFP4 allocation → memory win.
472 if ffn_fp8 {
473 residency.skip(fp8_residency::dense_ffn_nvfp4_bytes(h, ffn_inter(config)));
474 }
475 use crate::weight_map::QuantizedWeight;
476 crate::layers::dense_ffn::DenseFfnWeights {
477 gate_proj: QuantizedWeight::null(),
478 up_proj: QuantizedWeight::null(),
479 down_proj: QuantizedWeight::null(),
480 gate_proj_t: None,
481 up_proj_t: None,
482 down_proj_t: None,
483 }
484 };
485 residency.twins.ffn_nvfp4 |= ffn_nvfp4;
486 let mut dffn = DenseFfnLayer::new(ffn_weights, gpu)?;
487 if ffn_q2 {
488 dffn.set_q2_weights(
489 packed_q2_from_store(store, &format!("{lp}.mlp.gate_proj"))?,
490 packed_q2_from_store(store, &format!("{lp}.mlp.up_proj"))?,
491 packed_q2_from_store(store, &format!("{lp}.mlp.down_proj"))?,
492 gpu,
493 );
494 }
495 if ffn_fp8 {
496 // The FP8 `.weight` bytes are store-owned (zero-copy from the
497 // checkpoint); only the widened FP32 block-scale grid is a
498 // fresh allocation (`loaders_fp8.rs:109`). Adopt it — #736
499 // lists the loader sites that allocate without an owner.
500 let load_ffn_fp8 = |name: &str| -> Result<Fp8Weight> {
501 let w = load_fp8_block_scaled_as_fp8weight(
502 store,
503 &format!("{lp}.mlp.{name}"),
504 gpu,
505 )?;
506 let bytes = (w.n as usize).div_ceil(128) * (w.k as usize).div_ceil(128) * 4;
507 store
508 .derived()
509 .adopt("ffn fp8 block scale (widened)", w.row_scale, bytes);
510 Ok(w)
511 };
512 let mut gate = load_ffn_fp8("gate_proj")?;
513 let mut up = load_ffn_fp8("up_proj")?;
514 let down = load_ffn_fp8("down_proj")?;
515 // FUSED gate+up (#927). ONE `[2*inter, hidden]` E4M3 buffer
516 // and ONE `[2*inter/128, hidden/128]` FP32 scale grid, with
517 // `gate` and `up` re-pointed at VIEWS inside them — so the
518 // fused decode GEMM and every un-fused rung of `w8_gemm!` read
519 // the SAME bytes and neither needs a second copy.
520 //
521 // RESIDENCY IS NET ZERO, and that is the whole design
522 // constraint: a second copy is 178.3 MB x 64 layers = 11.4 GB,
523 // straight out of the bs32 KV budget. `prune_after_load`
524 // releases the two source store tensors this copied
525 // (`{lp}.mlp.{gate,up}_proj.weight` + their scale tensors),
526 // exactly as the SSM `[QKV|Z]` concat above has done since
527 // #915, and `predicted_residency` prices the pair at zero for
528 // the preflight ring fit.
529 let inter = ffn_inter(config);
530 let fused = if ffn_gateup_fused_selected(store, config, variant, &lp) {
531 let fused = concat_fp8_block_scaled(&gate, &up, h, gpu)?;
532 let (w_bytes, s_bytes) = fp8_residency::ffn_gateup_fused_parts(h, inter);
533 // The concat COPIED both widened grids, so the two
534 // per-projection allocations are dead. They were adopted a
535 // few lines up; disown before freeing, or teardown frees
536 // them a second time.
537 let d = store.derived();
538 let grid = inter.div_ceil(128) * h.div_ceil(128) * 4;
539 for ptr in [gate.row_scale, up.row_scale] {
540 d.disown(ptr);
541 gpu.free(ptr)?;
542 }
543 residency.free(2 * grid);
544 // The views. `gate` is the fused buffer's head and `up`
545 // starts one `[inter, hidden]` block in; the scale grids
546 // meet at the same boundary because `inter % 128 == 0` is
547 // a clause of the selector above.
548 gate.weight = fused.weight;
549 gate.row_scale = fused.row_scale;
550 up.weight = fused.weight.offset(inter * h);
551 up.row_scale = fused.row_scale.offset(grid);
552 d.adopt("ffn gate+up fp8 concat", fused.weight, w_bytes);
553 d.adopt("ffn gate+up fp8 block scale", fused.row_scale, s_bytes);
554 residency.keep(w_bytes + s_bytes);
555 residency.twins.ffn_gateup_fused = true;
556 Some(fused)
557 } else {
558 None
559 };
560 dffn.set_fp8_weights(gate, up, down);
561 if let Some(fused) = fused {
562 dffn.set_fp8_gate_up_fused(fused);
563 }
564 }
565 // ATLAS_FFN_MMQ: eagerly materialize Q4_K + free the dead `_t` copies at load,
566 // BEFORE KV cache sizing, so net FFN footprint == NVFP4 baseline (no decode OOM-throttle).
567 dffn.finalize_q4k_load(gpu, h as u32, config.intermediate_size as u32, stream)?;
568 // ATLAS_FFN_NVFP4_MMQ: same discipline for the W4A4 FP4-MMQ arm — repack
569 // gate/up to block_nvfp4 + free their `_t` copies (net ~0 footprint).
570 //
571 // SKIPPED when a LoRA adapter is pending. The forward-time FP4-MMQ
572 // arm is disabled while an adapter is installed (it leaves gate/up
573 // UNSCALED and folds weight_scale_2 inside the SiLU-mul, which
574 // would silently scale a true-valued delta). But this finalize
575 // FREES the transposed `_t` copies on the assumption that the MMQ
576 // arm will serve prefill — so running it and then disabling the arm
577 // left prefill on the slowest non-transposed GEMM with nothing to
578 // fall back to: 176 tok/s against 841 on a 2K prompt, and it had
579 // NOTHING to do with the cost of applying the deltas (measured with
580 // the deltas skipped entirely).
581 //
582 // `adapter_max_rank` is the load-time signal that `--lora-adapter`
583 // was given; the adapter itself is installed later (build step 8),
584 // so this is the only point where the decision can be made before
585 // the twins are freed.
586 if config.adapter_max_rank == 0 {
587 dffn.finalize_nvfp4_mmq_load(
588 gpu,
589 h as u32,
590 config.intermediate_size as u32,
591 stream,
592 )?;
593 }
594 // Native-BF16 dense-FFN overlay (Bf16Raw, no-metadata Holo dense): install the
595 // live BF16 gate/up/down snapshot so forward/forward_prefill's bf16 branch
596 // (preferred over the NVFP4 fallback) reads valid memory. The NVFP4 weights built
597 // by load_dense_ffn stay as the spec-decode/batched fallback (never null -> no
598 // CUDA-700 at concurrency). Snapshot was taken before load_dense_ffn freed the
599 // store's BF16 buffer (see ffn_bf16_snapshot above).
600 if let Some((g, u, d)) = ffn_bf16_snapshot {
601 dffn.set_bf16_weights(g, u, d);
602 }
603 let ffn = FfnComponent::Dense(dffn);
604
605 match lt {
606 LayerType::FullAttention => {
607 let p = format!("{lp}.self_attn");
608 let tp_rank = config.tp_rank;
609 let tp_size = config.tp_world_size.max(1);
610 // Bf16Raw installs a BF16 dense O-proj after the layer is
611 // built; other variants leave this None (NVFP4/FP8 dispatch).
612 let mut o_dense_bf16: Option<DenseWeight> = None;
613
614 // Native keep-packed ternary Q2_0 (Tier-1c): when the GGUF
615 // loader tagged q/k/v/o as `PackedQ2_0` (transform-free
616 // full-attention projections), install the raw 2-bit blocks
617 // and dispatch `q2_0_gemv_vec` at decode / transient-dequant
618 // at prefill — no NVFP4 requant, no `_t` copies. Requires
619 // tp_size=1 (packed-block sharding is unimplemented). Bonsai
620 // has no kill-switch here; the whole path is gated upstream
621 // by ATLAS_GGUF_NATIVE_Q2 (else these tensors are BF16 and
622 // `attn_q2` is false). ATLAS_NO_Q2_ATTN forces the BF16 path
623 // for A/B bisection.
624 let attn_q2 = tp_size == 1
625 && std::env::var_os("ATLAS_NO_Q2_ATTN").is_none()
626 && proj_q2_group(store, &format!("{p}.q_proj")).is_some()
627 && proj_q2_group(store, &format!("{p}.k_proj")).is_some()
628 && proj_q2_group(store, &format!("{p}.v_proj")).is_some()
629 && proj_q2_group(store, &format!("{p}.o_proj")).is_some();
630 if attn_q2 {
631 let (k_scale, v_scale) = load_kv_scales(store, &p, gpu);
632 let attn = AttentionWeights {
633 q_proj: DenseWeight {
634 weight: spark_runtime::gpu::DevicePtr::NULL,
635 },
636 k_proj: DenseWeight {
637 weight: spark_runtime::gpu::DevicePtr::NULL,
638 },
639 v_proj: DenseWeight {
640 weight: spark_runtime::gpu::DevicePtr::NULL,
641 },
642 o_proj: crate::weight_map::QuantizedWeight::null(),
643 q_norm: dense(store, &format!("{p}.q_norm.weight"))?,
644 k_norm: dense(store, &format!("{p}.k_norm.weight"))?,
645 q_norm_full: None,
646 k_norm_full: None,
647 k_scale,
648 v_scale,
649 };
650 let mut attn_layer = Qwen3AttentionLayer::new(
651 input_norm,
652 attn,
653 post_attn_norm,
654 ffn,
655 attn_idx,
656 None,
657 None,
658 None,
659 gpu,
660 layer_kv_dtypes[attn_idx],
661 config.fp8_kv_calibration_tokens,
662 config,
663 )?;
664 attn_layer.set_packed_q2_weights(
665 packed_q2_from_store(store, &format!("{p}.q_proj"))?,
666 packed_q2_from_store(store, &format!("{p}.k_proj"))?,
667 packed_q2_from_store(store, &format!("{p}.v_proj"))?,
668 packed_q2_from_store(store, &format!("{p}.o_proj"))?,
669 gpu,
670 );
671 tracing::info!(
672 "ATTN[{lp}] native keep-packed Q2_0: q/k/v/o 2-bit \
673 (q2_0_gemv_vec decode; transient-dequant prefill)"
674 );
675 layers.push(Box::new(attn_layer));
676 attn_idx += 1;
677 if (i + 1) % 10 == 0 {
678 tracing::info!("Loaded layers 0..{}", i + 1);
679 }
680 continue;
681 }
682 // #915: does the native FP8 attention overlay replace
683 // these weights below? Hoisted out of the overlay block so
684 // the NVFP4 build can be skipped rather than built and
685 // immediately orphaned — `set_fp8_weights` OVERWRITES
686 // `q/k/v/o_weight`, so on this route the NVFP4 q/k/v/o were
687 // unreachable the moment they were installed.
688 let attn_fp8 = dense_fp8_enabled()
689 && config.tp_world_size.max(1) == 1
690 && matches!(variant, Nvfp4Variant::Fp8Dequanted)
691 && proj_is_native_fp8(store, &format!("{p}.q_proj"));
692 let attn_nvfp4 = route_env.attn_nvfp4(attn_fp8);
693 let (attn, q_nvfp4, k_nvfp4, v_nvfp4) = match variant {
694 Nvfp4Variant::CompressedTensors => {
695 // NVFP4-from-disk path: column-parallel Q/K/V, row-parallel O.
696 let group_size = 16usize;
697 let load_nvfp4 = |name: &str,
698 full_n: usize,
699 full_k: usize,
700 kind: TpShardKind|
701 -> Result<crate::weight_map::QuantizedWeight> {
702 let prefix = format!("{p}.{name}");
703 // Mixed-precision compressed-tensors checkpoints
704 // (unsloth Qwen3.6-*-NVFP4, re-quantized 2026-07-10)
705 // NVFP4-pack most of the net but keep attention
706 // q/k/v/o as FP8 (`.weight` FP8E4M3 + a per-row
707 // `.weight_scale`, no `.weight_packed`). Without the
708 // pack metadata, dequant and runtime-quantize to NVFP4
709 // instead of failing on the absent
710 // `weight_global_scale`. Mirrors the MoE loader's
711 // attention arm (weight_loader/qwen35/load_layers/
712 // attention_arms.rs).
713 let src = if store.contains(&format!("{prefix}.weight_packed")) {
714 quantized_auto(store, &prefix, gpu, variant)?
715 } else {
716 let dense_bf16 =
717 dense_auto(store, &format!("{prefix}.weight"), gpu)?;
718 quantize_to_nvfp4(
719 &dense_bf16,
720 full_n,
721 full_k,
722 gpu,
723 absmax_k,
724 quantize_k,
725 stream,
726 )?
727 };
728 if tp_size == 1 {
729 return Ok(src);
730 }
731 let sharded = shard_quantized_nvfp4(
732 &src, full_n, full_k, kind, tp_rank, tp_size, group_size, gpu,
733 )?;
734 gpu.free(src.weight)?;
735 gpu.free(src.weight_scale)?;
736 Ok(sharded)
737 };
738 let [q, k, v, o] = load_qkvo_tp(config, load_nvfp4)?;
739 let dummy = DenseWeight {
740 weight: spark_runtime::gpu::DevicePtr::NULL,
741 };
742 let (k_scale, v_scale) = load_kv_scales(store, &p, gpu);
743 let attn = AttentionWeights {
744 q_proj: dummy,
745 k_proj: dummy,
746 v_proj: dummy,
747 o_proj: o,
748 q_norm: dense(store, &format!("{p}.q_norm.weight"))?,
749 k_norm: dense(store, &format!("{p}.k_norm.weight"))?,
750 q_norm_full: None,
751 k_norm_full: None,
752 k_scale,
753 v_scale,
754 };
755 (attn, Some(q), Some(k), Some(v))
756 }
757 Nvfp4Variant::Standard | Nvfp4Variant::Fp8Dequanted if !attn_nvfp4 => {
758 // #915: the FP8 overlay below replaces q/k/v/o, and
759 // nothing reads the NVFP4 copies afterwards — the
760 // transposed twins only under `ATLAS_CUTLASS_NVFP4_*`
761 // and the base `o_proj` only under `ATLAS_ATTN_W4A4`,
762 // both of which `RouteEnv::attn_nvfp4` accounts for.
763 // Skipping the build also skips the BF16 dequant of
764 // every FP8 projection that fed `quantize_to_nvfp4`.
765 // Same NULL-o_proj shape the Bf16Raw arm below uses.
766 let (k_scale, v_scale) = load_kv_scales(store, &p, gpu);
767 let (nh, hd) = (config.num_attention_heads, config.head_dim);
768 let (nkv, hh) = (config.num_key_value_heads, config.hidden_size);
769 let q_n = nh * hd * if config.attn_gated { 2 } else { 1 };
770 residency.skip(fp8_residency::attn_nvfp4_bytes(
771 q_n,
772 nkv * hd,
773 nh * hd,
774 hh,
775 ));
776 let null = || DenseWeight {
777 weight: spark_runtime::gpu::DevicePtr::NULL,
778 };
779 let attn = AttentionWeights {
780 q_proj: null(),
781 k_proj: null(),
782 v_proj: null(),
783 o_proj: crate::weight_map::QuantizedWeight::null(),
784 q_norm: dense(store, &format!("{p}.q_norm.weight"))?,
785 k_norm: dense(store, &format!("{p}.k_norm.weight"))?,
786 q_norm_full: None,
787 k_norm_full: None,
788 k_scale,
789 v_scale,
790 };
791 // The NULL o_proj is only sound while the FP8
792 // overlay is installed below — a null NVFP4 weight
793 // reached by a w4a16 dispatch is the
794 // CUDA-700-at-concurrency failure mode.
795 debug_assert!(
796 attn_fp8,
797 "attn_nvfp4=false must imply a native FP8 overlay"
798 );
799 (attn, None, None, None)
800 }
801 Nvfp4Variant::Standard | Nvfp4Variant::Fp8Dequanted => {
802 // BF16 → NVFP4 path: shard BF16 then quantize per-rank.
803 let load_bf16_then_nvfp4 = |name: &str,
804 full_n: usize,
805 full_k: usize,
806 kind: TpShardKind|
807 -> Result<(
808 DenseWeight,
809 crate::weight_map::QuantizedWeight,
810 )> {
811 // Pre-quantized Standard NVFP4 (e.g. sakamakismile): weight is U8
812 // on disk. Load directly as QuantizedWeight without BF16 roundtrip.
813 // TP sharding of pre-quantized NVFP4 is not yet supported: enforce
814 // tp_size=1 explicitly, or every rank silently loads the full
815 // unsharded weight (duplicated, not sharded — wrong results with
816 // no error).
817 let weight_key = format!("{p}.{name}.weight");
818 if matches!(
819 store.get(&weight_key).map(|w| w.dtype),
820 Ok(WeightDtype::UInt8)
821 ) {
822 anyhow::ensure!(
823 tp_size == 1,
824 "pre-quantized NVFP4 weight '{weight_key}' (U8 on disk) \
825 cannot be loaded under tensor parallelism (tp_size={tp_size}): \
826 TP sharding of pre-quantized NVFP4 checkpoints is not yet \
827 implemented. Use tp_size=1, or dequantize this checkpoint to \
828 BF16 first so it goes through the shard-then-requantize path."
829 );
830 let null_dense = DenseWeight {
831 weight: spark_runtime::gpu::DevicePtr::NULL,
832 };
833 let qw = quantized_auto(
834 store,
835 &format!("{p}.{name}"),
836 gpu,
837 Nvfp4Variant::Standard,
838 )?;
839 return Ok((null_dense, qw));
840 }
841 let src = dense_auto(store, &weight_key, gpu)?;
842 let (sharded_ptr, local_n, local_k) = shard_dense_bf16(
843 src.weight, full_n, full_k, kind, tp_rank, tp_size, gpu,
844 )?;
845 let sharded = DenseWeight {
846 weight: sharded_ptr,
847 };
848 let q = quantize_to_nvfp4(
849 &sharded, local_n, local_k, gpu, absmax_k, quantize_k, stream,
850 )?;
851 if sharded_ptr != src.weight {
852 gpu.free(sharded_ptr)?;
853 }
854 Ok((src, q))
855 };
856 let [
857 (q_dense, q_nvfp4),
858 (k_dense, k_nvfp4),
859 (v_dense, v_nvfp4),
860 (o_dense, o_nvfp4),
861 ] = load_qkvo_tp(config, load_bf16_then_nvfp4)?;
862
863 let (k_scale, v_scale) = load_kv_scales(store, &p, gpu);
864
865 // The BF16 q/k/v/o dense tensors are only the intermediate
866 // fed to the GPU quantize_to_nvfp4 above. Prefill AND decode
867 // always dispatch the NVFP4 weights, so the BF16 copies are
868 // dead once quantized. Free them instead of retaining a full
869 // second copy of every projection (Atlas issue #A1).
870 gpu.free(q_dense.weight)?;
871 gpu.free(k_dense.weight)?;
872 gpu.free(v_dense.weight)?;
873 gpu.free(o_dense.weight)?;
874
875 let attn = AttentionWeights {
876 q_proj: DenseWeight {
877 weight: spark_runtime::gpu::DevicePtr::NULL,
878 },
879 k_proj: DenseWeight {
880 weight: spark_runtime::gpu::DevicePtr::NULL,
881 },
882 v_proj: DenseWeight {
883 weight: spark_runtime::gpu::DevicePtr::NULL,
884 },
885 o_proj: o_nvfp4,
886 q_norm: dense(store, &format!("{p}.q_norm.weight"))?,
887 k_norm: dense(store, &format!("{p}.k_norm.weight"))?,
888 q_norm_full: None,
889 k_norm_full: None,
890 k_scale,
891 v_scale,
892 };
893 (attn, Some(q_nvfp4), Some(k_nvfp4), Some(v_nvfp4))
894 }
895 Nvfp4Variant::Bf16Raw => {
896 // Native BF16 dense attention: keep Q/K/V/O in BF16
897 // and dispatch the dense_gemv/dense_gemm kernels that
898 // ship in the nvfp4 bundle (common/ dense_*_bf16). No
899 // runtime BF16 -> NVFP4 quant — that lossily quantized
900 // these no-metadata Holo dense checkpoints. Mirrors
901 // qwen35/load_layers.rs:552 (BF16-dequant attention)
902 // and gemma4/loader_a.rs:300/418.
903 let load_bf16_dense =
904 |name: &str,
905 full_n: usize,
906 full_k: usize,
907 kind: TpShardKind|
908 -> Result<DenseWeight> {
909 let src =
910 dense_auto(store, &format!("{p}.{name}.weight"), gpu)?;
911 if tp_size == 1 {
912 return Ok(src);
913 }
914 let (sharded_ptr, _local_n, _local_k) = shard_dense_bf16(
915 src.weight, full_n, full_k, kind, tp_rank, tp_size, gpu,
916 )?;
917 if sharded_ptr != src.weight {
918 gpu.free(src.weight)?;
919 }
920 Ok(DenseWeight {
921 weight: sharded_ptr,
922 })
923 };
924 let [q_dense, k_dense, v_dense, o_dense] =
925 load_qkvo_tp(config, load_bf16_dense)?;
926
927 let (k_scale, v_scale) = load_kv_scales(store, &p, gpu);
928
929 // Keep q/k/v BF16 dense ALIVE (do NOT free): the dense
930 // forward reads them directly. o_proj stays NULL — the
931 // set_o_dense_bf16(o_dense) call after layer build
932 // installs the BF16 O-proj that decode/prefill prefer.
933 let attn = AttentionWeights {
934 q_proj: q_dense,
935 k_proj: k_dense,
936 v_proj: v_dense,
937 o_proj: crate::weight_map::QuantizedWeight::null(),
938 q_norm: dense(store, &format!("{p}.q_norm.weight"))?,
939 k_norm: dense(store, &format!("{p}.k_norm.weight"))?,
940 q_norm_full: None,
941 k_norm_full: None,
942 k_scale,
943 v_scale,
944 };
945 o_dense_bf16 = Some(o_dense);
946 // The NULL o_proj above is only sound while the BF16
947 // dense O-proj is installed after layer build — a null
948 // NVFP4 weight reached by a w4a16 dispatch is the
949 // CUDA-700-at-concurrency failure mode.
950 debug_assert!(
951 o_dense_bf16.is_some(),
952 "Bf16Raw attention must install a dense O-proj to cover the null o_proj"
953 );
954 // BF16 native: no NVFP4 q/k/v weights → dense fallback.
955 (attn, None, None, None)
956 }
957 };
958
959 let mut attn_layer = Qwen3AttentionLayer::new(
960 input_norm,
961 attn,
962 post_attn_norm,
963 ffn,
964 attn_idx,
965 q_nvfp4,
966 k_nvfp4,
967 v_nvfp4,
968 gpu,
969 layer_kv_dtypes[attn_idx],
970 config.fp8_kv_calibration_tokens,
971 config,
972 )?;
973 // Fast-prefill: transposed NVFP4 copies route the 16 full-attn
974 // layers' q/k/v/o prefill GEMMs onto w4a16_gemm_t_m128 (28.8%
975 // of prefill GPU time on the base w4a16_gemm path; ~1.3x e2e).
976 // predequant_for_prefill() is deliberately NOT called: the FP8
977 // predequant route is slower for these bandwidth-bound GEMMs.
978 if let (Some(qw), Some(kw), Some(vw)) = (q_nvfp4, k_nvfp4, v_nvfp4) {
979 let (nh, hd) = (config.num_attention_heads, config.head_dim);
980 let (nkv, hh) = (config.num_key_value_heads, config.hidden_size);
981 let q_n = nh * hd * if config.attn_gated { 2 } else { 1 };
982 let qt = qw.transpose_for_gemm(gpu, q_n, hh)?;
983 let kt = kw.transpose_for_gemm(gpu, nkv * hd, hh)?;
984 let vt = vw.transpose_for_gemm(gpu, nkv * hd, hh)?;
985 let op = &attn_layer.attn.o_proj;
986 let ot = op.transpose_for_gemm(gpu, hh, nh * hd)?;
987 attn_layer.set_prefill_weights(Some(qt), Some(kt), Some(vt), Some(ot));
988 // Fused [q|k|v] twin: k/v are N=1024, which against the
989 // 128-wide N tile is 8 CTAs on 48 SMs (40 idle, 23.6 GB/s,
990 // 9.75x off floor). Concatenated N=14336 runs 112 CTAs in
991 // ONE launch. Bit-identical ONLY when the three share a
992 // single `weight_scale_2` — the GEMM applies one scale2 per
993 // launch — so verify the device values rather than assume.
994 // Bit-exact float comparison, not an epsilon: the GEMM
995 // applies ONE scale2, so anything but exact equality
996 // changes results.
997 let scales_equal = qw.weight_scale_2.to_bits()
998 == kw.weight_scale_2.to_bits()
999 && kw.weight_scale_2.to_bits() == vw.weight_scale_2.to_bits();
1000 if scales_equal {
1001 let fused =
1002 crate::weight_map::QuantizedWeight::transpose_concat_for_gemm(
1003 gpu,
1004 &[(&qw, q_n), (&kw, nkv * hd), (&vw, nkv * hd)],
1005 hh,
1006 )?;
1007 attn_layer.set_fused_qkv_prefill_weight(Some(fused));
1008 } else if attn_idx == 0 {
1009 tracing::warn!(
1010 "attention q/k/v have differing weight_scale_2 — fused QKV GEMM disabled (3 separate launches per layer)"
1011 );
1012 }
1013 }
1014 // Native-BF16 (Bf16Raw): install the dense O-proj so decode +
1015 // prefill prefer it over the (NULL) NVFP4 o_proj. Mutually
1016 // exclusive with the transposed-NVFP4 block above (q_nvfp4 is
1017 // None on the Bf16Raw path).
1018 if let Some(o_dense) = o_dense_bf16 {
1019 attn_layer.set_o_dense_bf16(o_dense);
1020 }
1021 // Install native FP8 q/k/v/o (single-GPU FP8 checkpoint).
1022 // `set_fp8_weights` REPLACES `q/k/v/o_weight`, so on this
1023 // route the NVFP4 weights above are not a fallback — they
1024 // are unreachable, which is why `attn_nvfp4` skipped
1025 // building them (#915). The paths that could still read
1026 // NVFP4 (`ATLAS_CUTLASS_NVFP4_*`, `ATLAS_ATTN_W4A4`) are
1027 // exactly what `RouteEnv::attn_nvfp4` checks.
1028 if attn_fp8 {
1029 let load_fp8_proj = |name: &str,
1030 _n: usize,
1031 _k: usize,
1032 _kind: TpShardKind|
1033 -> Result<Fp8Weight> {
1034 // Only `row_scale` is allocated here; the E4M3
1035 // bytes are store-owned. Adopt it (#736).
1036 let w = load_fp8_block_scaled_as_fp8weight(
1037 store,
1038 &format!("{p}.{name}"),
1039 gpu,
1040 )?;
1041 let bytes =
1042 (w.n as usize).div_ceil(128) * (w.k as usize).div_ceil(128) * 4;
1043 store.derived().adopt(
1044 "attn fp8 block scale (widened)",
1045 w.row_scale,
1046 bytes,
1047 );
1048 Ok(w)
1049 };
1050 let [q_fp8, k_fp8, v_fp8, o_fp8] = load_qkvo_tp(config, load_fp8_proj)?;
1051 attn_layer.set_fp8_weights(
1052 Some(q_fp8),
1053 Some(k_fp8),
1054 Some(v_fp8),
1055 Some(o_fp8),
1056 );
1057 // #915: build only the twins a selected kernel reads,
1058 // and hand each to the store so teardown RELEASES it.
1059 // K/V are NOT optional — `prefill/cache_skip_qkv.rs` has
1060 // no W8A8 arm and is the first-chunk path of every
1061 // request. Table: `Fp8TwinSet` + `fp8_residency.rs`.
1062 let want =
1063 route_env.attn_fp8_twins(true, attn_layer.has_w8a8_prefill_kernels());
1064 let (nh, hd) = (config.num_attention_heads, config.head_dim);
1065 let (nkv, hh) = (config.num_key_value_heads, config.hidden_size);
1066 let q_n = nh * hd * if config.attn_gated { 2 } else { 1 };
1067 let twin_bytes = |set| {
1068 fp8_residency::attn_fp8_twin_bytes(set, q_n, nkv * hd, nh * hd, hh)
1069 };
1070 residency.twins.attn_fp8 |= want.any();
1071 residency.skip(twin_bytes(Fp8TwinSet::ALL) - twin_bytes(want));
1072 residency.keep(twin_bytes(want));
1073 if let Err(e) = attn_layer.transpose_fp8_for_prefill_selected(
1074 gpu,
1075 stream,
1076 want,
1077 Some(store.derived()),
1078 ) {
1079 tracing::warn!("Layer {i}: dense FP8 transpose failed: {e}");
1080 }
1081 }
1082 layers.push(Box::new(attn_layer));
1083 attn_idx += 1;
1084 }
1085 LayerType::LinearAttention => {
1086 let nv = config.linear_num_value_heads;
1087 let nk = config.linear_num_key_heads;
1088 // GDN HeadParallel: config holds per-rank-LOCAL linear head counts
1089 // (topology.rs divided them by tp_size). TpGdnDims rebuilds the FULL
1090 // pre-shard sizes so load/concat/interleave run at FULL, then the
1091 // shard_gdn_* slicers cut this rank's contiguous head range. value_dim
1092 // stays LOCAL (sizes the downstream quantize of the sharded buffers).
1093 let tp_size = config.tp_world_size.max(1);
1094 let dims = TpGdnDims::from_config(config);
1095 let qkv_rows = dims.full_conv_dim();
1096 let z_rows = dims.full_value_dim();
1097 let value_dim = nv * config.linear_value_head_dim;
1098 let la = format!("{lp}.linear_attn");
1099
1100 // Native keep-packed ternary Q2_0 GDN (Tier-1c): the GGUF
1101 // loader kept `in_proj_qkv` (V-region row-permuted) and
1102 // `in_proj_z` (row-permuted) 2-bit. Byte-concat them into the
1103 // fused [Q|K|V|Z] `qkvz` and dispatch `q2_0_gemv_vec` at decode
1104 // / transient-dequant at prefill. `out_proj` (a within-row
1105 // COLUMN reorder) is NOT packed here — it stays NVFP4. a/b/
1106 // conv1d/norm/A_log stay BF16/F32. Requires tp_size=1.
1107 // ATLAS_NO_Q2_GDN forces the BF16/NVFP4 path for A/B bisection.
1108 let gdn_q2 = config.tp_world_size.max(1) == 1
1109 && std::env::var_os("ATLAS_NO_Q2_GDN").is_none()
1110 && proj_q2_group(store, &format!("{la}.in_proj_qkv")).is_some()
1111 && proj_q2_group(store, &format!("{la}.in_proj_z")).is_some();
1112 if gdn_q2 {
1113 let qkv_q2 = packed_q2_from_store(store, &format!("{la}.in_proj_qkv"))?;
1114 let z_q2 = packed_q2_from_store(store, &format!("{la}.in_proj_z"))?;
1115 anyhow::ensure!(
1116 qkv_q2.group == z_q2.group && qkv_q2.k == z_q2.k,
1117 "GDN packed qkv/z group|k mismatch ({},{} vs {},{})",
1118 qkv_q2.group,
1119 qkv_q2.k,
1120 z_q2.group,
1121 z_q2.k
1122 );
1123 // Byte-concat packed rows: [Q|K|V] ++ [Z]. Each row is
1124 // (k/group)*block_bytes; whole-row copy never splits a block.
1125 let group = qkv_q2.group as usize;
1126 let block_bytes = 2 + group / 4;
1127 let row_bytes = (qkv_q2.k as usize / group) * block_bytes;
1128 let qkv_bytes = qkv_q2.n as usize * row_bytes;
1129 let z_bytes = z_q2.n as usize * row_bytes;
1130 let qkvz_buf = gpu.alloc(qkv_bytes + z_bytes)?;
1131 gpu.copy_d2d(qkv_q2.weight, qkvz_buf, qkv_bytes)?;
1132 gpu.copy_d2d(z_q2.weight, qkvz_buf.offset(qkv_bytes), z_bytes)?;
1133 let qkvz_q2 = PackedQ2Weight {
1134 weight: qkvz_buf,
1135 n: qkv_q2.n + z_q2.n,
1136 k: qkv_q2.k,
1137 group: qkv_q2.group,
1138 };
1139 // out_proj + a/b/conv1d/norm are BF16/F32 in the store
1140 // (sidecar dequanted the reorder tensors). out_proj → NVFP4.
1141 let in_proj_a = dense_auto(store, &format!("{la}.in_proj_a.weight"), gpu)?;
1142 let in_proj_b = dense_auto(store, &format!("{la}.in_proj_b.weight"), gpu)?;
1143 let conv1d = dense(store, &format!("{la}.conv1d.weight"))?;
1144 let a_log = dense_keep_f32(store, &format!("{la}.A_log"), gpu)?;
1145 let dt_bias = dense_keep_f32(store, &format!("{la}.dt_bias"), gpu)?;
1146 let norm = dense_f32_safe(store, &format!("{la}.norm.weight"), gpu)?;
1147 let ba_dense = interleave_ba(&in_proj_a, &in_proj_b, nv, nk, h, gpu)?;
1148 let out_proj_dense =
1149 dense_auto(store, &format!("{la}.out_proj.weight"), gpu)?;
1150 let out_proj_nvfp4 = quantize_to_nvfp4(
1151 &out_proj_dense,
1152 h,
1153 value_dim,
1154 gpu,
1155 absmax_k,
1156 quantize_k,
1157 stream,
1158 )?;
1159 let out_proj_nvfp4_t =
1160 out_proj_nvfp4.transpose_for_gemm(gpu, h, value_dim)?;
1161 gpu.free(out_proj_dense.weight)?;
1162 let ssm = SsmWeights {
1163 in_proj_qkvz: DenseWeight {
1164 weight: spark_runtime::gpu::DevicePtr::NULL,
1165 },
1166 in_proj_ba: ba_dense,
1167 conv1d,
1168 a_log,
1169 dt_bias,
1170 norm,
1171 out_proj: out_proj_nvfp4,
1172 };
1173 let mut layer = Qwen3SsmLayer::new_sequential(
1174 input_norm,
1175 ssm,
1176 post_attn_norm,
1177 ffn,
1178 None,
1179 None,
1180 Some(out_proj_nvfp4_t),
1181 config,
1182 gpu,
1183 )?;
1184 layer.set_packed_q2_qkvz(qkvz_q2, gpu);
1185 layer.predequant_for_prefill(gpu, config, stream)?;
1186 tracing::info!(
1187 "SSM[{lp}] native keep-packed Q2_0 GDN: qkvz 2-bit \
1188 (concat qkv+z row-permuted), out_proj NVFP4"
1189 );
1190 layers.push(Box::new(layer));
1191 continue;
1192 }
1193
1194 // SSM projections are loaded per-projection by on-disk dtype:
1195 // each of in_proj_qkv / in_proj_z / out_proj may independently
1196 // be NVFP4-packed (`weight_packed`) or plain (`weight`, routed
1197 // by `dense_auto` → BF16/FP32/FP8). The unsloth NVFP4 re-quant
1198 // of Qwen3.6-27B quantizes ONLY out_proj while keeping the
1199 // in_proj_* in BF16; the old all-or-nothing gate (keyed on
1200 // in_proj_qkv.weight_packed) then looked for a non-existent
1201 // out_proj.weight and failed to build. `dense_auto` is dequant-
1202 // to-BF16 for the concat pipeline regardless of source dtype.
1203 let load_ssm_proj =
1204 |name: &str, rows: usize, cols: usize| -> Result<DenseWeight> {
1205 if store.contains(&format!("{name}.weight_packed")) {
1206 dequant_nvfp4_to_bf16(store, name, rows, cols, gpu)
1207 } else if matches!(
1208 store.get(&format!("{name}.weight")).map(|w| w.dtype),
1209 Ok(WeightDtype::UInt8)
1210 ) {
1211 // Standard-convention NVFP4 (packed bytes at
1212 // `.weight`, not `.weight_packed`) — same dequant,
1213 // different on-disk key.
1214 dequant_nvfp4_to_bf16(store, name, rows, cols, gpu)
1215 } else {
1216 dense_auto(store, &format!("{name}.weight"), gpu)
1217 }
1218 };
1219 // Native FP8 GDN (nvidia mixed-precision checkpoint): the
1220 // in_proj_qkv / in_proj_z / out_proj projections ship as
1221 // F8_E4M3 + per-tensor scale — modelopt's sensitivity
1222 // analysis keeps the SSM projections high-precision. The
1223 // default path (`load_ssm_proj` → `dense_auto`) dequants to
1224 // BF16 then RE-quantizes to NVFP4 (4-bit), a lossy
1225 // double-quant of these 48/64 layers that regressed BFCL-ST
1226 // ~7pt (non_live 85.4→76.6). Load the on-disk FP8 directly
1227 // (concat qkv+z on-device into [Q|K|V|Z] order) and route
1228 // BOTH prefill (w8a16_gemm_pipelined) and decode
1229 // (w8a16_gemv) through the fp8w fields — no requant, decode
1230 // stays fast (FP8 = half BF16's weight bytes). MUST run
1231 // BEFORE `load_ssm_proj` consumes the store tensors.
1232 // Internal opt-out for the FP8-vs-NVFP4 GDN A/B + KL-drift
1233 // gate (not a user choice; mirrors the `ATLAS_NO_*` debug
1234 // levers). Default engages native FP8.
1235 if gdn_fp8_arm_selected(store, &la, config.tp_world_size) {
1236 let in_proj_a = dense(store, &format!("{la}.in_proj_a.weight"))?;
1237 let in_proj_b = dense(store, &format!("{la}.in_proj_b.weight"))?;
1238 let conv1d = dense(store, &format!("{la}.conv1d.weight"))?;
1239 let a_log = dense_keep_f32(store, &format!("{la}.A_log"), gpu)?;
1240 let dt_bias = dense_keep_f32(store, &format!("{la}.dt_bias"), gpu)?;
1241 let norm = dense_f32_safe(store, &format!("{la}.norm.weight"), gpu)?;
1242 let ba_dense = interleave_ba(&in_proj_a, &in_proj_b, nv, nk, h, gpu)?;
1243 let qkv_f = load_fp8_block_scaled_as_fp8weight(
1244 store,
1245 &format!("{la}.in_proj_qkv"),
1246 gpu,
1247 )?;
1248 let z_f = load_fp8_block_scaled_as_fp8weight(
1249 store,
1250 &format!("{la}.in_proj_z"),
1251 gpu,
1252 )?;
1253 let out_f = load_fp8_block_scaled_as_fp8weight(
1254 store,
1255 &format!("{la}.out_proj"),
1256 gpu,
1257 )?;
1258 let qkvz_f = concat_fp8_block_scaled(&qkv_f, &z_f, h, gpu)?;
1259 // The concat copied both grids; free the per-projection
1260 // scale allocs (weight bytes are store-owned, not freed).
1261 let scale_bytes =
1262 |n: usize, k: usize| n.div_ceil(128) * k.div_ceil(128) * 4;
1263 gpu.free(qkv_f.row_scale)?;
1264 gpu.free(z_f.row_scale)?;
1265 residency.free(
1266 scale_bytes(qkv_f.n as usize, h) + scale_bytes(z_f.n as usize, h),
1267 );
1268 // #736/#915: the fused `[QKV|Z]` weight is the SSM's
1269 // hottest tensor and there is no un-fused dispatch to
1270 // fall back to, so it STAYS — but it is a derived copy
1271 // that no `ModelResource` owned, which is the
1272 // 3,840 MB x48 row of the H100 teardown sweep. Adopt it.
1273 let qkvz_bytes = (qkvz_f.n as usize) * h;
1274 let qkvz_scale_bytes =
1275 scale_bytes(qkv_f.n as usize, h) + scale_bytes(z_f.n as usize, h);
1276 let ba_bytes = nv * 2 * h * 2; // [2*nv, h] BF16
1277 let out_scale_bytes = scale_bytes(out_f.n as usize, out_f.k as usize);
1278 let d = store.derived();
1279 d.adopt("ssm qkvz fp8 concat", qkvz_f.weight, qkvz_bytes);
1280 d.adopt(
1281 "ssm qkvz fp8 block scale",
1282 qkvz_f.row_scale,
1283 qkvz_scale_bytes,
1284 );
1285 d.adopt(
1286 "ssm out_proj fp8 block scale",
1287 out_f.row_scale,
1288 out_scale_bytes,
1289 );
1290 d.adopt("ssm in_proj_ba interleaved", ba_dense.weight, ba_bytes);
1291 residency.keep(qkvz_bytes + qkvz_scale_bytes + out_scale_bytes + ba_bytes);
1292 residency.twins.ssm_fp8_concat = true;
1293 let ssm = SsmWeights {
1294 in_proj_qkvz: DenseWeight {
1295 weight: spark_runtime::gpu::DevicePtr::NULL,
1296 },
1297 in_proj_ba: ba_dense,
1298 conv1d,
1299 a_log,
1300 dt_bias,
1301 norm,
1302 out_proj: crate::weight_map::QuantizedWeight::null(),
1303 };
1304 let mut layer = Qwen3SsmLayer::new_sequential(
1305 input_norm,
1306 ssm,
1307 post_attn_norm,
1308 ffn,
1309 None,
1310 None,
1311 None,
1312 config,
1313 gpu,
1314 )?;
1315 layer.set_fp8_decode_weights(Some(qkvz_f), Some(out_f));
1316 tracing::info!(
1317 "SSM[{lp}] native FP8 GDN: qkvz+out_proj block-scaled FP8 \
1318 (no NVFP4 requant; prefill+decode via w8a16)"
1319 );
1320 layers.push(Box::new(layer));
1321 continue;
1322 }
1323
1324 // A, B, conv1d, A_log, dt_bias, norm are independent of the
1325 // qkv/z/out_proj on-disk format below — load them once up
1326 // front so both the native-NVFP4 fast path and the legacy
1327 // dequant/requant path can share them.
1328 //
1329 // A_log and dt_bias MUST be FP32 — consumer kernels in
1330 // `ssm_preprocess.cu` and `mamba2_ssm_decode.cu` declare
1331 // them `const float*`. Loading via `dense()` kept BF16
1332 // storage, reinterpreting 48-elt BF16 (96B) as 48-elt
1333 // FP32 → per-head scrambled decay gates and exponential
1334 // error amplification through GDR recurrence at long
1335 // context. The MoE sister loader (`ssm_qwen35.rs`)
1336 // already promotes these; dense was missing the mirror.
1337 //
1338 // in_proj_a/b: route through `load_ssm_proj` (not the raw
1339 // `dense()` byte-reinterpret) so a Standard-NVFP4 A/B
1340 // (U8-packed) checkpoint dequants correctly instead of
1341 // being read as BF16 garbage.
1342 let in_proj_a = load_ssm_proj(&format!("{la}.in_proj_a"), nv, h)?;
1343 let in_proj_b = load_ssm_proj(&format!("{la}.in_proj_b"), nv, h)?;
1344 let conv1d = dense(store, &format!("{la}.conv1d.weight"))?;
1345 let a_log = dense_keep_f32(store, &format!("{la}.A_log"), gpu)?;
1346 let dt_bias = dense_keep_f32(store, &format!("{la}.dt_bias"), gpu)?;
1347 // norm.weight: use `dense_f32_safe` (FP32-aware: detects
1348 // a fp32 checkpoint and truncates to BF16 with logging;
1349 // bf16 passes through). Mirrors `weight_map/ssm_qwen35.rs`
1350 // MoE sister loader (backported here 2026-05-20).
1351 let norm = dense_f32_safe(store, &format!("{la}.norm.weight"), gpu)?;
1352 let ba_dense = interleave_ba(&in_proj_a, &in_proj_b, nv, nk, h, gpu)?;
1353 let qkvz_size = config.ssm_qkvz_size();
1354
1355 // Native Standard-NVFP4 GDN (pre-quantized checkpoint, e.g.
1356 // sakamakismile): in_proj_qkv / in_proj_z / out_proj ship
1357 // U8-packed NVFP4 directly on disk (`.weight` dtype UInt8,
1358 // not `.weight_packed` — that's the compressed-tensors
1359 // convention `load_ssm_proj` already dequants above). Load
1360 // them straight into `QuantizedWeight` and concat on GPU,
1361 // skipping the BF16-dequant→re-quantize roundtrip entirely
1362 // (that roundtrip is what the FP8/BF16-opt-in paths above
1363 // exist to avoid for FP8-native and BF16-preferring
1364 // checkpoints; here there's no lossy step to avoid in the
1365 // first place — the data is already NVFP4). Requires all
1366 // three projections to be U8; a partial-U8 checkpoint
1367 // falls through to the legacy path below, where the
1368 // `load_ssm_proj` UInt8 branch added above still dequants
1369 // each U8 tensor correctly on its own.
1370 let native_nvfp4 = matches!(
1371 store
1372 .get(&format!("{la}.in_proj_qkv.weight"))
1373 .map(|w| w.dtype),
1374 Ok(WeightDtype::UInt8)
1375 ) && matches!(
1376 store
1377 .get(&format!("{la}.in_proj_z.weight"))
1378 .map(|w| w.dtype),
1379 Ok(WeightDtype::UInt8)
1380 ) && matches!(
1381 store.get(&format!("{la}.out_proj.weight")).map(|w| w.dtype),
1382 Ok(WeightDtype::UInt8)
1383 );
1384 if native_nvfp4 {
1385 let qkv_qw = quantized_auto(
1386 store,
1387 &format!("{la}.in_proj_qkv"),
1388 gpu,
1389 Nvfp4Variant::Standard,
1390 )?;
1391 let z_qw = quantized_auto(
1392 store,
1393 &format!("{la}.in_proj_z"),
1394 gpu,
1395 Nvfp4Variant::Standard,
1396 )?;
1397 let qkvz_nvfp4 = qkv_qw.concat_rows(&z_qw, qkv_rows, z_rows, h, gpu)?;
1398 let qkvz_nvfp4_t = qkvz_nvfp4.transpose_for_gemm(gpu, qkvz_size, h)?;
1399
1400 let out_proj_nvfp4 = quantized_auto(
1401 store,
1402 &format!("{la}.out_proj"),
1403 gpu,
1404 Nvfp4Variant::Standard,
1405 )?;
1406 let out_proj_nvfp4_t =
1407 out_proj_nvfp4.transpose_for_gemm(gpu, h, value_dim)?;
1408
1409 let ssm = SsmWeights {
1410 in_proj_qkvz: DenseWeight {
1411 weight: spark_runtime::gpu::DevicePtr::NULL,
1412 },
1413 in_proj_ba: ba_dense,
1414 conv1d,
1415 a_log,
1416 dt_bias,
1417 norm,
1418 out_proj: out_proj_nvfp4,
1419 };
1420 let mut layer = Qwen3SsmLayer::new_sequential(
1421 input_norm,
1422 ssm,
1423 post_attn_norm,
1424 ffn,
1425 Some(qkvz_nvfp4),
1426 Some(qkvz_nvfp4_t),
1427 Some(out_proj_nvfp4_t),
1428 config,
1429 gpu,
1430 )?;
1431 layer.predequant_for_prefill(gpu, config, stream)?;
1432 tracing::info!(
1433 "SSM[{lp}] native NVFP4 GDN: qkvz+out_proj loaded pre-quantized \
1434 (U8-packed on disk; no BF16 dequant/requant roundtrip)"
1435 );
1436 layers.push(Box::new(layer));
1437 continue;
1438 }
1439
1440 // PER-ROW FP8 for the row-wise cuBLASLt PREFILL arm
1441 // (`ATLAS_FP8_ROWWISE=1`). Read from the store BEFORE the
1442 // dequant below, though the bytes are store-owned either
1443 // way; the NVFP4 build continues underneath because decode
1444 // still needs it — `w8a16_gemv` cannot index a per-row
1445 // scale. See weight_loader/qwen35_dense/rowwise_fp8.rs.
1446 let rowwise_gdn = rowwise_fp8::rowwise_fp8_enabled()
1447 && rowwise_fp8::proj_is_fp8_per_row(store, &format!("{la}.in_proj_qkv"))
1448 && rowwise_fp8::proj_is_fp8_per_row(store, &format!("{la}.in_proj_z"))
1449 && rowwise_fp8::proj_is_fp8_per_row(store, &format!("{la}.out_proj"));
1450 let (qkvz_rowwise, out_proj_rowwise) = if rowwise_gdn && tp_size == 1 {
1451 let qkv_r = rowwise_fp8::load_fp8_per_row(
1452 store,
1453 &format!("{la}.in_proj_qkv"),
1454 gpu,
1455 )?;
1456 let z_r =
1457 rowwise_fp8::load_fp8_per_row(store, &format!("{la}.in_proj_z"), gpu)?;
1458 let out_r =
1459 rowwise_fp8::load_fp8_per_row(store, &format!("{la}.out_proj"), gpu)?;
1460 let qkvz_r = rowwise_fp8::concat_fp8_per_row(&qkv_r, &z_r, h, gpu)?;
1461 // The concat copied both scale vectors; free the
1462 // per-projection allocs. Weight bytes are store-owned.
1463 gpu.free(qkv_r.row_scale)?;
1464 gpu.free(z_r.row_scale)?;
1465 (Some(qkvz_r), Some(out_r))
1466 } else {
1467 (None, None)
1468 };
1469
1470 let qkv_dense = load_ssm_proj(&format!("{la}.in_proj_qkv"), qkv_rows, h)?;
1471 let z_dense = load_ssm_proj(&format!("{la}.in_proj_z"), z_rows, h)?;
1472 let out_proj_dense =
1473 load_ssm_proj(&format!("{la}.out_proj"), h, dims.full_value_dim())?;
1474
1475 let qkvz_dense =
1476 gpu_concat_rows(&qkv_dense, qkv_rows, &z_dense, z_rows, h, gpu)?;
1477 // qkv/z BF16 are only inputs to the concat above; free them now
1478 // rather than leaking them for the layer's lifetime (Atlas issue #A1).
1479 gpu.free(qkv_dense.weight)?;
1480 gpu.free(z_dense.weight)?;
1481
1482 let ba_dense =
1483 interleave_ba(&in_proj_a, &in_proj_b, dims.full_nv, dims.full_nk, h, gpu)?;
1484
1485 // GDN HeadParallel shard: cut the FULL concat/interleave/on-disk buffers
1486 // to this rank's contiguous head range (mirrors the MoE loader,
1487 // linear_attn_arms.rs). Runs BEFORE both consumers below (the Bf16Raw
1488 // branch AND the NVFP4/FP8 path), so both get sharded weights. qkvz
1489 // (segmented [Q|K|V|Z]) + conv (segmented [Q|K|V]) slice per-block; ba
1490 // slices contiguously; a_log/dt_bias are per-value-head FP32 scalars;
1491 // out_proj is row-parallel on value_dim (partials summed by the
1492 // post-out_proj all-reduce already in Qwen3SsmLayer::forward). norm is
1493 // [vd] shared across value heads -> REPLICATE (never sliced). Free only
1494 // the fresh qkvz/ba concat buffers; conv/a_log/dt_bias/out_proj sources
1495 // are store aliases or dequant bufs freed downstream (the NVFP4 path
1496 // frees the sharded qkvz/out_proj later). At tp==1 the else arm is a
1497 // pure pass-through and every slicer no-ops -> byte-identical.
1498 let (qkvz_dense, ba_dense, conv1d, a_log, dt_bias, out_proj_dense) = if tp_size
1499 > 1
1500 {
1501 let d_conv = config.linear_conv_kernel_dim;
1502 let (qkvz_ptr, _, _) = shard_gdn_qkvz_rows(qkvz_dense.weight, &dims, gpu)?;
1503 gpu.free(qkvz_dense.weight)?;
1504 let (ba_ptr, _, _) = shard_gdn_ba_rows(ba_dense.weight, &dims, gpu)?;
1505 gpu.free(ba_dense.weight)?;
1506 let (conv_ptr, _, _) =
1507 shard_gdn_conv_rows(conv1d.weight, &dims, d_conv, gpu)?;
1508 let (a_log_ptr, _) =
1509 shard_gdn_value_vector(a_log.weight, &dims, 1, 4, gpu)?;
1510 let (dt_bias_ptr, _) =
1511 shard_gdn_value_vector(dt_bias.weight, &dims, 1, 4, gpu)?;
1512 let (out_ptr, _, _) =
1513 shard_gdn_out_proj_row_parallel(out_proj_dense.weight, &dims, gpu)?;
1514 (
1515 DenseWeight { weight: qkvz_ptr },
1516 DenseWeight { weight: ba_ptr },
1517 DenseWeight { weight: conv_ptr },
1518 DenseWeight { weight: a_log_ptr },
1519 DenseWeight {
1520 weight: dt_bias_ptr,
1521 },
1522 DenseWeight { weight: out_ptr },
1523 )
1524 } else {
1525 (qkvz_dense, ba_dense, conv1d, a_log, dt_bias, out_proj_dense)
1526 };
1527
1528 // Native-BF16 SSM arm (no-metadata dense Holo checkpoints:
1529 // Nvfp4Variant::Bf16Raw). The standard nvfp4 bundle ships the
1530 // common/ BF16 dense kernels (dense_gemv_bf16 / dense_gemm_bf16
1531 // / dense_gemm_bf16_pipelined) that every SSM forward arm's
1532 // dense fallback already dispatches, so keep the concatenated
1533 // qkvz_dense [Q|K|V|Z] and out_proj_dense ALIVE and route
1534 // through those instead of the lossy BF16->NVFP4 runtime
1535 // requant. No NVFP4/FP8 copy is built at all: in_proj_qkvz +
1536 // out_proj_dense feed dense_gemv (per-seq decode,
1537 // ssm_forward.rs:107/412), dense_gemm (batched decode,
1538 // trait_decode_batched.rs:113/350 + ssm_batched.rs:180/279)
1539 // and dense_gemm_bf16_pipelined (prefill,
1540 // trait_prefill_proj.rs:298 + trait_prefill_helper.rs:89).
1541 // ssm.out_proj stays null — every out_proj arm prefers
1542 // out_proj_dense when Some; predequant_for_prefill /
1543 // set_fp8_prefill_only_weights are skipped (NVFP4/FP8 only).
1544 if matches!(variant, Nvfp4Variant::Bf16Raw) {
1545 let ssm = SsmWeights {
1546 in_proj_qkvz: qkvz_dense,
1547 in_proj_ba: ba_dense,
1548 conv1d,
1549 a_log,
1550 dt_bias,
1551 norm,
1552 out_proj: crate::weight_map::QuantizedWeight::null(),
1553 };
1554 let mut layer = Qwen3SsmLayer::new_sequential(
1555 input_norm,
1556 ssm,
1557 post_attn_norm,
1558 ffn,
1559 None, // qkvz_nvfp4 — BF16 dense fallback used instead
1560 None, // qkvz_nvfp4_t
1561 None, // out_proj_nvfp4_t
1562 config,
1563 gpu,
1564 )?;
1565 // pub field (qwen3_ssm/mod.rs:46); selected by every
1566 // out_proj arm (ssm_forward.rs:412,
1567 // trait_decode_batched.rs:350, ssm_batched.rs:279,
1568 // trait_prefill_helper.rs:89).
1569 layer.out_proj_dense = Some(out_proj_dense);
1570 layers.push(Box::new(layer));
1571 continue;
1572 }
1573
1574 let qkvz_size = config.ssm_qkvz_size();
1575
1576 // GDN ≥FP8 precision policy (2026-07-04). The nvidia
1577 // Qwen3.6-27B-NVFP4 checkpoint ships GDN in_proj_qkv /
1578 // out_proj as native F8_E4M3 (modelopt sensitivity
1579 // analysis deliberately keeps the SSM projections
1580 // high-precision); `load_ssm_proj` dequants them to BF16,
1581 // and the code below then RE-quantizes to NVFP4 (4-bit) —
1582 // a lossy double-quant of the exact tensors the toolchain
1583 // protected. That regressed BFCL-ST ~7pt (non_live 85.4→
1584 // 76.6) vs the 06-15 reference. When enabled, keep the
1585 // BF16 dequant (≥FP8) and route qkvz + out_proj through the
1586 // dense_gemv / dense_gemm dispatch (in_proj_qkvz +
1587 // out_proj_dense fields), mirroring the MoE sister loader's
1588 // arm (qwen35/load_layers/linear_attn_arms.rs). Gated for a
1589 // clean A/B + KL-drift gate before flipping the default.
1590 let gdn_bf16 = matches!(
1591 std::env::var("ATLAS_GDN_BF16_WEIGHTS").ok().as_deref(),
1592 Some("1")
1593 );
1594 if gdn_bf16 {
1595 let ssm = SsmWeights {
1596 in_proj_qkvz: DenseWeight {
1597 weight: qkvz_dense.weight,
1598 },
1599 in_proj_ba: ba_dense,
1600 conv1d,
1601 a_log,
1602 dt_bias,
1603 norm,
1604 // Unused: out_proj_dense (set below) has higher
1605 // dispatch priority in both prefill and decode.
1606 out_proj: crate::weight_map::QuantizedWeight::null(),
1607 };
1608 let mut layer = Qwen3SsmLayer::new_sequential(
1609 input_norm,
1610 ssm,
1611 post_attn_norm,
1612 ffn,
1613 None,
1614 None,
1615 None,
1616 config,
1617 gpu,
1618 )?;
1619 layer.out_proj_dense = Some(out_proj_dense);
1620 tracing::info!(
1621 "SSM[{lp}] ATLAS_GDN_BF16_WEIGHTS: qkvz + out_proj kept BF16 \
1622 (≥FP8; NVFP4 requant skipped)"
1623 );
1624 layers.push(Box::new(layer));
1625 continue;
1626 }
1627
1628 let qkvz_nvfp4 = quantize_to_nvfp4(
1629 &qkvz_dense,
1630 qkvz_size,
1631 h,
1632 gpu,
1633 absmax_k,
1634 quantize_k,
1635 stream,
1636 )?;
1637
1638 let qkvz_nvfp4_t = qkvz_nvfp4.transpose_for_gemm(gpu, qkvz_size, h)?;
1639
1640 let out_proj_nvfp4 = quantize_to_nvfp4(
1641 &out_proj_dense,
1642 h,
1643 value_dim,
1644 gpu,
1645 absmax_k,
1646 quantize_k,
1647 stream,
1648 )?;
1649
1650 let out_proj_nvfp4_t = out_proj_nvfp4.transpose_for_gemm(gpu, h, value_dim)?;
1651
1652 // Native FP8 SSM prefill GEMM: build a single-scale FP8
1653 // copy of `qkvz_dense` [qkvz_size, h] and `out_proj_dense`
1654 // [h, value_dim] by direct BF16→FP8 truncation. SSM weight
1655 // magnitudes fit in FP8 E4M3 range (|w| ≤ 448), so no
1656 // separate scalar dequant is needed at GEMM time — the
1657 // `fp8_gemm_n128` kernel interprets the FP8 bytes as
1658 // values directly (mirrors how `predequant_nvfp4_to_fp8`
1659 // bakes `scale2` into the FP8 stream). PCND: gated.
1660 let (qkvz_fp8_prefill, out_proj_fp8_prefill) =
1661 if let Some(b2f_k) = bf16_to_fp8_k {
1662 let qkvz_total = (qkvz_size * h) as u32;
1663 let qkvz_fp8 = gpu.alloc(qkvz_size * h)?;
1664 crate::layers::ops::bf16_to_fp8(
1665 gpu,
1666 b2f_k,
1667 qkvz_dense.weight,
1668 qkvz_fp8,
1669 qkvz_total,
1670 stream,
1671 )?;
1672 let out_total = (h * value_dim) as u32;
1673 let out_fp8 = gpu.alloc(h * value_dim)?;
1674 crate::layers::ops::bf16_to_fp8(
1675 gpu,
1676 b2f_k,
1677 out_proj_dense.weight,
1678 out_fp8,
1679 out_total,
1680 stream,
1681 )?;
1682 gpu.synchronize(stream)?;
1683 (Some(qkvz_fp8), Some(out_fp8))
1684 } else {
1685 (None, None)
1686 };
1687
1688 // SSM prefill/decode always dispatch qkvz_nvfp4/_t and the NVFP4
1689 // out_proj; the BF16 qkvz_dense / out_proj_dense were only quantize
1690 // inputs. Free them rather than keep a third full-precision copy of
1691 // the largest SSM tensor across every layer (Atlas issue #A1).
1692 gpu.free(qkvz_dense.weight)?;
1693 gpu.free(out_proj_dense.weight)?;
1694
1695 let ssm = SsmWeights {
1696 in_proj_qkvz: DenseWeight {
1697 weight: spark_runtime::gpu::DevicePtr::NULL,
1698 },
1699 in_proj_ba: ba_dense,
1700 conv1d,
1701 a_log,
1702 dt_bias,
1703 norm,
1704 out_proj: out_proj_nvfp4,
1705 };
1706
1707 let mut layer = Qwen3SsmLayer::new_sequential(
1708 input_norm,
1709 ssm,
1710 post_attn_norm,
1711 ffn,
1712 Some(qkvz_nvfp4),
1713 Some(qkvz_nvfp4_t),
1714 Some(out_proj_nvfp4_t),
1715 config,
1716 gpu,
1717 )?;
1718 layer.predequant_for_prefill(gpu, config, stream)?;
1719 // Install the FP8 prefill weights AFTER `predequant_for_prefill`
1720 // (which sets `out_proj_fp8` from NVFP4 + scale2). The
1721 // native-FP8 path overrides both pointers when active,
1722 // routing prefill through `fp8_gemm_n128` instead of
1723 // `w4a16_gemm_t`. Decode batch paths keep their NVFP4
1724 // fallback (the `qkvz_nvfp4*` fields above).
1725 if qkvz_fp8_prefill.is_some() || out_proj_fp8_prefill.is_some() {
1726 layer.set_fp8_prefill_only_weights(qkvz_fp8_prefill, out_proj_fp8_prefill);
1727 }
1728 // …and LAST, so it wins over both of the prefill installs
1729 // above: the checkpoint's own per-row FP8, which reaches
1730 // the GEMM with no conversion at all. Decode is untouched.
1731 if qkvz_rowwise.is_some() {
1732 layer.set_fp8_rowwise_prefill_weights(qkvz_rowwise, out_proj_rowwise);
1733 if i == 0 {
1734 tracing::info!(
1735 "SSM[{lp}] ATLAS_FP8_ROWWISE: qkvz + out_proj prefill via \
1736 native per-row FP8 (no BF16 dequant, no NVFP4 requant); \
1737 decode keeps NVFP4"
1738 );
1739 }
1740 }
1741 layers.push(Box::new(layer));
1742 }
1743 LayerType::SlidingAttention => {
1744 unreachable!("unexpected SlidingAttention in this loader")
1745 }
1746 LayerType::Moe => unreachable!("Qwen3.5 dense has no standalone MoE layers"),
1747 // GLM-5.3's `deepseek_sparse_attention`: a full-rank mixer whose visible key set
1748 // is chosen at runtime by an indexer. Hard error, not a silent fallthrough into
1749 // the dense-attention arm -- that would attend over the WHOLE cache and look right.
1750 LayerType::SparseAttention => anyhow::bail!(
1751 "layer {i}: SparseAttention needs a DSA indexer and per-query top-k; Qwen3.5 dense has neither"
1752 ),
1753 }
1754
1755 if (i + 1) % 10 == 0 {
1756 tracing::info!("Loaded layers 0..{}", i + 1);
1757 spark_runtime::progress::layer(i + 1, config.num_hidden_layers);
1758 }
1759 }
1760
1761 tracing::info!(
1762 "Qwen3.5 dense weight loader: {} layers ({} attention, {} SSM, dense FFN)",
1763 layers.len(),
1764 attn_idx,
1765 layers.len() - attn_idx,
1766 );
1767 // #915: the line the next H100 serve is read against. `weights` is the
1768 // checkpoint as the ledger sees it; `derived` is everything this loader
1769 // built on top of it and now OWNS (released at teardown, not swept);
1770 // `not built` is what the pre-#915 loader allocated here and this one
1771 // proved unreachable. Emitted unconditionally — a residency regression
1772 // that only shows under a debug flag is one nobody sees.
1773 tracing::info!("{}", residency.summary(store.resident_bytes()));
1774 for (label, bytes, count) in store.derived().by_label() {
1775 tracing::debug!(
1776 " derived-weight owner: {:>9.1} MB x{:<5} {label}",
1777 bytes as f64 / (1024.0 * 1024.0),
1778 count,
1779 );
1780 }
1781
1782 Ok(layers)
1783 }
1784
1785 /// Drop the SSM source tensors the native-FP8 GDN arm copied and no longer
1786 /// reads (#915).
1787 ///
1788 /// That arm builds a fused `[QKV|Z]` FP8 weight by device-to-device
1789 /// appending `in_proj_qkv` and `in_proj_z` (`concat_fp8_block_scaled`), and
1790 /// builds `in_proj_ba` by a D2H -> host interleave -> H2D round trip
1791 /// (`interleave_ba`). All four store originals are dead the moment
1792 /// `load_layers` returns — 80 MiB + ~1 MB per SSM layer, ~3.9 GB across the
1793 /// 48 GDN layers of Qwen3.8-27B-FP8, which is what the fused copy costs.
1794 /// Keeping both is the duplicate that came straight out of the KV budget.
1795 ///
1796 /// 🪤 Narrow on purpose. `out_proj.weight` IS `out_proj_fp8w.weight`
1797 /// (zero-copy from the store), `conv1d`, `A_log`, `dt_bias` and
1798 /// `norm.weight` are aliased or conditionally aliased depending on their
1799 /// on-disk dtype, and every attention tensor — and `mlp.down_proj` — is
1800 /// bound zero-copy. Only the names below are freed, and only for layers
1801 /// where the module-private predicate that SELECTED the corresponding
1802 /// concat (`gdn_fp8_arm_selected` for the SSM `[QKV|Z]` weight,
1803 /// `ffn_gateup_fused_selected` for the dense-FFN gate+up weight, #927)
1804 /// says that arm actually ran.
1805 fn prune_after_load(
1806 &self,
1807 store: &mut WeightStore,
1808 config: &ModelConfig,
1809 gpu: &dyn GpuBackend,
1810 ) -> Result<()> {
1811 // Same resolution `load_layers` uses: an explicit `layer_types` list
1812 // wins over the computed pattern, and pruning must be decided from the
1813 // list that actually selected the arms.
1814 let layer_types = if config.layer_types.is_empty() {
1815 (0..config.num_hidden_layers)
1816 .map(|i| config.layer_type(i))
1817 .collect::<Vec<_>>()
1818 } else {
1819 config.layer_types.clone()
1820 };
1821 let mut doomed: std::collections::HashSet<String> = std::collections::HashSet::new();
1822 // The dense-FFN gate+up fusion (#927) consumed these the same way the
1823 // SSM `[QKV|Z]` concat below consumes its two: the fused buffer holds
1824 // the bytes, and `gate_proj`/`up_proj` are VIEWS inside it, so nothing
1825 // aliases the store tensors any more. Releasing them is what makes the
1826 // fusion residency-neutral; keeping them would be the 11.4 GB the
1827 // fused arm exists to not spend. `down_proj` is NOT pruned — it is
1828 // still bound zero-copy.
1829 //
1830 // Same `variant` the load used (`detect_nvfp4_variant` is a pure
1831 // function of the store and the config), and the SAME predicate, so a
1832 // layer that did not fuse cannot be pruned here.
1833 let variant = detect_nvfp4_variant(store, config);
1834 for i in 0..layer_types.len() {
1835 let lp = config.layer_prefix(i);
1836 if !ffn_gateup_fused_selected(store, config, variant, &lp) {
1837 continue;
1838 }
1839 for proj in ["gate_proj", "up_proj"] {
1840 for leaf in ["weight", "weight_scale_inv", "weight_scale"] {
1841 doomed.insert(format!("{lp}.mlp.{proj}.{leaf}"));
1842 }
1843 }
1844 }
1845 for (i, lt) in layer_types.iter().enumerate() {
1846 if *lt != LayerType::LinearAttention {
1847 continue;
1848 }
1849 let la = format!("{}.linear_attn", config.layer_prefix(i));
1850 if !gdn_fp8_arm_selected(store, &la, config.tp_world_size) {
1851 continue;
1852 }
1853 for leaf in [
1854 "in_proj_qkv.weight",
1855 "in_proj_qkv.weight_scale_inv",
1856 "in_proj_qkv.weight_scale",
1857 "in_proj_z.weight",
1858 "in_proj_z.weight_scale_inv",
1859 "in_proj_z.weight_scale",
1860 "in_proj_a.weight",
1861 "in_proj_b.weight",
1862 ] {
1863 doomed.insert(format!("{la}.{leaf}"));
1864 }
1865 }
1866 if doomed.is_empty() {
1867 return Ok(());
1868 }
1869 let (count, bytes) = store.free_matching(gpu, |name| doomed.contains(name))?;
1870 tracing::info!(
1871 "native FP8: released {count} store tensors ({:.2} GB) consumed by the fused [QKV|Z] SSM concat, the BA interleave and the dense-FFN gate+up fusion; out_proj/conv1d/A_log/dt_bias/norm/down_proj kept (still aliased)",
1872 bytes as f64 / 1e9,
1873 );
1874 Ok(())
1875 }
1876
1877 fn load_embedding(
1878 &self,
1879 store: &WeightStore,
1880 config: &ModelConfig,
1881 _gpu: &dyn GpuBackend,
1882 ) -> Result<DenseWeight> {
1883 loaders_b::load_embedding(store, config)
1884 }
1885
1886 fn load_final_norm(
1887 &self,
1888 store: &WeightStore,
1889 config: &ModelConfig,
1890 _gpu: &dyn GpuBackend,
1891 ) -> Result<DenseWeight> {
1892 loaders_b::load_final_norm(store, config)
1893 }
1894
1895 fn load_lm_head(
1896 &self,
1897 store: &WeightStore,
1898 config: &ModelConfig,
1899 gpu: &dyn GpuBackend,
1900 ) -> Result<DenseWeight> {
1901 loaders_b::load_lm_head(store, config, gpu)
1902 }
1903
1904 fn load_mtp_weights(
1905 &self,
1906 store: &WeightStore,
1907 config: &ModelConfig,
1908 gpu: &dyn GpuBackend,
1909 ) -> Result<Option<MtpWeights>> {
1910 if !store.contains("mtp.fc.weight") {
1911 return Ok(None);
1912 }
1913 let variant = detect_nvfp4_variant(store, config);
1914 tracing::info!(
1915 "Loading dense MTP weights (variant={:?}, hidden={}, inter={})",
1916 variant,
1917 config.hidden_size,
1918 config.intermediate_size,
1919 );
1920 // `load_mtp` auto-detects MoE vs dense FFN by inspecting the weight
1921 // names. For dense Qwen3.6-27B-FP8 it returns a MtpWeights with
1922 // `dense_ffn = Some(...)` and NULL placeholders for the MoE fields.
1923 let mtp = load_mtp(store, config.num_experts, gpu, variant)?;
1924 if mtp.dense_ffn.is_some() {
1925 tracing::info!("Dense MTP head ready (FP8 e4m3 projections + dense gate/up/down MLP)");
1926 } else {
1927 tracing::info!(
1928 "MoE MTP head ready ({} experts) — dense loader sees MoE bundle",
1929 mtp.experts.len(),
1930 );
1931 }
1932 Ok(Some(mtp))
1933 }
1934
1935 fn load_vision_encoder(
1936 &self,
1937 store: &WeightStore,
1938 config: &ModelConfig,
1939 gpu: &dyn GpuBackend,
1940 ) -> Result<Option<crate::layers::VisionEncoder>> {
1941 // Dense Qwen3.5 / Holo VL checkpoints (e.g. Holo-3.1-0.8B, Ornith-1.0-9B)
1942 // ship the SAME Qwen3-VL ViT tower as their MoE siblings. The MoE
1943 // loader's `load_vision_encoder` reads only `store` + `config.vision`
1944 // (no MoE-specific state), so reuse it verbatim. The shared model
1945 // forward (`model/trait_impl/*`, gated on `vision_encoder.is_some()`)
1946 // then merges image embeddings — no dense-specific forward changes.
1947 super::qwen35::Qwen35WeightLoader.load_vision_encoder(store, config, gpu)
1948 }
1949}