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;