1use 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#[derive(Debug, Clone, Default)]
29pub struct KernelTable {
30 handles: BTreeMap<KernelId, KernelHandle>,
31}
32
33impl KernelTable {
34 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 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
458pub 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
464pub 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}