metrale_model_layers/layers/qwen3_ssm/
gdn_flags.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: GDN / SSM decode-path flags, resolved once per process from the serve command line or, failing that, the environment.
4//!
5//! Owner: model-layers (qwen3 SSM).
6//! Invariants:
7//! - The flags are one process-wide cell: the first of `set_from_cli` or
8//!   `flags()` fixes them, and later calls see the same value. A model loaded
9//!   later in the same process keeps that resolution.
10//! - The server publishes `h_f16_pool` only together with `h_f16` (both come
11//!   from `ssm_h_dtype_bits`), and the environment fallback never sets it.
12//!
13//! The flags select kernels and are coupled: the FP16 h-state twins exist
14//! only on the fused-norm arm, so `h_f16` without `fused_norm` would reach an
15//! FP32-only kernel. The server's argument validation rejects that pair
16//! before load. When the command line gives any GDN flag, the server
17//! publishes the whole cell through `set_from_cli`; otherwise `flags()`
18//! reads the environment on first use.
19
20/// 2026-09-25: The resolved flags; empty until `set_from_cli` or the first
21/// `flags()`.
22static FLAGS: std::sync::OnceLock<GdnFlags> = std::sync::OnceLock::new();
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct GdnFlags {
26    /// 2026-09-25: `--ssm-h-dtype f16` (or `f16-pool`): store the GDN decode
27    /// h-state as FP16.
28    pub h_f16: bool,
29    /// 2026-09-25: `--ssm-h-dtype f16-pool`: also size the h pools at 2 bytes
30    /// per element. Always set together with `h_f16` (a narrow pool holding
31    /// FP32 would be an out-of-bounds write).
32    pub h_f16_pool: bool,
33    /// 2026-09-25: `--gdn-fused-norm`: fused GDN output-norm decode kernel.
34    pub fused_norm: bool,
35    /// 2026-09-25: `--ssm-batched-recurrent`: one strided recurrent launch per
36    /// batch.
37    pub batched_recurrent: bool,
38    /// 2026-09-25: `--exact-verify`: MTP verify runs the sequential-decode-exact
39    /// per-token GDN/SSM chain instead of the WY-chunkwise and fused BF16-conv
40    /// arms. Off by default. It makes only the GDN/SSM verify chain exact; the
41    /// FFN and attention projections still pick kernels by row count.
42    pub exact_verify: bool,
43}
44
45impl GdnFlags {
46    /// 2026-09-25: Whether the MTP-verify pass runs the exact chain:
47    /// `exact_verify`. Pure, so tests need not touch the process-wide cell.
48    ///
49    /// 2026-10-01: Also under an FP16 h-state, which runs the FP16 twins of the
50    /// exact chain (`gdn_exact_chain_f16_{K}`); a kernel set without them
51    /// refuses the serve at the boot audit (`carry.rs`).
52    pub fn verify_exact_active(self) -> bool {
53        self.exact_verify
54    }
55    /// 2026-09-25: The reading used when the command line published nothing.
56    ///
57    /// `batched_recurrent` is the resolved `ssm_batched_recurrent` setting: the
58    /// target's `HARDWARE.toml` `[defaults]` row (on for `hopper`, off for
59    /// `gb10`, `b200` and `b300`), overridden by `METRALE_SSM_BATCHED_RECURRENT`,
60    /// where `0`/`false`/`off`/`no` turn it off and any other value on.
61    ///
62    /// `METRALE_SSM_H_FP16` is presence-checked: any value, `0` included, turns
63    /// it on. `METRALE_GDN_FUSED_NORM` must be exactly `1`.
64    fn from_env() -> Self {
65        Self {
66            h_f16: std::env::var("METRALE_SSM_H_FP16").is_ok(),
67            // 2026-09-25: No environment variable feeds the pool width; only
68            // `--ssm-h-dtype f16-pool` sets it.
69            h_f16_pool: false,
70            fused_norm: std::env::var("METRALE_GDN_FUSED_NORM").as_deref() == Ok("1"),
71            batched_recurrent: crate::layers::ops::target_defaults::resolved()
72                .ssm_batched_recurrent
73                .value,
74            // 2026-09-25: No environment variable feeds exact verify; only
75            // `--exact-verify` sets it.
76            exact_verify: false,
77        }
78    }
79}
80
81/// 2026-09-25: Publish the command line's resolution. Call once, before the model
82/// builds.
83///
84/// Returns the value in force: the argument, unless something already read the
85/// flags, in which case that earlier reading stays and the caller should say
86/// so.
87pub fn set_from_cli(flags: GdnFlags) -> GdnFlags {
88    let _ = FLAGS.set(flags);
89    *FLAGS.get().expect("just set")
90}
91
92/// 2026-09-25: The resolved flags, falling back to the environment on first use.
93pub fn flags() -> GdnFlags {
94    *FLAGS.get_or_init(GdnFlags::from_env)
95}
96
97/// 2026-09-25: The widest chain-verify K with an FP16 h-state twin
98/// (`gated_delta_rule_wy{5..16}_f16`).
99///
100/// K=17, the DFlash arm at gamma 16, dispatches the FP32 `gated_delta_rule_wy17`,
101/// which has no twin, and an FP32 kernel over an FP16 h-state produces wrong
102/// output rather than an error. The DFlash verify width is gamma + 1, so the
103/// argument validator and the serve preflight gate on
104/// [`MAX_F16_TWIN_DFLASH_GAMMA`], derived from this.
105pub const MAX_F16_TWIN_K: usize = 16;
106
107/// 2026-09-25: The largest `--dflash-gamma` whose verify width still has an FP16
108/// twin.
109pub const MAX_F16_TWIN_DFLASH_GAMMA: usize = MAX_F16_TWIN_K - 1;
110
111/// 2026-09-25: The DFlash gamma for a drafter of this trained block size, when no
112/// `--dflash-gamma` was given.
113///
114/// The one rule for it: the drafter head resolves its gamma through this, and
115/// so do the preflight paths that size pools and reserves from a peeked
116/// `dflash_config.block_size`.
117///
118/// `block + 2`, because these drafters chain past their trained block.
119/// Measured 2026-08-29 on GB10 with Qwen3.8-27B DFlash2 (block 8): gamma 10
120/// was the fastest serve, 63.0 against 56.2 tok/s at gamma 8.
121///
122/// Clamped to `MAX_F16_TWIN_DFLASH_GAMMA`, so a block-16 drafter lands on 15
123/// (verify width 16, which has a kernel for both h-state dtypes) rather than
124/// 18.
125pub const fn default_dflash_gamma(trained_block_size: usize) -> usize {
126    let bumped = trained_block_size + 2;
127    if bumped > MAX_F16_TWIN_DFLASH_GAMMA {
128        MAX_F16_TWIN_DFLASH_GAMMA
129    } else {
130        bumped
131    }
132}
133
134/// 2026-09-30: The γ of a DFlash serve when neither `--dflash-gamma` nor a drafter block
135/// size decides it.
136pub const UNRESOLVED_DFLASH_GAMMA: usize = 16;
137
138/// 2026-09-30: The one resolver of a DFlash serve's γ: `--dflash-gamma` when pinned, else
139/// [`default_dflash_gamma`] of the drafter's trained block size
140/// (`DflashConfig::effective_block_size`), else [`UNRESOLVED_DFLASH_GAMMA`]. The server
141/// resolves it once before the preflight reserve (`serve_phases::apply_dflash_gamma`); the
142/// reserve, the pools the build sizes and the scheduler all read that value.
143pub const fn resolve_dflash_gamma(
144    pinned: Option<usize>,
145    drafter_block_size: Option<usize>,
146) -> usize {
147    match (pinned, drafter_block_size) {
148        (Some(g), _) => g,
149        (None, Some(b)) => default_dflash_gamma(b),
150        (None, None) => UNRESOLVED_DFLASH_GAMMA,
151    }
152}
153
154/// 2026-09-25: `--ssm-h-dtype f16` or `f16-pool` (environment fallback:
155/// `METRALE_SSM_H_FP16`).
156pub fn ssm_h_fp16_enabled() -> bool {
157    flags().h_f16
158}
159
160/// 2026-09-25: `--ssm-h-dtype f16-pool`: h pools sized at 2 bytes per element.
161/// Implies [`ssm_h_fp16_enabled`]; [`ssm_h_dtype_bits`] guarantees that where
162/// the flag is decoded.
163pub fn ssm_h_f16_pool_enabled() -> bool {
164    flags().h_f16_pool
165}
166
167/// 2026-09-25: Decode `--ssm-h-dtype` into the two h-state bits it publishes:
168/// `(h_f16, h_f16_pool)`.
169///
170/// The argument validator and the server's kernel-flag plan both call this, so
171/// they cannot read the flag differently. Anything other than exactly `f16` or
172/// `f16-pool`, including `f32` and an absent flag, is FP32.
173pub fn ssm_h_dtype_bits(dtype: Option<&str>) -> (bool, bool) {
174    match dtype {
175        Some("f16") => (true, false),
176        // 2026-09-25: f16-pool is f16 plus the narrow pool, never one without the
177        // other.
178        Some("f16-pool") => (true, true),
179        _ => (false, false),
180    }
181}
182
183/// 2026-09-25: `--gdn-fused-norm` (environment fallback:
184/// `METRALE_GDN_FUSED_NORM=1`).
185pub fn gdn_fused_norm_enabled() -> bool {
186    flags().fused_norm
187}
188
189/// 2026-09-25: `--ssm-batched-recurrent` (environment fallback:
190/// `METRALE_SSM_BATCHED_RECURRENT`).
191pub fn ssm_batched_recurrent_enabled() -> bool {
192    flags().batched_recurrent
193}
194
195/// 2026-09-25: `--exact-verify` given: the MTP-verify pass runs the
196/// sequential-decode-exact chain. See [`GdnFlags::verify_exact_active`].
197///
198/// 2026-09-30: Also on under a fixed `--activation-quantization` for the GDN family: a verify row
199/// then computes the bits a decode step computes, so a token's logits do not depend on which
200/// verify row, or which step, produced it.
201pub fn verify_exact_enabled() -> bool {
202    flags().verify_exact_active() || crate::layers::family_fixed(metrale_config::ProjFamily::Gdn)
203}
204
205/// 2026-09-25: Batch width at which the multi-sequence decode projections switch
206/// to the 128-row M-tile. `None` when `METRALE_NO_SSM_M128` is set (any value,
207/// `0` included), which keeps the 64-row tile at every width.
208///
209/// 65 is derived, not tuned: `ceil(m/64) > ceil(m/128)` first holds at m=65, so
210/// below it the wider tile saves no weight reads and only pads MMA rows.
211pub(crate) fn ssm_m128_min_m() -> Option<u32> {
212    static M: std::sync::OnceLock<Option<u32>> = std::sync::OnceLock::new();
213    *M.get_or_init(|| {
214        if std::env::var("METRALE_NO_SSM_M128").is_ok() {
215            None
216        } else {
217            Some(65)
218        }
219    })
220}
221
222/// 2026-09-25: Write-on-accept K=4 batched verify. Off unless `METRALE_GDN_WOA=1`.
223/// With it off the parent wy4 kernel runs, the fold in `woa.rs` returns
224/// `Ok(false)`, and the accept path (`async_chkpt.rs`) restores `h` for every K.
225///
226/// Off by default on a measurement: on GB10 on 2026-09-18, concurrency sweeps
227/// with it on had the semantic-loop watchdog fire at C=4 (4 fires) where runs
228/// with it off had none, and failed the gate; C=16..128 were unaffected. The
229/// cause inside the arm is not identified.
230///
231/// Public so the serve can tell the gamma resolver whether the K=4 rung has
232/// its kernel.
233pub fn gdn_woa_enabled() -> bool {
234    std::env::var("METRALE_GDN_WOA").ok().as_deref() == Some("1")
235}
236
237#[cfg(test)]
238mod tests {
239    use super::{GdnFlags, ssm_h_dtype_bits};
240
241    /// 2026-09-30: A pinned γ wins, even above the FP16-twin clamp the drafter default
242    /// takes; an unpinned one is the drafter's block + 2 (clamped); neither gives 16.
243    #[test]
244    fn dflash_gamma_resolves_pinned_then_drafter_then_unresolved() {
245        use super::{MAX_F16_TWIN_DFLASH_GAMMA, UNRESOLVED_DFLASH_GAMMA, resolve_dflash_gamma};
246        assert_eq!(resolve_dflash_gamma(Some(8), Some(8)), 8);
247        assert_eq!(resolve_dflash_gamma(Some(20), Some(8)), 20);
248        assert_eq!(resolve_dflash_gamma(None, Some(8)), 10);
249        assert_eq!(
250            resolve_dflash_gamma(None, Some(16)),
251            MAX_F16_TWIN_DFLASH_GAMMA
252        );
253        assert_eq!(resolve_dflash_gamma(None, None), UNRESOLVED_DFLASH_GAMMA);
254    }
255
256    const BASE: GdnFlags = GdnFlags {
257        h_f16: false,
258        h_f16_pool: false,
259        fused_norm: false,
260        batched_recurrent: false,
261        exact_verify: false,
262    };
263
264    /// 2026-09-25: With no flags the verify pass runs the WY/chunkwise arms, not
265    /// the exact chain.
266    #[test]
267    fn legacy_wy_verify_is_the_default() {
268        assert!(
269            !BASE.verify_exact_active(),
270            "default must be the legacy WY arms — exact verify is opt-in"
271        );
272        // 2026-09-25: The other flags do not turn exact mode on.
273        assert!(
274            !GdnFlags {
275                fused_norm: true,
276                batched_recurrent: true,
277                ..BASE
278            }
279            .verify_exact_active()
280        );
281    }
282
283    /// 2026-09-25: `--exact-verify` selects the exact chain, alone and beside
284    /// the other GDN flags.
285    #[test]
286    fn exact_verify_flag_selects_the_exact_chain() {
287        assert!(
288            GdnFlags {
289                exact_verify: true,
290                ..BASE
291            }
292            .verify_exact_active()
293        );
294        assert!(
295            GdnFlags {
296                exact_verify: true,
297                fused_norm: true,
298                batched_recurrent: true,
299                ..BASE
300            }
301            .verify_exact_active()
302        );
303    }
304
305    /// 2026-09-25: The environment fallback never turns exact verify on: no
306    /// variable feeds it. Deterministic despite reading the process
307    /// environment, because only fields no variable feeds are asserted.
308    #[test]
309    fn env_fallback_never_enables_exact_verify() {
310        assert!(!GdnFlags::from_env().exact_verify);
311        // 2026-09-25: The same holds for the pool width: `METRALE_SSM_H_FP16`
312        // alone keeps the FP32-sized pool.
313        assert!(!GdnFlags::from_env().h_f16_pool);
314    }
315
316    /// 2026-09-25: A narrow pool holding FP32 is an out-of-bounds write, so
317    /// `h_f16_pool` without `h_f16` must not come from any spelling. The
318    /// argument validator and the kernel-flag plan both decode through this
319    /// function.
320    #[test]
321    fn the_pool_bit_is_never_set_without_the_dtype_bit() {
322        for (spelling, expected) in [
323            (None, (false, false)),
324            (Some("f32"), (false, false)),
325            (Some("f16"), (true, false)),
326            (Some("f16-pool"), (true, true)),
327            (Some(""), (false, false)),
328            (Some("F16-POOL"), (false, false)),
329            (Some("f16 "), (false, false)),
330        ] {
331            assert_eq!(ssm_h_dtype_bits(spelling), expected, "{spelling:?}");
332        }
333    }
334
335    /// 2026-10-01: An FP16 h-state keeps an explicit exact request (its FP16 twins
336    /// run it) and turns nothing on by itself.
337    #[test]
338    fn h_f16_keeps_an_exact_request_and_adds_none() {
339        assert!(
340            GdnFlags {
341                exact_verify: true,
342                h_f16: true,
343                h_f16_pool: true,
344                fused_norm: true,
345                ..BASE
346            }
347            .verify_exact_active()
348        );
349        assert!(
350            !GdnFlags {
351                h_f16: true,
352                h_f16_pool: true,
353                fused_norm: true,
354                ..BASE
355            }
356            .verify_exact_active()
357        );
358    }
359}