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
170mod loaders_b;
171mod rowwise_fp8;
172
173pub struct Qwen35DenseWeightLoader;
174
175impl ModelWeightLoader for Qwen35DenseWeightLoader {
176    fn supports_tp(&self) -> bool {
177        // FullAttention layers are TP-sharded (NVFP4-from-disk and BF16
178        // → NVFP4 paths). LinearAttention (GDN SSM) layers run
179        // full-replica per rank — see qwen35.rs for the rationale.
180        true
181    }
182
183    fn load_layers(
184        &self,
185        store: &WeightStore,
186        config: &ModelConfig,
187        gpu: &dyn GpuBackend,
188        layer_kv_dtypes: &[KvCacheDtype],
189    ) -> Result<Vec<Box<dyn TransformerLayer>>> {
190        let layer_types = if config.layer_types.is_empty() {
191            (0..config.num_hidden_layers)
192                .map(|i| config.layer_type(i))
193                .collect::<Vec<_>>()
194        } else {
195            config.layer_types.clone()
196        };
197
198        let mut layers: Vec<Box<dyn TransformerLayer>> =
199            Vec::with_capacity(config.num_hidden_layers);
200        let mut attn_idx = 0usize;
201
202        let absmax_k = gpu.kernel("quantize_nvfp4", "nvfp4_global_absmax")?;
203        let quantize_k = gpu.kernel("quantize_nvfp4", "quantize_bf16_to_nvfp4")?;
204        let stream = gpu.default_stream();
205        let h = config.hidden_size;
206
207        let variant = detect_nvfp4_variant(store, config);
208        let weight_format = WeightFormat::detect(store, config);
209        tracing::info!(
210            "Weight format: {:?}, NVFP4 variant: {:?}",
211            weight_format,
212            variant
213        );
214
215        // Native FP8 SSM prefill GEMM (Qwen3.6-27B-FP8 root-cause fix,
216        // commit 3ebc08a). Atlas's prior SSM in_proj_qkv path was
217        // FP8 → BF16 → NVFP4 → BF16 (in `w4a16_gemm` dequant) → MMA — a
218        // double-quant chain whose NVFP4 hop's ~4-bit per-group precision
219        // is dominated by signal at q/v but attenuated into a k-direction
220        // error (HF conv-k ‖6.3‖ vs conv-v ‖117.2‖, ~18× smaller). For
221        // every FP8-on-disk checkpoint we install a single-scale FP8 copy
222        // of the stacked `[QKV|Z]` and `out_proj` weights for prefill,
223        // bypassing the NVFP4 intermediate. Prefill dispatches via the
224        // existing `fp8_gemm_n128` (BF16 act × FP8 weight) — same path
225        // the MoE shared-expert FP8 prefill uses. Decode/GEMV unchanged.
226        // Originally env-gated `ATLAS_FP8_SSM_PREFILL=1`; promoted to
227        // unconditional 2026-05-20 after live verification (commit
228        // dfb4e8a era, tokens_to_first_degeneration 1,196 → 16,968).
229        // 2026-07-03 precision policy: GDN projections stay ≥FP8 — the
230        // NVFP4 requant of qkvz/out_proj flattens tensors that both
231        // checkpoint toolchains deliberately keep high-precision (modelopt
232        // sensitivity analysis ships them FP8; unsloth ships BF16). Applies
233        // to ALL NVFP4 variants now, not just Fp8Dequanted. Kill-switch
234        // ATLAS_NO_GDN_FP8_PREFILL restores the pre-policy behavior for A/B.
235        let fp8_ssm_prefill = std::env::var_os("ATLAS_NO_GDN_FP8_PREFILL").is_none();
236        let bf16_to_fp8_k = if fp8_ssm_prefill {
237            tracing::info!(
238                // The old wording — "NVFP4 kept as structural fallback for
239                // decode batch paths" — was DISPROVEN by nsys at C=8
240                // (2026-08-17): the batched verify QKVZ was taking this FP8
241                // copy at every M>8, 26.3% of the step. It is true again only
242                // because that arm now reads NVFP4; say the actual rule so the
243                // log cannot re-drift from the dispatch.
244                "SSM in_proj_qkv + out_proj via native FP8 prefill GEMM \
245                 (BF16 act × FP8 weight via fp8_gemm_n128). PREFILL ONLY: \
246                 decode + batched verify read the NVFP4 copy — weight-streaming \
247                 GEMV at M<=8, tile GEMM above it (trait_decode_batched.rs). \
248                 The FP8 copy reaches decode only at M<=8 on a build whose \
249                 batched NVFP4 GEMVs are absent"
250            );
251            Some(gpu.kernel("w4a16", "bf16_to_fp8")?)
252        } else {
253            None
254        };
255
256        // ATLAS_MEM_PROFILE: per-phase GPU-free trace to pin the strix/APU
257        // load-time footprint (FP8-source persistence vs NVFP4 steady-state vs
258        // BF16 requant transients). Gated env so it's a no-op in production.
259        let mem_profile = std::env::var("ATLAS_MEM_PROFILE").is_ok();
260        let log_free = |tag: &str| {
261            if mem_profile && let Ok(free) = gpu.free_memory() {
262                tracing::info!("MEM_PROFILE[{tag}]: {:.2} GB GPU-free", free as f64 / 1e9);
263            }
264        };
265        log_free("dense-load-start");
266
267        for (i, lt) in layer_types.iter().enumerate() {
268            if i % 8 == 0 {
269                log_free(&format!("layer-{i}"));
270            }
271            let lp = config.layer_prefix(i);
272            let input_norm = dense(store, &format!("{lp}.input_layernorm.weight"))?;
273            let post_attn_norm = dense(store, &format!("{lp}.post_attention_layernorm.weight"))?;
274
275            // Dense FFN instead of MoE. Native FP8 checkpoints (single-GPU)
276            // load gate/up/down directly as block-scaled `Fp8Weight` and
277            // dispatch w8a16 — no NVFP4 requant. TP>1 still uses the NVFP4
278            // path (FP8 FFN sharding is a follow-up).
279            let ffn_fp8 = dense_fp8_enabled()
280                && config.tp_world_size.max(1) == 1
281                && matches!(variant, Nvfp4Variant::Fp8Dequanted)
282                && proj_is_native_fp8(store, &format!("{lp}.mlp.gate_proj"));
283            // Always load the NVFP4 weights so every dispatch path (incl. the
284            // batched spec-decode forward_k2/k3 paths that have no FP8 branch)
285            // has a valid weight to fall back to — a null NVFP4 weight under a
286            // w4a16 dispatch is the CUDA-700-at-concurrency bug. When native
287            // FP8 is enabled we overlay the block-scaled FP8 weights on top;
288            // the hot forward / forward_prefill paths then use FP8, the rare
289            // batched paths fall back to real NVFP4.
290            // Native-BF16 dense-FFN prefill (Holo/Ornith Bf16Raw): the fast
291            // tensor-core `dense_gemm_tc` path (dense_ffn::forward_prefill) needs
292            // LIVE BF16 gate/up/down. `load_dense_ffn`'s Bf16Raw arm runtime-
293            // quantizes each proj via `quantized_any`, whose Bf16Raw branch
294            // FREES the store's cached BF16 buffer (`gpu.free(w.ptr)` in
295            // nvfp4_detect.rs:288-309, the Bf16Raw arm). `dense_auto` returns that
296            // SAME (now-freed) store ptr for a BF16 tensor — quant_helpers.rs:290
297            // hands back `w.ptr` uncopied — so overlaying it AFTER load_dense_ffn hands
298            // `set_bf16_weights` freed GPU memory -> dense_gemm_tc CUDA-700 at
299            // grid=[div_ceil(intermediate_size,64),..] on the first prefill.
300            // Snapshot fresh D2D copies BEFORE the free (the clone is an
301            // independent allocation the layer owns for its lifetime).
302            // Native keep-packed ternary Q2_0 (ATLAS_GGUF_NATIVE_Q2=1): when the
303            // GGUF loader tagged gate/up/down as `PackedQ2_0`, DON'T requant to
304            // NVFP4 — install the raw 2-bit blocks and dispatch `q2_0_gemv` at
305            // decode. Requires tp_size=1 (packed-block sharding is unimplemented).
306            // Flag off → tensors are BF16, `ffn_q2` is false, path unchanged.
307            let ffn_q2 = config.tp_world_size.max(1) == 1
308                && proj_q2_group(store, &format!("{lp}.mlp.gate_proj")).is_some()
309                && proj_q2_group(store, &format!("{lp}.mlp.up_proj")).is_some()
310                && proj_q2_group(store, &format!("{lp}.mlp.down_proj")).is_some();
311            // Keep-packed projections are 2-bit blocks, not BF16 — there is
312            // nothing to snapshot (dense_auto has no PackedQ2_0 arm and would
313            // abort the load), and the ffn_q2 arm below owns their compute.
314            let ffn_bf16_snapshot = if !ffn_q2 && matches!(variant, Nvfp4Variant::Bf16Raw) {
315                let inter = if config.intermediate_size > 0 {
316                    config.intermediate_size
317                } else {
318                    config.moe_intermediate_size
319                };
320                let clone_bf16 = |name: &str, rows: usize, cols: usize| -> Result<DenseWeight> {
321                    let src = dense_auto(store, &format!("{lp}.mlp.{name}.weight"), gpu)?;
322                    let bytes = rows * cols * 2; // BF16 = 2 bytes/elem
323                    let dst = gpu.alloc(bytes)?;
324                    // The whole point of this snapshot is an allocation
325                    // INDEPENDENT of the store's cached ptr (which load_dense_ffn
326                    // frees below); an aliased dst would silently re-create the
327                    // freed-memory CUDA-700 this helper exists to prevent.
328                    debug_assert_ne!(
329                        dst, src.weight,
330                        "ffn_bf16_snapshot must be a fresh allocation, not an alias of the store ptr"
331                    );
332                    gpu.copy_d2d(src.weight, dst, bytes)?;
333                    Ok(DenseWeight { weight: dst })
334                };
335                Some((
336                    clone_bf16("gate_proj", inter, h)?,
337                    clone_bf16("up_proj", inter, h)?,
338                    clone_bf16("down_proj", h, inter)?,
339                ))
340            } else {
341                None
342            };
343
344            let ffn_weights = if ffn_q2 {
345                // NULL NVFP4 fallback: decode uses the packed weights; prefill /
346                // batched paths bail (Tier-2). No NVFP4 allocation → memory win.
347                use crate::weight_map::QuantizedWeight;
348                crate::layers::dense_ffn::DenseFfnWeights {
349                    gate_proj: QuantizedWeight::null(),
350                    up_proj: QuantizedWeight::null(),
351                    down_proj: QuantizedWeight::null(),
352                    gate_proj_t: None,
353                    up_proj_t: None,
354                    down_proj_t: None,
355                }
356            } else {
357                load_dense_ffn(
358                    store, &lp, gpu, variant, absmax_k, quantize_k, stream, config,
359                )?
360            };
361            let mut dffn = DenseFfnLayer::new(ffn_weights, gpu)?;
362            if ffn_q2 {
363                dffn.set_q2_weights(
364                    packed_q2_from_store(store, &format!("{lp}.mlp.gate_proj"))?,
365                    packed_q2_from_store(store, &format!("{lp}.mlp.up_proj"))?,
366                    packed_q2_from_store(store, &format!("{lp}.mlp.down_proj"))?,
367                    gpu,
368                );
369            }
370            if ffn_fp8 {
371                let load_ffn_fp8 = |name: &str| {
372                    load_fp8_block_scaled_as_fp8weight(store, &format!("{lp}.mlp.{name}"), gpu)
373                };
374                dffn.set_fp8_weights(
375                    load_ffn_fp8("gate_proj")?,
376                    load_ffn_fp8("up_proj")?,
377                    load_ffn_fp8("down_proj")?,
378                );
379            }
380            // ATLAS_FFN_MMQ: eagerly materialize Q4_K + free the dead `_t` copies at load,
381            // BEFORE KV cache sizing, so net FFN footprint == NVFP4 baseline (no decode OOM-throttle).
382            dffn.finalize_q4k_load(gpu, h as u32, config.intermediate_size as u32, stream)?;
383            // ATLAS_FFN_NVFP4_MMQ: same discipline for the W4A4 FP4-MMQ arm — repack
384            // gate/up to block_nvfp4 + free their `_t` copies (net ~0 footprint).
385            //
386            // SKIPPED when a LoRA adapter is pending. The forward-time FP4-MMQ
387            // arm is disabled while an adapter is installed (it leaves gate/up
388            // UNSCALED and folds weight_scale_2 inside the SiLU-mul, which
389            // would silently scale a true-valued delta). But this finalize
390            // FREES the transposed `_t` copies on the assumption that the MMQ
391            // arm will serve prefill — so running it and then disabling the arm
392            // left prefill on the slowest non-transposed GEMM with nothing to
393            // fall back to: 176 tok/s against 841 on a 2K prompt, and it had
394            // NOTHING to do with the cost of applying the deltas (measured with
395            // the deltas skipped entirely).
396            //
397            // `adapter_max_rank` is the load-time signal that `--lora-adapter`
398            // was given; the adapter itself is installed later (build step 8),
399            // so this is the only point where the decision can be made before
400            // the twins are freed.
401            if config.adapter_max_rank == 0 {
402                dffn.finalize_nvfp4_mmq_load(
403                    gpu,
404                    h as u32,
405                    config.intermediate_size as u32,
406                    stream,
407                )?;
408            }
409            // Native-BF16 dense-FFN overlay (Bf16Raw, no-metadata Holo dense): install the
410            // live BF16 gate/up/down snapshot so forward/forward_prefill's bf16 branch
411            // (preferred over the NVFP4 fallback) reads valid memory. The NVFP4 weights built
412            // by load_dense_ffn stay as the spec-decode/batched fallback (never null -> no
413            // CUDA-700 at concurrency). Snapshot was taken before load_dense_ffn freed the
414            // store's BF16 buffer (see ffn_bf16_snapshot above).
415            if let Some((g, u, d)) = ffn_bf16_snapshot {
416                dffn.set_bf16_weights(g, u, d);
417            }
418            let ffn = FfnComponent::Dense(dffn);
419
420            match lt {
421                LayerType::FullAttention => {
422                    let p = format!("{lp}.self_attn");
423                    let tp_rank = config.tp_rank;
424                    let tp_size = config.tp_world_size.max(1);
425                    // Bf16Raw installs a BF16 dense O-proj after the layer is
426                    // built; other variants leave this None (NVFP4/FP8 dispatch).
427                    let mut o_dense_bf16: Option<DenseWeight> = None;
428
429                    // Native keep-packed ternary Q2_0 (Tier-1c): when the GGUF
430                    // loader tagged q/k/v/o as `PackedQ2_0` (transform-free
431                    // full-attention projections), install the raw 2-bit blocks
432                    // and dispatch `q2_0_gemv_vec` at decode / transient-dequant
433                    // at prefill — no NVFP4 requant, no `_t` copies. Requires
434                    // tp_size=1 (packed-block sharding is unimplemented). Bonsai
435                    // has no kill-switch here; the whole path is gated upstream
436                    // by ATLAS_GGUF_NATIVE_Q2 (else these tensors are BF16 and
437                    // `attn_q2` is false). ATLAS_NO_Q2_ATTN forces the BF16 path
438                    // for A/B bisection.
439                    let attn_q2 = tp_size == 1
440                        && std::env::var_os("ATLAS_NO_Q2_ATTN").is_none()
441                        && proj_q2_group(store, &format!("{p}.q_proj")).is_some()
442                        && proj_q2_group(store, &format!("{p}.k_proj")).is_some()
443                        && proj_q2_group(store, &format!("{p}.v_proj")).is_some()
444                        && proj_q2_group(store, &format!("{p}.o_proj")).is_some();
445                    if attn_q2 {
446                        let (k_scale, v_scale) = load_kv_scales(store, &p, gpu);
447                        let attn = AttentionWeights {
448                            q_proj: DenseWeight {
449                                weight: spark_runtime::gpu::DevicePtr::NULL,
450                            },
451                            k_proj: DenseWeight {
452                                weight: spark_runtime::gpu::DevicePtr::NULL,
453                            },
454                            v_proj: DenseWeight {
455                                weight: spark_runtime::gpu::DevicePtr::NULL,
456                            },
457                            o_proj: crate::weight_map::QuantizedWeight::null(),
458                            q_norm: dense(store, &format!("{p}.q_norm.weight"))?,
459                            k_norm: dense(store, &format!("{p}.k_norm.weight"))?,
460                            q_norm_full: None,
461                            k_norm_full: None,
462                            k_scale,
463                            v_scale,
464                        };
465                        let mut attn_layer = Qwen3AttentionLayer::new(
466                            input_norm,
467                            attn,
468                            post_attn_norm,
469                            ffn,
470                            attn_idx,
471                            None,
472                            None,
473                            None,
474                            gpu,
475                            layer_kv_dtypes[attn_idx],
476                            config.fp8_kv_calibration_tokens,
477                            config,
478                        )?;
479                        attn_layer.set_packed_q2_weights(
480                            packed_q2_from_store(store, &format!("{p}.q_proj"))?,
481                            packed_q2_from_store(store, &format!("{p}.k_proj"))?,
482                            packed_q2_from_store(store, &format!("{p}.v_proj"))?,
483                            packed_q2_from_store(store, &format!("{p}.o_proj"))?,
484                            gpu,
485                        );
486                        tracing::info!(
487                            "ATTN[{lp}] native keep-packed Q2_0: q/k/v/o 2-bit \
488                             (q2_0_gemv_vec decode; transient-dequant prefill)"
489                        );
490                        layers.push(Box::new(attn_layer));
491                        attn_idx += 1;
492                        if (i + 1) % 10 == 0 {
493                            tracing::info!("Loaded layers 0..{}", i + 1);
494                        }
495                        continue;
496                    }
497                    let (attn, q_nvfp4, k_nvfp4, v_nvfp4) = match variant {
498                        Nvfp4Variant::CompressedTensors => {
499                            // NVFP4-from-disk path: column-parallel Q/K/V, row-parallel O.
500                            let group_size = 16usize;
501                            let load_nvfp4 = |name: &str,
502                                              full_n: usize,
503                                              full_k: usize,
504                                              kind: TpShardKind|
505                             -> Result<crate::weight_map::QuantizedWeight> {
506                                let prefix = format!("{p}.{name}");
507                                // Mixed-precision compressed-tensors checkpoints
508                                // (unsloth Qwen3.6-*-NVFP4, re-quantized 2026-07-10)
509                                // NVFP4-pack most of the net but keep attention
510                                // q/k/v/o as FP8 (`.weight` FP8E4M3 + a per-row
511                                // `.weight_scale`, no `.weight_packed`). Without the
512                                // pack metadata, dequant and runtime-quantize to NVFP4
513                                // instead of failing on the absent
514                                // `weight_global_scale`. Mirrors the MoE loader's
515                                // attention arm (weight_loader/qwen35/load_layers/
516                                // attention_arms.rs).
517                                let src = if store.contains(&format!("{prefix}.weight_packed")) {
518                                    quantized_auto(store, &prefix, gpu, variant)?
519                                } else {
520                                    let dense_bf16 =
521                                        dense_auto(store, &format!("{prefix}.weight"), gpu)?;
522                                    quantize_to_nvfp4(
523                                        &dense_bf16,
524                                        full_n,
525                                        full_k,
526                                        gpu,
527                                        absmax_k,
528                                        quantize_k,
529                                        stream,
530                                    )?
531                                };
532                                if tp_size == 1 {
533                                    return Ok(src);
534                                }
535                                let sharded = shard_quantized_nvfp4(
536                                    &src, full_n, full_k, kind, tp_rank, tp_size, group_size, gpu,
537                                )?;
538                                gpu.free(src.weight)?;
539                                gpu.free(src.weight_scale)?;
540                                Ok(sharded)
541                            };
542                            let [q, k, v, o] = load_qkvo_tp(config, load_nvfp4)?;
543                            let dummy = DenseWeight {
544                                weight: spark_runtime::gpu::DevicePtr::NULL,
545                            };
546                            let (k_scale, v_scale) = load_kv_scales(store, &p, gpu);
547                            let attn = AttentionWeights {
548                                q_proj: dummy,
549                                k_proj: dummy,
550                                v_proj: dummy,
551                                o_proj: o,
552                                q_norm: dense(store, &format!("{p}.q_norm.weight"))?,
553                                k_norm: dense(store, &format!("{p}.k_norm.weight"))?,
554                                q_norm_full: None,
555                                k_norm_full: None,
556                                k_scale,
557                                v_scale,
558                            };
559                            (attn, Some(q), Some(k), Some(v))
560                        }
561                        Nvfp4Variant::Standard | Nvfp4Variant::Fp8Dequanted => {
562                            // BF16 → NVFP4 path: shard BF16 then quantize per-rank.
563                            let load_bf16_then_nvfp4 = |name: &str,
564                                                        full_n: usize,
565                                                        full_k: usize,
566                                                        kind: TpShardKind|
567                             -> Result<(
568                                DenseWeight,
569                                crate::weight_map::QuantizedWeight,
570                            )> {
571                                // Pre-quantized Standard NVFP4 (e.g. sakamakismile): weight is U8
572                                // on disk. Load directly as QuantizedWeight without BF16 roundtrip.
573                                // TP sharding of pre-quantized NVFP4 is not yet supported: enforce
574                                // tp_size=1 explicitly, or every rank silently loads the full
575                                // unsharded weight (duplicated, not sharded — wrong results with
576                                // no error).
577                                let weight_key = format!("{p}.{name}.weight");
578                                if matches!(
579                                    store.get(&weight_key).map(|w| w.dtype),
580                                    Ok(WeightDtype::UInt8)
581                                ) {
582                                    anyhow::ensure!(
583                                        tp_size == 1,
584                                        "pre-quantized NVFP4 weight '{weight_key}' (U8 on disk) \
585                                         cannot be loaded under tensor parallelism (tp_size={tp_size}): \
586                                         TP sharding of pre-quantized NVFP4 checkpoints is not yet \
587                                         implemented. Use tp_size=1, or dequantize this checkpoint to \
588                                         BF16 first so it goes through the shard-then-requantize path."
589                                    );
590                                    let null_dense = DenseWeight {
591                                        weight: spark_runtime::gpu::DevicePtr::NULL,
592                                    };
593                                    let qw = quantized_auto(
594                                        store,
595                                        &format!("{p}.{name}"),
596                                        gpu,
597                                        Nvfp4Variant::Standard,
598                                    )?;
599                                    return Ok((null_dense, qw));
600                                }
601                                let src = dense_auto(store, &weight_key, gpu)?;
602                                let (sharded_ptr, local_n, local_k) = shard_dense_bf16(
603                                    src.weight, full_n, full_k, kind, tp_rank, tp_size, gpu,
604                                )?;
605                                let sharded = DenseWeight {
606                                    weight: sharded_ptr,
607                                };
608                                let q = quantize_to_nvfp4(
609                                    &sharded, local_n, local_k, gpu, absmax_k, quantize_k, stream,
610                                )?;
611                                if sharded_ptr != src.weight {
612                                    gpu.free(sharded_ptr)?;
613                                }
614                                Ok((src, q))
615                            };
616                            let [
617                                (q_dense, q_nvfp4),
618                                (k_dense, k_nvfp4),
619                                (v_dense, v_nvfp4),
620                                (o_dense, o_nvfp4),
621                            ] = load_qkvo_tp(config, load_bf16_then_nvfp4)?;
622
623                            let (k_scale, v_scale) = load_kv_scales(store, &p, gpu);
624
625                            // The BF16 q/k/v/o dense tensors are only the intermediate
626                            // fed to the GPU quantize_to_nvfp4 above. Prefill AND decode
627                            // always dispatch the NVFP4 weights, so the BF16 copies are
628                            // dead once quantized. Free them instead of retaining a full
629                            // second copy of every projection (Atlas issue #A1).
630                            gpu.free(q_dense.weight)?;
631                            gpu.free(k_dense.weight)?;
632                            gpu.free(v_dense.weight)?;
633                            gpu.free(o_dense.weight)?;
634
635                            let attn = AttentionWeights {
636                                q_proj: DenseWeight {
637                                    weight: spark_runtime::gpu::DevicePtr::NULL,
638                                },
639                                k_proj: DenseWeight {
640                                    weight: spark_runtime::gpu::DevicePtr::NULL,
641                                },
642                                v_proj: DenseWeight {
643                                    weight: spark_runtime::gpu::DevicePtr::NULL,
644                                },
645                                o_proj: o_nvfp4,
646                                q_norm: dense(store, &format!("{p}.q_norm.weight"))?,
647                                k_norm: dense(store, &format!("{p}.k_norm.weight"))?,
648                                q_norm_full: None,
649                                k_norm_full: None,
650                                k_scale,
651                                v_scale,
652                            };
653                            (attn, Some(q_nvfp4), Some(k_nvfp4), Some(v_nvfp4))
654                        }
655                        Nvfp4Variant::Bf16Raw => {
656                            // Native BF16 dense attention: keep Q/K/V/O in BF16
657                            // and dispatch the dense_gemv/dense_gemm kernels that
658                            // ship in the nvfp4 bundle (common/ dense_*_bf16). No
659                            // runtime BF16 -> NVFP4 quant — that lossily quantized
660                            // these no-metadata Holo dense checkpoints. Mirrors
661                            // qwen35/load_layers.rs:552 (BF16-dequant attention)
662                            // and gemma4/loader_a.rs:300/418.
663                            let load_bf16_dense =
664                                |name: &str,
665                                 full_n: usize,
666                                 full_k: usize,
667                                 kind: TpShardKind|
668                                 -> Result<DenseWeight> {
669                                    let src =
670                                        dense_auto(store, &format!("{p}.{name}.weight"), gpu)?;
671                                    if tp_size == 1 {
672                                        return Ok(src);
673                                    }
674                                    let (sharded_ptr, _local_n, _local_k) = shard_dense_bf16(
675                                        src.weight, full_n, full_k, kind, tp_rank, tp_size, gpu,
676                                    )?;
677                                    if sharded_ptr != src.weight {
678                                        gpu.free(src.weight)?;
679                                    }
680                                    Ok(DenseWeight {
681                                        weight: sharded_ptr,
682                                    })
683                                };
684                            let [q_dense, k_dense, v_dense, o_dense] =
685                                load_qkvo_tp(config, load_bf16_dense)?;
686
687                            let (k_scale, v_scale) = load_kv_scales(store, &p, gpu);
688
689                            // Keep q/k/v BF16 dense ALIVE (do NOT free): the dense
690                            // forward reads them directly. o_proj stays NULL — the
691                            // set_o_dense_bf16(o_dense) call after layer build
692                            // installs the BF16 O-proj that decode/prefill prefer.
693                            let attn = AttentionWeights {
694                                q_proj: q_dense,
695                                k_proj: k_dense,
696                                v_proj: v_dense,
697                                o_proj: crate::weight_map::QuantizedWeight::null(),
698                                q_norm: dense(store, &format!("{p}.q_norm.weight"))?,
699                                k_norm: dense(store, &format!("{p}.k_norm.weight"))?,
700                                q_norm_full: None,
701                                k_norm_full: None,
702                                k_scale,
703                                v_scale,
704                            };
705                            o_dense_bf16 = Some(o_dense);
706                            // The NULL o_proj above is only sound while the BF16
707                            // dense O-proj is installed after layer build — a null
708                            // NVFP4 weight reached by a w4a16 dispatch is the
709                            // CUDA-700-at-concurrency failure mode.
710                            debug_assert!(
711                                o_dense_bf16.is_some(),
712                                "Bf16Raw attention must install a dense O-proj to cover the null o_proj"
713                            );
714                            // BF16 native: no NVFP4 q/k/v weights → dense fallback.
715                            (attn, None, None, None)
716                        }
717                    };
718
719                    let mut attn_layer = Qwen3AttentionLayer::new(
720                        input_norm,
721                        attn,
722                        post_attn_norm,
723                        ffn,
724                        attn_idx,
725                        q_nvfp4,
726                        k_nvfp4,
727                        v_nvfp4,
728                        gpu,
729                        layer_kv_dtypes[attn_idx],
730                        config.fp8_kv_calibration_tokens,
731                        config,
732                    )?;
733                    // Fast-prefill: transposed NVFP4 copies route the 16 full-attn
734                    // layers' q/k/v/o prefill GEMMs onto w4a16_gemm_t_m128 (28.8%
735                    // of prefill GPU time on the base w4a16_gemm path; ~1.3x e2e).
736                    // predequant_for_prefill() is deliberately NOT called: the FP8
737                    // predequant route is slower for these bandwidth-bound GEMMs.
738                    if let (Some(qw), Some(kw), Some(vw)) = (q_nvfp4, k_nvfp4, v_nvfp4) {
739                        let (nh, hd) = (config.num_attention_heads, config.head_dim);
740                        let (nkv, hh) = (config.num_key_value_heads, config.hidden_size);
741                        let q_n = nh * hd * if config.attn_gated { 2 } else { 1 };
742                        let qt = qw.transpose_for_gemm(gpu, q_n, hh)?;
743                        let kt = kw.transpose_for_gemm(gpu, nkv * hd, hh)?;
744                        let vt = vw.transpose_for_gemm(gpu, nkv * hd, hh)?;
745                        let op = &attn_layer.attn.o_proj;
746                        let ot = op.transpose_for_gemm(gpu, hh, nh * hd)?;
747                        attn_layer.set_prefill_weights(Some(qt), Some(kt), Some(vt), Some(ot));
748                        // Fused [q|k|v] twin: k/v are N=1024, which against the
749                        // 128-wide N tile is 8 CTAs on 48 SMs (40 idle, 23.6 GB/s,
750                        // 9.75x off floor). Concatenated N=14336 runs 112 CTAs in
751                        // ONE launch. Bit-identical ONLY when the three share a
752                        // single `weight_scale_2` — the GEMM applies one scale2 per
753                        // launch — so verify the device values rather than assume.
754                        // Bit-exact float comparison, not an epsilon: the GEMM
755                        // applies ONE scale2, so anything but exact equality
756                        // changes results.
757                        let scales_equal = qw.weight_scale_2.to_bits()
758                            == kw.weight_scale_2.to_bits()
759                            && kw.weight_scale_2.to_bits() == vw.weight_scale_2.to_bits();
760                        if scales_equal {
761                            let fused =
762                                crate::weight_map::QuantizedWeight::transpose_concat_for_gemm(
763                                    gpu,
764                                    &[(&qw, q_n), (&kw, nkv * hd), (&vw, nkv * hd)],
765                                    hh,
766                                )?;
767                            attn_layer.set_fused_qkv_prefill_weight(Some(fused));
768                        } else if attn_idx == 0 {
769                            tracing::warn!(
770                                "attention q/k/v have differing weight_scale_2 — fused QKV GEMM disabled (3 separate launches per layer)"
771                            );
772                        }
773                    }
774                    // Native-BF16 (Bf16Raw): install the dense O-proj so decode +
775                    // prefill prefer it over the (NULL) NVFP4 o_proj. Mutually
776                    // exclusive with the transposed-NVFP4 block above (q_nvfp4 is
777                    // None on the Bf16Raw path).
778                    if let Some(o_dense) = o_dense_bf16 {
779                        attn_layer.set_o_dense_bf16(o_dense);
780                    }
781                    // Overlay native FP8 q/k/v/o on top of the NVFP4 weights when
782                    // enabled (single-GPU FP8 checkpoint). Hot decode/prefill paths
783                    // dispatch FP8 (w8a16); any path without an FP8 branch falls back
784                    // to the real NVFP4 weights above (never a null → no CUDA-700).
785                    if dense_fp8_enabled()
786                        && config.tp_world_size.max(1) == 1
787                        && matches!(variant, Nvfp4Variant::Fp8Dequanted)
788                        && proj_is_native_fp8(store, &format!("{p}.q_proj"))
789                    {
790                        let load_fp8_proj = |name: &str,
791                                             _n: usize,
792                                             _k: usize,
793                                             _kind: TpShardKind|
794                         -> Result<Fp8Weight> {
795                            load_fp8_block_scaled_as_fp8weight(store, &format!("{p}.{name}"), gpu)
796                        };
797                        let [q_fp8, k_fp8, v_fp8, o_fp8] = load_qkvo_tp(config, load_fp8_proj)?;
798                        attn_layer.set_fp8_weights(
799                            Some(q_fp8),
800                            Some(k_fp8),
801                            Some(v_fp8),
802                            Some(o_fp8),
803                        );
804                        if let Err(e) = attn_layer.transpose_fp8_for_prefill(gpu, stream) {
805                            tracing::warn!("Layer {i}: dense FP8 transpose failed: {e}");
806                        }
807                    }
808                    layers.push(Box::new(attn_layer));
809                    attn_idx += 1;
810                }
811                LayerType::LinearAttention => {
812                    let nv = config.linear_num_value_heads;
813                    let nk = config.linear_num_key_heads;
814                    // GDN HeadParallel: config holds per-rank-LOCAL linear head counts
815                    // (topology.rs divided them by tp_size). TpGdnDims rebuilds the FULL
816                    // pre-shard sizes so load/concat/interleave run at FULL, then the
817                    // shard_gdn_* slicers cut this rank's contiguous head range. value_dim
818                    // stays LOCAL (sizes the downstream quantize of the sharded buffers).
819                    let tp_size = config.tp_world_size.max(1);
820                    let dims = TpGdnDims::from_config(config);
821                    let qkv_rows = dims.full_conv_dim();
822                    let z_rows = dims.full_value_dim();
823                    let value_dim = nv * config.linear_value_head_dim;
824                    let la = format!("{lp}.linear_attn");
825
826                    // Native keep-packed ternary Q2_0 GDN (Tier-1c): the GGUF
827                    // loader kept `in_proj_qkv` (V-region row-permuted) and
828                    // `in_proj_z` (row-permuted) 2-bit. Byte-concat them into the
829                    // fused [Q|K|V|Z] `qkvz` and dispatch `q2_0_gemv_vec` at decode
830                    // / transient-dequant at prefill. `out_proj` (a within-row
831                    // COLUMN reorder) is NOT packed here — it stays NVFP4. a/b/
832                    // conv1d/norm/A_log stay BF16/F32. Requires tp_size=1.
833                    // ATLAS_NO_Q2_GDN forces the BF16/NVFP4 path for A/B bisection.
834                    let gdn_q2 = config.tp_world_size.max(1) == 1
835                        && std::env::var_os("ATLAS_NO_Q2_GDN").is_none()
836                        && proj_q2_group(store, &format!("{la}.in_proj_qkv")).is_some()
837                        && proj_q2_group(store, &format!("{la}.in_proj_z")).is_some();
838                    if gdn_q2 {
839                        let qkv_q2 = packed_q2_from_store(store, &format!("{la}.in_proj_qkv"))?;
840                        let z_q2 = packed_q2_from_store(store, &format!("{la}.in_proj_z"))?;
841                        anyhow::ensure!(
842                            qkv_q2.group == z_q2.group && qkv_q2.k == z_q2.k,
843                            "GDN packed qkv/z group|k mismatch ({},{} vs {},{})",
844                            qkv_q2.group,
845                            qkv_q2.k,
846                            z_q2.group,
847                            z_q2.k
848                        );
849                        // Byte-concat packed rows: [Q|K|V] ++ [Z]. Each row is
850                        // (k/group)*block_bytes; whole-row copy never splits a block.
851                        let group = qkv_q2.group as usize;
852                        let block_bytes = 2 + group / 4;
853                        let row_bytes = (qkv_q2.k as usize / group) * block_bytes;
854                        let qkv_bytes = qkv_q2.n as usize * row_bytes;
855                        let z_bytes = z_q2.n as usize * row_bytes;
856                        let qkvz_buf = gpu.alloc(qkv_bytes + z_bytes)?;
857                        gpu.copy_d2d(qkv_q2.weight, qkvz_buf, qkv_bytes)?;
858                        gpu.copy_d2d(z_q2.weight, qkvz_buf.offset(qkv_bytes), z_bytes)?;
859                        let qkvz_q2 = PackedQ2Weight {
860                            weight: qkvz_buf,
861                            n: qkv_q2.n + z_q2.n,
862                            k: qkv_q2.k,
863                            group: qkv_q2.group,
864                        };
865                        // out_proj + a/b/conv1d/norm are BF16/F32 in the store
866                        // (sidecar dequanted the reorder tensors). out_proj → NVFP4.
867                        let in_proj_a = dense_auto(store, &format!("{la}.in_proj_a.weight"), gpu)?;
868                        let in_proj_b = dense_auto(store, &format!("{la}.in_proj_b.weight"), gpu)?;
869                        let conv1d = dense(store, &format!("{la}.conv1d.weight"))?;
870                        let a_log = dense_keep_f32(store, &format!("{la}.A_log"), gpu)?;
871                        let dt_bias = dense_keep_f32(store, &format!("{la}.dt_bias"), gpu)?;
872                        let norm = dense_f32_safe(store, &format!("{la}.norm.weight"), gpu)?;
873                        let ba_dense = interleave_ba(&in_proj_a, &in_proj_b, nv, nk, h, gpu)?;
874                        let out_proj_dense =
875                            dense_auto(store, &format!("{la}.out_proj.weight"), gpu)?;
876                        let out_proj_nvfp4 = quantize_to_nvfp4(
877                            &out_proj_dense,
878                            h,
879                            value_dim,
880                            gpu,
881                            absmax_k,
882                            quantize_k,
883                            stream,
884                        )?;
885                        let out_proj_nvfp4_t =
886                            out_proj_nvfp4.transpose_for_gemm(gpu, h, value_dim)?;
887                        gpu.free(out_proj_dense.weight)?;
888                        let ssm = SsmWeights {
889                            in_proj_qkvz: DenseWeight {
890                                weight: spark_runtime::gpu::DevicePtr::NULL,
891                            },
892                            in_proj_ba: ba_dense,
893                            conv1d,
894                            a_log,
895                            dt_bias,
896                            norm,
897                            out_proj: out_proj_nvfp4,
898                        };
899                        let mut layer = Qwen3SsmLayer::new_sequential(
900                            input_norm,
901                            ssm,
902                            post_attn_norm,
903                            ffn,
904                            None,
905                            None,
906                            Some(out_proj_nvfp4_t),
907                            config,
908                            gpu,
909                        )?;
910                        layer.set_packed_q2_qkvz(qkvz_q2, gpu);
911                        layer.predequant_for_prefill(gpu, config, stream)?;
912                        tracing::info!(
913                            "SSM[{lp}] native keep-packed Q2_0 GDN: qkvz 2-bit \
914                             (concat qkv+z row-permuted), out_proj NVFP4"
915                        );
916                        layers.push(Box::new(layer));
917                        continue;
918                    }
919
920                    // SSM projections are loaded per-projection by on-disk dtype:
921                    // each of in_proj_qkv / in_proj_z / out_proj may independently
922                    // be NVFP4-packed (`weight_packed`) or plain (`weight`, routed
923                    // by `dense_auto` → BF16/FP32/FP8). The unsloth NVFP4 re-quant
924                    // of Qwen3.6-27B quantizes ONLY out_proj while keeping the
925                    // in_proj_* in BF16; the old all-or-nothing gate (keyed on
926                    // in_proj_qkv.weight_packed) then looked for a non-existent
927                    // out_proj.weight and failed to build. `dense_auto` is dequant-
928                    // to-BF16 for the concat pipeline regardless of source dtype.
929                    let load_ssm_proj =
930                        |name: &str, rows: usize, cols: usize| -> Result<DenseWeight> {
931                            if store.contains(&format!("{name}.weight_packed")) {
932                                dequant_nvfp4_to_bf16(store, name, rows, cols, gpu)
933                            } else if matches!(
934                                store.get(&format!("{name}.weight")).map(|w| w.dtype),
935                                Ok(WeightDtype::UInt8)
936                            ) {
937                                // Standard-convention NVFP4 (packed bytes at
938                                // `.weight`, not `.weight_packed`) — same dequant,
939                                // different on-disk key.
940                                dequant_nvfp4_to_bf16(store, name, rows, cols, gpu)
941                            } else {
942                                dense_auto(store, &format!("{name}.weight"), gpu)
943                            }
944                        };
945                    // Native FP8 GDN (nvidia mixed-precision checkpoint): the
946                    // in_proj_qkv / in_proj_z / out_proj projections ship as
947                    // F8_E4M3 + per-tensor scale — modelopt's sensitivity
948                    // analysis keeps the SSM projections high-precision. The
949                    // default path (`load_ssm_proj` → `dense_auto`) dequants to
950                    // BF16 then RE-quantizes to NVFP4 (4-bit), a lossy
951                    // double-quant of these 48/64 layers that regressed BFCL-ST
952                    // ~7pt (non_live 85.4→76.6). Load the on-disk FP8 directly
953                    // (concat qkv+z on-device into [Q|K|V|Z] order) and route
954                    // BOTH prefill (w8a16_gemm_pipelined) and decode
955                    // (w8a16_gemv) through the fp8w fields — no requant, decode
956                    // stays fast (FP8 = half BF16's weight bytes). MUST run
957                    // BEFORE `load_ssm_proj` consumes the store tensors.
958                    // Internal opt-out for the FP8-vs-NVFP4 GDN A/B + KL-drift
959                    // gate (not a user choice; mirrors the `ATLAS_NO_*` debug
960                    // levers). Default engages native FP8.
961                    if std::env::var_os("ATLAS_NO_GDN_FP8").is_none()
962                        && proj_is_fp8_any_scale(store, &format!("{la}.in_proj_qkv"))
963                        && proj_is_fp8_any_scale(store, &format!("{la}.in_proj_z"))
964                        && proj_is_fp8_any_scale(store, &format!("{la}.out_proj"))
965                    {
966                        let in_proj_a = dense(store, &format!("{la}.in_proj_a.weight"))?;
967                        let in_proj_b = dense(store, &format!("{la}.in_proj_b.weight"))?;
968                        let conv1d = dense(store, &format!("{la}.conv1d.weight"))?;
969                        let a_log = dense_keep_f32(store, &format!("{la}.A_log"), gpu)?;
970                        let dt_bias = dense_keep_f32(store, &format!("{la}.dt_bias"), gpu)?;
971                        let norm = dense_f32_safe(store, &format!("{la}.norm.weight"), gpu)?;
972                        let ba_dense = interleave_ba(&in_proj_a, &in_proj_b, nv, nk, h, gpu)?;
973                        let qkv_f = load_fp8_block_scaled_as_fp8weight(
974                            store,
975                            &format!("{la}.in_proj_qkv"),
976                            gpu,
977                        )?;
978                        let z_f = load_fp8_block_scaled_as_fp8weight(
979                            store,
980                            &format!("{la}.in_proj_z"),
981                            gpu,
982                        )?;
983                        let out_f = load_fp8_block_scaled_as_fp8weight(
984                            store,
985                            &format!("{la}.out_proj"),
986                            gpu,
987                        )?;
988                        let qkvz_f = concat_fp8_block_scaled(&qkv_f, &z_f, h, gpu)?;
989                        // The concat copied both grids; free the per-projection
990                        // scale allocs (weight bytes are store-owned, not freed).
991                        gpu.free(qkv_f.row_scale)?;
992                        gpu.free(z_f.row_scale)?;
993                        let ssm = SsmWeights {
994                            in_proj_qkvz: DenseWeight {
995                                weight: spark_runtime::gpu::DevicePtr::NULL,
996                            },
997                            in_proj_ba: ba_dense,
998                            conv1d,
999                            a_log,
1000                            dt_bias,
1001                            norm,
1002                            out_proj: crate::weight_map::QuantizedWeight::null(),
1003                        };
1004                        let mut layer = Qwen3SsmLayer::new_sequential(
1005                            input_norm,
1006                            ssm,
1007                            post_attn_norm,
1008                            ffn,
1009                            None,
1010                            None,
1011                            None,
1012                            config,
1013                            gpu,
1014                        )?;
1015                        layer.set_fp8_decode_weights(Some(qkvz_f), Some(out_f));
1016                        tracing::info!(
1017                            "SSM[{lp}] native FP8 GDN: qkvz+out_proj block-scaled FP8 \
1018                             (no NVFP4 requant; prefill+decode via w8a16)"
1019                        );
1020                        layers.push(Box::new(layer));
1021                        continue;
1022                    }
1023
1024                    // A, B, conv1d, A_log, dt_bias, norm are independent of the
1025                    // qkv/z/out_proj on-disk format below — load them once up
1026                    // front so both the native-NVFP4 fast path and the legacy
1027                    // dequant/requant path can share them.
1028                    //
1029                    // A_log and dt_bias MUST be FP32 — consumer kernels in
1030                    // `ssm_preprocess.cu` and `mamba2_ssm_decode.cu` declare
1031                    // them `const float*`. Loading via `dense()` kept BF16
1032                    // storage, reinterpreting 48-elt BF16 (96B) as 48-elt
1033                    // FP32 → per-head scrambled decay gates and exponential
1034                    // error amplification through GDR recurrence at long
1035                    // context. The MoE sister loader (`ssm_qwen35.rs`)
1036                    // already promotes these; dense was missing the mirror.
1037                    //
1038                    // in_proj_a/b: route through `load_ssm_proj` (not the raw
1039                    // `dense()` byte-reinterpret) so a Standard-NVFP4 A/B
1040                    // (U8-packed) checkpoint dequants correctly instead of
1041                    // being read as BF16 garbage.
1042                    let in_proj_a = load_ssm_proj(&format!("{la}.in_proj_a"), nv, h)?;
1043                    let in_proj_b = load_ssm_proj(&format!("{la}.in_proj_b"), nv, h)?;
1044                    let conv1d = dense(store, &format!("{la}.conv1d.weight"))?;
1045                    let a_log = dense_keep_f32(store, &format!("{la}.A_log"), gpu)?;
1046                    let dt_bias = dense_keep_f32(store, &format!("{la}.dt_bias"), gpu)?;
1047                    // norm.weight: use `dense_f32_safe` (FP32-aware: detects
1048                    // a fp32 checkpoint and truncates to BF16 with logging;
1049                    // bf16 passes through). Mirrors `weight_map/ssm_qwen35.rs`
1050                    // MoE sister loader (backported here 2026-05-20).
1051                    let norm = dense_f32_safe(store, &format!("{la}.norm.weight"), gpu)?;
1052                    let ba_dense = interleave_ba(&in_proj_a, &in_proj_b, nv, nk, h, gpu)?;
1053                    let qkvz_size = config.ssm_qkvz_size();
1054
1055                    // Native Standard-NVFP4 GDN (pre-quantized checkpoint, e.g.
1056                    // sakamakismile): in_proj_qkv / in_proj_z / out_proj ship
1057                    // U8-packed NVFP4 directly on disk (`.weight` dtype UInt8,
1058                    // not `.weight_packed` — that's the compressed-tensors
1059                    // convention `load_ssm_proj` already dequants above). Load
1060                    // them straight into `QuantizedWeight` and concat on GPU,
1061                    // skipping the BF16-dequant→re-quantize roundtrip entirely
1062                    // (that roundtrip is what the FP8/BF16-opt-in paths above
1063                    // exist to avoid for FP8-native and BF16-preferring
1064                    // checkpoints; here there's no lossy step to avoid in the
1065                    // first place — the data is already NVFP4). Requires all
1066                    // three projections to be U8; a partial-U8 checkpoint
1067                    // falls through to the legacy path below, where the
1068                    // `load_ssm_proj` UInt8 branch added above still dequants
1069                    // each U8 tensor correctly on its own.
1070                    let native_nvfp4 = matches!(
1071                        store
1072                            .get(&format!("{la}.in_proj_qkv.weight"))
1073                            .map(|w| w.dtype),
1074                        Ok(WeightDtype::UInt8)
1075                    ) && matches!(
1076                        store
1077                            .get(&format!("{la}.in_proj_z.weight"))
1078                            .map(|w| w.dtype),
1079                        Ok(WeightDtype::UInt8)
1080                    ) && matches!(
1081                        store.get(&format!("{la}.out_proj.weight")).map(|w| w.dtype),
1082                        Ok(WeightDtype::UInt8)
1083                    );
1084                    if native_nvfp4 {
1085                        let qkv_qw = quantized_auto(
1086                            store,
1087                            &format!("{la}.in_proj_qkv"),
1088                            gpu,
1089                            Nvfp4Variant::Standard,
1090                        )?;
1091                        let z_qw = quantized_auto(
1092                            store,
1093                            &format!("{la}.in_proj_z"),
1094                            gpu,
1095                            Nvfp4Variant::Standard,
1096                        )?;
1097                        let qkvz_nvfp4 = qkv_qw.concat_rows(&z_qw, qkv_rows, z_rows, h, gpu)?;
1098                        let qkvz_nvfp4_t = qkvz_nvfp4.transpose_for_gemm(gpu, qkvz_size, h)?;
1099
1100                        let out_proj_nvfp4 = quantized_auto(
1101                            store,
1102                            &format!("{la}.out_proj"),
1103                            gpu,
1104                            Nvfp4Variant::Standard,
1105                        )?;
1106                        let out_proj_nvfp4_t =
1107                            out_proj_nvfp4.transpose_for_gemm(gpu, h, value_dim)?;
1108
1109                        let ssm = SsmWeights {
1110                            in_proj_qkvz: DenseWeight {
1111                                weight: spark_runtime::gpu::DevicePtr::NULL,
1112                            },
1113                            in_proj_ba: ba_dense,
1114                            conv1d,
1115                            a_log,
1116                            dt_bias,
1117                            norm,
1118                            out_proj: out_proj_nvfp4,
1119                        };
1120                        let mut layer = Qwen3SsmLayer::new_sequential(
1121                            input_norm,
1122                            ssm,
1123                            post_attn_norm,
1124                            ffn,
1125                            Some(qkvz_nvfp4),
1126                            Some(qkvz_nvfp4_t),
1127                            Some(out_proj_nvfp4_t),
1128                            config,
1129                            gpu,
1130                        )?;
1131                        layer.predequant_for_prefill(gpu, config, stream)?;
1132                        tracing::info!(
1133                            "SSM[{lp}] native NVFP4 GDN: qkvz+out_proj loaded pre-quantized \
1134                             (U8-packed on disk; no BF16 dequant/requant roundtrip)"
1135                        );
1136                        layers.push(Box::new(layer));
1137                        continue;
1138                    }
1139
1140                    // PER-ROW FP8 for the row-wise cuBLASLt PREFILL arm
1141                    // (`ATLAS_FP8_ROWWISE=1`). Read from the store BEFORE the
1142                    // dequant below, though the bytes are store-owned either
1143                    // way; the NVFP4 build continues underneath because decode
1144                    // still needs it — `w8a16_gemv` cannot index a per-row
1145                    // scale. See weight_loader/qwen35_dense/rowwise_fp8.rs.
1146                    let rowwise_gdn = rowwise_fp8::rowwise_fp8_enabled()
1147                        && rowwise_fp8::proj_is_fp8_per_row(store, &format!("{la}.in_proj_qkv"))
1148                        && rowwise_fp8::proj_is_fp8_per_row(store, &format!("{la}.in_proj_z"))
1149                        && rowwise_fp8::proj_is_fp8_per_row(store, &format!("{la}.out_proj"));
1150                    let (qkvz_rowwise, out_proj_rowwise) = if rowwise_gdn && tp_size == 1 {
1151                        let qkv_r = rowwise_fp8::load_fp8_per_row(
1152                            store,
1153                            &format!("{la}.in_proj_qkv"),
1154                            gpu,
1155                        )?;
1156                        let z_r =
1157                            rowwise_fp8::load_fp8_per_row(store, &format!("{la}.in_proj_z"), gpu)?;
1158                        let out_r =
1159                            rowwise_fp8::load_fp8_per_row(store, &format!("{la}.out_proj"), gpu)?;
1160                        let qkvz_r = rowwise_fp8::concat_fp8_per_row(&qkv_r, &z_r, h, gpu)?;
1161                        // The concat copied both scale vectors; free the
1162                        // per-projection allocs. Weight bytes are store-owned.
1163                        gpu.free(qkv_r.row_scale)?;
1164                        gpu.free(z_r.row_scale)?;
1165                        (Some(qkvz_r), Some(out_r))
1166                    } else {
1167                        (None, None)
1168                    };
1169
1170                    let qkv_dense = load_ssm_proj(&format!("{la}.in_proj_qkv"), qkv_rows, h)?;
1171                    let z_dense = load_ssm_proj(&format!("{la}.in_proj_z"), z_rows, h)?;
1172                    let out_proj_dense =
1173                        load_ssm_proj(&format!("{la}.out_proj"), h, dims.full_value_dim())?;
1174
1175                    let qkvz_dense =
1176                        gpu_concat_rows(&qkv_dense, qkv_rows, &z_dense, z_rows, h, gpu)?;
1177                    // qkv/z BF16 are only inputs to the concat above; free them now
1178                    // rather than leaking them for the layer's lifetime (Atlas issue #A1).
1179                    gpu.free(qkv_dense.weight)?;
1180                    gpu.free(z_dense.weight)?;
1181
1182                    let ba_dense =
1183                        interleave_ba(&in_proj_a, &in_proj_b, dims.full_nv, dims.full_nk, h, gpu)?;
1184
1185                    // GDN HeadParallel shard: cut the FULL concat/interleave/on-disk buffers
1186                    // to this rank's contiguous head range (mirrors the MoE loader,
1187                    // linear_attn_arms.rs). Runs BEFORE both consumers below (the Bf16Raw
1188                    // branch AND the NVFP4/FP8 path), so both get sharded weights. qkvz
1189                    // (segmented [Q|K|V|Z]) + conv (segmented [Q|K|V]) slice per-block; ba
1190                    // slices contiguously; a_log/dt_bias are per-value-head FP32 scalars;
1191                    // out_proj is row-parallel on value_dim (partials summed by the
1192                    // post-out_proj all-reduce already in Qwen3SsmLayer::forward). norm is
1193                    // [vd] shared across value heads -> REPLICATE (never sliced). Free only
1194                    // the fresh qkvz/ba concat buffers; conv/a_log/dt_bias/out_proj sources
1195                    // are store aliases or dequant bufs freed downstream (the NVFP4 path
1196                    // frees the sharded qkvz/out_proj later). At tp==1 the else arm is a
1197                    // pure pass-through and every slicer no-ops -> byte-identical.
1198                    let (qkvz_dense, ba_dense, conv1d, a_log, dt_bias, out_proj_dense) = if tp_size
1199                        > 1
1200                    {
1201                        let d_conv = config.linear_conv_kernel_dim;
1202                        let (qkvz_ptr, _, _) = shard_gdn_qkvz_rows(qkvz_dense.weight, &dims, gpu)?;
1203                        gpu.free(qkvz_dense.weight)?;
1204                        let (ba_ptr, _, _) = shard_gdn_ba_rows(ba_dense.weight, &dims, gpu)?;
1205                        gpu.free(ba_dense.weight)?;
1206                        let (conv_ptr, _, _) =
1207                            shard_gdn_conv_rows(conv1d.weight, &dims, d_conv, gpu)?;
1208                        let (a_log_ptr, _) =
1209                            shard_gdn_value_vector(a_log.weight, &dims, 1, 4, gpu)?;
1210                        let (dt_bias_ptr, _) =
1211                            shard_gdn_value_vector(dt_bias.weight, &dims, 1, 4, gpu)?;
1212                        let (out_ptr, _, _) =
1213                            shard_gdn_out_proj_row_parallel(out_proj_dense.weight, &dims, gpu)?;
1214                        (
1215                            DenseWeight { weight: qkvz_ptr },
1216                            DenseWeight { weight: ba_ptr },
1217                            DenseWeight { weight: conv_ptr },
1218                            DenseWeight { weight: a_log_ptr },
1219                            DenseWeight {
1220                                weight: dt_bias_ptr,
1221                            },
1222                            DenseWeight { weight: out_ptr },
1223                        )
1224                    } else {
1225                        (qkvz_dense, ba_dense, conv1d, a_log, dt_bias, out_proj_dense)
1226                    };
1227
1228                    // Native-BF16 SSM arm (no-metadata dense Holo checkpoints:
1229                    // Nvfp4Variant::Bf16Raw). The standard nvfp4 bundle ships the
1230                    // common/ BF16 dense kernels (dense_gemv_bf16 / dense_gemm_bf16
1231                    // / dense_gemm_bf16_pipelined) that every SSM forward arm's
1232                    // dense fallback already dispatches, so keep the concatenated
1233                    // qkvz_dense [Q|K|V|Z] and out_proj_dense ALIVE and route
1234                    // through those instead of the lossy BF16->NVFP4 runtime
1235                    // requant. No NVFP4/FP8 copy is built at all: in_proj_qkvz +
1236                    // out_proj_dense feed dense_gemv (per-seq decode,
1237                    // ssm_forward.rs:107/412), dense_gemm (batched decode,
1238                    // trait_decode_batched.rs:113/350 + ssm_batched.rs:180/279)
1239                    // and dense_gemm_bf16_pipelined (prefill,
1240                    // trait_prefill_proj.rs:298 + trait_prefill_helper.rs:89).
1241                    // ssm.out_proj stays null — every out_proj arm prefers
1242                    // out_proj_dense when Some; predequant_for_prefill /
1243                    // set_fp8_prefill_only_weights are skipped (NVFP4/FP8 only).
1244                    if matches!(variant, Nvfp4Variant::Bf16Raw) {
1245                        let ssm = SsmWeights {
1246                            in_proj_qkvz: qkvz_dense,
1247                            in_proj_ba: ba_dense,
1248                            conv1d,
1249                            a_log,
1250                            dt_bias,
1251                            norm,
1252                            out_proj: crate::weight_map::QuantizedWeight::null(),
1253                        };
1254                        let mut layer = Qwen3SsmLayer::new_sequential(
1255                            input_norm,
1256                            ssm,
1257                            post_attn_norm,
1258                            ffn,
1259                            None, // qkvz_nvfp4  — BF16 dense fallback used instead
1260                            None, // qkvz_nvfp4_t
1261                            None, // out_proj_nvfp4_t
1262                            config,
1263                            gpu,
1264                        )?;
1265                        // pub field (qwen3_ssm/mod.rs:46); selected by every
1266                        // out_proj arm (ssm_forward.rs:412,
1267                        // trait_decode_batched.rs:350, ssm_batched.rs:279,
1268                        // trait_prefill_helper.rs:89).
1269                        layer.out_proj_dense = Some(out_proj_dense);
1270                        layers.push(Box::new(layer));
1271                        continue;
1272                    }
1273
1274                    let qkvz_size = config.ssm_qkvz_size();
1275
1276                    // GDN ≥FP8 precision policy (2026-07-04). The nvidia
1277                    // Qwen3.6-27B-NVFP4 checkpoint ships GDN in_proj_qkv /
1278                    // out_proj as native F8_E4M3 (modelopt sensitivity
1279                    // analysis deliberately keeps the SSM projections
1280                    // high-precision); `load_ssm_proj` dequants them to BF16,
1281                    // and the code below then RE-quantizes to NVFP4 (4-bit) —
1282                    // a lossy double-quant of the exact tensors the toolchain
1283                    // protected. That regressed BFCL-ST ~7pt (non_live 85.4→
1284                    // 76.6) vs the 06-15 reference. When enabled, keep the
1285                    // BF16 dequant (≥FP8) and route qkvz + out_proj through the
1286                    // dense_gemv / dense_gemm dispatch (in_proj_qkvz +
1287                    // out_proj_dense fields), mirroring the MoE sister loader's
1288                    // arm (qwen35/load_layers/linear_attn_arms.rs). Gated for a
1289                    // clean A/B + KL-drift gate before flipping the default.
1290                    let gdn_bf16 = matches!(
1291                        std::env::var("ATLAS_GDN_BF16_WEIGHTS").ok().as_deref(),
1292                        Some("1")
1293                    );
1294                    if gdn_bf16 {
1295                        let ssm = SsmWeights {
1296                            in_proj_qkvz: DenseWeight {
1297                                weight: qkvz_dense.weight,
1298                            },
1299                            in_proj_ba: ba_dense,
1300                            conv1d,
1301                            a_log,
1302                            dt_bias,
1303                            norm,
1304                            // Unused: out_proj_dense (set below) has higher
1305                            // dispatch priority in both prefill and decode.
1306                            out_proj: crate::weight_map::QuantizedWeight::null(),
1307                        };
1308                        let mut layer = Qwen3SsmLayer::new_sequential(
1309                            input_norm,
1310                            ssm,
1311                            post_attn_norm,
1312                            ffn,
1313                            None,
1314                            None,
1315                            None,
1316                            config,
1317                            gpu,
1318                        )?;
1319                        layer.out_proj_dense = Some(out_proj_dense);
1320                        tracing::info!(
1321                            "SSM[{lp}] ATLAS_GDN_BF16_WEIGHTS: qkvz + out_proj kept BF16 \
1322                             (≥FP8; NVFP4 requant skipped)"
1323                        );
1324                        layers.push(Box::new(layer));
1325                        continue;
1326                    }
1327
1328                    let qkvz_nvfp4 = quantize_to_nvfp4(
1329                        &qkvz_dense,
1330                        qkvz_size,
1331                        h,
1332                        gpu,
1333                        absmax_k,
1334                        quantize_k,
1335                        stream,
1336                    )?;
1337
1338                    let qkvz_nvfp4_t = qkvz_nvfp4.transpose_for_gemm(gpu, qkvz_size, h)?;
1339
1340                    let out_proj_nvfp4 = quantize_to_nvfp4(
1341                        &out_proj_dense,
1342                        h,
1343                        value_dim,
1344                        gpu,
1345                        absmax_k,
1346                        quantize_k,
1347                        stream,
1348                    )?;
1349
1350                    let out_proj_nvfp4_t = out_proj_nvfp4.transpose_for_gemm(gpu, h, value_dim)?;
1351
1352                    // Native FP8 SSM prefill GEMM: build a single-scale FP8
1353                    // copy of `qkvz_dense` [qkvz_size, h] and `out_proj_dense`
1354                    // [h, value_dim] by direct BF16→FP8 truncation. SSM weight
1355                    // magnitudes fit in FP8 E4M3 range (|w| ≤ 448), so no
1356                    // separate scalar dequant is needed at GEMM time — the
1357                    // `fp8_gemm_n128` kernel interprets the FP8 bytes as
1358                    // values directly (mirrors how `predequant_nvfp4_to_fp8`
1359                    // bakes `scale2` into the FP8 stream). PCND: gated.
1360                    let (qkvz_fp8_prefill, out_proj_fp8_prefill) =
1361                        if let Some(b2f_k) = bf16_to_fp8_k {
1362                            let qkvz_total = (qkvz_size * h) as u32;
1363                            let qkvz_fp8 = gpu.alloc(qkvz_size * h)?;
1364                            crate::layers::ops::bf16_to_fp8(
1365                                gpu,
1366                                b2f_k,
1367                                qkvz_dense.weight,
1368                                qkvz_fp8,
1369                                qkvz_total,
1370                                stream,
1371                            )?;
1372                            let out_total = (h * value_dim) as u32;
1373                            let out_fp8 = gpu.alloc(h * value_dim)?;
1374                            crate::layers::ops::bf16_to_fp8(
1375                                gpu,
1376                                b2f_k,
1377                                out_proj_dense.weight,
1378                                out_fp8,
1379                                out_total,
1380                                stream,
1381                            )?;
1382                            gpu.synchronize(stream)?;
1383                            (Some(qkvz_fp8), Some(out_fp8))
1384                        } else {
1385                            (None, None)
1386                        };
1387
1388                    // SSM prefill/decode always dispatch qkvz_nvfp4/_t and the NVFP4
1389                    // out_proj; the BF16 qkvz_dense / out_proj_dense were only quantize
1390                    // inputs. Free them rather than keep a third full-precision copy of
1391                    // the largest SSM tensor across every layer (Atlas issue #A1).
1392                    gpu.free(qkvz_dense.weight)?;
1393                    gpu.free(out_proj_dense.weight)?;
1394
1395                    let ssm = SsmWeights {
1396                        in_proj_qkvz: DenseWeight {
1397                            weight: spark_runtime::gpu::DevicePtr::NULL,
1398                        },
1399                        in_proj_ba: ba_dense,
1400                        conv1d,
1401                        a_log,
1402                        dt_bias,
1403                        norm,
1404                        out_proj: out_proj_nvfp4,
1405                    };
1406
1407                    let mut layer = Qwen3SsmLayer::new_sequential(
1408                        input_norm,
1409                        ssm,
1410                        post_attn_norm,
1411                        ffn,
1412                        Some(qkvz_nvfp4),
1413                        Some(qkvz_nvfp4_t),
1414                        Some(out_proj_nvfp4_t),
1415                        config,
1416                        gpu,
1417                    )?;
1418                    layer.predequant_for_prefill(gpu, config, stream)?;
1419                    // Install the FP8 prefill weights AFTER `predequant_for_prefill`
1420                    // (which sets `out_proj_fp8` from NVFP4 + scale2). The
1421                    // native-FP8 path overrides both pointers when active,
1422                    // routing prefill through `fp8_gemm_n128` instead of
1423                    // `w4a16_gemm_t`. Decode batch paths keep their NVFP4
1424                    // fallback (the `qkvz_nvfp4*` fields above).
1425                    if qkvz_fp8_prefill.is_some() || out_proj_fp8_prefill.is_some() {
1426                        layer.set_fp8_prefill_only_weights(qkvz_fp8_prefill, out_proj_fp8_prefill);
1427                    }
1428                    // …and LAST, so it wins over both of the prefill installs
1429                    // above: the checkpoint's own per-row FP8, which reaches
1430                    // the GEMM with no conversion at all. Decode is untouched.
1431                    if qkvz_rowwise.is_some() {
1432                        layer.set_fp8_rowwise_prefill_weights(qkvz_rowwise, out_proj_rowwise);
1433                        if i == 0 {
1434                            tracing::info!(
1435                                "SSM[{lp}] ATLAS_FP8_ROWWISE: qkvz + out_proj prefill via \
1436                                 native per-row FP8 (no BF16 dequant, no NVFP4 requant); \
1437                                 decode keeps NVFP4"
1438                            );
1439                        }
1440                    }
1441                    layers.push(Box::new(layer));
1442                }
1443                LayerType::SlidingAttention => {
1444                    unreachable!("unexpected SlidingAttention in this loader")
1445                }
1446                LayerType::Moe => unreachable!("Qwen3.5 dense has no standalone MoE layers"),
1447                // GLM-5.3's `deepseek_sparse_attention`: a full-rank mixer whose visible key set
1448                // is chosen at runtime by an indexer. Hard error, not a silent fallthrough into
1449                // the dense-attention arm -- that would attend over the WHOLE cache and look right.
1450                LayerType::SparseAttention => anyhow::bail!(
1451                    "layer {i}: SparseAttention needs a DSA indexer and per-query top-k; Qwen3.5 dense has neither"
1452                ),
1453            }
1454
1455            if (i + 1) % 10 == 0 {
1456                tracing::info!("Loaded layers 0..{}", i + 1);
1457                spark_runtime::progress::layer(i + 1, config.num_hidden_layers);
1458            }
1459        }
1460
1461        tracing::info!(
1462            "Qwen3.5 dense weight loader: {} layers ({} attention, {} SSM, dense FFN)",
1463            layers.len(),
1464            attn_idx,
1465            layers.len() - attn_idx,
1466        );
1467
1468        Ok(layers)
1469    }
1470
1471    fn load_embedding(
1472        &self,
1473        store: &WeightStore,
1474        config: &ModelConfig,
1475        _gpu: &dyn GpuBackend,
1476    ) -> Result<DenseWeight> {
1477        loaders_b::load_embedding(store, config)
1478    }
1479
1480    fn load_final_norm(
1481        &self,
1482        store: &WeightStore,
1483        config: &ModelConfig,
1484        _gpu: &dyn GpuBackend,
1485    ) -> Result<DenseWeight> {
1486        loaders_b::load_final_norm(store, config)
1487    }
1488
1489    fn load_lm_head(
1490        &self,
1491        store: &WeightStore,
1492        config: &ModelConfig,
1493        gpu: &dyn GpuBackend,
1494    ) -> Result<DenseWeight> {
1495        loaders_b::load_lm_head(store, config, gpu)
1496    }
1497
1498    fn load_mtp_weights(
1499        &self,
1500        store: &WeightStore,
1501        config: &ModelConfig,
1502        gpu: &dyn GpuBackend,
1503    ) -> Result<Option<MtpWeights>> {
1504        if !store.contains("mtp.fc.weight") {
1505            return Ok(None);
1506        }
1507        let variant = detect_nvfp4_variant(store, config);
1508        tracing::info!(
1509            "Loading dense MTP weights (variant={:?}, hidden={}, inter={})",
1510            variant,
1511            config.hidden_size,
1512            config.intermediate_size,
1513        );
1514        // `load_mtp` auto-detects MoE vs dense FFN by inspecting the weight
1515        // names. For dense Qwen3.6-27B-FP8 it returns a MtpWeights with
1516        // `dense_ffn = Some(...)` and NULL placeholders for the MoE fields.
1517        let mtp = load_mtp(store, config.num_experts, gpu, variant)?;
1518        if mtp.dense_ffn.is_some() {
1519            tracing::info!("Dense MTP head ready (FP8 e4m3 projections + dense gate/up/down MLP)");
1520        } else {
1521            tracing::info!(
1522                "MoE MTP head ready ({} experts) — dense loader sees MoE bundle",
1523                mtp.experts.len(),
1524            );
1525        }
1526        Ok(Some(mtp))
1527    }
1528
1529    fn load_vision_encoder(
1530        &self,
1531        store: &WeightStore,
1532        config: &ModelConfig,
1533        gpu: &dyn GpuBackend,
1534    ) -> Result<Option<crate::layers::VisionEncoder>> {
1535        // Dense Qwen3.5 / Holo VL checkpoints (e.g. Holo-3.1-0.8B, Ornith-1.0-9B)
1536        // ship the SAME Qwen3-VL ViT tower as their MoE siblings. The MoE
1537        // loader's `load_vision_encoder` reads only `store` + `config.vision`
1538        // (no MoE-specific state), so reuse it verbatim. The shared model
1539        // forward (`model/trait_impl/*`, gated on `vision_encoder.is_some()`)
1540        // then merges image embeddings — no dense-specific forward changes.
1541        super::qwen35::Qwen35WeightLoader.load_vision_encoder(store, config, gpu)
1542    }
1543}