spark_model/layers/qwen3_attention/
init.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! `Qwen3AttentionLayer` constructors: `new`, `new_ungated`, and the
4//! private `new_with_gating` (kernel-loading core).
5
6use anyhow::Result;
7use spark_runtime::gpu::{GpuBackend, KernelHandle};
8use spark_runtime::kv_cache::KvCacheDtype;
9
10// `gate` must be called through a real path, not through a `let`-bound
11// function pointer: coercing a `#[track_caller]` fn to a pointer inserts a shim
12// and the audit would name the shim instead of the dispatch site below.
13use super::init_arch_gates::{ArchProbes, gated as gate, present};
14use super::types::{HeadGateActivation, Qwen3AttentionLayer};
15use crate::layers::FfnComponent;
16use crate::layers::fp8_calibration::Fp8KvCalibration;
17use crate::weight_map::{AttentionWeights, DenseWeight, QuantWeight, QuantizedWeight};
18
19impl Qwen3AttentionLayer {
20    pub fn new(
21        input_norm: DenseWeight,
22        attn: AttentionWeights,
23        post_attn_norm: DenseWeight,
24        ffn: FfnComponent,
25        attn_layer_idx: usize,
26        q_nvfp4: Option<QuantizedWeight>,
27        k_nvfp4: Option<QuantizedWeight>,
28        v_nvfp4: Option<QuantizedWeight>,
29        gpu: &dyn GpuBackend,
30        kv_dtype: KvCacheDtype,
31        fp8_calibration_tokens: usize,
32        config: &avarok_core::config::ModelConfig,
33    ) -> Result<Self> {
34        Self::new_with_gating(
35            input_norm,
36            attn,
37            post_attn_norm,
38            ffn,
39            attn_layer_idx,
40            q_nvfp4,
41            k_nvfp4,
42            v_nvfp4,
43            true,
44            gpu,
45            kv_dtype,
46            fp8_calibration_tokens,
47            config,
48        )
49    }
50
51    pub fn new_ungated(
52        input_norm: DenseWeight,
53        attn: AttentionWeights,
54        post_attn_norm: DenseWeight,
55        ffn: FfnComponent,
56        attn_layer_idx: usize,
57        q_nvfp4: Option<QuantizedWeight>,
58        k_nvfp4: Option<QuantizedWeight>,
59        v_nvfp4: Option<QuantizedWeight>,
60        gpu: &dyn GpuBackend,
61        kv_dtype: KvCacheDtype,
62        fp8_calibration_tokens: usize,
63        config: &avarok_core::config::ModelConfig,
64    ) -> Result<Self> {
65        Self::new_with_gating(
66            input_norm,
67            attn,
68            post_attn_norm,
69            ffn,
70            attn_layer_idx,
71            q_nvfp4,
72            k_nvfp4,
73            v_nvfp4,
74            false,
75            gpu,
76            kv_dtype,
77            fp8_calibration_tokens,
78            config,
79        )
80    }
81
82    #[allow(clippy::too_many_arguments)]
83    fn new_with_gating(
84        input_norm: DenseWeight,
85        attn: AttentionWeights,
86        post_attn_norm: DenseWeight,
87        ffn: FfnComponent,
88        attn_layer_idx: usize,
89        q_nvfp4: Option<QuantizedWeight>,
90        k_nvfp4: Option<QuantizedWeight>,
91        v_nvfp4: Option<QuantizedWeight>,
92        gated: bool,
93        gpu: &dyn GpuBackend,
94        kv_dtype: KvCacheDtype,
95        fp8_calibration_tokens: usize,
96        config: &avarok_core::config::ModelConfig,
97    ) -> Result<Self> {
98        let (reshape_mod, reshape_fn, decode_mod, decode_fn) =
99            super::init_kernel_dispatch::kernel_modules_for_dtype(kv_dtype, config.head_dim);
100        // Which cross-architecture kernel families this config says exist. A
101        // family the model does not have is never LOOKED UP, so it leaves no
102        // failed row in the boot audit. See `init_arch_gates`.
103        let probes = ArchProbes::from_config(config);
104        let mrope_interleaved = config.mrope_interleaved;
105        Ok(Self {
106            input_norm,
107            attn,
108            post_attn_norm,
109            ffn,
110            attn_layer_idx,
111            lora: None,
112            gated,
113            mrope_interleaved,
114            kv_dtype,
115            head_dim_override: None,
116            num_q_heads_override: None,
117            num_kv_heads_override: None,
118            sliding_window: None,
119            rope_theta_override: None,
120            rotary_dim_override: None,
121            rope_proportional: false,
122            attn_scale_override: None,
123            k_eq_v: false,
124            v_norm_weight: None,
125            head_gate_weight: None,
126            head_gate_activation: HeadGateActivation::Sigmoid,
127            sigmoid_gate_head_broadcast_k: super::super::try_kernel(
128                gpu,
129                "residual_add",
130                "sigmoid_gate_mul_head_broadcast",
131            ),
132            softplus_gate_head_broadcast_k: super::super::try_kernel(
133                gpu,
134                "residual_add",
135                "softplus_gate_mul_head_broadcast",
136            ),
137            yarn_inv_freq: spark_runtime::gpu::DevicePtr::NULL,
138            yarn_attention_factor: 1.0,
139            post_attn_out_norm: None,
140            post_ffn_out_norm: None,
141            layer_scalar: None,
142            moe_ffn: None,
143            shortcut_carry_out: None,
144            shortcut_carry_in: None,
145            pre_moe_norm: None,
146            post_moe_out_norm: None,
147            post_dense_ffn_norm: None,
148            sparse_v_threshold: 0.0,
149            q_weight: q_nvfp4.map(QuantWeight::Nvfp4),
150            k_weight: k_nvfp4.map(QuantWeight::Nvfp4),
151            v_weight: v_nvfp4.map(QuantWeight::Nvfp4),
152            o_weight: None,
153            o_dense_bf16: None,
154            mla: None,
155            // ── DeepSeek-V4 Manifold-Constrained Hyper-Connections (mHC) ──
156            // `hc` stays None for non-V4 models; the V4 loader attaches real
157            // HcWeights after this constructor. Kernel handles are lazy (null
158            // when the hyper_connection module is absent), so non-V4 models
159            // still start cleanly.
160            hc: None,
161            qsa: None,
162            hc_pre_k: gate(probes.hyper_connection, gpu, "hyper_connection", "hc_pre"),
163            hc_post_k: gate(probes.hyper_connection, gpu, "hyper_connection", "hc_post"),
164            hc_expand_k: gate(
165                probes.hyper_connection,
166                gpu,
167                "hyper_connection",
168                "hc_expand",
169            ),
170            hc_head_k: gate(probes.hyper_connection, gpu, "hyper_connection", "hc_head"),
171            qkv_nvfp4_t: None,
172            q_nvfp4_t: None,
173            k_nvfp4_t: None,
174            v_nvfp4_t: None,
175            o_nvfp4_t: None,
176            q_fp8w_t: None,
177            k_fp8w_t: None,
178            v_fp8w_t: None,
179            o_fp8w_t: None,
180            w8a16_gemm_t_k: super::super::try_kernel(gpu, "w8a16_gemm_t", "w8a16_gemm_t"),
181            w8a16_gemm_t_pipelined_k: super::super::try_kernel(
182                gpu,
183                "w8a16_gemm_t",
184                "w8a16_gemm_t_pipelined",
185            ),
186            w8a16_gemm_t_m128_k: super::super::try_kernel(
187                gpu,
188                "w8a16_gemm_t_m128",
189                "w8a16_gemm_t_m128",
190            ),
191            // `Fp8ActQuant` probes the shared quantizer AND the Hopper
192            // twin, which only `kernels/hopper` ships, and carries both
193            // handles so a launcher can never pair one kernel's entry point
194            // with the other's grid. Every target still has the shared one.
195            // The shared name is the one `W8A8_PREFILL_KERNELS[0]` (#915)
196            // spells for preflight, which derives it from the same constants.
197            per_token_group_quant_fp8_k: crate::layers::ops::Fp8ActQuant::resolve(gpu),
198            fp8_gemm_t_blockscaled_k: super::super::try_kernel(
199                gpu,
200                super::types_weights::W8A8_PREFILL_KERNELS[1].0,
201                super::types_weights::W8A8_PREFILL_KERNELS[1].1,
202            ),
203            // Same optional adapter the SSM layer loads (`init.rs`): absent on
204            // a shadow that has no `fp8_scale_transpose` module, which makes
205            // the cuBLASLt W8A8 arms decline rather than hand the library the
206            // wrong scale order.
207            fp8_act_scale_kmajor_k: super::super::try_kernel(
208                gpu,
209                "fp8_scale_transpose",
210                "fp8_act_scale_to_kmajor",
211            ),
212            rms_norm_k: gpu.kernel("norm", "rms_norm")?,
213            rms_norm_w_k: if crate::ships_vanilla_norm_weights(config) {
214                gpu.kernel("rms_norm_vanilla", "rms_norm_vanilla")?
215            } else {
216                gpu.kernel("norm", "rms_norm")?
217            },
218            rms_norm_w_warp_row_k: if crate::ships_vanilla_norm_weights(config) {
219                gpu.kernel("rms_norm_vanilla", "rms_norm_vanilla_warp_row")
220                    .unwrap_or(KernelHandle(0))
221            } else {
222                KernelHandle(0)
223            },
224            norm_vanilla: crate::ships_vanilla_norm_weights(config),
225            rms_norm_residual_k: if crate::ships_vanilla_norm_weights(config) {
226                gpu.kernel("norm", "rms_norm_residual_vanilla")?
227            } else {
228                gpu.kernel("norm", "rms_norm_residual")?
229            },
230            dense_gemv_k: gpu.kernel("gemv", "dense_gemv_bf16")?,
231            dequant_q2_0_gn_k: super::super::try_kernel(
232                gpu,
233                "dequant_gguf_bf16",
234                "dequant_q2_0_gn_to_bf16",
235            ),
236            // Resolved by `set_packed_q2_weights`, never here: q2_0_mmq /
237            // the Q8_1 quantizer ship only in GGUF-serving targets, and an
238            // unconditional probe fails the boot audit everywhere else.
239            q2_0_mmq_nc_k: KernelHandle(0),
240            q2_0_mmq_wc_k: KernelHandle(0),
241            q4k_quant_act_k: KernelHandle(0),
242            q2_0_gemv_k: super::super::try_kernel(gpu, "q2_0_gemv_vec", "q2_0_gemv_vec"),
243            dense_gemv_batchm_k: gpu
244                .kernel("dense_gemv_bf16_batchm", "dense_gemv_bf16_batchm")
245                .unwrap_or(KernelHandle(0)),
246            w4a16_gemv_k: gpu.kernel("w4a16_gemv", "w4a16_gemv")?,
247            w4a16_gemv_sw_k: super::super::try_kernel(gpu, "w4a16_gemv", "w4a16_gemv_sw"),
248            w8a16_gemv_k: gpu.kernel("w8a16_gemv", "w8a16_gemv")?,
249            w8a16_gemv_batch4_k: super::super::try_kernel(
250                gpu,
251                "w8a16_gemv_batch4",
252                "w8a16_gemv_batch4",
253            ),
254            w8a16_gemv_batch16_k: super::super::try_kernel(
255                gpu,
256                "w8a16_gemv_batch4",
257                "w8a16_gemv_batch16",
258            ),
259            w8a16_gemv_batch4_strided_k: super::super::try_kernel(
260                gpu,
261                "w8a16_gemv_batch4",
262                "w8a16_gemv_batch4_strided",
263            ),
264            w8a16_gemv_batch16_strided_k: super::super::try_kernel(
265                gpu,
266                "w8a16_gemv_batch4",
267                "w8a16_gemv_batch16_strided",
268            ),
269            w8a16_gemm_m16_k: super::super::try_target_kernel(
270                gpu,
271                "w8a16_gemm_m16",
272                "w8a16_gemm_m16",
273            ),
274            w8a16_gemm_m16_strided_k: super::super::try_target_kernel(
275                gpu,
276                "w8a16_gemm_m16",
277                "w8a16_gemm_m16_strided",
278            ),
279            m16_tc: crate::layers::dense_ffn::m16_tc::m16_tc_levers().attn,
280            w8a16_gemv_ncol2_k: super::super::try_target_kernel(
281                gpu,
282                "w8a16_gemv_ncol",
283                "w8a16_gemv_batch16_ncol2",
284            ),
285            w8a16_gemv_ncol4_k: super::super::try_target_kernel(
286                gpu,
287                "w8a16_gemv_ncol",
288                "w8a16_gemv_batch16_ncol4",
289            ),
290            w8a16_gemv_ncol2_strided_k: super::super::try_target_kernel(
291                gpu,
292                "w8a16_gemv_ncol",
293                "w8a16_gemv_batch16_ncol2_strided",
294            ),
295            w8a16_gemv_ncol4_strided_k: super::super::try_target_kernel(
296                gpu,
297                "w8a16_gemv_ncol",
298                "w8a16_gemv_batch16_ncol4_strided",
299            ),
300            attn_ncol: super::attn_ncol_gemv::ncol_gemv_enabled()
301                .then(super::attn_ncol_gemv::ncol_gemv_width),
302            w8a16_gemm_k: super::super::try_kernel(gpu, "w8a16_gemm", "w8a16_gemm"),
303            w8a16_gemm_pipelined_k: super::super::try_kernel(
304                gpu,
305                "w8a16_gemm_pipelined",
306                "w8a16_gemm_pipelined",
307            ),
308            w4a16_gemv_dual_k: gpu.kernel("w4a16_gemv_fused", "w4a16_gemv_dual")?,
309            rope_k: gpu.kernel("rope", "rope_forward")?,
310            rope_strided_k: super::super::try_kernel(gpu, "rope", "rope_forward_strided"),
311            rms_norm_strided_k: super::super::try_kernel(gpu, "norm", "rms_norm_strided"),
312            rope_mrope_interleaved_k: super::super::try_kernel(
313                gpu,
314                "rope_mrope_interleaved",
315                "rope_forward_mrope_interleaved",
316            ),
317            rope_mrope_interleaved_k_only_k: super::super::try_kernel(
318                gpu,
319                "rope_mrope_interleaved",
320                "rope_forward_mrope_interleaved_k_only",
321            ),
322            rope_yarn_k: super::super::try_kernel(gpu, "rope", "rope_forward_yarn"),
323            rope_yarn_scaled_k: super::super::try_kernel(gpu, "rope", "rope_forward_yarn_scaled"),
324            // Interleaved (GPT-J / is_neox_style=False) YaRN RoPE — DeepSeek-V4 MLA.
325            rope_yarn_interleaved_k: super::super::try_kernel(
326                gpu,
327                "rope",
328                "rope_forward_yarn_interleaved",
329            ),
330            rope_yarn_interleaved_inv_k: super::super::try_kernel(
331                gpu,
332                "rope",
333                "rope_forward_yarn_interleaved_inv",
334            ),
335            rope_proportional_k: super::super::try_kernel(gpu, "rope", "rope_forward_proportional"),
336            reshape_cache_k: gpu.kernel(reshape_mod, reshape_fn)?,
337            // ★ try_TARGET_kernel, not try_kernel. `fused_k_norm_rope_cache`
338            // is a module only SOME targets compile -- it is absent from the
339            // `strix` and `strix-hip` trees. A plain `try_kernel` there issues
340            // a lookup that fails, and the boot audit records every failed
341            // lookup as a dispatch site on a silent fallback path and REFUSES
342            // TO SERVE. A target that never built the module has no fallback
343            // to be silent about; it has its only path. This is the exact
344            // class that left stack 1089308's first campaign unable to boot
345            // with 17 such lookups. On a target that DOES carry the module
346            // this is identical to `try_kernel`, audit included.
347            fused_k_norm_rope_cache_write_bf16_k: super::super::try_target_kernel(
348                gpu,
349                "fused_k_norm_rope_cache",
350                "fused_k_norm_rope_cache_write_bf16",
351            ),
352            // Same module, same reason — see the note above.
353            fused_k_norm_rope_mrope_cache_write_bf16_k: super::super::try_target_kernel(
354                gpu,
355                "fused_k_norm_rope_cache",
356                "fused_k_norm_rope_mrope_cache_write_bf16",
357            ),
358            reshape_and_cache_flash_v_only_k: super::super::try_kernel(
359                gpu,
360                "reshape_and_cache",
361                "reshape_and_cache_flash_v_only",
362            ),
363            // `try_target_kernel`, not `try_kernel`:
364            // `reshape_and_cache_fused_k_fp8.cu` is a gb10-tree file, mirrored
365            // into the targets that inherit gb10's common/ (hopper, b200) and
366            // absent from the ones with their own (b300, strix, metal). A
367            // plain lookup on a target that never built the module is recorded
368            // by the boot audit as a dispatch site on a silent fallback and
369            // REFUSES TO SERVE — this probes for the module first and issues
370            // no lookup when it is absent, so those targets keep the un-fused
371            // chain.
372            fused_k_norm_rope_cache_write_fp8_kv_k: super::super::try_target_kernel(
373                gpu,
374                "reshape_and_cache_fused_k_fp8",
375                "fused_k_norm_rope_cache_write_fp8_kv",
376            ),
377            wht_bf16_k: super::super::try_kernel(gpu, "wht_bf16", "wht_bf16_inplace"),
378            wht_bf16_k_inv: super::super::try_kernel(gpu, "wht_bf16", "wht_bf16_inplace_inv"),
379            innerq_apply_q_k: super::super::try_kernel(
380                gpu,
381                "tq_plus_innerq_apply",
382                "tq_plus_innerq_apply_q",
383            ),
384            innerq_apply_k_k: super::super::try_kernel(
385                gpu,
386                "tq_plus_innerq_apply",
387                "tq_plus_innerq_apply_k",
388            ),
389            paged_decode_k: gpu.kernel(decode_mod, decode_fn)?,
390            // HDIM>256 decode arm. Every dispatch site gates on
391            // `head_dim > 256 && paged_decode_512_k.0 != 0`, so on a head_dim
392            // 128 model this whole family was a per-dtype probe that could
393            // never be used. `probes.wide_head_dim` is derived from the same
394            // `config.head_dim` those sites read.
395            paged_decode_512_k: match kv_dtype {
396                KvCacheDtype::Bf16 => gate(
397                    probes.wide_head_dim,
398                    gpu,
399                    "paged_decode_attn_512",
400                    "paged_decode_attn",
401                ),
402                KvCacheDtype::Turbo4 => gate(
403                    probes.wide_head_dim,
404                    gpu,
405                    "paged_decode_turbo4_512",
406                    "paged_decode_attn_turbo4",
407                ),
408                KvCacheDtype::Turbo8 => gate(
409                    probes.wide_head_dim,
410                    gpu,
411                    "paged_decode_turbo8_512",
412                    "paged_decode_attn_turbo8",
413                ),
414                KvCacheDtype::Turbo3 | KvCacheDtype::Turbo2 => gate(
415                    probes.wide_head_dim,
416                    gpu,
417                    "paged_decode_turbo4_512",
418                    "paged_decode_attn_turbo4",
419                ),
420                _ => gate(
421                    probes.wide_head_dim,
422                    gpu,
423                    "paged_decode_attn_fp8_512",
424                    "paged_decode_attn_fp8",
425                ),
426            },
427            paged_decode_mla_k: gate(probes.mla, gpu, "paged_decode_mla", "paged_decode_attn"),
428            // DeepSeek-V4-Flash MLA paged decode (compressed 576-dim KV cache).
429            mla_paged_decode_k: gate(
430                probes.mla,
431                gpu,
432                "mla_paged_decode",
433                "mla_paged_decode_nvfp4",
434            ),
435            mla_paged_decode_fp8_k: gate(
436                probes.mla,
437                gpu,
438                "mla_paged_decode_fp8",
439                "mla_paged_decode_fp8",
440            ),
441            mla_batched_gemv_k: gate(probes.mla, gpu, "mla_absorbed", "mla_batched_gemv"),
442            mla_q_rope_scatter_k: gate(probes.mla, gpu, "mla_absorbed", "mla_q_rope_scatter"),
443            mla_q_rope_writeback_k: gate(probes.mla, gpu, "mla_absorbed", "mla_q_rope_writeback"),
444            mla_cache_assemble_k: gate(probes.mla, gpu, "mla_absorbed", "mla_cache_assemble"),
445            mla_q_rope_extract_batched_k: gate(
446                probes.mla,
447                gpu,
448                "mla_absorbed",
449                "mla_q_rope_extract_batched",
450            ),
451            mla_q_rope_writeback_batched_k: gate(
452                probes.mla,
453                gpu,
454                "mla_absorbed",
455                "mla_q_rope_writeback_batched",
456            ),
457            mla_kv_assemble_batched_k: gate(
458                probes.mla,
459                gpu,
460                "mla_absorbed",
461                "mla_kv_assemble_batched",
462            ),
463            mla_cache_assemble_batched_k: gate(
464                probes.mla,
465                gpu,
466                "mla_absorbed",
467                "mla_cache_assemble_batched",
468            ),
469            prefill_attn_mla320_k: gate(
470                probes.mla,
471                gpu,
472                "mla_prefill_attn",
473                "mla_prefill_attn_320",
474            ),
475            grouped_gemm_mla_k: gate(probes.mla, gpu, "grouped_gemm_mla", "grouped_gemm_mla"),
476            mla_q_final_assemble_k: gate(
477                probes.mla,
478                gpu,
479                "mla_absorbed",
480                "mla_q_final_assemble_batched",
481            ),
482            mla_fused_prefill_k: gate(probes.mla, gpu, "mla_fused_prefill", "mla_fused_prefill"),
483            gemm_splitk_partial_k: super::super::try_kernel(
484                gpu,
485                "gemm_splitk",
486                "dense_gemm_splitk_partial",
487            ),
488            gemm_splitk_reduce_k: super::super::try_kernel(
489                gpu,
490                "gemm_splitk",
491                "dense_gemm_splitk_reduce",
492            ),
493            dense_gemm_tc_k: super::super::try_kernel(gpu, "gemm_tc", "dense_gemm_tc"),
494            paged_decode_splitk_k: match kv_dtype {
495                KvCacheDtype::Nvfp4 => {
496                    Some(gpu.kernel("paged_decode_nvfp4", "paged_decode_attn_splitk_nvfp4")?)
497                }
498                KvCacheDtype::Turbo3
499                | KvCacheDtype::Turbo4
500                | KvCacheDtype::Turbo8
501                | KvCacheDtype::Bf16KTurbo3V
502                | KvCacheDtype::Bf16KTurbo4V
503                | KvCacheDtype::Bf16KTurbo2V
504                | KvCacheDtype::Fp8KTurbo3V
505                | KvCacheDtype::Fp8KTurbo4V
506                | KvCacheDtype::Fp8KTurbo2V
507                | KvCacheDtype::Turbo4KTurbo3V
508                | KvCacheDtype::Turbo4KTurbo8V
509                | KvCacheDtype::Turbo3KTurbo8V => None,
510                _ => Some(gpu.kernel("paged_decode_fp8", "paged_decode_attn_splitk_fp8")?),
511            },
512            paged_decode_reduce_k: match kv_dtype {
513                KvCacheDtype::Nvfp4 => {
514                    Some(gpu.kernel("paged_decode_nvfp4", "paged_decode_attn_reduce_nvfp4")?)
515                }
516                KvCacheDtype::Turbo3
517                | KvCacheDtype::Turbo4
518                | KvCacheDtype::Turbo8
519                | KvCacheDtype::Bf16KTurbo3V
520                | KvCacheDtype::Bf16KTurbo4V
521                | KvCacheDtype::Bf16KTurbo2V
522                | KvCacheDtype::Fp8KTurbo3V
523                | KvCacheDtype::Fp8KTurbo4V
524                | KvCacheDtype::Fp8KTurbo2V
525                | KvCacheDtype::Turbo4KTurbo3V
526                | KvCacheDtype::Turbo4KTurbo8V
527                | KvCacheDtype::Turbo3KTurbo8V => None,
528                _ => Some(gpu.kernel("paged_decode_fp8", "paged_decode_attn_reduce_fp8")?),
529            },
530            // The GQA-packed non-split twins. `try_target_kernel`, not
531            // `kernel`: the sources are gb10's
532            // (`kernels/gb10/common/paged_decode_attn_{bf16,fp8}_gqa.cu`), so
533            // a target that does not carry them resolves a zero handle and the
534            // dispatch keeps the unpacked kernel. Resolved unconditionally
535            // rather than behind `AVAROK_ATTN_DECODE_GQA_PACK` for the same
536            // reason the Hopper twins below are: a handle set that depended on
537            // the environment is a handle set a CUDA graph capture cannot
538            // trust.
539            paged_decode_bf16_gqa_k: present(super::super::try_target_kernel(
540                gpu,
541                "paged_decode_attn_bf16_gqa",
542                "paged_decode_attn_bf16_gqa",
543            )),
544            paged_decode_fp8_gqa_k: present(super::super::try_target_kernel(
545                gpu,
546                "paged_decode_attn_fp8_gqa",
547                "paged_decode_attn_fp8_gqa",
548            )),
549            // The Hopper split-K twins (#928). `try_kernel`, not `kernel`: the
550            // sources live only in `kernels/hopper/common`, so on gb10, b200,
551            // strix and metal the lookup returns a zero handle and the dispatch
552            // keeps its existing arm. Resolved unconditionally rather than
553            // behind the `attn_decode_splitk` lever because the FP8 twin is a
554            // drop-in for the gb10 pair whenever split-K runs at all, and
555            // probing on a lever the operator can flip at boot would make the
556            // handle set depend on the environment — which a CUDA graph
557            // capture must not.
558            paged_decode_splitk_hopper_k: present(super::super::try_target_kernel(
559                gpu,
560                "paged_decode_fp8_splitk_hopper",
561                "paged_decode_attn_splitk_fp8_hopper",
562            )),
563            paged_decode_reduce_hopper_k: present(super::super::try_target_kernel(
564                gpu,
565                "paged_decode_fp8_splitk_hopper",
566                "paged_decode_attn_reduce_fp8_hopper",
567            )),
568            paged_decode_splitk_bf16_hopper_k: present(super::super::try_target_kernel(
569                gpu,
570                "paged_decode_bf16_splitk_hopper",
571                "paged_decode_attn_splitk_bf16_hopper",
572            )),
573            paged_decode_reduce_bf16_hopper_k: present(super::super::try_target_kernel(
574                gpu,
575                "paged_decode_bf16_splitk_hopper",
576                "paged_decode_attn_reduce_bf16_hopper",
577            )),
578            residual_add_k: gpu.kernel("residual_add", "bf16_residual_add")?,
579            // Gemma-4 rms-norm uses the absolute formula `out = x * rms * w`.
580            rms_norm_f32_in_k: KernelHandle(0),
581            sigmoid_gate_mul_k: gpu.kernel("residual_add", "sigmoid_gate_mul")?,
582            deinterleave_qg_k: gpu.kernel("ssm_preprocess", "deinterleave_qg")?,
583            w4a16_gemv_qg_k: gpu.kernel("w4a16_gemv", "w4a16_gemv_qg")?,
584            residual_add_rms_norm_k: if crate::ships_vanilla_norm_weights(config) {
585                gpu.kernel("norm", "residual_add_rms_norm_vanilla")?
586            } else {
587                gpu.kernel("norm", "residual_add_rms_norm")?
588            },
589            residual_add_rms_norm_gatef32_k: crate::layers::try_kernel(
590                gpu,
591                "norm",
592                "residual_add_rms_norm_gatef32",
593            ),
594            w4a16_gemv_qg_batch2_k: gpu.kernel("w4a16_gemv", "w4a16_gemv_qg_batch2")?,
595            w4a16_gemv_dual_batch2_k: gpu.kernel("w4a16_gemv", "w4a16_gemv_dual_batch2")?,
596            w4a16_gemv_batch2_k: gpu.kernel("w4a16_gemv", "w4a16_gemv_batch2")?,
597            w4a16_gemv_qg_batch3_k: gpu.kernel("w4a16_gemv", "w4a16_gemv_qg_batch3")?,
598            w4a16_gemv_dual_batch3_k: gpu.kernel("w4a16_gemv", "w4a16_gemv_dual_batch3")?,
599            w4a16_gemv_batch3_k: gpu.kernel("w4a16_gemv", "w4a16_gemv_batch3")?,
600            w4a16_batchm: crate::layers::w4a16_gemv_tiers::W4a16BatchmTiers::resolve(gpu),
601            w4a16_gemm_k: gpu.kernel("w4a16", "w4a16_gemm")?,
602            w4a16_gemm_t_k: crate::layers::tgemm_kernel(gpu),
603            w4a16_gemm_t_k64_k: crate::layers::k64_kernel(gpu)?,
604            w4a16_gemm_t_k64_n64_k: crate::layers::k64_n64_kernel(gpu),
605            w4a16_gemm_t_m128_k: gpu.kernel("w4a16", "w4a16_gemm_t_m128")?,
606            w4a16_gemm_t_m128_bf16_k: super::super::try_kernel(
607                gpu,
608                "w4a16",
609                "w4a16_gemm_t_m128_bf16",
610            ),
611            w4a16_gemm_t_m128_v2_k: super::super::w4a16_v2_kernel(gpu),
612            w4a16_gemm_t_m128_v3_k: super::super::w4a16_v3_kernel(gpu),
613            dense_gemm_k: gpu.kernel("gemm", "dense_gemm_bf16")?,
614            dense_gemm_pipelined_k: super::super::try_kernel(
615                gpu,
616                "gemm",
617                "dense_gemm_bf16_pipelined",
618            ),
619            prefill_attn_k: gpu.kernel("inferspark_prefill", "inferspark_prefill")?,
620            // Name comes from the SSOT helper that also supplies the BR the
621            // launcher builds its grid from — see `ops::wide_prefill_kernel`.
622            // Module and entry share a name for both variants.
623            // Resolved WITH FALLBACK — see `ops::wide_prefill_kernel`. A target
624            // that ships only the scalar HDIM=512 kernel must still get it.
625            prefill_attn_512_k: if probes.wide_head_dim {
626                crate::layers::ops::wide_prefill_kernel(gpu).0
627            } else {
628                spark_runtime::gpu::KernelHandle(0)
629            },
630            // BR=32 is the tensor-core instantiation; BR=16 the scalar reference.
631            prefill_attn_512_is_tc: probes.wide_head_dim
632                && crate::layers::ops::wide_prefill_kernel(gpu).1 == 32,
633            // DeepSeek-V4 sparse-attention compressor + compressed-KV prefill.
634            csa_compress_k: gate(probes.compressed_attn, gpu, "csa_compress", "csa_compress"),
635            prefill_attn_compressed_k: gate(
636                probes.compressed_attn,
637                gpu,
638                "prefill_attn_compressed",
639                "prefill_attn_compressed",
640            ),
641            v4_comp_pool_filled: std::sync::atomic::AtomicU32::new(0),
642            v4_comp_prev_valid: std::sync::atomic::AtomicBool::new(false),
643            v4_decode_started: std::sync::atomic::AtomicBool::new(false),
644            v4_decode_first_pos: std::sync::atomic::AtomicU32::new(0),
645            prefill_attn_paged_512_k: gate(
646                probes.wide_head_dim,
647                gpu,
648                "inferspark_prefill_paged_512",
649                "inferspark_prefill_paged_512",
650            ),
651            prefill_attn_64_k: gpu.kernel("inferspark_prefill", "inferspark_prefill_64")?,
652            prefill_attn_paged_k: gpu.kernel("prefill_paged", "inferspark_prefill_paged")?,
653            prefill_attn_paged_fp8_k: gpu
654                .kernel("prefill_paged_fp8", "inferspark_prefill_paged_fp8")?,
655            prefill_attn_paged_nvfp4_k: gpu
656                .kernel("prefill_paged_nvfp4", "inferspark_prefill_paged_nvfp4")?,
657            prefill_attn_paged_turbo4_k: super::super::try_kernel(
658                gpu,
659                "prefill_paged_turbo4",
660                "inferspark_prefill_paged_turbo4",
661            ),
662            prefill_attn_paged_64_k: gpu.kernel("prefill_paged", "inferspark_prefill_paged_64")?,
663            prefill_attn_paged_fp8_64_k: gpu
664                .kernel("prefill_paged_fp8", "inferspark_prefill_paged_fp8_64")?,
665            prefill_attn_paged_nvfp4_64_k: gpu
666                .kernel("prefill_paged_nvfp4", "inferspark_prefill_paged_nvfp4_64")?,
667            prefill_attn_paged_turbo2_64_k: super::super::try_kernel(
668                gpu,
669                "prefill_paged_turbo2",
670                "inferspark_prefill_paged_turbo2",
671            ),
672            prefill_attn_paged_turbo3_64_k: super::super::try_kernel(
673                gpu,
674                "prefill_paged_turbo3",
675                "inferspark_prefill_paged_turbo3_64",
676            ),
677            prefill_attn_paged_turbo4_64_k: super::super::try_kernel(
678                gpu,
679                "prefill_paged_turbo4",
680                "inferspark_prefill_paged_turbo4_64",
681            ),
682            prefill_attn_paged_turbo8_64_k: super::super::try_kernel(
683                gpu,
684                "prefill_paged_turbo8",
685                "inferspark_prefill_paged_turbo8_64",
686            ),
687            // TurboQuant+ safer-asym Bf16K + Turbo3V BR=64 prefill kernel.
688            // Compiled from inferspark_prefill_paged_bf16k_turbo3v.cu which
689            // forks prefill_paged_compute_asym.cuh (LOAD_K_TILE = bf16,
690            // LOAD_V_TILE = turbo3 3-bit dequant).
691            prefill_attn_paged_bf16k_turbo3v_64_k: super::super::try_kernel(
692                gpu,
693                "prefill_paged_bf16k_turbo3v",
694                "inferspark_prefill_paged_bf16k_turbo3v_64",
695            ),
696            // Bf16K + Turbo4V BR=64 prefill (4-bit V dequant in LOAD_V_TILE).
697            prefill_attn_paged_bf16k_turbo4v_64_k: super::super::try_kernel(
698                gpu,
699                "prefill_paged_bf16k_turbo4v",
700                "inferspark_prefill_paged_bf16k_turbo4v_64",
701            ),
702            // Bf16K + Turbo2V BR=64 prefill (2-bit V dequant in LOAD_V_TILE).
703            prefill_attn_paged_bf16k_turbo2v_64_k: super::super::try_kernel(
704                gpu,
705                "prefill_paged_bf16k_turbo2v",
706                "inferspark_prefill_paged_bf16k_turbo2v_64",
707            ),
708            // Fp8K + TurboNV BR=64 prefill kernels — K loaded as FP8 (per-tensor
709            // `k_scale` dequant in LOAD_K_TILE), V as 3/4/2-bit Lloyd-Max packed.
710            prefill_attn_paged_fp8k_turbo3v_64_k: super::super::try_kernel(
711                gpu,
712                "prefill_paged_fp8k_turbo3v",
713                "inferspark_prefill_paged_fp8k_turbo3v_64",
714            ),
715            prefill_attn_paged_fp8k_turbo4v_64_k: super::super::try_kernel(
716                gpu,
717                "prefill_paged_fp8k_turbo4v",
718                "inferspark_prefill_paged_fp8k_turbo4v_64",
719            ),
720            prefill_attn_paged_fp8k_turbo2v_64_k: super::super::try_kernel(
721                gpu,
722                "prefill_paged_fp8k_turbo2v",
723                "inferspark_prefill_paged_fp8k_turbo2v_64",
724            ),
725            // Both-sides-quantized TurboQuant+ asym BR=64 prefill kernels.
726            // K loaded via turbo* dequant in LOAD_K_TILE, V via the corresponding
727            // turbo* dequant in LOAD_V_TILE — separate (block_stride, data_section)
728            // pairs per side.
729            prefill_attn_paged_turbo4k_turbo3v_64_k: super::super::try_kernel(
730                gpu,
731                "prefill_paged_turbo4k_turbo3v",
732                "inferspark_prefill_paged_turbo4k_turbo3v_64",
733            ),
734            prefill_attn_paged_turbo4k_turbo8v_64_k: super::super::try_kernel(
735                gpu,
736                "prefill_paged_turbo4k_turbo8v",
737                "inferspark_prefill_paged_turbo4k_turbo8v_64",
738            ),
739            prefill_attn_paged_turbo3k_turbo8v_64_k: super::super::try_kernel(
740                gpu,
741                "prefill_paged_turbo3k_turbo8v",
742                "inferspark_prefill_paged_turbo3k_turbo8v_64",
743            ),
744            // ── Q12 Phase 3: batched paged-prefill kernel handles ──
745            prefill_attn_paged_batched_k: super::super::try_kernel(
746                gpu,
747                "inferspark_prefill_paged_batched",
748                "inferspark_prefill_paged_batched",
749            ),
750            prefill_attn_paged_fp8_batched_k: super::super::try_kernel(
751                gpu,
752                "inferspark_prefill_paged_fp8_batched",
753                "inferspark_prefill_paged_fp8_batched",
754            ),
755            prefill_attn_paged_nvfp4_batched_k: super::super::try_kernel(
756                gpu,
757                "inferspark_prefill_paged_nvfp4_batched",
758                "inferspark_prefill_paged_nvfp4_batched",
759            ),
760            prefill_attn_paged_batched_64_k: super::super::try_kernel(
761                gpu,
762                "inferspark_prefill_paged_batched",
763                "inferspark_prefill_paged_batched_64",
764            ),
765            prefill_attn_paged_fp8_batched_64_k: super::super::try_kernel(
766                gpu,
767                "inferspark_prefill_paged_fp8_batched",
768                "inferspark_prefill_paged_fp8_batched_64",
769            ),
770            prefill_attn_paged_nvfp4_batched_64_k: super::super::try_kernel(
771                gpu,
772                "inferspark_prefill_paged_nvfp4_batched",
773                "inferspark_prefill_paged_nvfp4_batched_64",
774            ),
775            deinterleave_qg_split_k: gpu.kernel("ssm_preprocess", "deinterleave_qg_split")?,
776            deinterleave_qg_split_qnorm_k: gpu
777                .kernel("ssm_preprocess", "deinterleave_qg_split_qnorm")?,
778            deinterleave_qg_split_qnorm_mrope_k: super::super::try_kernel(
779                gpu,
780                "ssm_preprocess",
781                "deinterleave_qg_split_qnorm_mrope",
782            ),
783            sigmoid_gate_mul_batched_k: gpu.kernel("residual_add", "sigmoid_gate_mul_batched")?,
784            q_fp8: None,
785            k_fp8: None,
786            v_fp8: None,
787            o_fp8: None,
788            fp8_gemm_k: gpu.kernel("w4a16", "fp8_gemm_t")?,
789            bf16_to_fp8_k: gpu.kernel("w4a16", "bf16_to_fp8")?,
790            fp8_fp8_gemm_k: gpu.kernel("w4a16", "fp8_fp8_gemm_t")?,
791            fp8_gemm_t_m128_k: gpu.kernel("w4a16", "fp8_gemm_t_m128")?,
792            fp8_fp8_gemm_t_m128_k: gpu.kernel("w4a16", "fp8_fp8_gemm_t_m128")?,
793            w4a4_gemm_k: crate::layers::try_kernel(gpu, "w4a4", "w4a4_gemm_mfast"),
794            quantize_nvfp4_k: crate::layers::try_kernel(
795                gpu,
796                "quantize_nvfp4",
797                "quantize_bf16_to_nvfp4",
798            ),
799            fp8_calibration: if fp8_calibration_tokens > 0
800                && crate::layers::fp8_calibration::dtype_runs_online_fp8_kv_calibration(kv_dtype)
801            {
802                Some(Fp8KvCalibration::new(
803                    attn_layer_idx,
804                    fp8_calibration_tokens,
805                    config.fp8_kv_headroom,
806                    gpu,
807                )?)
808            } else {
809                None
810            },
811        })
812    }
813}
814
815#[cfg(test)]
816mod fused_kv_probe_guard {
817    /// Every lookup of `fused_k_norm_rope_cache` must go through
818    /// `try_target_kernel`, never plain `try_kernel`.
819    ///
820    /// That module is absent from the `strix` and `strix-hip` kernel trees. A
821    /// plain `try_kernel` there issues a lookup that FAILS, and the boot audit
822    /// records every failed lookup as a dispatch site on a silent fallback
823    /// path and refuses to serve — so the engine will not boot on those
824    /// targets at all. A target that never built the module has no fallback to
825    /// be silent about; it has its only path. This is the class that left
826    /// stack 1089308's first campaign unable to boot with 17 such lookups.
827    ///
828    /// Source-level rather than behavioural on purpose: the failure is a
829    /// BOOT-time refusal on a target this test suite never runs on, so no
830    /// mock backend reproduces it. The guard that can actually fire here is
831    /// the one that reads the call site.
832    #[test]
833    fn fused_k_norm_rope_cache_is_probed_target_scoped() {
834        let src = include_str!("init.rs");
835        let mut offenders = Vec::new();
836        for (i, window) in src.match_indices("\"fused_k_norm_rope_cache\"") {
837            let _ = window;
838            // Walk back to the probe call that owns this module argument.
839            let head = &src[..i];
840            let call = head.rfind("try_kernel(").map(|p| (p, "try_kernel"));
841            let tcall = head
842                .rfind("try_target_kernel(")
843                .map(|p| (p, "try_target_kernel"));
844            let chosen = match (call, tcall) {
845                (Some((a, _)), Some((b, n))) if b >= a => Some((b, n)),
846                (Some((a, n)), _) => Some((a, n)),
847                (None, t) => t,
848            };
849            match chosen {
850                Some((_, "try_target_kernel")) => {}
851                other => offenders.push(format!("{other:?} before byte {i}")),
852            }
853        }
854        assert!(
855            !offenders.is_empty() || src.contains("fused_k_norm_rope_cache"),
856            "guard found no lookups at all — it has stopped measuring anything"
857        );
858        assert!(
859            offenders.is_empty(),
860            "fused_k_norm_rope_cache probed without try_target_kernel: {offenders:?}"
861        );
862    }
863}