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}