spark_model/layers/ops/target_defaults.rs
1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! Resolving the compiled target's serving defaults — **baked first,
4//! environment second**.
5//!
6//! # The defect this closes
7//!
8//! Maintainer review of the H100 integration branch, 2026-09-11 (tbraun96):
9//!
10//! > There is no arch separation at all. H100 builds compile GB10's kernel
11//! > tree. Every Hopper/GB10 divergence is expressed as an env lever set by an
12//! > H100 recipe living outside this repo — not as arch-selected code. "No
13//! > interference" rests on discipline rather than structure.
14//!
15//! Every lever here used to read the environment and fall back to a literal
16//! that described GB10. The H100 configuration therefore lived in a launch
17//! script nobody in this repository could see, review or test, and "GB10 is
18//! unaffected" was a promise about which prefixes people remembered to type.
19//!
20//! Now the fallback is [`atlas_kernels::TARGET_DEFAULTS`], baked by
21//! `build.rs` from the ONE `kernels/<hw>/HARDWARE.toml` this binary compiled
22//! (`[defaults]`). A GB10 build cannot carry Hopper's numbers, an H100 serve
23//! needs no prefixes, and every value is reviewable beside the arch it
24//! belongs to.
25//!
26//! # The override grammar
27//!
28//! | input | meaning |
29//! |---|---|
30//! | variable absent | the baked target default |
31//! | `0`, `false`, `off`, `no` (any case, trimmed) | OFF — explicit override |
32//! | any other value, including empty | ON — explicit override |
33//!
34//! ⚠️ **`VAR=0` NOW MEANS OFF.** These levers were PRESENCE-gated
35//! (`var_os(..).is_some()`), chosen so an A/B recipe could stay a bare `VAR=1`
36//! prefix with no "`=0` means on" trap. Presence cannot express "off", and
37//! once a target's default can be ON, an operator with no way to turn a lever
38//! off is back to editing launch scripts. The trap the old rule avoided is
39//! gone in the direction that matters: `VAR=0` now means what it reads as.
40//! `VAR=1` is unchanged everywhere.
41//!
42//! The legacy `ATLAS_NO_*` kill switches stay PRESENCE-gated and still force
43//! their lever OFF, so no script that predates this file changes meaning.
44//! `ATLAS_NO_DECODE_SPLIT_SILU` is the one in this table.
45//!
46//! # One resolution, one log line
47//!
48//! [`resolved`] is the SSOT: every consumer below reads it, and
49//! `spark-server` prints it as `target defaults (<hw>): …` with the
50//! environment-sourced values marked. A lever resolved in two places is a
51//! lever that can disagree with the line that claims to report it.
52//!
53//! # Adding a lever — the contract
54//!
55//! ONE commit touches all of: the field in
56//! `atlas_kernels::TargetDefaults`, the parse arm in
57//! `atlas-kernels/build_defaults.rs`, the row in EVERY
58//! `kernels/<hw>/HARDWARE.toml` that has a `[defaults]` table, the
59//! [`TargetLevers`] field and its arm in [`resolve`], the field in
60//! [`format_levers`]'s line, and a test. `parse_defaults` panics on an
61//! unknown key, so a half-landed lever fails the build rather than reading as
62//! agreement with the baseline.
63//!
64//! ★ And the commit that does all that is the one landing the lever's
65//! CONSUMER. A row whose dispatch site does not exist yet cannot be graded,
66//! answers nothing when an operator sets its variable, and puts an ` (env)`
67//! tag in the boot line against a decision that changes no code. So a kernel
68//! PR brings its own row; this module ships only the rows whose arms are
69//! already here.
70
71use atlas_kernels::attn_splitk::{self, SplitkPolicy};
72
73use super::gemm_quant::{DENSE_GEMV_BATCHM_DECODE_MAX_M, DENSE_GEMV_BATCHM_MAX_M};
74
75/// Where a resolved value came from — the whole point of the log line.
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum Source {
78 /// `kernels/<hw>/HARDWARE.toml` `[defaults]`.
79 Target,
80 /// An `ATLAS_*` variable in the process environment.
81 Env,
82}
83
84impl Source {
85 /// The suffix the serve log appends to an environment-sourced value.
86 pub fn tag(self) -> &'static str {
87 match self {
88 Source::Target => "",
89 Source::Env => " (env)",
90 }
91 }
92}
93
94/// A resolved lever and where it came from.
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub struct Resolved<T> {
97 pub value: T,
98 pub source: Source,
99}
100
101impl<T> Resolved<T> {
102 fn target(value: T) -> Self {
103 Self {
104 value,
105 source: Source::Target,
106 }
107 }
108 fn env(value: T) -> Self {
109 Self {
110 value,
111 source: Source::Env,
112 }
113 }
114 /// True when the environment overrode the target's declaration.
115 pub fn from_env(&self) -> bool {
116 self.source == Source::Env
117 }
118}
119
120/// The override grammar for a boolean lever, as a pure function.
121///
122/// `raw` is the positive variable's value (`None` = absent). `legacy_off` is
123/// the presence of the matching `ATLAS_NO_*` kill switch, which wins over
124/// everything: it is the escape hatch an operator reaches for while a serve
125/// misbehaves, and a hatch that a stale positive variable can veto is not one.
126pub fn resolve_toggle(default_on: bool, raw: Option<&str>, legacy_off: bool) -> Resolved<bool> {
127 if legacy_off {
128 return Resolved::env(false);
129 }
130 match raw {
131 None => Resolved::target(default_on),
132 Some(v) => match v.trim().to_ascii_lowercase().as_str() {
133 "0" | "false" | "off" | "no" => Resolved::env(false),
134 _ => Resolved::env(true),
135 },
136 }
137}
138
139/// The BF16 decode head's batched-GEMV band.
140///
141/// 🔴 Read `layers/ops/gemm_quant.rs` before touching the DEFAULT. The band's
142/// upper edge decides whether a width lands on the batched GEMV or on a
143/// REASSOCIATING tile GEMM, and the A/B behind GB10's 8 measured the GEMV
144/// NEGATIVE above it (-14.4% at C=16, commits 84d5b763c / 78d276832).
145///
146/// Clamped to [`DENSE_GEMV_BATCHM_MAX_M`], the kernel's compile-time row
147/// bound — `dense_gemv_batchm` refuses above it rather than writing 16 of m
148/// rows, and a lever that produced an `Err` at every decode step would be a
149/// worse failure than ignoring the excess. An unparseable or `0` environment
150/// value keeps the target's declaration; the value is a BAND, not a switch, so
151/// there is no "off".
152pub fn resolve_batchm_max(default_max: u32, raw: Option<&str>) -> Resolved<u32> {
153 let clamp = |v: u32| v.min(DENSE_GEMV_BATCHM_MAX_M);
154 match raw
155 .and_then(|v| v.trim().parse::<u32>().ok())
156 .filter(|&v| v > 0)
157 {
158 Some(v) => Resolved::env(clamp(v)),
159 None => Resolved::target(clamp(default_max)),
160 }
161}
162
163/// Upper `M` for the W8A8 dense-FFN prefill, per projection shape.
164///
165/// Unlike [`resolve_batchm_max`] a parsed **0 is honoured**, because 0 is a
166/// meaningful operator answer here ("never take the W8A8 arm on this shape")
167/// and silently ignoring it would make `…=0` read as agreement with the
168/// target — the same silent-agreement failure `parse_defaults` panics over.
169/// Anything that is not a u32 falls back to the target's declaration.
170pub fn resolve_max_m(default_max: u32, raw: Option<&str>) -> Resolved<u32> {
171 match raw.and_then(|v| v.trim().parse::<u32>().ok()) {
172 Some(v) => Resolved::env(v),
173 None => Resolved::target(default_max),
174 }
175}
176
177/// Every serving lever this target declares, resolved against the environment.
178///
179/// Field order is the order the serve log prints them in.
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub struct TargetLevers {
182 /// `kernels/<hw>` this binary was compiled from, for the log line only.
183 pub hw: &'static str,
184 pub lm_head_batchm_max: Resolved<u32>,
185 pub ssm_batched_recurrent: Resolved<bool>,
186 pub gdn_prefill_tc: Resolved<bool>,
187 pub ssm_ba_gates_hopper: Resolved<bool>,
188 pub fp8_act_quant_hopper: Resolved<bool>,
189 pub decode_split_silu: Resolved<bool>,
190 pub attn_decode_splitk: Resolved<SplitkPolicy>,
191 /// The `w8a16_gemm_m16` tier on the dense-FFN decode arm (#927).
192 pub ffn_m16_tc: Resolved<bool>,
193 /// The `w8a16_gemm_m16` tiers on the decode Q/K/V and o_proj (#927).
194 pub attn_m16_tc: Resolved<bool>,
195 /// The `dense_gemm_m16_bf16` arm on the BF16 decode head (#927).
196 pub lm_head_m16_tc: Resolved<bool>,
197 /// `w8a16_gemv_batch16_ncol{2,4}` on the decode attention projections
198 /// (#927). No serving receipt on any target — off everywhere.
199 pub attn_ncol_gemv: Resolved<bool>,
200 pub ffn_gateup_fused: Resolved<bool>,
201 pub w8a8_prefill_max_m_widening: Resolved<u32>,
202 pub w8a8_prefill_max_m_narrowing: Resolved<u32>,
203}
204
205/// The whole table, as a pure function of the baked declaration and a variable
206/// lookup — so the resolution is testable for ANY target from a CPU test, on
207/// any host, without touching the process environment.
208pub fn resolve(
209 defaults: &atlas_kernels::TargetDefaults,
210 mut var: impl FnMut(&str) -> Option<String>,
211) -> TargetLevers {
212 let split_silu_off = var("ATLAS_NO_DECODE_SPLIT_SILU").is_some();
213
214 TargetLevers {
215 hw: defaults.hw,
216 lm_head_batchm_max: resolve_batchm_max(
217 defaults.lm_head_batchm_max,
218 var("ATLAS_LM_HEAD_BATCHM_MAX").as_deref(),
219 ),
220 w8a8_prefill_max_m_widening: resolve_max_m(
221 defaults.w8a8_prefill_max_m_widening,
222 var("ATLAS_W8A8_PREFILL_MAX_M_WIDENING").as_deref(),
223 ),
224 w8a8_prefill_max_m_narrowing: resolve_max_m(
225 defaults.w8a8_prefill_max_m_narrowing,
226 var("ATLAS_W8A8_PREFILL_MAX_M_NARROWING").as_deref(),
227 ),
228 // `ATLAS_SSM_BATCHED_RECURRENT` was `== "1"` in `gdn_flags::from_env`;
229 // under the 2026-09-11 grammar `=0` now turns it OFF instead of
230 // reading as absent. Everything that ever set it set it to `1`, so no
231 // existing recipe changes meaning. The DEFAULT is the target's:
232 // `kernels/hopper` declares ON (+6% on the serve, md5-identical
233 // output), which is the line that used to live in an external launch
234 // script. `--ssm-batched-recurrent` on the CLI still outranks both.
235 ssm_batched_recurrent: resolve_toggle(
236 defaults.ssm_batched_recurrent,
237 var("ATLAS_SSM_BATCHED_RECURRENT").as_deref(),
238 false,
239 ),
240 // ⚠️ `ATLAS_GDN_PREFILL_TC` was PRESENCE-gated and is now grammar-gated
241 // like its neighbours, so `=0` turns it OFF instead of on. Everything
242 // that ever set it set it to `1`; the A/B recipes in
243 // `GDN-PREFILL-ATTRIBUTION.md` are unaffected.
244 gdn_prefill_tc: resolve_toggle(
245 defaults.gdn_prefill_tc,
246 var("ATLAS_GDN_PREFILL_TC").as_deref(),
247 false,
248 ),
249 // The Hopper BA-gates twin (#928). Hopper declares it ON; the twin is
250 // BIT-IDENTICAL to its gb10 parent by construction, so unlike every
251 // other Hopper-owned row this one carries no accuracy question and no
252 // `ATLAS_NO_*` legacy spelling — `ATLAS_SSM_BA_GATES_HOPPER=0` is the
253 // whole A/B, under the 2026-09-11 grammar above.
254 ssm_ba_gates_hopper: resolve_toggle(
255 defaults.ssm_ba_gates_hopper,
256 var("ATLAS_SSM_BA_GATES_HOPPER").as_deref(),
257 false,
258 ),
259 // The Hopper FP8 activation-quant twin (#928, round-16 receipt § 2.1).
260 // Hopper declares it ON. The twin is BIT-IDENTICAL to its gb10 parent,
261 // so the row carries no accuracy question and no `ATLAS_NO_*` legacy
262 // spelling — the lever is new, so there is no older script for a
263 // presence rule to keep faith with. It is also not the whole rule: the
264 // twin is 0.76x-0.95x at M <= 25 for K in {5120, 6144}, so it passes a
265 // CTA-count floor (`layers/ops/fp8_act_quant_floor.rs`) before it takes
266 // a launch. `ATLAS_FP8_ACT_QUANT_HOPPER=0` declines the twin at EVERY
267 // width, which is the A/B.
268 fp8_act_quant_hopper: resolve_toggle(
269 defaults.fp8_act_quant_hopper,
270 var("ATLAS_FP8_ACT_QUANT_HOPPER").as_deref(),
271 false,
272 ),
273 // DECLARATION plus the legacy kill switch, and no positive variable:
274 // `decode_split_silu` never had one. `ATLAS_NO_DECODE_SPLIT_SILU`
275 // stays PRESENCE-gated and unchanged, so every script that predates
276 // this file means what it meant.
277 decode_split_silu: resolve_toggle(defaults.decode_split_silu, None, split_silu_off),
278 // The paged-decode split-K policy (#928). The RULE is
279 // `atlas_kernels::attn_splitk::resolve_policy`, not a fourth copy of
280 // the rung order here: `spark-runtime`'s buffer arena has to reach the
281 // same answer to size the split-K workspace, and it sits BELOW this
282 // crate. One pure function, two callers — a second spelling is how the
283 // grid comes to index past the allocation, silently, into device memory
284 // it does not own. This table is still the only thing that REPORTS it.
285 attn_decode_splitk: {
286 let (policy, from_env) = attn_splitk::resolve_policy(
287 defaults.attn_decode_splitk,
288 var("ATLAS_ATTN_DECODE_SPLITK").as_deref(),
289 );
290 if from_env {
291 Resolved::env(policy)
292 } else {
293 Resolved::target(policy)
294 }
295 },
296 // Two rows for ONE kernel family, because round 6 measured the FFN
297 // arm and the attention arms moving in opposite directions on the same
298 // serve. `ATLAS_M16_TC` is the round-6 umbrella that arms both; it is
299 // folded in HERE rather than in the consumer so that an umbrella can
300 // never DISARM a target's declaration, which would make the recipe
301 // depend on export order.
302 ffn_m16_tc: resolve_toggle(
303 defaults.ffn_m16_tc,
304 var("ATLAS_FFN_M16_TC")
305 .or_else(|| var("ATLAS_M16_TC"))
306 .as_deref(),
307 false,
308 ),
309 attn_m16_tc: resolve_toggle(
310 defaults.attn_m16_tc,
311 var("ATLAS_ATTN_M16_TC")
312 .or_else(|| var("ATLAS_M16_TC"))
313 .as_deref(),
314 false,
315 ),
316 // NOT under `ATLAS_M16_TC`. The umbrella is round 6's, which predates
317 // this arm and never measured it; folding the head in would silently
318 // widen what an old recipe means. Its own variable, or the target's
319 // declaration.
320 lm_head_m16_tc: resolve_toggle(
321 defaults.lm_head_m16_tc,
322 var("ATLAS_LM_HEAD_M16_TC").as_deref(),
323 false,
324 ),
325 // `ATLAS_NO_ATTN_DECODE_BATCH` is the pre-existing kill switch for the
326 // whole batched attention-decode family, and it OUTRANKS both the
327 // declaration and the positive variable: a switch that turns a family
328 // off must not be silently narrowed by a new row underneath it.
329 attn_ncol_gemv: resolve_toggle(
330 defaults.attn_ncol_gemv,
331 var("ATLAS_ATTN_NCOL_GEMV").as_deref(),
332 var("ATLAS_NO_ATTN_DECODE_BATCH").is_some(),
333 ),
334 // DECLARATION plus `ATLAS_FFN_GATEUP_FUSED`, which is the A/B a Hopper
335 // round runs against the new default. `=0` kills the arm and returns
336 // the layer to two cuBLASLt calls; there is no positive spelling that
337 // arms it on a target whose tree lacks `silu_mul_strided.cu`, because
338 // the handle probe would then fail the boot audit closed.
339 ffn_gateup_fused: resolve_toggle(
340 defaults.ffn_gateup_fused,
341 var("ATLAS_FFN_GATEUP_FUSED").as_deref(),
342 false,
343 ),
344 }
345}
346
347/// The process-wide resolution.
348///
349/// `OnceLock`-cached for the reason every lever it replaces was: these are
350/// read per projection per layer per step, `std::env::var` allocates and takes
351/// the process-wide environment lock (measured on GB10: 0.57 us
352/// single-threaded, **5.76 us at 16 threads**), and the route must be CONSTANT
353/// across CUDA-graph replays — a per-call read could change the captured
354/// launch set between capture and replay.
355pub fn resolved() -> &'static TargetLevers {
356 static LEVERS: std::sync::OnceLock<TargetLevers> = std::sync::OnceLock::new();
357 LEVERS.get_or_init(|| {
358 resolve(&atlas_kernels::TARGET_DEFAULTS, |name| {
359 std::env::var(name).ok()
360 })
361 })
362}
363
364/// The baked declaration this binary carries, for the serve log's header and
365/// for callers that must stay pure over their own inputs (`ModelLevers`).
366pub fn declared() -> &'static atlas_kernels::TargetDefaults {
367 &atlas_kernels::TARGET_DEFAULTS
368}
369
370/// `target defaults (<hw>): …` — one line naming every resolved value and
371/// which came from the environment.
372///
373/// Built here rather than in `spark-server` so the line and the resolution are
374/// the same code: a log that formats its own idea of the table is how a dead
375/// lever stays invisible for a campaign (`serve_flags.rs`'s own lesson).
376pub fn summary_line() -> String {
377 format_levers(resolved())
378}
379
380/// [`summary_line`] over a table the caller already has — pure, so the line can
381/// be graded for ANY target from a CPU test without touching the process
382/// environment or sealing the `OnceLock`.
383pub fn format_levers(l: &TargetLevers) -> String {
384 let onoff =
385 |r: Resolved<bool>| format!("{}{}", if r.value { "on" } else { "off" }, r.source.tag());
386 // `u32::MAX` is the no-cap baseline, not a chosen bound. Printing
387 // 4294967295 in the serve log would read as a decision someone made.
388 let cap = |v: u32| {
389 if v == u32::MAX {
390 "max".to_string()
391 } else {
392 v.to_string()
393 }
394 };
395 format!(
396 "target defaults ({hw}): sm_count={sms} \
397 lm_head_batchm_max={batchm}{batchm_src} \
398 ssm_batched_recurrent={recurrent} gdn_prefill_tc={gdn_tc} \
399 ssm_ba_gates_hopper={ba_gates} decode_split_silu={silu} \
400 attn_decode_splitk={splitk}{splitk_src} ffn_m16_tc={ffn_m16_tc} \
401 attn_m16_tc={attn_m16_tc} lm_head_m16_tc={lm_head_m16_tc} \
402 attn_ncol_gemv={attn_ncol_gemv} ffn_gateup_fused={gateup} \
403 fp8_act_quant_hopper={act_quant} \
404 w8a8_prefill_max_m={w8a8_wide}/{w8a8_narrow}{w8a8_src}",
405 hw = if l.hw.is_empty() { "unknown" } else { l.hw },
406 // Not a resolvable lever — it is a FACT about the part, cross-checked
407 // at boot against the driver. Printed on this line because the levers
408 // that will read it (grid sizing) are on it, and a reader comparing
409 // two campaign logs needs both in one grep.
410 sms = atlas_kernels::TARGET_SM_COUNT,
411 batchm = l.lm_head_batchm_max.value,
412 batchm_src = l.lm_head_batchm_max.source.tag(),
413 recurrent = onoff(l.ssm_batched_recurrent),
414 gdn_tc = onoff(l.gdn_prefill_tc),
415 ba_gates = onoff(l.ssm_ba_gates_hopper),
416 act_quant = onoff(l.fp8_act_quant_hopper),
417 silu = onoff(l.decode_split_silu),
418 splitk = l.attn_decode_splitk.value.label(),
419 splitk_src = l.attn_decode_splitk.source.tag(),
420 ffn_m16_tc = onoff(l.ffn_m16_tc),
421 attn_m16_tc = onoff(l.attn_m16_tc),
422 lm_head_m16_tc = onoff(l.lm_head_m16_tc),
423 attn_ncol_gemv = onoff(l.attn_ncol_gemv),
424 gateup = onoff(l.ffn_gateup_fused),
425 // Printed as widening/narrowing. `max` reads as "no cap" rather than
426 // 4294967295, which would look like a number someone chose.
427 w8a8_wide = cap(l.w8a8_prefill_max_m_widening.value),
428 w8a8_narrow = cap(l.w8a8_prefill_max_m_narrowing.value),
429 w8a8_src = l.w8a8_prefill_max_m_widening.source.tag(),
430 )
431}
432
433/// The declaration a target that says nothing gets — kept in sync with
434/// `atlas-kernels/build_defaults.rs::baseline` by
435/// `target_defaults_tests::the_baseline_band_is_the_frozen_one`.
436pub const BASELINE_BATCHM_MAX: u32 = DENSE_GEMV_BATCHM_DECODE_MAX_M;
437
438#[cfg(test)]
439#[path = "target_defaults_tests.rs"]
440mod tests;