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 ENVIRONMENT IS READ EXACTLY ONCE PER PROCESS. KEEP IT THAT WAY.
6//!
7//! Every `ATLAS_*` variable below is a process constant: nothing mutates the
8//! environment after start (the runtime `set_var` that once could was
9//! deliberately removed — see `main_modules/serve_load.rs` and `config.rs`).
10//! So resolving them more than once is pure waste, and on a hot path it is
11//! worse than waste:
12//!
13//! * `std::env::var` allocates a `String` per read, and
14//! * it takes the PROCESS-WIDE environment lock, so concurrent readers
15//!   SERIALISE against each other.
16//!
17//! MEASURED on GB10: one resolve of the ~30 variables here costs 0.57 us
18//! single-threaded but **4.00 us at 8 threads and 5.76 us at 16** — the cost
19//! grows with concurrency, which makes it invisible to any single-stream
20//! benchmark. `from_env()` was called **32,513 times in one
21//! `concurrency-sweep`** (48 layers x ~680 prefills) while its own doc claimed
22//! it was "called once, when the model is built".
23//!
24//! **The rule for this module and anything like it:** read the environment in
25//! ONE place, at ONE time, and pass the resolved value down. Use
26//! [`ModelLevers::get`] for the process-wide copy; take `levers` from the
27//! `ForwardContext` or the model when you already have one. If you find
28//! yourself calling anything named `*_from_env`, `resolve_*` or `*_env()`
29//! inside a function that runs per token, per layer, per forward pass or per
30//! request, that is the bug this note exists to prevent.
31//!
32//! The second of the two lever categories on [`crate::layer::ForwardContext`]:
33//!
34//! * [`super::GemmDispatch`] — which GEMM implementation each projection takes.
35//! * [`ModelLevers`] — everything else the model's kernel paths branch on:
36//!   the SSM/GDN recurrence variant, FFN routing, MoE quantization, LoRA
37//!   application mode, diagnostics.
38//!
39//! Both were `OnceLock<bool>` statics reading `ATLAS_*` at first touch. Two
40//! problems with that, and only the first is about hot-swap:
41//!
42//! 1. A static outlives the model whose flags it encodes. Load a second model
43//!    whose recipe sets different levers and the process keeps taking the
44//!    previous model's branches — silently, because a cached `bool` cannot
45//!    report that it is stale.
46//! 2. It hides the dependency. A function that reads the environment through a
47//!    static declares nothing in its signature, cannot be exercised with a
48//!    different configuration without mutating the process, and gives the
49//!    compiler nothing to check.
50//!
51//! Carrying it fixes both, and a site that forgets the field fails to build.
52
53/// Kernel-path levers for one loaded model.
54///
55/// Plain `Copy` data resolved from the environment at model construction. Group
56/// membership follows the subsystem the lever steers, so a reader can see at a
57/// glance which part of the forward pass a flag reaches.
58// `Eq` is deliberately absent since `draft_conf_tau` joined: it is an f32
59// threshold. Comparing two resolutions is a test-only need and `PartialEq`
60// covers it.
61#[derive(Clone, Copy, Debug, PartialEq, Default)]
62pub struct ModelLevers {
63    // ── SSM / GDN recurrence ──
64    /// Keep GDN recurrent state in registers across the prefill chunk loop.
65    /// Default ON (the fold that shipped in PR #369, −7.25 % wall); the env var
66    /// is an opt-OUT, which is why the field is stored positively and the
67    /// resolution inverts it.
68    pub gdn_regresident: bool,
69    /// Batched FLA path for multi-sequence GDN decode.
70    pub gdn_batched_fla: bool,
71    /// WY17 GDN recurrence variant. Ships ON; `ATLAS_GDN_WY17=0` opts out.
72    pub gdn_wy17: bool,
73    /// WY-N GDN recurrence variant. Ships ON; `ATLAS_GDN_WYN=0` opts out.
74    pub gdn_wyn: bool,
75
76    // ── FFN / MoE ──
77    /// Lossless single-warp decode GEMV (`w4a16_gemv_sw`, `w4a16_gemv_dual_sw`).
78    /// Ships ON; `ATLAS_NO_GEMV_SW=1` restores the 64-thread kernels.
79    pub gemv_sw: bool,
80    /// Route decode FFN through the tile GEMM rather than the scalar GEMV.
81    pub decode_ffn_via_gemm: bool,
82    /// Small-M FFN GEMM tile shape. Ships ON; `ATLAS_FFN_SMALLM=0` opts out.
83    pub ffn_small_m: bool,
84    /// FP4 holo layout for the MoE down projection.
85    pub holo_moe_down_fp4: bool,
86    /// FP4 holo layout for the MoE gate/up projections.
87    pub holo_moe_gateup_fp4: bool,
88    /// Collect per-layer MoE expert-union statistics. Diagnostic.
89    pub moe_union_stats: bool,
90    /// `ATLAS_FP32_ROUTING=1` — emit the MoE-input norm in FP32 so the gate
91    /// GEMM routes at full precision, removing the bf16-store rounding that
92    /// flips experts on gfx1151. Read once per LAYER per DECODE TOKEN from
93    /// six call sites via `MoeFfnLayer::fp32_routing_active`, which also
94    /// checks four weight/kernel preconditions — the lever is only the last
95    /// term of that conjunction, which is why it lives here and the
96    /// preconditions stay on the layer.
97    pub fp32_routing: bool,
98    /// `ATLAS_FP32_GATE=1` — the batched-gate sibling of
99    /// [`Self::fp32_routing`].
100    pub fp32_gate: bool,
101    /// `ATLAS_FRANKENSTEIN_DECODE_VIA_PREFILL=1` — route the five DFlash
102    /// capture layers' decode through the PREFILL MoE kernel, on the
103    /// hypothesis that the decode MoE kernel is the dominant cause of low
104    /// drafter acceptance. ~250 us per capture layer, so ~1.25 ms/token
105    /// against a ~58 ms/token decode. Diagnostic; non-capture layers are
106    /// untouched.
107    pub frankenstein_decode_via_prefill: bool,
108    /// `ATLAS_K2_DIAG=1` — K=2 routed-decode diagnostics.
109    pub k2_diag: bool,
110
111    // ── Dense FFN: which GEMM each prefill/decode arm takes ──
112    //
113    // These twelve were read with `std::env::var_os` from inside
114    // `DenseFfnLayer::forward` and `forward_prefill_inner`, i.e. once per
115    // LAYER per decode token and once per layer per prefill chunk — and one
116    // of them from inside a per-GEMM macro, so three times per layer. No
117    // allocation (that is `var_os`'s advantage over `var`) but the same
118    // process-wide environment lock, which serialises concurrent decode
119    // threads. The load-time readers in `finalize_q4k_load` and
120    // `finalize_nvfp4_mmq_load` are deliberately left where they are: they
121    // run once per weight, at load.
122    /// Split SiLU+down on the decode path: `silu_mul` into `gate_out`, then a
123    /// separate `w4a16_decode_gemv` for down. Ships ON;
124    /// `ATLAS_NO_DECODE_SPLIT_SILU` (presence) restores the fused kernel.
125    /// A LoRA adapter pins this path on regardless — the fused alternative
126    /// never materialises `silu(gate)*up`, which the down delta must
127    /// contract over — so the call site is `levers.decode_split_silu ||
128    /// self.lora.is_some()`.
129    pub decode_split_silu: bool,
130    /// `ATLAS_BF16_TC_PREFILL` (presence) — BF16 tensor-core prefill GEMM.
131    /// Read here only; the usable gate is derived at the call site AFTER
132    /// v1/v2 selection, from the handle actually launched. Gating on v1's
133    /// handle while dispatching v2 admitted launches of a kernel the target
134    /// may not carry.
135    pub bf16_tc_prefill: bool,
136    /// `ATLAS_FP8_M64_PREFILL` (presence) — m16n8k32 e4m3 M64 prefill GEMM,
137    /// ~1.47x vs v2 BF16. Lossy (cosine 0.9997), so opt-in only.
138    pub fp8_m64_prefill: bool,
139    /// `ATLAS_INT8_PREFILL` (presence) — requant→`int8_gemm_faith2` prefill
140    /// (cosine 0.999978 vs the host full-precision dequant GEMM).
141    pub int8_prefill: bool,
142    /// `ATLAS_INT8_FAITH5` (presence) — int32 per-sub-block accumulation,
143    /// which breaks the MMA→scale dependency chain. Same kernel signature
144    /// and launch geometry as faith2, so it is a handle swap.
145    pub int8_faith5: bool,
146    /// Vendored llama NVFP4 W4A4 MMQ for the gate/up prefill GEMMs
147    /// (~80 TFLOP/s vs t_m128's ~51). Ships ON;
148    /// `ATLAS_NO_FFN_NVFP4_MMQ` (presence) is the kill switch.
149    pub ffn_nvfp4_mmq: bool,
150    /// The same MMQ arm for the down projection — t_m128 runs the narrow-N
151    /// down at only ~34 TFLOP/s. Ships ON; `ATLAS_NO_FFN_NVFP4_MMQ_DOWN`
152    /// (presence) is the kill switch. Separate from
153    /// [`Self::ffn_nvfp4_mmq`] because down is the heavy-tailed projection
154    /// (W4A4 cosine 0.9961) and gets its own gate.
155    pub ffn_nvfp4_mmq_down: bool,
156    /// `ATLAS_FFN_MMQ` (presence) — Q4_K MMQ prefill arm.
157    pub ffn_mmq: bool,
158    /// `ATLAS_FFN_MMQ_DOWN_Q4K` (presence) — keep the down projection ON
159    /// Q4_K instead of the near-lossless faith2 NVFP4 hybrid.
160    ///
161    /// Stores the POSITIVE of a variable whose call site reads the negative
162    /// (`!levers.ffn_mmq_down_q4k`), the same shape as
163    /// [`Self::moe_legacy_pertoken_decode`]. down = SiLU(gate)*up is
164    /// heavy-tailed and Q4_K superblock scaling clips it — BFCL `multiple`
165    /// −4.0%, which is why llama promotes only down→Q6_K.
166    pub ffn_mmq_down_q4k: bool,
167    /// `ATLAS_FP4_PREFILL` (presence) — native W4A4 FP4 tensor cores
168    /// (sm_121a), NVFP4 weights used directly with no requant. Lossy
169    /// (cos ~0.99 vs fp32).
170    pub fp4_prefill: bool,
171    /// The v2 BF16 t_m128 prefill kernel — faster and bit-identical to v1.
172    /// Ships ON; `ATLAS_DISABLE_PREFILL_V2` (presence) forces v1 so the two
173    /// can be compared for TTFT in one binary.
174    pub prefill_v2: bool,
175
176    // ── MoE routed prefill ──
177    //
178    // Read once per LAYER per prefill chunk from `forward_prefill_routed`,
179    // and the CUTLASS gate is asked TWICE per call through a free function.
180    /// `ATLAS_HOLO_MOE_GROUPED_CUTLASS=1` — single-launch CUTLASS grouped
181    /// NVFP4 gate_up. Off by default; unset falls back to the hand-rolled
182    /// fused FP4/FP8 grouped kernels.
183    pub moe_grouped_cutlass: bool,
184    /// `ATLAS_HOLO_MOE_GROUPED_DOWN=1` — take the down projection through
185    /// the same CUTLASS grouped path. Requires
186    /// [`Self::moe_grouped_cutlass`]; a separate gate because down consumes
187    /// the already-expert-contiguous post-SiLU output and needs no gather.
188    pub moe_grouped_down: bool,
189    /// `ATLAS_MOE_PREFILL_EXACT_TILES=1|0` overrides the tile bound;
190    /// `None` (unset) defers to the checkpoint — the win was measured on
191    /// NVFP4, so the default is scoped to where it was measured.
192    ///
193    /// Tri-state on purpose. Measured: exact_tiles ON gave p90 +4.9% against
194    /// a +5.0% limit (0.1% from failing the gate) and OFF gave p90 −5.0%,
195    /// while the median barely moved either way (+0.1% vs −0.9%). Only the
196    /// tail shows it, so both directions must stay reachable. Graph capture
197    /// forces it off regardless — the bound is read back from device memory.
198    pub moe_prefill_exact_tiles: Option<bool>,
199    /// `ATLAS_MOE_PREFILL_MAX_LOAD_FACTOR=<n>` — cap the per-expert tile
200    /// bound at n times the average when exact tiles are off. `None` (unset
201    /// or `0`) means the worst case.
202    pub moe_prefill_max_load_factor: Option<usize>,
203    /// `ATLAS_MOE_PREFILL_ZERO=1` — memset the grouped scratch before
204    /// dispatch. Implied by EP (`ctx.comm.is_some()`). In non-EP the sort
205    /// produces a dense permutation over exactly the rows the grouped
206    /// kernels write, so skipping the clear removes ~138 MB/layer on Holo.
207    pub moe_prefill_zero: bool,
208    /// `ATLAS_MOE_PREFILL_FP8_DOWN=1` — FP8 grouped GEMM for the routed
209    /// down projection.
210    pub moe_prefill_fp8_down: bool,
211
212    // ── Nemotron prefill (Mamba2 SSM + MoE) ──
213    //
214    // Nine reads across three functions, each once per LAYER per PREFILL
215    // CHUNK. All are PRESENCE-gated — the sites spelled them
216    // `std::env::var(..).is_err()` / `.is_ok()`, so `=0` neither arms an
217    // opt-in nor re-enables an opt-out. Resolution uses `var_os`, which
218    // differs from `var` only for a non-UTF-8 value: `var` reports that as
219    // absent, `var_os` as present. The difference lands on the safe side of
220    // every one of these.
221    /// W4A4 native-FP4 SSM projections at N >= 512. Ships ON;
222    /// `ATLAS_NO_SSM_W4A4` (presence) is the kill switch.
223    pub ssm_w4a4: bool,
224    /// The chunked SSD scan. Ships ON; `ATLAS_NO_SSD` (presence) falls back
225    /// to the sequential scan. Gated additionally on `ssd_scan_fits`, since
226    /// Nano-30B's state_size=128 overflows the shared-memory budget that
227    /// Puzzle-75B's 96 fits — which is why that never surfaced until it did.
228    pub ssd: bool,
229    /// The persistent SSM prefill kernel, which keeps H in shared memory and
230    /// is only reachable when SSD is unavailable. Ships ON;
231    /// `ATLAS_NO_SSM_PERSISTENT` (presence) disables, for a same-binary A/B
232    /// against the sequential scan.
233    pub ssm_persistent: bool,
234    /// Zero the grouped-MoE intermediate arena buffers before dispatch.
235    /// Ships ON; `ATLAS_MOE_NO_ZERO_INTERMEDIATES` (presence) skips.
236    ///
237    /// Defence in depth: these buffers are reused across requests and nothing
238    /// else clears them, so a row a future change fails to write would leak
239    /// the PREVIOUS request's activations rather than merely being wrong.
240    /// Asked twice in one call before this — once for up, once for down.
241    pub moe_zero_intermediates: bool,
242    /// `ATLAS_MOE_MAX_M_TILES_ESTIMATE` (presence) — restore the old
243    /// average-based tile bound. A/B only; the comment at the site says it
244    /// is NOT safe to serve on, because the estimate can under-bound the
245    /// worst case of one expert taking every routed token.
246    pub moe_max_m_tiles_estimate: bool,
247    /// `ATLAS_MOE_W4A4` (presence) — W4A4 grouped up-projection at N >= 512.
248    pub moe_w4a4: bool,
249    /// W4A4 for the shared-expert UP projection at N >= 512. Ships ON;
250    /// `ATLAS_NO_SHARED_W4A4` (presence) is the kill switch.
251    pub shared_w4a4: bool,
252    /// `ATLAS_SHARED_W4A4_DOWN` (presence) — the DOWN half of the same, and
253    /// a SEPARATE opt-in: down is the heavy-tailed projection, so it does not
254    /// inherit [`Self::shared_w4a4`].
255    pub shared_w4a4_down: bool,
256
257    // ── Attention ──
258    /// Contiguous-attention path for the DFlash head.
259    pub dflash_contig_attn: bool,
260
261    // ── LoRA ──
262    /// Apply LoRA eagerly at load instead of at each forward.
263    pub lora_eager: bool,
264    /// Allow hot rotation of LoRA adapters.
265    pub lora_rotate: bool,
266
267    // ── Diagnostics ──
268    /// K=4 chain-widening diagnostics.
269    pub k4_diag: bool,
270    /// Per-layer hidden-state norm dumps on the Gemma-4 decode path. Heavy —
271    /// one device-to-host copy per layer.
272    pub gemma4_diag: bool,
273    /// `ATLAS_DFLASH_DEBUG_DUMP_FULL=1` — the model-side half of the DFlash
274    /// full dump: emit the whole token sequence ONCE so a Python reference
275    /// can run the same tokens through HF transformers.
276    ///
277    /// ★ The SAME variable that [`crate::layers::dflash_head::levers::DFlashLevers::debug_dump_full`]
278    /// carries. Two structs, one flag — deliberately, because the two halves
279    /// of the dump are armed together by design and the head is not reachable
280    /// from `TransformerModel` (`proposer` is a `dyn DraftProposer`, so there
281    /// is nothing to read the head's levers through without a downcast).
282    /// `the_two_halves_of_the_dflash_dump_agree` pins that the two
283    /// resolutions cannot drift, which is what makes the duplication safe —
284    /// an unchecked second spelling of one lever is how
285    /// `ATLAS_DSPARK_ANCHOR_BIAS` came to have two implementations.
286    pub dflash_debug_dump_full: bool,
287    /// `ATLAS_MTP_DEBUG_NORMS=1` — per-stage norm dumps inside the MTP
288    /// drafter's `forward_one`, which asked for it FOUR times per drafted
289    /// token, each read only to decide whether to do nothing.
290    pub mtp_debug_norms: bool,
291    /// `ATLAS_MTP_DRAFT_CONF=<t>` — confidence floor for submitting drafts
292    /// to verification, clamped to `[0.0, 0.99]`. `0.0` (unset) disables.
293    ///
294    /// When the drafter's chain confidence (the min top-1 softmax prob
295    /// across one propose's drafts) is below this, the drafts are discarded
296    /// and the next step decodes serially, skipping a verify that would most
297    /// likely reject. Economics at K=1 on the 35B MoE: verify ~35 ms for
298    /// 1+accepted tokens against decode+propose ~21 ms for 1, so a draft is
299    /// only worth verifying at p(accept) >~ 0.66. STAGED OFF pending its
300    /// measured A/B.
301    ///
302    /// Three of its four readers asked per propose whether the feature was
303    /// on, i.e. paid the environment lock to learn it was off. The fourth,
304    /// `MtpHead::last_confidence`, is reached only when it is already ON, so
305    /// it keeps its own read and its own contract — see the note there.
306    pub draft_conf_tau: f32,
307    /// `ATLAS_SSM_SAVE_DUMP` (presence) — the CBD scratch/SSM-state
308    /// fingerprint probe. Asked THREE times per decode step by the decode
309    /// path alone, each read only to decide whether to do nothing.
310    pub ssm_save_dump: bool,
311
312    // ── Batched decode dispatch ──
313    //
314    // Five reads in `decode_batch_dispatch` / `decode_batch_compute_main`,
315    // once per BATCHED DECODE STEP. Note the spellings differ between
316    // neighbouring lines of the same function — two are truthy
317    // (`"1"` or `"true"`, case-SENSITIVE) and three are strict `"1"` — and
318    // each field keeps the one its site had.
319    /// `ATLAS_MLA_PERSEQ_FALLBACK=1|true` — route MLA batches through the
320    /// per-sequence path instead of the batched one.
321    pub mla_perseq_fallback: bool,
322    /// `ATLAS_HC_PERSEQ_DECODE=1` — per-sequence hyper-connection decode.
323    /// ORed with `qsa_active`, and the routing decision is resolved ABOVE
324    /// the EP branch on purpose: it used to sit below, so under EP a
325    /// QSA-active batch returned before reaching the gate, landed on the
326    /// batched multi-seq path, and died on its guard.
327    pub hc_perseq_decode: bool,
328    /// `ATLAS_DECODE_BATCH_LOG=1` — log the batch's slot/position vectors
329    /// each step.
330    pub decode_batch_log: bool,
331    /// `ATLAS_MS_PROFILE=1` — per-phase multi-seq profiling, which forces
332    /// eager execution so the per-phase syncs are legal under capture.
333    ///
334    /// NOT [`Self::ssm_ms_profile`], which is `ATLAS_SSM_MS_PROFILE`. Two
335    /// different variables one underscore apart, both live.
336    pub ms_profile: bool,
337    /// `ATLAS_CONC_HSD=1|true` — per-sequence hidden-state dump, to localize
338    /// where `pos >= 1` diverges from `pos 0` in concurrent batched decode.
339    pub conc_hsd: bool,
340
341    // ── Decode graph capture ──
342    /// `ATLAS_EP_GRAPHS=1|true` — allow CUDA-graph capture under expert
343    /// parallelism. The EP all-reduce queues ncclSend/Recv plus a local add
344    /// on the capture stream and NCCL >= 2.9 supports capture, so this MAY
345    /// capture cleanly; env-gated so a deploy can revert instantly if
346    /// capture crashes or replay hangs.
347    pub ep_graphs: bool,
348    /// `ATLAS_GDN_DECODE_GRAPH=1|true` — capture the whole single-token GDN
349    /// HeadParallel TP decode forward (~130 kernels plus the per-layer TP
350    /// all-reduces) into one replayable graph. Default OFF.
351    pub gdn_decode_graph: bool,
352
353    // ── Attention (cont.) ──
354    /// BF16 tensor-core attention projections: dequant FP4 to BF16 and use a
355    /// BF16 MMA instead of the default path, which crushes activations to FP8
356    /// E4M3. Removes the FP8 prefill perturbation on those projections.
357    pub bf16_tc_proj: bool,
358    /// The checkpoint's attention weights are ALREADY Hadamard-rotated at load
359    /// (`TQ_PLUS_WEIGHT_ROTATION`), so the runtime must not rotate again.
360    ///
361    /// A property of the loaded checkpoint, and the SSOT for it. It previously
362    /// had FIVE implementations of the same `=1`-or-`true` test — four raw
363    /// `std::env::var` calls on attention paths (one per attention layer per
364    /// DECODE TOKEN in `decode/attention_forward.rs`, one per layer per
365    /// batched decode step in `multi_seq/attn.rs`, two per layer per prefill
366    /// chunk) plus a fifth in the weight loader, whose `#[allow(dead_code)]`
367    /// was stale — `attention_arms.rs` calls it. Reading this per token cost
368    /// an allocation and the process-wide environment lock on the hottest path
369    /// in the model, and five copies of one predicate is how a flag ends up
370    /// decoded two different ways in one binary.
371    pub weight_pre_rotated: bool,
372
373    // ── SSM / GDN decode ──
374    // These five ran on the batched-decode path — per SSM layer per decode
375    // step, ~6-7M environment reads per sweep on a 36-SSM-layer hybrid, the
376    // largest raw count in this crate. Three are diagnostics that are off in
377    // every shipped configuration, and were paying a `String` allocation and
378    // the process-wide environment lock to say so on every layer of every
379    // token. Their neighbour `ssm_tc_proj_min_n()` in the same file was
380    // already `OnceLock`'d with the note "Read ONCE — this site runs under
381    // graph capture", so these were an inconsistency, not a design.
382    /// Per-step multi-sequence SSM profiling dump.
383    pub ssm_ms_profile: bool,
384    /// Finer per-sub-step SSM profiling inside the batched recurrence.
385    pub ssm_detail_profile: bool,
386    /// Ships ON: use the batch-4 GEMV tier for the SSM projections when the
387    /// kernel is resolved and n <= 16. `ATLAS_SSM_GEMV_BATCH4=0` opts out.
388    pub ssm_gemv_batch4: bool,
389    /// Fuse the GDN conv with the F32 norm when the head geometry allows.
390    pub gdn_fused_conv: bool,
391    /// Take the pre-token-major MoE decode kernel. The field stores the
392    /// POSITIVE of the variable's name, so the call site reads
393    /// `!levers.moe_legacy_pertoken_decode` for the default token-major path —
394    /// the inversion lives here, once, rather than at the branch.
395    pub moe_legacy_pertoken_decode: bool,
396    /// Configured max decode batch (`--max-batch-size`), the reference count
397    /// the split-K attention split count is pinned to. Not from the
398    /// environment: `TransformerModel::new` writes it from the serve arg.
399    ///
400    /// It pins DETERMINISM — the online-softmax split-merge is
401    /// non-associative, so a sequence decoded alone must see the same
402    /// reduction tree as one co-batched with fifteen others. Held in a
403    /// `OnceLock` it was also idempotent, so a second model with a different
404    /// max batch would silently keep the first model's split count.
405    pub max_decode_seqs: u32,
406    /// `ATLAS_MTP_SHADOW_TOPK=k` (0 = off, clamped to 8): the drafter D2Hs
407    /// its logits and logs the top-k candidates. Observational only.
408    pub shadow_topk: usize,
409    /// `ATLAS_KV_POISON=1` — fill a fresh KV block with NaN instead of zero,
410    /// the discriminator for the "unwritten fresh tail block read"
411    /// hypothesis. A diagnostic that changes what the kernels READ, so it
412    /// must not leak across a swap.
413    pub kv_poison: bool,
414    /// MTP drafter context policy (`ATLAS_NO_DRAFTER_CONTEXT` /
415    /// `ATLAS_DRAFTER_PREFILL_ONLY`), resolved and logged once per model.
416    /// The two halves are coupled — prefill without carry is a measured
417    /// −927 ms/turn loss — so they travel as one value.
418    pub drafter: crate::model::drafter_context::DrafterContext,
419}
420
421/// How the levers above are READ. Private: the environment is touched in
422/// exactly one place, and `ModelLevers::get` is the only way out of it.
423#[path = "model_levers_resolve.rs"]
424mod resolve;
425
426#[cfg(test)]
427#[path = "model_levers_tests.rs"]
428mod tests;
429
430/// ★ Where the environment may be read at all. Kept next to the levers it
431/// exists to protect, not inside `tests` — it guards other modules too.
432#[cfg(test)]
433#[path = "hot_path_env_guards.rs"]
434mod hot_path_env_guards;