metrale_model_layers/layers/ops/dispatch_helpers.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: Batched-prefill admission switches (codispatch, varlen) and GEMM
4//! route and shape logging.
5//!
6//! Owner: model-layers ops.
7//! Invariants:
8//! - The codispatch and varlen decisions are each resolved at most once per
9//! process, by whichever comes first: the command line or the first read
10//! from the environment. Every later read returns that value.
11
12#![allow(unused_imports)]
13
14use super::*;
15
16use metrale_gpu_runtime::gpu::GpuBackend;
17
18// 2026-09-25: The codispatch and varlen switches gate batched prefill
19// admission, not a GEMM path, and have no `GemmDispatch` field.
20// `METRALE_Q12_BATCHED_FIRST_CHUNK` has no command-line flag.
21
22/// 2026-09-25: The resolved codispatch decision, set once so it cannot change
23/// mid-serve.
24static PREFILL_CODISPATCH: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
25
26/// 2026-09-25: Publish the command line's `--prefill-codispatch` decision.
27/// Returns the value in force, which differs from `enabled` when the cell was
28/// already resolved; the command line then did not take effect. The serve
29/// calls this only when the flag is given, so without it the
30/// `METRALE_PREFILL_CODISPATCH` fallback applies.
31pub fn set_prefill_codispatch_from_cli(enabled: bool) -> bool {
32 let _ = PREFILL_CODISPATCH.set(enabled);
33 *PREFILL_CODISPATCH.get().expect("just set")
34}
35
36/// 2026-09-25: Whether cross-request co-dispatch of fresh prompts is on:
37/// `--prefill-codispatch`, else `METRALE_PREFILL_CODISPATCH` (`1` or `true`,
38/// any case); off when neither is set. The scheduler's levers and
39/// [`prefill_batched_first_chunk_enabled`] both read it.
40pub fn prefill_codispatch_enabled() -> bool {
41 *PREFILL_CODISPATCH.get_or_init(|| {
42 bool_value_enabled(std::env::var("METRALE_PREFILL_CODISPATCH").ok().as_deref())
43 })
44}
45
46/// 2026-09-25: Whether chunk-zero streams may use the paged batched-prefill
47/// path: codispatch on, or `METRALE_Q12_BATCHED_FIRST_CHUNK` set to `1` or
48/// `true`. The scheduler reads codispatch alone.
49pub fn prefill_batched_first_chunk_enabled() -> bool {
50 prefill_batched_first_chunk_from_parts(
51 prefill_codispatch_enabled(),
52 std::env::var("METRALE_Q12_BATCHED_FIRST_CHUNK")
53 .ok()
54 .as_deref(),
55 )
56}
57
58/// 2026-09-25: The pure OR, testable without the process-wide cell or the
59/// environment. `codispatch` is already resolved; `q12` is the variable's raw
60/// value.
61fn prefill_batched_first_chunk_from_parts(codispatch: bool, q12: Option<&str>) -> bool {
62 codispatch || bool_value_enabled(q12)
63}
64
65/// 2026-09-28: Whether a batched prefill wave may carry chunk-zero streams: the
66/// first-chunk lever ([`prefill_batched_first_chunk_enabled`]) or varlen batching
67/// ([`prefill_varlen_enabled`]). The single rule for the model engine's admission
68/// (`kernel_batched_eligible`), its paged metadata upload, and the attention layer's
69/// routing; before 2026-09-28 the layer read the lever alone and refused a
70/// varlen-admitted chunk-zero wave after admission, which failed every stream in it.
71pub fn prefill_batched_chunk_zero_admitted() -> bool {
72 batched_chunk_zero_admitted(
73 prefill_batched_first_chunk_enabled(),
74 prefill_varlen_enabled(),
75 )
76}
77
78/// 2026-09-28: The pure rule behind [`prefill_batched_chunk_zero_admitted`].
79pub fn batched_chunk_zero_admitted(first_chunk_lever: bool, varlen: bool) -> bool {
80 first_chunk_lever || varlen
81}
82
83/// 2026-09-25: The resolved varlen decision, set once so it cannot change
84/// mid-serve.
85static PREFILL_VARLEN: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
86
87/// 2026-09-25: Publish the command line's `--prefill-varlen-batch` decision.
88/// Returns the value in force, which differs from `enabled` when the cell was
89/// already resolved; the command line then did not take effect. The serve
90/// calls this only when the flag is given, so without it the
91/// `METRALE_PREFILL_VARLEN` fallback applies.
92pub fn set_prefill_varlen_from_cli(enabled: bool) -> bool {
93 let _ = PREFILL_VARLEN.set(enabled);
94 *PREFILL_VARLEN.get().expect("just set")
95}
96
97/// 2026-09-25: Whether varlen (ragged) batched prefill is on:
98/// `--prefill-varlen-batch`, else `METRALE_PREFILL_VARLEN` (`1` or `true`, any
99/// case); off when neither is set. The admission predicate
100/// (`check_kernel_batched_eligible`), the batched attention layer and the
101/// scheduler's levers all read this one value.
102pub fn prefill_varlen_enabled() -> bool {
103 *PREFILL_VARLEN
104 .get_or_init(|| bool_value_enabled(std::env::var("METRALE_PREFILL_VARLEN").ok().as_deref()))
105}
106
107fn bool_value_enabled(value: Option<&str>) -> bool {
108 matches!(value, Some("1")) || value.is_some_and(|value| value.eq_ignore_ascii_case("true"))
109}
110
111pub fn log_cutlass_nvfp4_route(gpu: &dyn GpuBackend, name: &str, m: u32, n: u32, k: u32) {
112 // 2026-09-25: Debug level, because the dedup key includes M and every
113 // prefill length is a new M. The dedup probe is skipped when no subscriber
114 // takes debug events.
115 if !tracing::enabled!(tracing::Level::DEBUG) {
116 return;
117 }
118 // 2026-09-25: Deduplicated per backend by `OpCache::first_shape`.
119 if gpu.op_cache().first_shape(name, m, n, k) {
120 tracing::debug!("CUTLASS_NVFP4_ROUTE {name} M={m} N={n} K={k}");
121 }
122}
123
124/// 2026-09-25: With `METRALE_GEMM_SHAPE_LOG=1`, log each (kernel, M, N, K) GEMM
125/// shape once per backend at warn level, with its FLOP count.
126pub fn log_gemm_shape(gpu: &dyn GpuBackend, name: &str, m: u32, n: u32, k: u32) {
127 if std::env::var("METRALE_GEMM_SHAPE_LOG").ok().as_deref() != Some("1") {
128 return;
129 }
130 if gpu.op_cache().first_shape(name, m, n, k) {
131 let flop = 2.0 * m as f64 * n as f64 * k as f64;
132 tracing::warn!("GEMM_SHAPE {name} M={m} N={n} K={k} FLOP={flop:.3e}");
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::{bool_value_enabled, prefill_batched_first_chunk_from_parts};
139
140 #[test]
141 fn accepts_boolean_environment_spellings() {
142 assert!(bool_value_enabled(Some("1")));
143 assert!(bool_value_enabled(Some("true")));
144 assert!(bool_value_enabled(Some("TRUE")));
145 assert!(!bool_value_enabled(Some("0")));
146 assert!(!bool_value_enabled(Some("false")));
147 assert!(!bool_value_enabled(None));
148 }
149
150 /// 2026-09-25: Either spelling enables the path, and codispatch off does
151 /// not veto `METRALE_Q12_BATCHED_FIRST_CHUNK`.
152 #[test]
153 fn either_chunk_zero_spelling_enables_admission() {
154 assert!(prefill_batched_first_chunk_from_parts(true, None));
155 assert!(prefill_batched_first_chunk_from_parts(false, Some("true")));
156 assert!(prefill_batched_first_chunk_from_parts(false, Some("1")));
157 assert!(!prefill_batched_first_chunk_from_parts(false, None));
158 assert!(!prefill_batched_first_chunk_from_parts(false, Some("0")));
159 assert!(!prefill_batched_first_chunk_from_parts(
160 false,
161 Some("false")
162 ));
163 assert!(prefill_batched_first_chunk_from_parts(false, Some("1")));
164 }
165}