spark_model/layers/ops/model_levers.rs
1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! Model-side kernel-path levers, resolved once and then carried.
4//!
5//! The second of the two lever categories on [`crate::layer::ForwardContext`]:
6//!
7//! * [`super::GemmDispatch`] — which GEMM implementation each projection takes.
8//! * [`ModelLevers`] — everything else the model's kernel paths branch on:
9//! the SSM/GDN recurrence variant, FFN routing, MoE quantization, LoRA
10//! application mode, diagnostics.
11//!
12//! Both were `OnceLock<bool>` statics reading `ATLAS_*` at first touch. Two
13//! problems with that, and only the first is about hot-swap:
14//!
15//! 1. A static outlives the model whose flags it encodes. Load a second model
16//! whose recipe sets different levers and the process keeps taking the
17//! previous model's branches — silently, because a cached `bool` cannot
18//! report that it is stale.
19//! 2. It hides the dependency. A function that reads the environment through a
20//! static declares nothing in its signature, cannot be exercised with a
21//! different configuration without mutating the process, and gives the
22//! compiler nothing to check.
23//!
24//! Carrying it fixes both, and a site that forgets the field fails to build.
25
26/// Kernel-path levers for one loaded model.
27///
28/// Plain `Copy` data resolved from the environment at model construction. Group
29/// membership follows the subsystem the lever steers, so a reader can see at a
30/// glance which part of the forward pass a flag reaches.
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
32pub struct ModelLevers {
33 // ── SSM / GDN recurrence ──
34 /// Keep GDN recurrent state in registers across the prefill chunk loop.
35 /// Default ON (the fold that shipped in PR #369, −7.25 % wall); the env var
36 /// is an opt-OUT, which is why the field is stored positively and the
37 /// resolution inverts it.
38 pub gdn_regresident: bool,
39 /// Batched FLA path for multi-sequence GDN decode.
40 pub gdn_batched_fla: bool,
41 /// WY17 GDN recurrence variant. Ships ON; `ATLAS_GDN_WY17=0` opts out.
42 pub gdn_wy17: bool,
43 /// WY-N GDN recurrence variant. Ships ON; `ATLAS_GDN_WYN=0` opts out.
44 pub gdn_wyn: bool,
45
46 // ── FFN / MoE ──
47 /// Lossless single-warp decode GEMV (`w4a16_gemv_sw`, `w4a16_gemv_dual_sw`).
48 /// Ships ON; `ATLAS_NO_GEMV_SW=1` restores the 64-thread kernels.
49 pub gemv_sw: bool,
50 /// Route decode FFN through the tile GEMM rather than the scalar GEMV.
51 pub decode_ffn_via_gemm: bool,
52 /// Small-M FFN GEMM tile shape. Ships ON; `ATLAS_FFN_SMALLM=0` opts out.
53 pub ffn_small_m: bool,
54 /// FP4 holo layout for the MoE down projection.
55 pub holo_moe_down_fp4: bool,
56 /// FP4 holo layout for the MoE gate/up projections.
57 pub holo_moe_gateup_fp4: bool,
58 /// Collect per-layer MoE expert-union statistics. Diagnostic.
59 pub moe_union_stats: bool,
60
61 // ── Attention ──
62 /// Contiguous-attention path for the DFlash head.
63 pub dflash_contig_attn: bool,
64
65 // ── LoRA ──
66 /// Apply LoRA eagerly at load instead of at each forward.
67 pub lora_eager: bool,
68 /// Allow hot rotation of LoRA adapters.
69 pub lora_rotate: bool,
70
71 // ── Diagnostics ──
72 /// K=4 chain-widening diagnostics.
73 pub k4_diag: bool,
74 /// Per-layer hidden-state norm dumps on the Gemma-4 decode path. Heavy —
75 /// one device-to-host copy per layer.
76 pub gemma4_diag: bool,
77
78 // ── Attention (cont.) ──
79 /// BF16 tensor-core attention projections: dequant FP4 to BF16 and use a
80 /// BF16 MMA instead of the default path, which crushes activations to FP8
81 /// E4M3. Removes the FP8 prefill perturbation on those projections.
82 pub bf16_tc_proj: bool,
83 /// Configured max decode batch (`--max-batch-size`), the reference count
84 /// the split-K attention split count is pinned to. Not from the
85 /// environment: `TransformerModel::new` writes it from the serve arg.
86 ///
87 /// It pins DETERMINISM — the online-softmax split-merge is
88 /// non-associative, so a sequence decoded alone must see the same
89 /// reduction tree as one co-batched with fifteen others. Held in a
90 /// `OnceLock` it was also idempotent, so a second model with a different
91 /// max batch would silently keep the first model's split count.
92 pub max_decode_seqs: u32,
93 /// `ATLAS_MTP_SHADOW_TOPK=k` (0 = off, clamped to 8): the drafter D2Hs
94 /// its logits and logs the top-k candidates. Observational only.
95 pub shadow_topk: usize,
96 /// `ATLAS_KV_POISON=1` — fill a fresh KV block with NaN instead of zero,
97 /// the discriminator for the "unwritten fresh tail block read"
98 /// hypothesis. A diagnostic that changes what the kernels READ, so it
99 /// must not leak across a swap.
100 pub kv_poison: bool,
101 /// MTP drafter context policy (`ATLAS_NO_DRAFTER_CONTEXT` /
102 /// `ATLAS_DRAFTER_PREFILL_ONLY`), resolved and logged once per model.
103 /// The two halves are coupled — prefill without carry is a measured
104 /// −927 ms/turn loss — so they travel as one value.
105 pub drafter: crate::model::drafter_context::DrafterContext,
106}
107
108fn from_values(
109 mut value: impl FnMut(&str) -> Option<String>,
110 mut present: impl FnMut(&str) -> bool,
111 shadow_topk: usize,
112 drafter: crate::model::drafter_context::DrafterContext,
113) -> ModelLevers {
114 fn opt_in(value: Option<&str>) -> bool {
115 value == Some("1")
116 }
117 fn opt_out(value: Option<&str>) -> bool {
118 value != Some("0")
119 }
120 fn opt_in_truthy(value: Option<&str>) -> bool {
121 value.is_some_and(|value| value == "1" || value.eq_ignore_ascii_case("true"))
122 }
123
124 ModelLevers {
125 max_decode_seqs: 1,
126 shadow_topk,
127 kv_poison: opt_in(value("ATLAS_KV_POISON").as_deref()),
128 drafter,
129 gdn_regresident: value("ATLAS_NO_GDN_REGRESIDENT").as_deref() != Some("1"),
130 gdn_batched_fla: opt_in(value("ATLAS_GDN_BATCHED_FLA").as_deref()),
131 gdn_wy17: opt_out(value("ATLAS_GDN_WY17").as_deref()),
132 gdn_wyn: opt_out(value("ATLAS_GDN_WYN").as_deref()),
133 ffn_small_m: opt_out(value("ATLAS_FFN_SMALLM").as_deref()),
134 gemv_sw: super::gemv_sw::gemv_sw_from(value("ATLAS_NO_GEMV_SW").as_deref()),
135 decode_ffn_via_gemm: opt_in(value("ATLAS_DECODE_FFN_VIA_GEMM").as_deref()),
136 holo_moe_down_fp4: opt_in_truthy(value("ATLAS_HOLO_MOE_DOWN_FP4").as_deref()),
137 holo_moe_gateup_fp4: opt_in_truthy(value("ATLAS_HOLO_MOE_GATEUP_FP4").as_deref()),
138 moe_union_stats: opt_in(value("ATLAS_MOE_UNION_STATS").as_deref()),
139 dflash_contig_attn: opt_in(value("ATLAS_DFLASH_CONTIG_ATTN").as_deref()),
140 lora_eager: opt_in_truthy(value("ATLAS_LORA_EAGER").as_deref()),
141 lora_rotate: opt_in_truthy(value("ATLAS_LORA_ROTATE").as_deref()),
142 k4_diag: opt_in(value("ATLAS_K4_DIAG").as_deref()),
143 gemma4_diag: opt_in_truthy(value("ATLAS_DIAG_GEMMA4").as_deref()),
144 bf16_tc_proj: present("ATLAS_BF16_TC_PROJ"),
145 }
146}
147
148impl ModelLevers {
149 /// Resolve from the environment. Called once, when the model is built.
150 pub fn from_env() -> Self {
151 from_values(
152 |var| std::env::var(var).ok(),
153 |var| std::env::var_os(var).is_some(),
154 crate::speculative::shadow_topk(),
155 crate::model::drafter_context::resolve_from_env(),
156 )
157 }
158
159 /// What a build resolves to with no `ATLAS_*` set — every opt-in off, the
160 /// one opt-out lever on. Tests construct a context with this instead of
161 /// mutating the process environment.
162 pub fn defaults() -> Self {
163 Self {
164 max_decode_seqs: 1,
165 shadow_topk: 0,
166 kv_poison: false,
167 drafter: crate::model::drafter_context::DrafterContext::BOTH,
168 gdn_regresident: true,
169 gdn_wy17: true,
170 gdn_wyn: true,
171 ffn_small_m: true,
172 gemv_sw: true,
173 ..Self::default()
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use std::collections::HashMap;
182
183 fn resolve(values: &[(&str, &str)]) -> ModelLevers {
184 let values: HashMap<_, _> = values.iter().copied().collect();
185 from_values(
186 |name| values.get(name).map(|value| (*value).to_owned()),
187 |name| values.contains_key(name),
188 0,
189 crate::model::drafter_context::DrafterContext::BOTH,
190 )
191 }
192
193 #[test]
194 fn the_opt_out_lever_is_on_by_default_and_every_opt_in_is_off() {
195 let d = ModelLevers::defaults();
196 assert_eq!(
197 resolve(&[]),
198 d,
199 "absent environment uses the public default"
200 );
201 assert_eq!(
202 d,
203 ModelLevers {
204 gdn_regresident: true,
205 gdn_wy17: true,
206 gdn_wyn: true,
207 gemv_sw: true,
208 ffn_small_m: true,
209 max_decode_seqs: 1,
210 drafter: crate::model::drafter_context::DrafterContext::BOTH,
211 ..ModelLevers::default()
212 }
213 );
214 }
215
216 #[test]
217 fn exact_one_opt_ins_map_to_their_own_fields() {
218 let cases = [
219 ("ATLAS_KV_POISON", [true, false, false, false, false, false]),
220 (
221 "ATLAS_GDN_BATCHED_FLA",
222 [false, true, false, false, false, false],
223 ),
224 (
225 "ATLAS_DECODE_FFN_VIA_GEMM",
226 [false, false, true, false, false, false],
227 ),
228 (
229 "ATLAS_MOE_UNION_STATS",
230 [false, false, false, true, false, false],
231 ),
232 (
233 "ATLAS_DFLASH_CONTIG_ATTN",
234 [false, false, false, false, true, false],
235 ),
236 ("ATLAS_K4_DIAG", [false, false, false, false, false, true]),
237 ];
238 for (name, expected) in cases {
239 let d = resolve(&[(name, "1")]);
240 assert_eq!(
241 [
242 d.kv_poison,
243 d.gdn_batched_fla,
244 d.decode_ffn_via_gemm,
245 d.moe_union_stats,
246 d.dflash_contig_attn,
247 d.k4_diag
248 ],
249 expected,
250 "{name}"
251 );
252 }
253 assert!(!resolve(&[("ATLAS_K4_DIAG", "true")]).k4_diag);
254 }
255
256 #[test]
257 fn truthy_opt_ins_map_independently_and_presence_is_distinct() {
258 let cases = [
259 (
260 "ATLAS_HOLO_MOE_DOWN_FP4",
261 [true, false, false, false, false],
262 ),
263 (
264 "ATLAS_HOLO_MOE_GATEUP_FP4",
265 [false, true, false, false, false],
266 ),
267 ("ATLAS_LORA_EAGER", [false, false, true, false, false]),
268 ("ATLAS_LORA_ROTATE", [false, false, false, true, false]),
269 ("ATLAS_DIAG_GEMMA4", [false, false, false, false, true]),
270 ];
271 for (name, expected) in cases {
272 let d = resolve(&[(name, "TrUe")]);
273 assert_eq!(
274 [
275 d.holo_moe_down_fp4,
276 d.holo_moe_gateup_fp4,
277 d.lora_eager,
278 d.lora_rotate,
279 d.gemma4_diag
280 ],
281 expected,
282 "{name}"
283 );
284 }
285 assert!(resolve(&[("ATLAS_BF16_TC_PROJ", "0")]).bf16_tc_proj);
286 }
287
288 #[test]
289 fn kill_switches_and_zero_opt_outs_keep_their_distinct_polarities() {
290 let d = resolve(&[
291 ("ATLAS_NO_GDN_REGRESIDENT", "1"),
292 ("ATLAS_NO_GEMV_SW", "1"),
293 ("ATLAS_GDN_WY17", "0"),
294 ("ATLAS_GDN_WYN", "0"),
295 ("ATLAS_FFN_SMALLM", "0"),
296 ]);
297 assert!(!d.gdn_regresident);
298 assert!(!d.gemv_sw);
299 assert!(!d.gdn_wy17);
300 assert!(!d.gdn_wyn);
301 assert!(!d.ffn_small_m);
302 assert!(resolve(&[("ATLAS_NO_GDN_REGRESIDENT", "0")]).gdn_regresident);
303 assert!(resolve(&[("ATLAS_GDN_WY17", "1")]).gdn_wy17);
304 }
305
306 #[test]
307 fn externally_resolved_shadow_and_drafter_values_are_carried() {
308 let d = from_values(
309 |_| None,
310 |_| false,
311 7,
312 crate::model::drafter_context::DrafterContext::OFF,
313 );
314 assert_eq!(d.shadow_topk, 7);
315 assert_eq!(
316 d.drafter,
317 crate::model::drafter_context::DrafterContext::OFF
318 );
319 }
320}