atlas_kernels/
target_defaults.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! The SERVING defaults baked from the compiled target's
4//! `kernels/<hw>/HARDWARE.toml` `[defaults]` table.
5//!
6//! # Why this is code and not a launch script
7//!
8//! Maintainer review, 2026-09-11 (tbraun96), on the H100 integration branch:
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//! Each field below was a line in that external recipe. Baking them from the
16//! target's own HARDWARE.toml makes the recipe STRUCTURAL: `build.rs` reads
17//! exactly one `kernels/<hw>` tree, so a binary built for GB10 cannot carry
18//! Hopper's numbers, and an H100 serve with an empty environment reproduces
19//! the measured configuration without anyone remembering a prefix.
20//!
21//! # The rule every consumer follows
22//!
23//! **Baked default first, environment second.** The environment is an
24//! EXPLICIT operator override, not the source of truth, and `spark-server`
25//! logs one `target defaults (<hw>): …` line naming every resolved value and
26//! which of them came from the environment. See
27//! `spark_model::layers::ops::target_defaults` for the resolvers and the
28//! override grammar.
29//!
30//! # Adding a lever — four places, one commit
31//!
32//! A lever row is: the field here, the parse arm in `build_defaults.rs`, the
33//! resolver field in `spark_model::layers::ops::target_defaults`, and the row
34//! in EVERY `kernels/<hw>/HARDWARE.toml` that has a `[defaults]` table — plus
35//! the boot line and a test, which the resolver and
36//! `tests/target_defaults.rs` already force. All in ONE commit.
37//!
38//! ★ THE CONTRACT IS ALSO A SCOPE RULE. A row belongs in the commit that
39//! lands its CONSUMER, not in the commit that builds this table. A row whose
40//! dispatch site does not exist yet is a declaration nothing reads: it cannot
41//! be graded, an operator who sets its variable gets silence, and the `(env)`
42//! tag in the boot line would report a decision that changes no code. So a
43//! kernel PR adds its own row here, in `build_defaults.rs`, in the resolver
44//! and in all three tables, together with the arm that reads it.
45//!
46//! `parse_defaults` panics on an unknown `[defaults]` key, so a table that
47//! names a lever the code does not have fails the build instead of reading as
48//! agreement — which is what makes "one commit" enforceable rather than
49//! merely asked for.
50
51/// One compiled target's serving defaults.
52///
53/// `Copy` and entirely `'static` — it is a `const` emitted by `build.rs` into
54/// `OUT_DIR/target_ptx.rs` and `include!`d by `lib.rs`, so
55/// [`crate::TARGET_DEFAULTS`] is resolved at compile time with no I/O.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct TargetDefaults {
58    /// `kernels/<hw>` this binary's kernels were compiled from — `gb10`,
59    /// `hopper`, `b200`, …. Empty only when a build read no HARDWARE.toml at
60    /// all; consumers print it verbatim and must not branch on it (branching
61    /// on the name would re-create the per-arch `if` this table replaces).
62    pub hw: &'static str,
63    /// Upper edge of the BF16 decode head's batched-GEMV band
64    /// (`model/trait_impl/lm_head_batched.rs`). Clamped by the resolver to the
65    /// kernel's compile-time row bound; it is a BAND, not a switch, so there
66    /// is no "off".
67    pub lm_head_batchm_max: u32,
68    /// One strided recurrent launch per batch on the GDN decode path
69    /// (`layers/qwen3_ssm/gdn_flags.rs`).
70    ///
71    /// TRUE on hopper: +6% on the serve, and md5-identical output to the
72    /// per-sequence launches. It was `ATLAS_SSM_BATCHED_RECURRENT=1` in an
73    /// H100 launch script outside this repository, which is the arrangement
74    /// the 2026-09-11 review called discipline rather than structure.
75    pub ssm_batched_recurrent: bool,
76    /// `gated_delta_rule_chunk_delta_h_tcfuse_x2` serves the GDN chunked
77    /// PREFILL state spine on tensor cores (`layers/ops/ssm_gdn_a3.rs`).
78    ///
79    /// FALSE on every target here, deliberately. The kernel lives in
80    /// `kernels/hopper/common/gated_delta_rule_chunk_tc.cu` — developed and
81    /// validated on GB10, arch-neutral, but declared in the Hopper tree because
82    /// rule S1 refuses new cross-hardware symlinks — and the arm reassociates
83    /// the k-reduction into the MMA tree, so promotion needs the ssm-poisoning
84    /// tripwire rather than a cosine (#928).
85    /// It is here so the probe that loads it is GATED on the same bit that
86    /// launches it, like every other kernel in this table.
87    pub gdn_prefill_tc: bool,
88    /// `dense_gemm_ba_gates_prefill_hopper` serves the SSM BA projection +
89    /// GDN gate transforms with ONE CTA per token
90    /// (`layers/ops/ssm_ba_gates_hopper.rs`), in place of its gb10 parent's
91    /// `ceil(N/4)` CTAs per token.
92    ///
93    /// TRUE on hopper, false elsewhere. The twin is BIT-IDENTICAL to the
94    /// parent by construction — same lane-strided K sweep, same butterfly,
95    /// same cross-warp order — so the row is purely a speed claim, and the
96    /// claim is about ISSUED work: at N=96 the parent re-reads and re-converts
97    /// each token's whole `K=5120` activation row 96 times, once per BA output
98    /// (nsys round 13: 26 881.8 us = 5.85% of a 4593-token H100 prefill, at
99    /// 88 GB/s of compulsory traffic — 2.6% of HBM, so not a bandwidth bound).
100    /// The twin reads it 12 times and issues ~1.8x fewer instructions for the
101    /// same bits (SASS, sm_90a). `kernels/gb10`
102    /// and `kernels/b200` do not carry the source, so the row is INERT there
103    /// and declared only because the lever list is one list (#928).
104    /// `ATLAS_SSM_BA_GATES_HOPPER=0` is the A/B. Numbers:
105    /// `SSM-BA-GATES-ATTRIBUTION.md`.
106    pub ssm_ba_gates_hopper: bool,
107    /// `per_token_group_quant_fp8_hopper` serves the per-token FP8 activation
108    /// quantizer with 16 threads per 128-element K-group and **8 groups per
109    /// CTA** (`layers/ops/fp8_act_quant.rs`), in place of its gb10 parent's
110    /// one CTA per group.
111    ///
112    /// TRUE on hopper, false elsewhere. The twin is BIT-IDENTICAL to the
113    /// parent — same `amax / 448.0f`, same `1e-12f` floor, same per-element
114    /// `div.rn.f32`, same saturating E4M3 convert; only the reduction tree
115    /// moves — so the row is purely a speed claim, and it is a claim with a
116    /// WIDTH. `native_fp8_act_quant_hopper_microtest`, 1xH100 80GB HBM3,
117    /// round 16: **3.30-3.59x at M in {1168, 4576}** (63.7-68.4% of HBM
118    /// against the parent's 18.6-19.1%) and **0.76x-0.95x at M in {16, 17, 25}
119    /// for K in {5120, 6144}** — 8 groups per CTA is 8x fewer CTAs, and at
120    /// those M the parent's grid is already under one wave on 132 SMs.
121    ///
122    /// So the row arms a kernel that is also behind a CTA-count floor
123    /// (`layers/ops/fp8_act_quant_floor.rs`): the twin takes a launch only
124    /// when its own grid clears `2 * sm_count` CTAs. `kernels/gb10` and
125    /// `kernels/b200` do not carry the source, so the row is INERT there and
126    /// declared only because the lever list is one list.
127    /// `ATLAS_FP8_ACT_QUANT_HOPPER=0` is the A/B. Numbers:
128    /// `FP8-ACT-QUANT-ATTRIBUTION.md`.
129    pub fp8_act_quant_hopper: bool,
130    /// Split SiLU+down on the decode path (`ModelLevers::decode_split_silu`).
131    pub decode_split_silu: bool,
132    /// How the paged-decode attention path picks its KV split count (#928):
133    /// `legacy` (the pre-#928 rule), `auto` (fill this target's SMs at the
134    /// single-stream shape) or a pinned decimal count. Parsed by
135    /// [`crate::attn_splitk::parse`], which owns the grammar and the clamps.
136    ///
137    /// A STRING rather than a number: the policy is a small grammar, and the
138    /// target declares WHICH RULE it wants rather than a count that would
139    /// silently be wrong on the next card.
140    pub attn_decode_splitk: &'static str,
141    /// The `w8a16_gemm_m16` tensor-core tier on the DENSE-FFN decode arm
142    /// (`layers/dense_ffn_m16_tc.rs`), rungs 2-3 of the `w8_gemm!` ladder.
143    ///
144    /// FALSE on hopper, and it is the one row in this table whose receipt is a
145    /// LOSS. H100 round 6, serve J against serve I on the same binary: C=16
146    /// aggregate 228.02 -> 216.27 (-5.2%) on the short shape and 177.60 ->
147    /// 171.62 (-3.4%) on the long one, TPOT +5.7% / +4.3%, against deltas
148    /// 30-95x the rep-to-rep spread. The kernel is 3.4-3.7x faster than the
149    /// tier it replaces in the microtest and still costs the serve, because it
150    /// dispatches by ROW COUNT and so catches a chunked prefill's tail chunk.
151    /// The ATTENTION half of the same kernel wins (`attn_m16_tc`), which is why
152    /// this is two rows and not one.
153    pub ffn_m16_tc: bool,
154    /// The `w8a16_gemm_m16{,_strided}` tensor-core tiers on the decode Q/K/V
155    /// and o_proj projections (`layers/qwen3_attention/`).
156    ///
157    /// TRUE on hopper: H100 round 9 cell W against cell U, C=16 aggregate
158    /// 235.47 -> 247.85 (+5.26%) and TPOT 53.42 -> 50.01 ms (-6.38%) against a
159    /// 0.15% rep spread; long shape +4.12% / -5.40%. C=1 is 17.87 vs 17.89 ms,
160    /// a measured null, which is correct by construction — the tier is
161    /// restricted to 5..16 rows. Same kernel family as [`Self::ffn_m16_tc`],
162    /// opposite verdict, which is why they are two rows.
163    pub attn_m16_tc: bool,
164    /// The `dense_gemm_m16_bf16` tensor-core arm on the BF16 decode head
165    /// (`model/trait_impl/lm_head_batched.rs`), for 5..16 rows.
166    ///
167    /// TRUE on hopper: H100 round 9 cell Y against cell U, C=16 aggregate
168    /// 235.47 -> 245.10 (+4.09%), TPOT 53.42 -> 50.79 ms (-4.92%). The head is
169    /// ~7% of the step (round 12 nsys: 943.8 us, 4.31%), so this is most of
170    /// what is there to win at that site.
171    ///
172    /// 🔴 It REASSOCIATES the K reduction against `dense_gemv_bf16`, and at the
173    /// LM head that is a token-visible seam rather than a rounding detail — a
174    /// near-tie argmax can flip. It is ON here on a measured serve receipt, not
175    /// on a microtest.
176    pub lm_head_m16_tc: bool,
177    /// `w8a16_gemv_batch16_ncol{2,4}` on the decode attention projections
178    /// (`layers/qwen3_attention/attn_ncol_gemv.rs`).
179    ///
180    /// FALSE everywhere, INCLUDING hopper, and the reason is stated rather
181    /// than implied: there is no serving A/B for it on any target. The
182    /// microtest exists; the receipt does not. The row is here so the kernel
183    /// has a declared way to be turned on for the measurement that would earn
184    /// it, not because it has been shown to pay.
185    pub attn_ncol_gemv: bool,
186    /// One fused `[gate | up]` cuBLASLt W8A8 GEMM at `N = 2 * intermediate` on
187    /// the 5..=16-row decode band, instead of two at `N = intermediate`
188    /// (#927). TRUE only on hopper: the arm's strided-SiLU consumer
189    /// (`silu_mul_strided.cu`) is a Hopper-owned source, so the row is inert
190    /// on a target whose tree does not carry it.
191    pub ffn_gateup_fused: bool,
192    /// Upper `M` for the W8A8 block-scaled dense-FFN prefill on a WIDENING
193    /// projection (`n > k`: gate/up). `u32::MAX` = no cap, the baseline.
194    ///
195    /// W8A8 feeds the FP8 tensor cores instead of dequantizing into a BF16
196    /// MMA, and on H100 that is 2.0-3.1x at every M measured — so Hopper
197    /// declares nothing here and keeps the baseline. On sm_121 it is not: W8A8
198    /// throughput is FLAT at ~14 TFLOP/s from M=128 to M=2048 while W8A16
199    /// climbs to ~26 and stays there. A kernel whose throughput does not move
200    /// with M is not compute-bound — it is pinned by the per-token activation
201    /// quantization and its FP32 scale epilogue, which W8A16 never pays. So
202    /// W8A8 wins only while the GEMM is small enough that the quantization is
203    /// not the bill, and where that stops is a property of the ARCH.
204    ///
205    /// Two rows and not one because the crossover is shape-dependent: measured
206    /// 2026-09-11 on spark-256a at the real Qwen3.8-27B dims, gate/up
207    /// (N=17408, K=5120) crosses at M~64-128 and down (N=5120, K=17408) at
208    /// M~384-512.
209    pub w8a8_prefill_max_m_widening: u32,
210    /// Upper `M` for the same path on a NARROWING projection (`n <= k`: down).
211    /// See [`Self::w8a8_prefill_max_m_widening`].
212    pub w8a8_prefill_max_m_narrowing: u32,
213}