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