spark_model/model/
impl_a1.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3#![allow(unused_imports, dead_code)]
4
5use parking_lot::Mutex;
6use std::collections::HashMap;
7use std::sync::Arc;
8
9use anyhow::{Result, bail};
10use atlas_core::config::{LayerType, ModelConfig};
11use spark_runtime::buffers::BufferArena;
12use spark_runtime::gpu::{DevicePtr, GpuBackend, GraphHandle, KernelHandle};
13use spark_runtime::kv_cache::PagedKvCache;
14
15use super::block_mgmt::{
16    apply_evicted_blocks, ensure_blocks_through_decode, ensure_blocks_through_prefill,
17    extract_layer_refs, reuse_prefix_match_disk_ids,
18};
19use super::ssm_pool::SsmStatePool;
20use super::ssm_snapshot::SsmSnapshotPool;
21use super::types::{PinnedMetaStaging, TransformerModel};
22use crate::layer::{
23    AttnMetadataDev, ForwardContext, GdnPrefillBuffers, LayerState, SsmLayerState, TransformerLayer,
24};
25use crate::layers::ops;
26use crate::speculative::DraftProposer;
27use crate::traits::{ChunkedPrefillPageMetadata, Model, SequenceState};
28use crate::weight_map::{DenseWeight, MtpWeights, QuantizedWeight};
29
30/// lm_head tile-GEMM decode path: **ON by default**, disabled by
31/// `ATLAS_NO_LMHEAD_TGEMM=1`. Evaluated ONCE at construction — the switch
32/// decides whether the transposed twin is built at all, so setting it later has
33/// no effect. Presence-style check (`ATLAS_*=0` is NOT "off").
34///
35/// Measured C=16: 113.10 -> 119.32 tok/s (+5.50%, disjoint ranges, 4 reps).
36/// `padded_n <= 4` is untouched and stays byte-identical, so C=1 is unaffected.
37/// The twin costs ~681 MB and leaves the KV pool at 4759 blocks vs 4757 without
38/// it — no measurable KV impact.
39fn lmhead_tgemm_enabled() -> bool {
40    std::env::var("ATLAS_NO_LMHEAD_TGEMM").ok().as_deref() != Some("1")
41}
42
43impl TransformerModel {
44    pub fn new(
45        config: ModelConfig,
46        embed_tokens: DenseWeight,
47        final_norm: DenseWeight,
48        lm_head_weight: DenseWeight,
49        lm_head_nvfp4: Option<QuantizedWeight>,
50        // Runtime FP8 LM head (`--lm-head-dtype fp8`). Mutually exclusive with
51        // `lm_head_nvfp4`; `None` for the NVFP4/BF16/default paths (byte-identical).
52        lm_head_fp8: Option<crate::weight_map::Fp8DenseWeight>,
53        // Separate NVFP4 head used ONLY by the MTP draft proposer when the
54        // main head is kept BF16 (`skip_lm_head_quantization()`). `None` for
55        // the NVFP4-main default, in which case the proposer falls back to
56        // `lm_head_nvfp4`. Drafts are always verified by the main BF16 head,
57        // so this approximate head never affects an accepted token.
58        mtp_lm_head_nvfp4: Option<QuantizedWeight>,
59        layers: Vec<Box<dyn TransformerLayer>>,
60        buffers: BufferArena,
61        kv_cache: PagedKvCache,
62        mtp_weights: Vec<MtpWeights>,
63        gpu: Box<dyn GpuBackend>,
64        max_seq_len: usize,
65        max_batch_size: usize,
66        mtp_quant: crate::layers::MtpQuantization,
67        use_speculative: bool,
68        prefix_cache: Box<dyn spark_runtime::prefix_cache::PrefixCache>,
69        mtp_vocab_size: u32,
70        comm: Option<std::sync::Arc<dyn spark_comm::CommBackend>>,
71        self_speculative: bool,
72        num_drafts: usize,
73        vision_encoder: Option<crate::layers::VisionEncoder>,
74        ssm_cache_slots: usize,
75        ssm_checkpoint_interval: usize,
76    ) -> Result<Self> {
77        // `rms_norm_kernel` normalizes exactly one weight: `final_norm` (a
78        // checkpoint tensor). Models that ship HF-vanilla norm weights load it
79        // exactly and must use the vanilla kernel.
80        let rms_norm_kernel = if crate::ships_vanilla_norm_weights(&config) {
81            gpu.kernel("rms_norm_vanilla", "rms_norm_vanilla")?
82        } else {
83            gpu.kernel("norm", "rms_norm")?
84        };
85        let dense_gemv_kernel = gpu.kernel("gemv", "dense_gemv_bf16")?;
86        // FP32-output dense GEMV — the FP32 logits path required an FP32
87        // residual stream, which no longer exists, so this stays
88        // KernelHandle(0) and the BF16 path is always taken.
89        let dense_gemv_fp32out_kernel = KernelHandle(0);
90        let w4a16_gemv_kernel = gpu.kernel("w4a16_gemv", "w4a16_gemv")?;
91        let w4a16_gemv_logits_kernel = gpu.kernel("w4a16_gemv", "w4a16_gemv_logits")?;
92        // lm_head shares the tile GEMM, so route it through the same resolver as
93        // the SSM/attention sites — it picks the 3-deep pipeline variant when
94        // present. lm_head launches 1938 CTAs and already sits at ~83% of
95        // achievable, so the expected gain here is small; measured, not assumed.
96        let w4a16_gemm_t_kernel = crate::layers::tgemm_kernel(gpu.as_ref());
97        // Lossless BF16-MMA sibling for lm_head, OPT-IN via ATLAS_LMHEAD_LOSSLESS=1.
98        // Measured cost 1.81% at C=16 (129.68 -> 127.33). Default is the faster
99        // FP8-activation path because the accuracy question it addresses CANNOT
100        // BE MEASURED until vLLM parity lifts the BFCL embargo — and the 1.81%
101        // is throughput needed to REACH parity. The risk is real but indirect:
102        // the bf16-floor finding was superseded on the WEIGHT axis, and this is
103        // the ACTIVATION axis, which was never examined. Re-decide at parity.
104        let w4a16_gemm_t_bf16_kernel = if std::env::var("ATLAS_LMHEAD_LOSSLESS").is_ok() {
105            crate::layers::try_kernel(gpu.as_ref(), "w4a16", "w4a16_gemm_t_m128_bf16_v2")
106        } else {
107            spark_runtime::gpu::KernelHandle(0)
108        };
109        let w4a16_gemm_kernel = gpu.kernel("w4a16", "w4a16_gemm")?;
110        let w4a16_gemv_batch2_kernel = gpu.kernel("w4a16_gemv", "w4a16_gemv_batch2")?;
111        // Narrow batched-GEMV family (M=4..8) for the K=3..8 verify lm_head
112        // (try_kernel per tier: 0-handle on targets that predate a tier;
113        // dispatch widens, then falls back to the GEMM).
114        let w4a16_batchm = crate::layers::w4a16_gemv_tiers::W4a16BatchmTiers::resolve(gpu.as_ref());
115        // M<=16 batched GEMV for the wide BATCHED-DECODE lm_head. The SSM mixer
116        // already carries this handle (qwen3_ssm/mod.rs); the model level did
117        // not, so the decode head had no arm above 8 and fell to the M64-tile
118        // GEMM. Same try_kernel contract: 0-handle -> dispatch falls back.
119        let w4a16_gemv_batch16_kernel =
120            crate::layers::try_kernel(gpu.as_ref(), "w4a16_gemv", "w4a16_gemv_batch16");
121        // FP8 E4M3 LUT GEMV for the `--lm-head-dtype fp8` head. Loaded
122        // unconditionally (a handle is cheap); only invoked when `lm_head_fp8`
123        // is set, so the NVFP4/BF16 paths never touch it.
124        let dense_gemv_fp8w_kernel = gpu.kernel("gemv_fp8w", "dense_gemv_fp8w")?;
125        // FP8 dual-GEMV (batch=2): present on images that ship the kernel;
126        // try_kernel keeps the handle 0 on older sets so dispatch falls back
127        // to the per-token loop.
128        let dense_gemv_fp8w_batch2_kernel = crate::layers::try_kernel(
129            gpu.as_ref(),
130            "dense_gemv_fp8w_batch2",
131            "dense_gemv_fp8w_batch2",
132        );
133        let dense_gemm_kernel = gpu.kernel("gemm", "dense_gemm_bf16")?;
134        let dense_gemv_batchm_kernel = gpu
135            .kernel("dense_gemv_bf16_batchm", "dense_gemv_bf16_batchm")
136            .unwrap_or(spark_runtime::gpu::KernelHandle(0));
137        let argmax_kernel = gpu.kernel("argmax", "argmax_bf16")?;
138        let argmax_batch_kernel = gpu
139            .kernel("argmax", "argmax_bf16_batch")
140            .unwrap_or(spark_runtime::gpu::KernelHandle(0));
141        let argmax_logits_kernel = gpu.kernel("argmax", "argmax_fp32")?;
142        let batched_embed_kernel = gpu.kernel("embed_from_argmax", "batched_embed")?;
143        let fill_slots_kernel = gpu.kernel("metadata_fill", "fill_slots_from_block_table")?;
144        let profile = config.profile;
145        let profile_first = std::env::var("ATLAS_PROFILE_FIRST").is_ok();
146
147        // Pin the split-K attention split count to the configured max batch so
148        // a sequence's attention reduction is invariant to how many other
149        // sequences are co-batched (concurrent-decode determinism — see
150        // tasks/determinism_investigation.md).
151        let mut levers = ops::ModelLevers::from_env();
152        levers.max_decode_seqs = (max_batch_size as u32).max(1);
153
154        tracing::info!(
155            "TransformerModel: {} layers, vocab={}, hidden={}{}{}",
156            layers.len(),
157            config.vocab_size,
158            config.hidden_size,
159            if profile { " [PROFILE MODE]" } else { "" },
160            if profile_first {
161                " [PROFILE_FIRST]"
162            } else {
163                ""
164            },
165        );
166
167        // Build SSM state pool (with MTP intermediate/checkpoint pools only if speculative decoding enabled)
168        // num_intermediates = K, the verify-width ceiling. The CONV pools
169        // allocate K snapshots per slot; the H pools allocate K-1 (index
170        // K-1 is never written or read — see ssm_reserve) and tier by slot.
171        // For MTP K=2/3/4 verify: K = num_drafts + 1.
172        // For DFlash K=γ verify: K = γ + 1 (drafter's γ drafts + 1 verified bonus slot).
173        // Pool size = max of both so DFlash and MTP can coexist on the same model.
174        let dflash_kgamma = if !config.dflash_capture_layers.is_empty() {
175            // The +1 is the prefix bonus position in the verify input
176            // `[last_token, draft_0, ..., draft_{γ-1}]`. Sized from the
177            // RESOLVED drafter γ (factory sets `config.dflash_gamma` from the
178            // drafter checkpoint / --dflash-gamma): the legacy 17-wide
179            // ceiling (γ=16-era) cost ~1.5 GB of intermediates per slot per
180            // GB at γ=8 — ~12 GB across 8 slots on qwen3.8-27B — for slots
181            // the verify never touches (2026-08-19 256K/C8 boot ledger).
182            // Unknown γ keeps the 17-wide fallback.
183            config.dflash_gamma.map(|g| g + 1).unwrap_or(17)
184        } else {
185            0
186        };
187        // DFlash needs the SSM verify pools regardless of MTP weight presence
188        // or lm_head quantization — its K=γ verify path checkpoints SSM state
189        // for partial-accept rollback. Force `has_mtp` on whenever DFlash is
190        // active so the checkpoint pools exist.
191        // The MTP proposer needs an NVFP4 vocab head for drafting: either the
192        // main head (NVFP4 default) or the draft-only head built when the main
193        // head is BF16. `draft_lm_head_nvfp4` resolves to whichever is present.
194        let draft_lm_head_nvfp4 = mtp_lm_head_nvfp4.or(lm_head_nvfp4);
195        // 🔴 This flag SIZES THE RECURRENT ROLLBACK POOLS (checkpoints + per-token
196        // intermediates). It has to be true for every proposer that can reject a draft, not
197        // just the Qwen-shaped one.
198        //
199        // 🪤 GLM-5.3 populates NEITHER of the first two signals: its MTP block is
200        // `layers.{num_hidden_layers}`, not the Qwen `MtpWeights`, and its LM head is BF16 so
201        // there is no NVFP4 draft head. Its proposer is installed AFTER construction via
202        // `set_dflash_proposer`, so `new()` cannot see it either. `mtp_layer_types` is what the
203        // config parser records when the CHECKPOINT declares MTP layers — the one signal
204        // available this early. Without it the pools are never allocated and the first decode
205        // panics in `ssm_pool::h_checkpoint` ("len is 0 but the index is 0").
206        let checkpoint_declares_mtp = !config.mtp_layer_types.is_empty();
207        let has_mtp = self_speculative
208            || (use_speculative
209                && ((!mtp_weights.is_empty() && draft_lm_head_nvfp4.is_some())
210                    || checkpoint_declares_mtp))
211            || dflash_kgamma > 0;
212        let num_intermediates = if !has_mtp {
213            0
214        } else if dflash_kgamma > 0 {
215            // DFlash serve: the block drafter OWNS the verify path, so the
216            // widest verify is K = gamma + 1 and `num_drafts` never reaches
217            // the pool. Taking max(num_drafts+1, kgamma) sized these pools for
218            // the MTP K=2/3/4 ladder that a DFlash serve never runs: at
219            // num_drafts=15 that is 16 wide against a real ceiling of 9, and
220            // these pools are the single largest non-weight allocation on the
221            // box — 21.9 GB at 8 slots x 48 layers, as large as the model
222            // itself. Sizing to the real ceiling reclaims ~9.6 GB
223            // (2026-08-19 128K/C8 boot ledger; observed verify widths were
224            // K=8 and ks=[8;n], never above).
225            //
226            // The K2/K3/K4 arms still reachable on a DFlash serve (when the
227            // drafter returns <4 drafts) verify at K<=4, comfortably inside
228            // this. `require_verify_rollback_supported` remains the backstop
229            // if a future path asks for more.
230            dflash_kgamma
231        } else {
232            num_drafts + 1
233        };
234        let ssm_pool = std::sync::Arc::new(SsmStatePool::new(
235            &config,
236            max_batch_size,
237            has_mtp,
238            num_intermediates,
239            num_drafts,
240            // Stage-3 f16-SIZED h pools. No CLI surface publishes this and
241            // preflight refuses it until prefill narrowing lands, so it is
242            // false on every serveable config today.
243            crate::layers::qwen3_ssm::ssm_h_f16_pool_enabled(),
244            // `--ssm-rollback-mode` (EXPERIMENTAL replay scaffold; default
245            // snapshot, published by spark-server's serve_flags).
246            crate::ssm_reserve::ssm_rollback_mode(),
247            gpu.as_ref(),
248        )?);
249
250        // Fail fast if an SSM tier was requested (`ATLAS_SSM_TIER`) on a model
251        // with no recurrent state — a tier request there was previously a
252        // silent no-op. No-op when the tier is unset (default path).
253        super::ssm_tier::ensure_ssm_tier_capability(&config)?;
254
255        // SSM snapshot pool: Marconi prefix-cache slots + Phase-C
256        // decode-rollback ring. The decode-rollback region is only sized
257        // for SSM models — `num_ssm_layers == 0` makes both regions
258        // collapse to empty. The ring retains DECODE_ROLLBACK_RING_SLOTS
259        // boundary snapshots per sequence (DECOUPLED from ROLLBACK_RESTEER_CAP:
260        // the cap bounds re-steer attempts, the ring must retain enough
261        // boundaries that a clean PRE-loop one survives — `CAP+1=3` was too
262        // small and forced NoSsmSnapshot declines). Sized for every
263        // active-sequence pool slot (`max_batch_size`).
264        // The ring's ONLY writer (scheduler snapshot_boundary_if_ssm) and
265        // reader (content-loop rollback_to_boundary) live on the PLAIN decode
266        // path — the speculative path does its rejection rollback through the
267        // verify snapshot, never this ring. Under `--speculative` the ring is
268        // therefore unreachable, and on this model it is NOT cheap: 8 slots x
269        // max_batch x the full SSM blob (27B: 158.9 MB) = ~19.9 GB at batch 16,
270        // allocated up front. Skip it when speculative decode is on.
271        // The ring-depth decision (env overrides + speculative/watchdog
272        // skip) is SSOT'd in `crate::ssm_reserve::decode_rollback_ring_slots`
273        // — spark-server's `preflight_reserve` calls the SAME helper, so the
274        // GPU reservation and this allocation cannot drift. The scheduler
275        // keys off `decode_rollback_ring_slots()`, so a 0 here disables save
276        // AND rollback coherently (rollback declines, the documented
277        // fail-open).
278        let ring = crate::ssm_reserve::decode_rollback_ring_slots(
279            ssm_pool.num_ssm_layers,
280            use_speculative,
281        );
282        if let Some(reason) = ring.skip_reason {
283            let per_seq = (ssm_pool.h_bytes + ssm_pool.conv_bytes)
284                * ssm_pool.num_ssm_layers
285                * atlas_kernels::DECODE_ROLLBACK_RING_SLOTS;
286            tracing::info!(
287                "SSM decode-rollback ring: SKIPPED ({}) — the ring's save/rollback \
288                 path only runs on plain decode with watchdogs enabled. Saves {:.1} GB \
289                 ({} seqs x {} slots x full SSM blob). If plain-decode loop re-steer is \
290                 ever reached it fail-opens to decline; ATLAS_SSM_DECODE_RING=1 \
291                 force-restores the ring.",
292                reason,
293                (per_seq * max_batch_size) as f64 / 1e9,
294                max_batch_size,
295                atlas_kernels::DECODE_ROLLBACK_RING_SLOTS,
296            );
297        }
298        let decode_ring_slots = ring.slots;
299        // Marconi snapshot region (2380 MiB on GLM-5.3 at 16 slots). SSOT:
300        // `ssm_reserve::marconi_snapshot_slots` makes the SAME decision
301        // `preflight_reserve` made before the weights loaded. The region's
302        // only reader is a prefix-cache lookup, so an inactive cache makes
303        // every slot unreachable for the life of the process. Asking the
304        // constructed cache (`is_active`) rather than the CLI flag also
305        // covers the compressed-DeepSeek-V4 downgrade, where the flag is set
306        // but `NoPrefixCaching` is what actually gets installed.
307        let marconi =
308            crate::ssm_reserve::marconi_snapshot_slots(ssm_cache_slots, prefix_cache.is_active());
309        if let Some(reason) = marconi.skip_reason {
310            tracing::info!(
311                "SSM snapshot pool: Marconi region SKIPPED ({}) — {} slot(s) x {} layer(s) \
312                 = {:.0} MB freed for KV (restore with --enable-prefix-caching, or \
313                 ATLAS_SSM_MARCONI_FULL to allocate anyway)",
314                reason,
315                ssm_cache_slots,
316                ssm_pool.num_ssm_layers,
317                (ssm_cache_slots
318                    * ssm_pool.num_ssm_layers
319                    * (ssm_pool.h_bytes + ssm_pool.conv_bytes)) as f64
320                    / (1024.0 * 1024.0),
321            );
322        }
323        let ssm_cache_slots = marconi.slots;
324        let ssm_snapshots = SsmSnapshotPool::new(
325            ssm_cache_slots,
326            ssm_pool.h_bytes,
327            ssm_pool.conv_bytes,
328            ssm_pool.num_ssm_layers,
329            decode_ring_slots,
330            max_batch_size,
331            // Last-token hidden snapshot: post-final-norm `norm_output` is
332            // BF16 (`hidden_size` elements). Used to emit exact-hit logits
333            // without re-running the last token through the SSM layers.
334            config.hidden_size * 2,
335            gpu.as_ref(),
336        )?;
337        // Optional SSM snapshot spill tier. `None` (default) keeps the reclaim
338        // drop path byte-identical; blob sizing tracks the pool's spill layout.
339        let ssm_tier_store = super::impl_a1_init::build_ssm_tier_store(
340            &config,
341            ssm_snapshots.spill_blob_bytes(),
342            ssm_pool.num_ssm_layers,
343        )?;
344        if ssm_checkpoint_interval > 0 && ssm_cache_slots > 0 {
345            tracing::info!(
346                "Marconi intermediate checkpoints: every {} blocks ({} tokens at block_size={})",
347                ssm_checkpoint_interval,
348                ssm_checkpoint_interval * kv_cache.block_size(),
349                kv_cache.block_size(),
350            );
351        }
352
353        // Fixed metadata stride for CUDA graph compatibility
354        let max_blocks_per_seq = (max_seq_len / kv_cache.block_size() + 1) as u32;
355
356        // Permanent dummy KV block for padding sequences. Must be explicitly
357        // zeroed: `gpu.alloc()` returns uninitialized memory, and any kernel
358        // OOB-read (now routed here via the sentinel block_table_flat default
359        // fill in upload_batch_metadata_*) would otherwise dequant random
360        // bytes and inject garbage into attention scores.
361        let mut kv_cache = kv_cache;
362        let dummy_kv_block = kv_cache.alloc_block()?;
363        kv_cache.zero_block(dummy_kv_block, gpu.as_ref(), gpu.default_stream())?;
364        gpu.synchronize(gpu.default_stream())?;
365
366        // Transposed lm_head twin, PADDED so the tile GEMM's 16-byte cp.async B
367        // loads are aligned. The stride must be a multiple of 16; 128 also keeps
368        // whole N-tiles. Without the pad, N = vocab = 248077 (ODD) misaligns 15 of
369        // every 16 k-rows => the campaign's long-standing sticky CUDA 716.
370        // Default ON (kill: ATLAS_NO_LMHEAD_TGEMM=1). KV impact is nil: the pool
371        // reads 4759 blocks with the twin vs 4757 without. See STATE.md.
372        let lm_head_nvfp4_t = match (&lm_head_nvfp4, lmhead_tgemm_enabled()) {
373            (Some(w), true) => {
374                let (t, stride) =
375                    crate::weight_map::QuantizedWeight::transpose_concat_for_gemm_padded(
376                        gpu.as_ref(),
377                        &[(w, config.vocab_size)],
378                        config.hidden_size,
379                        16,
380                        128,
381                    )?;
382                // A padded stride is only safe on targets whose `w4a16_gemm_t`
383                // actually takes `ldb`. Every served vocab except this one is a
384                // multiple of 128 (stride == vocab, so `ldb` is a no-op and any
385                // kernel is fine); when it is NOT, a kernel missing the parameter
386                // strides by N and shears every row past the first — silently, on
387                // architectures that tolerate the misalignment. Say so loudly.
388                if stride != config.vocab_size {
389                    tracing::warn!(
390                        "lm_head twin uses a PADDED stride ({} != vocab {}): this target's \
391                         w4a16_gemm_t MUST accept the `ldb` argument, or decode at padded_n>=5 \
392                         will read sheared rows. Disable with ATLAS_NO_LMHEAD_TGEMM=1.",
393                        stride,
394                        config.vocab_size
395                    );
396                }
397                tracing::info!(
398                    "lm_head transposed twin: vocab={} -> padded stride={} (vocab%16={}), tile GEMM active",
399                    config.vocab_size,
400                    stride,
401                    config.vocab_size % 16
402                );
403                Some((t, stride as u32))
404            }
405            _ => None,
406        };
407        // Drafter-side view of the twin: valid ONLY when the drafter head IS
408        // the shared main head (`mtp_lm_head_nvfp4` absent) — the twin is a
409        // transpose of `lm_head_nvfp4` specifically, so handing it to a
410        // DEDICATED draft head would silently score drafts against the wrong
411        // weight. Zero extra memory in the shared case (aliases the twin).
412        let draft_lm_head_nvfp4_t = if mtp_lm_head_nvfp4.is_none() {
413            lm_head_nvfp4_t
414        } else {
415            None
416        };
417        // Build MTP proposer (extracted to keep `new` under the file cap).
418        let proposer: Option<Arc<dyn DraftProposer>> = super::impl_a1_init::build_mtp_proposer(
419            use_speculative,
420            mtp_weights,
421            embed_tokens,
422            draft_lm_head_nvfp4,
423            draft_lm_head_nvfp4_t,
424            &config,
425            gpu.as_ref(),
426            mtp_quant,
427            mtp_vocab_size,
428            max_seq_len,
429            kv_cache.num_blocks(),
430            &levers,
431        );
432
433        if self_speculative {
434            let num_ssm = config.num_ssm_layers();
435            let num_attn = config.num_attention_layers();
436            tracing::info!(
437                "Self-speculative decoding: ENABLED (skipping {} SSM layers, keeping {} attention layers)",
438                num_ssm,
439                num_attn,
440            );
441        }
442
443        // MTP hidden state save buffer (1 × hidden_size FP32)
444        let mtp_hidden_save = gpu.alloc(config.hidden_size * 4)?;
445        // Batched-verify hidden stash: [VERIFY_WY_TABLE_SEQS, hidden] BF16 —
446        // one slot per sequence of the widest batched verify chunk (n ≤ 32,
447        // the K-vs-batch ladder envelope, SSOT in `crate::layer`). Only
448        // meaningful with an MTP proposer — NULL otherwise (the batched
449        // verify path self-gates on it via can_batch_verify).
450        let verify_hidden_stash = if proposer.is_some() {
451            gpu.alloc(crate::layer::VERIFY_WY_TABLE_SEQS * config.hidden_size * 2)?
452        } else {
453            DevicePtr::NULL
454        };
455        // Batched-verify WY pointer-table staging (fixed address for CUDA
456        // graph stability; contents refreshed pre-graph every batched verify
457        // step). One [h|Hi0|Hi1|Hi2] x 4-entry slice per GDN layer — ~6 KB.
458        // NULL without an MTP proposer or on non-SSM models (path self-gates).
459        let verify_wy_tables = if proposer.is_some() && config.num_ssm_layers() > 0 {
460            let bytes = config.num_ssm_layers() * crate::layer::VERIFY_WY_LAYER_STRIDE_BYTES;
461            let buf = gpu.alloc(bytes)?;
462            gpu.memset(buf, 0, bytes)?;
463            buf
464        } else {
465            DevicePtr::NULL
466        };
467        // Catch-up ring: 512 rows covers the gate's serial re-probe interval
468        // (256 tokens) with 2x margin; ~4 MB at hidden 4096. Only allocated
469        // when the staged feature is enabled.
470        let mtp_catchup_ring = if crate::speculative::mtp_catchup_enabled() {
471            gpu.alloc(super::types::MTP_CATCHUP_RING_ROWS * config.hidden_size * 2)?
472        } else {
473            DevicePtr::NULL
474        };
475
476        // Whole-prompt hidden capture buffer, [rows, hidden_size] BF16 —
477        // 335 MB at 32k/h=5120. Backs BOTH halves of the drafter-context
478        // feature (see `crate::model::drafter_context`); NULL here disables
479        // prefill AND carry, since the carry path reads this buffer.
480        //
481        // Three conditions, all necessary: MTP must be active, the feature must
482        // not be killed, and the head must be a precision the batched prefill
483        // can actually run at — an NVFP4/FP8 MTP head would allocate this and
484        // never write it.
485        //
486        // `rows` is `max_seq_len` unless the proposer declares a smaller ceiling
487        // it can never be asked past (`DraftProposer::prefill_hidden_rows`, default
488        // `max_seq_len`). GLM-5.3's drafter is a DSA block capped at
489        // `max_dsa_context`, so at `--max-seq-len 524288` this buffer was 4.0 GiB
490        // of which all but 128 MiB was unreachable — and unreserved, because it is
491        // allocated after the KV pool is sized. ANOMALIES A59.
492        let mtp_prefill_rows = proposer
493            .as_ref()
494            .map_or(max_seq_len, |p| p.prefill_hidden_rows(max_seq_len))
495            .min(max_seq_len);
496        let mtp_prefill_hidden = if has_mtp
497            && mtp_quant.supports_drafter_prefill()
498            && crate::layers::mtp_drafter_prefill_enabled(&levers)
499        {
500            // Bound the capture to what the drafter can actually CONSUME.
501            // `prefill_drafter` writes into the drafter's own KV, which is
502            // capped at the DFlash ctx window, so a capture longer than that
503            // window is memory nothing can read: 1342 MB at --max-seq-len
504            // 131072 against a 16K window that can hold 168 MB of it.
505            // A prompt past the window simply does not get the whole-prompt
506            // drafter prefill (the coverage check at the consume site already
507            // handles that — blind beats poisoned); it costs acceptance on
508            // very long cold turns, not correctness.
509            //
510            // Pure-MTP serves keep the full ceiling: this narrowing is only
511            // sound because the DFlash drafter's own capacity is the binding
512            // constraint, and that reasoning does not transfer.
513            let capture_rows = if dflash_kgamma > 0 {
514                let cap = crate::layers::dflash_ctx_cap();
515                if cap == 0 {
516                    max_seq_len
517                } else {
518                    max_seq_len.min(cap)
519                }
520            } else {
521                max_seq_len
522            };
523            let bytes = capture_rows * config.hidden_size * 2;
524            tracing::info!(
525                "MTP drafter context: allocating {:.0} MB prompt-hidden capture \
526                 ({} x {} BF16){}",
527                bytes as f64 / 1e6,
528                capture_rows,
529                config.hidden_size,
530                if mtp_prefill_rows < max_seq_len {
531                    format!(
532                        " — capped from --max-seq-len {max_seq_len} to the proposer's \
533                         reachable context (A59)"
534                    )
535                } else {
536                    String::new()
537                },
538            );
539            gpu.alloc(bytes)?
540        } else {
541            if has_mtp
542                && !mtp_quant.supports_drafter_prefill()
543                && crate::layers::mtp_drafter_prefill_enabled(&levers)
544            {
545                tracing::info!(
546                    "MTP drafter context: INACTIVE — the batched drafter prefill \
547                     needs a BF16 MTP head (--mtp-quantization bf16); this head is \
548                     {mtp_quant:?}. No prompt-hidden capture allocated.",
549                );
550            }
551            DevicePtr::NULL
552        };
553
554        // DFlash 5-layer hidden-state stack. Allocated only when a
555        // BlockDiffusionDraftHead is the active proposer (`config.dflash_capture_layers`
556        // populated by the loader from the drafter's `dflash_config.target_layer_ids`).
557        // Size: N_capture × hidden_size × bf16 (typically 5 × 2048 × 2 = 20 KB).
558        let dflash_capture_layers: Vec<usize> = config.dflash_capture_layers.clone();
559        // Row capacity of the K-row capture buffer. KMAX = dflash_kgamma (=17 >=
560        // max verify K = gamma) so the K=gamma EAGLE path can capture every verify row;
561        // pre-fix paths use only rows 0-1. Stored on the model as the single
562        // source of truth so `try_dflash_capture_all` can bound its writes.
563        //
564        // Widened to `max_batch_size` K-row BANDS: a cross-sequence batched
565        // K=gamma verify (and batched decode) captures every (sequence, row)
566        // pair, sequence i writing band i at row `i * dflash_kgamma`. The
567        // scheduler reads a band back through `commit_ctx`'s `scratch_row`.
568        // Cost is trivial (8 seqs x 9 rows x 5 layers x 5120 x 2 B ~ 3.7 MB)
569        // and the single-sequence paths keep using band 0 unchanged.
570        let dflash_hidden_save_rows = if dflash_capture_layers.is_empty() {
571            0
572        } else {
573            dflash_kgamma.max(2) * max_batch_size.max(1)
574        };
575        let dflash_hidden_save = if dflash_capture_layers.is_empty() {
576            None
577        } else {
578            let n = dflash_capture_layers.len();
579            // Row-major K-row buffer: [row0 | row1 | ... | row_{KMAX-1}], each row =
580            // n_capture * hidden_size * bf16. Rows 0/1 keep their legacy offsets
581            // (0 and ctx_slot_bytes) so all K=2 readers (propose row 0,
582            // dflash_accept_append row 1) are unaffected.
583            Some(gpu.alloc(dflash_hidden_save_rows * n * config.hidden_size * 2)?)
584        };
585
586        // EP command buffer for token broadcast (4 bytes, u32)
587        let ep_cmd_buf = gpu.alloc(4)?;
588
589        // SOLID Incr-4: dedicated fixed-address buffer for the batched-decode MoE
590        // per-row fold map. max_batch_size i32 rows (e.g. 32·4 = 128 B). Allocated
591        // unconditionally like ep_cmd_buf/mtp_hidden_save — self.lora is populated
592        // post-construction (set_lora_weights), so we can't gate on it here, and
593        // the cost is negligible. Fixed address → graph-safe; contents copied per
594        // decode step. Moving the map off the +160 metadata gap frees seq_slot to
595        // reclaim +128..+256, lifting the concurrent-LoRA decode cap from 8 to 32.
596        let moe_row_adapter_buf = gpu.alloc(max_batch_size.max(1) * 4)?;
597
598        // Secondary stream + event for pipelining checkpoint D2D with MTP propose.
599        let secondary_stream = gpu.create_stream()?;
600        let secondary_event = gpu.create_event()?;
601        // Event ordering SSM-snapshot saves (default stream) before a warm
602        // Marconi restore (prefill stream). See `snapshot_event` doc in types.rs.
603        let snapshot_event = gpu.create_event()?;
604
605        // EP/TP: register the all-reduce target buffers with NCCL (caches the
606        // IB/RoCE memory registration, enabling zero-copy user-buffer
607        // collectives) and provide the bf16_add kernel for the 2-rank
608        // send/recv fast path.
609        //   - moe_output: EP MoE reduce + ALL GDN HeadParallel SSM out_proj
610        //     reduces (decode `ssm_forward`, batched decode, multi-seq
611        //     batched, prefill, prefill phase-3 all write out_proj into
612        //     `buffers.moe_output()`).
613        //   - norm_output: attention o_proj decode output
614        //     (`attention_forward_oproj` writes o_out = `buffers.norm_output()`),
615        //     reduced per attention layer under TP.
616        // 🔴 D1. Levers that decide the COLLECTIVE SCHEDULE are read per-rank from the
617        // environment, so a rank skew is a hang or a wrong-extent reduce rather than a perf
618        // difference. Agree on them before the first token. See `crate::rank_agree`.
619        if let Some(ref comm) = comm {
620            crate::rank_agree::assert_ranks_agree(
621                &*gpu,
622                comm.as_ref(),
623                &[
624                    // Splits a prefill chunk into sub-chunks, and every sub-chunk issues its own
625                    // `reduce_partial` at the attention site and the MLP site.
626                    (
627                        "ATLAS_GLM_PREFILL_ROWS",
628                        crate::layers::glm5next_layer::prefill_rows() as u64,
629                    ),
630                    // Perf-only (the MLP reduces once per site whichever arm runs), but a skew
631                    // here is still a confusing asymmetry and the check is free.
632                    (
633                        "ATLAS_GLM_MOE_ROW_BATCH_MAX",
634                        crate::layers::glm5next_mlp::forward::row_batch_max() as u64,
635                    ),
636                    // Documented as "both ranks must agree" in `model/types.rs` since it was
637                    // introduced, and never checked.
638                    (
639                        "ATLAS_EP_PROTOCOL(v2)",
640                        u64::from(matches!(
641                            std::env::var("ATLAS_EP_PROTOCOL").as_deref(),
642                            Ok("v2")
643                        )),
644                    ),
645                ],
646            )?;
647        }
648
649        if let Some(ref comm) = comm
650            && comm.world_size() == 2
651        {
652            let moe_ptr = buffers.moe_output().0;
653            let moe_bytes = buffers.sizes().moe_output;
654            match comm.register_buffer(moe_ptr, moe_bytes) {
655                Ok(_) => tracing::info!("Registered moe_output ({moe_bytes} B) with NCCL"),
656                Err(e) => tracing::warn!("ncclCommRegister moe_output failed (non-fatal): {e}"),
657            }
658            let norm_ptr = buffers.norm_output().0;
659            let norm_bytes = buffers.sizes().norm_output;
660            match comm.register_buffer(norm_ptr, norm_bytes) {
661                Ok(_) => tracing::info!("Registered norm_output ({norm_bytes} B) with NCCL"),
662                Err(e) => tracing::warn!("ncclCommRegister norm_output failed (non-fatal): {e}"),
663            }
664            //   - logits: the vocab-parallel BF16 LM head's all-reduce target
665            //     (`impl_a3::lm_head`). Unregistered it is the only per-step collective
666            //     whose SEND pointer NCCL has never seen, which costs an ibv_reg_mr on
667            //     the critical path of every token.
668            let logits_ptr = buffers.logits().0;
669            let logits_bytes = buffers.sizes().logits;
670            match comm.register_buffer(logits_ptr, logits_bytes) {
671                Ok(_) => tracing::info!("Registered logits ({logits_bytes} B) with NCCL"),
672                Err(e) => tracing::warn!("ncclCommRegister logits failed (non-fatal): {e}"),
673            }
674            match gpu.kernel("bf16_add", "bf16_add_inplace") {
675                Ok(k) => comm.set_add_kernel(k.0),
676                Err(e) => {
677                    tracing::warn!("bf16_add_inplace kernel not found (send/recv disabled): {e}")
678                }
679            }
680        }
681
682        // Allocate pinned host staging buffer for batched metadata H2D.
683        let pinned_bytes = buffers.sizes().scratch.max(64 * 1024);
684        let pinned_ptr = gpu.alloc_host_pinned(pinned_bytes)?;
685        tracing::info!("Pinned metadata staging: {} KB", pinned_bytes / 1024);
686        let max_batch_tokens = buffers.max_batch_tokens();
687        let pinned_staging = std::cell::UnsafeCell::new(PinnedMetaStaging {
688            ptr: pinned_ptr,
689            bytes: pinned_bytes,
690            positions: Vec::with_capacity(max_batch_tokens),
691            positions_h: Vec::with_capacity(max_batch_tokens),
692            positions_w: Vec::with_capacity(max_batch_tokens),
693            slots: Vec::with_capacity(max_batch_tokens),
694        });
695
696        // SSM state normalization kernel + pointer buffer (for chunked prefill).
697        let ssm_norm_k = gpu
698            .kernel("ssm_state_norm", "ssm_state_clamp_norm_fused")
699            .unwrap_or(KernelHandle(0));
700        let ssm_norm_f16_k = gpu
701            .kernel("ssm_state_norm", "ssm_state_clamp_norm_fused_f16")
702            .unwrap_or(KernelHandle(0));
703        let ssm_h_f32_to_f16_k =
704            crate::layers::try_kernel(gpu.as_ref(), "ssm_h_dtype", "ssm_h_state_f32_to_f16");
705        let ssm_h_f16_to_f32_k =
706            crate::layers::try_kernel(gpu.as_ref(), "ssm_h_dtype", "ssm_h_state_f16_to_f32");
707
708        // Logit softcapping (Gemma-4: cap=30.0). Only load if model uses it.
709        let logit_softcap_kernel = if config.final_logit_softcapping > 0.0 {
710            gpu.kernel("logit_softcap", "logit_softcap_bf16")
711                .unwrap_or_else(|e| {
712                    tracing::warn!("logit_softcap kernel not found: {e}");
713                    KernelHandle(0)
714                })
715        } else {
716            KernelHandle(0)
717        };
718        // FP32 softcap variant — only loaded when both softcap and FP32
719        // residual are active (i.e. Gemma-4 dense). Other models keep the
720        // BF16 softcap (or no softcap at all).
721        // The FP32 logit softcap variant required an FP32 residual stream,
722        // which no longer exists, so the BF16 softcap path is always taken.
723        let logit_softcap_fp32_kernel = KernelHandle(0);
724        // FP32 logits gate. The LM head produces FP32 (rather than BF16)
725        // logits when the residual stream is FP32 AND the LM head is a
726        // dense BF16 weight (no NVFP4 quant). NVFP4 LM heads keep their
727        // existing path because that quantization is a much larger
728        // precision floor than the BF16 store; FP32 wouldn't help there.
729        // Today this only affects Gemma-4 dense (model_type=="gemma4",
730        // num_experts==0, tied BF16 embed→lm_head).
731        // Gemma-4-31B FP32 lm_head experiment. Disabled by default —
732        // session 2026-05-01 verified the BF16 lm_head store is NOT the
733        // source of Gemma-4's haiku argmax flip: FP32 view of step-1
734        // logits keeps top1=` a` (21.85), top2=` waves` (21.706) — same
735        // 0.14-margin tiebreak as BF16. The drift is upstream in attention
736        // or MLP, not in the lm_head precision boundary. Code paths kept
737        // wired so a future bisection (Phase 2 of the plan) can re-enable
738        // via `ATLAS_GEMMA4_FP32_LMHEAD=1`. Keep `use_fp32_logits=false`
739        // by default so the rest of the model behaves identically to the
740        // pre-fix BF16 path on every model family.
741        // FP32 lm_head + softcap. Default OFF — empirically the gain on
742        // Gemma-4-31B is marginal (Creative occasionally cleaner; fib still
743        // fails the same broken-indentation pattern) but the cost is huge:
744        // FP32 forces host-side sampling (vocab=262144 × 4 bytes per
745        // decode step → ~1 MB D2H per token) which crushes decode TPS
746        // from ~35 tok/s to ~6 tok/s on Gemma-4-31B. Not worth it without
747        // a GPU-side FP32 argmax kernel. `ATLAS_GEMMA4_FP32_LMHEAD=1`
748        // re-enables for bisection / future work.
749        //
750        // The earlier "FP32 doesn't fix haiku" comment in this file was
751        // arrived at via incomplete bisection (the scheduler readback
752        // always assumed BF16 — see commit 16b2f3a's commit body). The
753        // 2026-05-01 evening run with the dispatch wired confirmed the
754        // bisection's *qualitative* conclusion: FP32 lm_head + softcap
755        // doesn't materially fix Gemma-4's structural NVFP4 attention
756        // drift on greedy code generation. Fix is upstream of lm_head.
757        // FP32 logits (ATLAS_GEMMA4_FP32_LMHEAD) required an FP32 residual
758        // stream as a precondition. With the residual stream now always BF16,
759        // the FP32 logits path can never activate, so it is permanently off.
760        let use_fp32_logits = false;
761        // Dedicated FP32 logits scratch — only the single-token decode path
762        // uses it. Prefill and batched-decode lm_head still write BF16 to the
763        // shared `buffers.logits()`. Sized for one row of `vocab_size` FP32.
764        let logits_fp32_buf = if use_fp32_logits {
765            let bytes = config.vocab_size * 4;
766            let p = gpu.alloc(bytes)?;
767            tracing::info!(
768                "FP32 LM head + softcap active (model_type={}, vocab={}). \
769                 Decode logits scratch: {} bytes.",
770                config.model_type,
771                config.vocab_size,
772                bytes,
773            );
774            p
775        } else {
776            DevicePtr::NULL
777        };
778
779        // Embedding scale (Gemma-4: sqrt(hidden_size)). Only load if model uses it.
780        let embed_scale_kernel = if config.embed_scale > 0.0 {
781            gpu.kernel("embed_scale", "bf16_scale_inplace")
782                .unwrap_or_else(|e| {
783                    tracing::warn!("embed_scale kernel not found: {e}");
784                    KernelHandle(0)
785                })
786        } else {
787            KernelHandle(0)
788        };
789        if config.embed_scale > 0.0 {
790            tracing::info!(
791                "Embedding scale: {:.4} (sqrt({}))",
792                config.embed_scale,
793                config.hidden_size
794            );
795        }
796        let ssm_norm_ptrs = if ssm_pool.num_ssm_layers > 0 {
797            gpu.alloc(ssm_pool.num_ssm_layers * 8)
798                .unwrap_or(DevicePtr::NULL)
799        } else {
800            DevicePtr::NULL
801        };
802
803        // GDN prefill buffers: sized for max_batch_tokens (the prefill chunk size),
804        // NOT max_seq_len. For prompts longer than this, prefill_twophase falls back
805        // to standard chunked prefill which carries h_state/conv_state between chunks.
806        // The GDN recurrence is sequential anyway, so chunking is mathematically identical.
807        let (gdn_qkv, gdn_gate_beta, gdn_out, gdn_z, gdn_buf_len) =
808            super::impl_a1_init::build_gdn_prefill_buffers(
809                &config,
810                max_batch_tokens,
811                max_seq_len,
812                gpu.as_ref(),
813            )?;
814
815        // FP8 calibration only runs when the cache is actually FP8 — the
816        // observe() call in decode.rs sits inside the FP8 cache branch. For
817        // BF16 or NVFP4 caches the MODEL.toml fp8_kv_calibration_tokens
818        // value is dead code and must not suppress CUDA graphs.
819        let has_fp8_calibration = config.fp8_kv_calibration_tokens > 0
820            && kv_cache.dtype() == spark_runtime::kv_cache::KvCacheDtype::Fp8;
821        // Feature-2 overlay kernels: resolve before `gpu` is moved into Self.
822        let overlay_kernels = crate::layers::ops::token_overlay::OverlayKernels::new(gpu.as_ref());
823        Ok(Self {
824            // Installed by the factory after construction: the layers read
825            // from the store during `new`, so it cannot be moved in here.
826            weight_store: None,
827            config,
828            dispatch: crate::layers::ops::GemmDispatch::from_env(),
829            derived: crate::layers::ops::DerivedWeights::new(),
830            levers,
831            stats: ops::ModelStats::new(),
832            #[cfg(feature = "cuda")]
833            innerq: gpu.kernel_registry().and_then(|reg| {
834                let driver = crate::layers::qwen3_attention::InnerQDriver::from_env(reg)?;
835                match driver.start() {
836                    Ok(()) => Some(driver),
837                    Err(e) => {
838                        tracing::warn!("InnerQ calibration disabled: start() failed: {e:#}");
839                        None
840                    }
841                }
842            }),
843            embed_tokens,
844            ngram_embed: None,
845            final_norm,
846            lm_head_weight,
847            lm_head_nvfp4,
848            lm_head_nvfp4_t,
849            lm_head_fp8,
850            layers,
851            buffers,
852            lora: None,
853            lora_rotatable: false,
854            kv_cache: Mutex::new(kv_cache),
855            gpu,
856            rms_norm_kernel,
857            dense_gemv_kernel,
858            dense_gemv_fp32out_kernel,
859            w4a16_gemv_kernel,
860            w4a16_gemv_logits_kernel,
861            w4a16_gemm_t_kernel,
862            w4a16_gemm_t_bf16_kernel,
863            w4a16_gemm_kernel,
864            w4a16_gemv_batch2_kernel,
865            w4a16_batchm,
866            w4a16_gemv_batch16_kernel,
867            dense_gemv_fp8w_kernel,
868            dense_gemv_fp8w_batch2_kernel,
869            dense_gemm_kernel,
870            dense_gemv_batchm_kernel,
871            argmax_kernel,
872            argmax_batch_kernel,
873            argmax_logits_kernel,
874            batched_embed_kernel,
875            fill_slots_kernel,
876            decode_graph: Mutex::new(std::collections::HashMap::new()),
877            batch_decode_graphs: Mutex::new((HashMap::new(), 0)),
878            // Suppress graphs during FP8 calibration only. MLA used to be
879            // suppressed because an internal sync was placed inside the graph
880            // capture region — that sync is now conditional on eager mode
881            // (see line ~3881), so graphs work for MLA too. The zero_all call
882            // at line ~3751 runs in Phase 1 BEFORE begin_capture, so it is
883            // naturally outside the captured region.
884            suppress_graphs: std::sync::atomic::AtomicBool::new(
885                has_fp8_calibration
886                    || std::env::var("ATLAS_DIAG_GEMMA4").is_ok_and(|v| v == "1" || v == "true")
887                    // PCND diagnostic: force eager decode (no CUDA-graph capture)
888                    // so ATLAS_DEBUG_SYNC_KERNELS can synchronize per launch and
889                    // surface async faults at the culprit kernel. Default-off.
890                    || std::env::var("ATLAS_DEBUG_NO_GRAPH").as_deref() == Ok("1"),
891            ),
892            ssm_pool,
893            ssm_snapshots,
894            ssm_tier_store,
895            max_blocks_per_seq,
896            dummy_kv_block,
897            profile,
898            profile_first_pending: std::sync::atomic::AtomicBool::new(profile_first),
899            proposer,
900            mtp_hidden_save,
901            verify_hidden_stash,
902            mtp_catchup_ring,
903            mtp_catchup_meta: parking_lot::Mutex::new((0, 0)),
904            mtp_prefill_hidden,
905            // SSOT for the capture bounds check — must be the ROW COUNT actually
906            // allocated, not `max_seq_len` (A59): a capacity above the allocation
907            // would let the capture epilogue write past it.
908            mtp_prefill_capacity: if mtp_prefill_hidden.is_null() {
909                0
910            } else {
911                mtp_prefill_rows
912            },
913            mtp_prefill_capture_len: std::sync::atomic::AtomicUsize::new(0),
914            mtp_prefill_capture_gen: std::sync::atomic::AtomicU64::new(0),
915            mtp_store_gen_seq: std::sync::atomic::AtomicU64::new(0),
916            mtp_carry: parking_lot::Mutex::new(None),
917            mtp_store_range: parking_lot::Mutex::new(super::mtp_carry::StoreRange::EMPTY),
918            dflash_hidden_save,
919            dflash_hidden_save_rows,
920            dflash_kgamma,
921            dflash_capture_layers,
922            verify2_graph: Mutex::new(std::collections::HashMap::new()),
923            verify3_graph: Mutex::new(std::collections::HashMap::new()),
924            verify4_graph: Mutex::new(std::collections::HashMap::new()),
925            verify_batched_graphs: Mutex::new((std::collections::HashMap::new(), 0)),
926            verify_wy_tables,
927            // Nothing staged yet: the buffer was memset to zero above, and no
928            // key describes zero, so the first verify step always uploads.
929            verify_wy_cache: Mutex::new(None),
930            verify_kgamma_graph: Mutex::new(std::collections::HashMap::new()),
931            fused_graph: Mutex::new(std::collections::HashMap::new()),
932            prefix_cache,
933            secondary_stream,
934            secondary_event,
935            snapshot_event,
936            comm,
937            ep_cmd_buf,
938            ep_protocol_v2: matches!(std::env::var("ATLAS_EP_PROTOCOL").as_deref(), Ok("v2")),
939            self_speculative,
940            last_mtp_hidden_idx: std::sync::atomic::AtomicUsize::new(0),
941            vision_encoder,
942            vision_embed_patches: Mutex::new(0),
943            vision_image_grids: Mutex::new(Vec::new()),
944            vision_row_base: Mutex::new(0),
945            vision_grid_base: Mutex::new(0),
946            vision_owned_images: Mutex::new(0),
947            pinned_staging,
948            ssm_checkpoint_interval,
949            ssm_state_norm_kernel: ssm_norm_k,
950            ssm_state_norm_f16_kernel: ssm_norm_f16_k,
951            ssm_h_f32_to_f16_kernel: ssm_h_f32_to_f16_k,
952            ssm_h_f16_to_f32_kernel: ssm_h_f16_to_f32_k,
953            ssm_h_f16_scratch: std::sync::OnceLock::new(),
954            ssm_norm_ptrs_buf: ssm_norm_ptrs,
955            moe_row_adapter_buf,
956            gdn_buf_qkv: gdn_qkv,
957            gdn_buf_gate_beta: gdn_gate_beta,
958            gdn_buf_out: gdn_out,
959            gdn_buf_z: gdn_z,
960            gdn_buf_max_len: gdn_buf_len,
961            logit_softcap_kernel,
962            logit_softcap_fp32_kernel,
963            use_fp32_logits,
964            logits_fp32_buf,
965            embed_scale_kernel,
966            overlays: None,
967            overlay_kernels,
968            overlay_route_slot: std::sync::atomic::AtomicI32::new(-1),
969            decode_moe_route: std::sync::atomic::AtomicI32::new(1), // Fold (safe default)
970        })
971    }
972}