metrale_config/
kv_completeness.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-26: The two KV-completeness gates: whether the radix prefix cache and the
4//! `--swap-space-gb` swap-out may treat a sequence's KV blocks as its whole state.
5//!
6//! Owner: config.
7//! Invariants:
8//! - Without `METRALE_GLM53_PREFIX_CACHE_UNPROVEN`, the two gates give the same answer.
9//! - That switch opens only the prefix-cache gate, and only for `glm5_next` / `glm5_next_text`.
10
11use super::ModelConfig;
12
13impl ModelConfig {
14    /// 2026-09-26: Whether every byte of a sequence's per-layer state is held in its KV blocks.
15    ///
16    /// False for `glm5_next` / `glm5_next_text` (the DSA indexer rows, `Glm5NextDsaState`) and
17    /// for `deepseek_v4` when any `compress_ratios` entry is non-zero.
18    fn per_sequence_state_is_kv_complete(&self) -> bool {
19        match self.model_type.as_str() {
20            "glm5_next" | "glm5_next_text" => false,
21            "deepseek_v4" => self.compress_ratios.iter().all(|&ratio| ratio == 0),
22            _ => true,
23        }
24    }
25
26    /// 2026-09-26: Whether the radix prefix cache captures every state needed to resume this
27    /// model exactly. `preflight_reserve` and `build_prefix_cache` both read it.
28    ///
29    /// `METRALE_GLM53_PREFIX_CACHE_UNPROVEN` (any value, `0` included) also opens it for
30    /// `glm5_next`. `kv_only_swap_out_is_safe` ignores that switch: the swap image cannot
31    /// carry the DSA rows (see there).
32    pub fn kv_only_prefix_cache_is_safe(&self) -> bool {
33        self.kv_only_prefix_cache_is_safe_with(Self::glm53_prefix_cache_validation_env())
34    }
35
36    /// 2026-09-26: Env-free core of [`Self::kv_only_prefix_cache_is_safe`]; the override opens
37    /// only `glm5_next` / `glm5_next_text`.
38    pub(crate) fn kv_only_prefix_cache_is_safe_with(&self, validation_override: bool) -> bool {
39        self.per_sequence_state_is_kv_complete()
40            || (validation_override
41                && matches!(self.model_type.as_str(), "glm5_next" | "glm5_next_text"))
42    }
43
44    fn glm53_prefix_cache_validation_env() -> bool {
45        std::env::var_os("METRALE_GLM53_PREFIX_CACHE_UNPROVEN").is_some()
46    }
47
48    /// 2026-09-26: Whether a sequence may be swapped out to the `--swap-space-gb` pool and
49    /// restored from it; `resolve_swap_space_gb` reads it. `save_sequence_state_dispatch`
50    /// writes the KV blocks and the `SsmLayerState` of each `LayerType::LinearAttention`
51    /// layer and nothing else, so any other per-sequence state would not survive the swap.
52    pub fn kv_only_swap_out_is_safe(&self) -> bool {
53        self.per_sequence_state_is_kv_complete()
54    }
55}