metrale_model_layers/circuit_exec/
kernels.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-28: The kernels the circuit executor can launch, resolved at boot through literal
4//! lookups (so the kernel-lookup audit records each at its line here), and the probe of every
5//! kernel any rule names, which is what the fuser treats as available.
6//!
7//! Owner: model-layers circuit executor.
8//! Invariants:
9//! - The fuser sees every rule kernel the loaded modules contain, not only the ones an emitter
10//!   launches: hiding a present kernel would let a lower-priority rule win and the plan leave
11//!   legacy routing. A plan that selects a kernel with no emitter handle is refused instead
12//!   ([`KernelTable::handle`]).
13
14use std::collections::BTreeMap;
15
16use anyhow::{Result, anyhow};
17use metrale_circuit::{AvailableKernels, KernelId, Rule};
18use metrale_gpu_runtime::gpu::{GpuBackend, KernelHandle};
19
20use crate::layers::{try_kernel, try_target_kernel};
21
22mod declared;
23mod draft_rows;
24mod gdn;
25mod verify_batch;
26
27/// 2026-09-28: Emitter handles by kernel id; only present kernels are kept.
28#[derive(Debug, Clone, Default)]
29pub struct KernelTable {
30    handles: BTreeMap<KernelId, KernelHandle>,
31}
32
33impl KernelTable {
34    /// 2026-09-28: Resolve every kernel an emitter can launch that `available` holds. The
35    /// lookups are written out literally; one outside `available` is not issued.
36    pub fn resolve(gpu: &dyn GpuBackend, available: &AvailableKernels) -> Self {
37        let has = |m: &str, f: &str| {
38            available.kernels.contains(&KernelId {
39                module: m.to_string(),
40                func: f.to_string(),
41            })
42        };
43        let look = |m: &str, f: &str, lookup: &dyn Fn() -> KernelHandle| {
44            if has(m, f) { lookup() } else { KernelHandle(0) }
45        };
46        let entries = [
47            (
48                "norm",
49                "rms_norm",
50                look("norm", "rms_norm", &|| try_kernel(gpu, "norm", "rms_norm")),
51            ),
52            (
53                "norm",
54                "rms_norm_residual",
55                look("norm", "rms_norm_residual", &|| {
56                    try_kernel(gpu, "norm", "rms_norm_residual")
57                }),
58            ),
59            (
60                "norm",
61                "residual_add_rms_norm",
62                look("norm", "residual_add_rms_norm", &|| {
63                    try_kernel(gpu, "norm", "residual_add_rms_norm")
64                }),
65            ),
66            (
67                "norm",
68                "gated_rms_norm_f32_input",
69                look("norm", "gated_rms_norm_f32_input", &|| {
70                    try_kernel(gpu, "norm", "gated_rms_norm_f32_input")
71                }),
72            ),
73            (
74                "residual_add_rms_norm_exact",
75                "residual_add_rms_norm_exact",
76                look(
77                    "residual_add_rms_norm_exact",
78                    "residual_add_rms_norm_exact",
79                    &|| {
80                        try_target_kernel(
81                            gpu,
82                            "residual_add_rms_norm_exact",
83                            "residual_add_rms_norm_exact",
84                        )
85                    },
86                ),
87            ),
88            (
89                "residual_add",
90                "bf16_residual_add",
91                look("residual_add", "bf16_residual_add", &|| {
92                    try_kernel(gpu, "residual_add", "bf16_residual_add")
93                }),
94            ),
95            (
96                "residual_add",
97                "sigmoid_gate_mul",
98                look("residual_add", "sigmoid_gate_mul", &|| {
99                    try_kernel(gpu, "residual_add", "sigmoid_gate_mul")
100                }),
101            ),
102            (
103                "w4a16_gemv",
104                "w4a16_gemv_sw",
105                look("w4a16_gemv", "w4a16_gemv_sw", &|| {
106                    try_kernel(gpu, "w4a16_gemv", "w4a16_gemv_sw")
107                }),
108            ),
109            (
110                "w4a16_gemv",
111                "w4a16_gemv_qg",
112                look("w4a16_gemv", "w4a16_gemv_qg", &|| {
113                    try_kernel(gpu, "w4a16_gemv", "w4a16_gemv_qg")
114                }),
115            ),
116            (
117                "w4a16_gemv_fused",
118                "w4a16_gemv_dual",
119                look("w4a16_gemv_fused", "w4a16_gemv_dual", &|| {
120                    try_kernel(gpu, "w4a16_gemv_fused", "w4a16_gemv_dual")
121                }),
122            ),
123            (
124                "w4a16_gemv_fused",
125                "w4a16_gemv_dual_sw",
126                look("w4a16_gemv_fused", "w4a16_gemv_dual_sw", &|| {
127                    try_kernel(gpu, "w4a16_gemv_fused", "w4a16_gemv_dual_sw")
128                }),
129            ),
130            (
131                "moe_silu_mul",
132                "moe_silu_mul",
133                look("moe_silu_mul", "moe_silu_mul", &|| {
134                    try_kernel(gpu, "moe_silu_mul", "moe_silu_mul")
135                }),
136            ),
137            (
138                "ssm_preprocess",
139                "dense_gemv_ba_gates",
140                look("ssm_preprocess", "dense_gemv_ba_gates", &|| {
141                    try_kernel(gpu, "ssm_preprocess", "dense_gemv_ba_gates")
142                }),
143            ),
144            (
145                "causal_conv1d",
146                "causal_conv1d_update_l2norm_f32",
147                look("causal_conv1d", "causal_conv1d_update_l2norm_f32", &|| {
148                    try_kernel(gpu, "causal_conv1d", "causal_conv1d_update_l2norm_f32")
149                }),
150            ),
151            (
152                "gated_delta_rule",
153                "gated_delta_rule_decode_f32",
154                look("gated_delta_rule", "gated_delta_rule_decode_f32", &|| {
155                    try_kernel(gpu, "gated_delta_rule", "gated_delta_rule_decode_f32")
156                }),
157            ),
158            (
159                "rope_mrope_interleaved",
160                "rope_forward_mrope_interleaved",
161                look(
162                    "rope_mrope_interleaved",
163                    "rope_forward_mrope_interleaved",
164                    &|| {
165                        try_target_kernel(
166                            gpu,
167                            "rope_mrope_interleaved",
168                            "rope_forward_mrope_interleaved",
169                        )
170                    },
171                ),
172            ),
173            (
174                "reshape_and_cache",
175                "reshape_and_cache_flash",
176                look("reshape_and_cache", "reshape_and_cache_flash", &|| {
177                    try_kernel(gpu, "reshape_and_cache", "reshape_and_cache_flash")
178                }),
179            ),
180            (
181                "paged_decode",
182                "paged_decode_attn",
183                look("paged_decode", "paged_decode_attn", &|| {
184                    try_kernel(gpu, "paged_decode", "paged_decode_attn")
185                }),
186            ),
187            (
188                "gemv",
189                "dense_gemv_bf16",
190                look("gemv", "dense_gemv_bf16", &|| {
191                    try_kernel(gpu, "gemv", "dense_gemv_bf16")
192                }),
193            ),
194            (
195                "residual_add",
196                "sigmoid_gate_mul_batched",
197                look("residual_add", "sigmoid_gate_mul_batched", &|| {
198                    try_kernel(gpu, "residual_add", "sigmoid_gate_mul_batched")
199                }),
200            ),
201            (
202                "ssm_preprocess",
203                "deinterleave_qg",
204                look("ssm_preprocess", "deinterleave_qg", &|| {
205                    try_kernel(gpu, "ssm_preprocess", "deinterleave_qg")
206                }),
207            ),
208            (
209                "norm",
210                "rms_norm_strided",
211                look("norm", "rms_norm_strided", &|| {
212                    try_kernel(gpu, "norm", "rms_norm_strided")
213                }),
214            ),
215            (
216                "rope",
217                "rope_forward_strided",
218                look("rope", "rope_forward_strided", &|| {
219                    try_kernel(gpu, "rope", "rope_forward_strided")
220                }),
221            ),
222            (
223                "w4a16_gemv_tc",
224                "w4a16_gemv_tc8",
225                look("w4a16_gemv_tc", "w4a16_gemv_tc8", &|| {
226                    try_kernel(gpu, "w4a16_gemv_tc", "w4a16_gemv_tc8")
227                }),
228            ),
229            (
230                "w4a16_gemv",
231                "w4a16_gemv_batch2",
232                look("w4a16_gemv", "w4a16_gemv_batch2", &|| {
233                    try_kernel(gpu, "w4a16_gemv", "w4a16_gemv_batch2")
234                }),
235            ),
236            (
237                "w4a16_gemv",
238                "w4a16_gemv_dual_batch2",
239                look("w4a16_gemv", "w4a16_gemv_dual_batch2", &|| {
240                    try_kernel(gpu, "w4a16_gemv", "w4a16_gemv_dual_batch2")
241                }),
242            ),
243            (
244                "w4a16_gemv",
245                "w4a16_gemv_qg_batch2",
246                look("w4a16_gemv", "w4a16_gemv_qg_batch2", &|| {
247                    try_kernel(gpu, "w4a16_gemv", "w4a16_gemv_qg_batch2")
248                }),
249            ),
250            (
251                "w4a16",
252                "w4a16_gemm_t_p3",
253                look("w4a16", "w4a16_gemm_t_p3", &|| {
254                    try_kernel(gpu, "w4a16", "w4a16_gemm_t_p3")
255                }),
256            ),
257            (
258                "w4a16",
259                "w4a16_gemm_t_k64_p3",
260                look("w4a16", "w4a16_gemm_t_k64_p3", &|| {
261                    try_kernel(gpu, "w4a16", "w4a16_gemm_t_k64_p3")
262                }),
263            ),
264            (
265                "w4a16",
266                "w4a16_gemm_t_k64_n64_p3",
267                look("w4a16", "w4a16_gemm_t_k64_n64_p3", &|| {
268                    try_kernel(gpu, "w4a16", "w4a16_gemm_t_k64_n64_p3")
269                }),
270            ),
271            (
272                "w4a16",
273                "w4a16_gemm_t_m128",
274                look("w4a16", "w4a16_gemm_t_m128", &|| {
275                    try_kernel(gpu, "w4a16", "w4a16_gemm_t_m128")
276                }),
277            ),
278            (
279                "nvfp4_mmq",
280                "metrale_nvfp4_quantize_bf16",
281                look("nvfp4_mmq", "metrale_nvfp4_quantize_bf16", &|| {
282                    try_kernel(gpu, "nvfp4_mmq", "metrale_nvfp4_quantize_bf16")
283                }),
284            ),
285            (
286                "nvfp4_mmq",
287                "metrale_nvfp4_mmq16_nc",
288                look("nvfp4_mmq", "metrale_nvfp4_mmq16_nc", &|| {
289                    try_kernel(gpu, "nvfp4_mmq", "metrale_nvfp4_mmq16_nc")
290                }),
291            ),
292            (
293                "nvfp4_mmq",
294                "metrale_nvfp4_mmq32_nc",
295                look("nvfp4_mmq", "metrale_nvfp4_mmq32_nc", &|| {
296                    try_kernel(gpu, "nvfp4_mmq", "metrale_nvfp4_mmq32_nc")
297                }),
298            ),
299            (
300                "nvfp4_mmq",
301                "metrale_nvfp4_mmq64_nc",
302                look("nvfp4_mmq", "metrale_nvfp4_mmq64_nc", &|| {
303                    try_kernel(gpu, "nvfp4_mmq", "metrale_nvfp4_mmq64_nc")
304                }),
305            ),
306            (
307                "nvfp4_mmq",
308                "metrale_nvfp4_gemm_pipe",
309                look("nvfp4_mmq", "metrale_nvfp4_gemm_pipe", &|| {
310                    try_kernel(gpu, "nvfp4_mmq", "metrale_nvfp4_gemm_pipe")
311                }),
312            ),
313            (
314                "nvfp4_mmq",
315                "metrale_nvfp4_silu_mul_quant",
316                look("nvfp4_mmq", "metrale_nvfp4_silu_mul_quant", &|| {
317                    try_kernel(gpu, "nvfp4_mmq", "metrale_nvfp4_silu_mul_quant")
318                }),
319            ),
320            (
321                "nvfp4_mmq",
322                "metrale_nvfp4_scale_bf16",
323                look("nvfp4_mmq", "metrale_nvfp4_scale_bf16", &|| {
324                    try_kernel(gpu, "nvfp4_mmq", "metrale_nvfp4_scale_bf16")
325                }),
326            ),
327            (
328                "dense_gemv_bf16_batchm",
329                "dense_gemv_bf16_batchm",
330                look("dense_gemv_bf16_batchm", "dense_gemv_bf16_batchm", &|| {
331                    try_kernel(gpu, "dense_gemv_bf16_batchm", "dense_gemv_bf16_batchm")
332                }),
333            ),
334            (
335                "gemm",
336                "dense_gemm_bf16",
337                look("gemm", "dense_gemm_bf16", &|| {
338                    try_kernel(gpu, "gemm", "dense_gemm_bf16")
339                }),
340            ),
341            (
342                "argmax",
343                "argmax_bf16",
344                look("argmax", "argmax_bf16", &|| {
345                    try_kernel(gpu, "argmax", "argmax_bf16")
346                }),
347            ),
348            (
349                "causal_conv1d",
350                "causal_conv1d_update_l2norm",
351                look("causal_conv1d", "causal_conv1d_update_l2norm", &|| {
352                    try_kernel(gpu, "causal_conv1d", "causal_conv1d_update_l2norm")
353                }),
354            ),
355            (
356                "gated_delta_rule_wy",
357                "gated_delta_rule_wy2",
358                look("gated_delta_rule_wy", "gated_delta_rule_wy2", &|| {
359                    try_kernel(gpu, "gated_delta_rule_wy", "gated_delta_rule_wy2")
360                }),
361            ),
362            (
363                "gated_delta_rule_wy3",
364                "gated_delta_rule_wy3",
365                look("gated_delta_rule_wy3", "gated_delta_rule_wy3", &|| {
366                    try_kernel(gpu, "gated_delta_rule_wy3", "gated_delta_rule_wy3")
367                }),
368            ),
369            (
370                "gated_delta_rule_wy4",
371                "gated_delta_rule_wy4",
372                look("gated_delta_rule_wy4", "gated_delta_rule_wy4", &|| {
373                    try_kernel(gpu, "gated_delta_rule_wy4", "gated_delta_rule_wy4")
374                }),
375            ),
376            (
377                "norm",
378                "gated_rms_norm_prefill",
379                look("norm", "gated_rms_norm_prefill", &|| {
380                    try_kernel(gpu, "norm", "gated_rms_norm_prefill")
381                }),
382            ),
383            (
384                "ssm_preprocess",
385                "dense_gemm_ba_gates_prefill",
386                look("ssm_preprocess", "dense_gemm_ba_gates_prefill", &|| {
387                    try_kernel(gpu, "ssm_preprocess", "dense_gemm_ba_gates_prefill")
388                }),
389            ),
390            (
391                "w4a16_gemv",
392                "w4a16_gemv_batch3",
393                look("w4a16_gemv", "w4a16_gemv_batch3", &|| {
394                    try_kernel(gpu, "w4a16_gemv", "w4a16_gemv_batch3")
395                }),
396            ),
397            (
398                "w4a16_gemv",
399                "w4a16_gemv_dual_batch3",
400                look("w4a16_gemv", "w4a16_gemv_dual_batch3", &|| {
401                    try_kernel(gpu, "w4a16_gemv", "w4a16_gemv_dual_batch3")
402                }),
403            ),
404            (
405                "w4a16_gemv",
406                "w4a16_gemv_qg_batch3",
407                look("w4a16_gemv", "w4a16_gemv_qg_batch3", &|| {
408                    try_kernel(gpu, "w4a16_gemv", "w4a16_gemv_qg_batch3")
409                }),
410            ),
411            (
412                "rope",
413                "rope_forward",
414                look("rope", "rope_forward", &|| {
415                    try_kernel(gpu, "rope", "rope_forward")
416                }),
417            ),
418            (
419                "residual_add",
420                "bf16_concat",
421                look("residual_add", "bf16_concat", &|| {
422                    try_kernel(gpu, "residual_add", "bf16_concat")
423                }),
424            ),
425        ];
426        let handles = entries
427            .into_iter()
428            .chain(declared::entries(gpu, &look))
429            .chain(gdn::entries(gpu, &look))
430            .chain(verify_batch::entries(gpu, &look))
431            .chain(draft_rows::entries(gpu, &look))
432            .filter(|(_, _, h)| h.0 != 0)
433            .map(|(m, f, h)| {
434                (
435                    KernelId {
436                        module: m.to_string(),
437                        func: f.to_string(),
438                    },
439                    h,
440                )
441            })
442            .collect();
443        KernelTable { handles }
444    }
445
446    /// 2026-09-28: The handle of `k`; an error when no emitter launches it or it is absent.
447    pub fn handle(&self, k: &KernelId) -> Result<KernelHandle> {
448        self.handles.get(k).copied().ok_or_else(|| {
449            anyhow!(
450                "the plan selects {}::{}, which no circuit emitter launches on this target",
451                k.module,
452                k.func
453            )
454        })
455    }
456}
457
458/// 2026-09-28: Whether PTX text defines the entry point `func` (`.entry func(`).
459pub fn ptx_defines(ptx: &[u8], func: &str) -> bool {
460    let needle = format!(".entry {func}(");
461    ptx.windows(needle.len()).any(|w| w == needle.as_bytes())
462}
463
464/// 2026-09-28: Every kernel `rules` name that the target's modules (`(name, PTX)`) define. Read
465/// from the PTX, not by lookup: a lookup of an entry the target lacks would count as an
466/// unresolved kernel at the boot gate. Capability bits are not probed yet, so a rule that
467/// requires one is refused rather than assumed present or silently skipped.
468pub fn available_in(rules: &[Rule], modules: &[(&str, &[u8])]) -> Result<AvailableKernels> {
469    if let Some(r) = rules.iter().find(|r| !r.requires.is_empty()) {
470        anyhow::bail!(
471            "rule `{}` requires capabilities {:?}; the circuit executor does not probe \
472             capabilities yet",
473            r.id,
474            r.requires
475        );
476    }
477    let mut out = AvailableKernels::default();
478    for k in rules.iter().flat_map(|r| r.kernels.iter()) {
479        if modules
480            .iter()
481            .any(|(m, ptx)| *m == k.module && ptx_defines(ptx, &k.func))
482        {
483            out.kernels.insert(k.clone());
484        }
485    }
486    Ok(out)
487}