Skip to main content

ferrum_types/
runtime_config.rs

1//! Runtime configuration snapshot and small env parsing helpers.
2//!
3//! This is intentionally a narrow data surface first: it makes effective
4//! `FERRUM_*` overrides visible in health and bench artifacts while the
5//! hot-path env reads are migrated to typed config structs.
6
7use serde::{Deserialize, Serialize};
8use std::sync::RwLock;
9use std::{collections::BTreeMap, path::PathBuf};
10
11/// Process-wide runtime snapshot, installed once at the composition root.
12///
13/// This is the single env-bridge seam the test-architecture goal asks for:
14/// the CLI (`serve`/`run`/`bench`) captures `FERRUM_*` via
15/// [`RuntimeConfigSnapshot::capture_current`] and installs it here when it
16/// applies the snapshot to the engine config; model code downstream reads
17/// [`active_runtime_snapshot`] instead of `std::env`, so no model/engine
18/// module freezes its own env config. Re-installable (RwLock, not OnceLock)
19/// so per-construction test paths can vary it after `std::env::set_var`.
20static ACTIVE_SNAPSHOT: RwLock<Option<RuntimeConfigSnapshot>> = RwLock::new(None);
21
22/// Install the process-wide runtime snapshot resolved at the composition root.
23pub fn install_runtime_snapshot(snapshot: RuntimeConfigSnapshot) {
24    *ACTIVE_SNAPSHOT
25        .write()
26        .expect("runtime snapshot lock poisoned") = Some(snapshot);
27}
28
29/// The installed runtime snapshot, or an empty snapshot when none was
30/// installed (unit tests that do not exercise runtime knobs see defaults).
31pub fn active_runtime_snapshot() -> RuntimeConfigSnapshot {
32    ACTIVE_SNAPSHOT
33        .read()
34        .expect("runtime snapshot lock poisoned")
35        .clone()
36        .unwrap_or_default()
37}
38
39/// Stable snapshot of non-default runtime configuration visible to the process.
40#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
41pub struct RuntimeConfigSnapshot {
42    /// Sorted by key for stable JSON and machine-readable diffs.
43    pub entries: Vec<RuntimeConfigEntry>,
44}
45
46impl RuntimeConfigSnapshot {
47    /// Capture all currently set `FERRUM_*` env overrides.
48    pub fn capture_current() -> Self {
49        Self::from_env_vars(std::env::vars())
50    }
51
52    /// Build a snapshot from a supplied environment map or iterator.
53    pub fn from_env_vars<I, K, V>(vars: I) -> Self
54    where
55        I: IntoIterator<Item = (K, V)>,
56        K: Into<String>,
57        V: Into<String>,
58    {
59        let mut sorted = BTreeMap::new();
60        for (key, value) in vars {
61            let key = key.into();
62            if key.starts_with("FERRUM_") {
63                sorted.insert(key, value.into());
64            }
65        }
66
67        Self {
68            entries: sorted
69                .into_iter()
70                .map(|(key, effective_value)| RuntimeConfigEntry {
71                    affects: infer_effects(&key),
72                    key,
73                    effective_value,
74                    source: RuntimeConfigSource::Env,
75                })
76                .collect(),
77        }
78    }
79
80    /// Build a stable snapshot from explicit entries. Later entries for the
81    /// same key replace earlier entries.
82    pub fn from_entries<I>(entries: I) -> Self
83    where
84        I: IntoIterator<Item = RuntimeConfigEntry>,
85    {
86        let mut sorted = BTreeMap::new();
87        for entry in entries {
88            sorted.insert(entry.key.clone(), entry);
89        }
90        Self {
91            entries: sorted.into_values().collect(),
92        }
93    }
94
95    /// Insert or replace one effective value, preserving stable key order.
96    pub fn upsert(
97        &mut self,
98        key: impl Into<String>,
99        effective_value: impl Into<String>,
100        source: RuntimeConfigSource,
101    ) {
102        self.upsert_entry(RuntimeConfigEntry::new(key, effective_value, source));
103    }
104
105    /// Insert or replace one explicit entry, preserving stable key order.
106    pub fn upsert_entry(&mut self, entry: RuntimeConfigEntry) {
107        let mut entries = std::mem::take(&mut self.entries);
108        entries.retain(|existing| existing.key != entry.key);
109        entries.push(entry);
110        *self = Self::from_entries(entries);
111    }
112
113    /// Return a snapshot with one additional effective value.
114    pub fn with_entry(
115        mut self,
116        key: impl Into<String>,
117        effective_value: impl Into<String>,
118        source: RuntimeConfigSource,
119    ) -> Self {
120        self.upsert(key, effective_value, source);
121        self
122    }
123}
124
125/// One effective config value in a runtime snapshot.
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127pub struct RuntimeConfigEntry {
128    pub key: String,
129    pub effective_value: String,
130    pub source: RuntimeConfigSource,
131    pub affects: Vec<RuntimeConfigEffect>,
132}
133
134impl RuntimeConfigEntry {
135    pub fn new(
136        key: impl Into<String>,
137        effective_value: impl Into<String>,
138        source: RuntimeConfigSource,
139    ) -> Self {
140        let key = key.into();
141        Self {
142            affects: infer_effects(&key),
143            key,
144            effective_value: effective_value.into(),
145            source,
146        }
147    }
148}
149
150/// Source of an effective config value.
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(rename_all = "snake_case")]
153pub enum RuntimeConfigSource {
154    Default,
155    ConfigFile,
156    Cli,
157    Env,
158    ScriptCase,
159    MemoryProfile,
160}
161
162/// Impact classes used by config snapshots and artifact diffs.
163#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
164#[serde(rename_all = "snake_case")]
165pub enum RuntimeConfigEffect {
166    Correctness,
167    Performance,
168    Memory,
169    Diagnostics,
170}
171
172/// Tri-state env override used by paths that distinguish unset from forced off.
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
174#[serde(rename_all = "snake_case")]
175pub enum EnvTriState {
176    Default,
177    ForcedOff,
178    ForcedOn,
179}
180
181pub fn parse_bool_env_value(raw: &str) -> Result<bool, String> {
182    match raw.trim().to_ascii_lowercase().as_str() {
183        "1" | "true" | "yes" | "on" => Ok(true),
184        "0" | "false" | "no" | "off" => Ok(false),
185        other => Err(format!("invalid boolean env value: {other:?}")),
186    }
187}
188
189pub fn parse_usize_env_value(raw: &str) -> Result<usize, String> {
190    raw.trim()
191        .parse::<usize>()
192        .map_err(|_| format!("invalid integer env value: {raw:?}"))
193}
194
195pub fn parse_path_env_value(raw: &str) -> Result<PathBuf, String> {
196    let trimmed = raw.trim();
197    if trimmed.is_empty() {
198        return Err("path env value must not be empty".to_string());
199    }
200    Ok(PathBuf::from(trimmed))
201}
202
203pub fn parse_tri_state_env_value(raw: Option<&str>) -> Result<EnvTriState, String> {
204    let Some(raw) = raw else {
205        return Ok(EnvTriState::Default);
206    };
207    if raw.trim().is_empty() {
208        return Ok(EnvTriState::Default);
209    }
210    Ok(if parse_bool_env_value(raw)? {
211        EnvTriState::ForcedOn
212    } else {
213        EnvTriState::ForcedOff
214    })
215}
216
217fn infer_effects(key: &str) -> Vec<RuntimeConfigEffect> {
218    let mut effects = Vec::new();
219
220    if key.contains("DIAG")
221        || key.contains("PROF")
222        || key.contains("TRACE")
223        || key.contains("DUMP")
224        || key.contains("LOG_CONFIG")
225        || key.contains("CAPTURE")
226        || key.contains("DEBUG")
227    {
228        effects.push(RuntimeConfigEffect::Diagnostics);
229    }
230
231    if key.contains("KV")
232        || key.contains("BATCHED_TOKENS")
233        || key.contains("PAGED_MAX_SEQS")
234        || key.contains("MODEL_LEN")
235        || key.contains("FIT_POLICY")
236        || key.contains("STATE_MAX_SLOTS")
237        || key.contains("REUSABLE_EXECUTION")
238        || key.contains("MEMORY")
239    {
240        effects.push(RuntimeConfigEffect::Memory);
241    }
242
243    if key.contains("PREFIX_CACHE")
244        || key.contains("MODEL_PATH")
245        || key.contains("MODEL_LEN")
246        || key.contains("FIT_POLICY")
247        || key.contains("RUNTIME_MEMORY_BUDGET")
248        || key.contains("NATIVE")
249        || key.contains("ARTIFACT")
250        || key.contains("SPEC_")
251        || key.contains("REF_")
252        || key.contains("DTYPE")
253        || key.contains("ATTENTION_POLICY")
254        || key.contains("REUSABLE_EXECUTION")
255    {
256        effects.push(RuntimeConfigEffect::Correctness);
257    }
258
259    if effects.is_empty()
260        || key.contains("MOE")
261        || key.contains("VLLM")
262        || key.contains("MARLIN")
263        || key.contains("PAGED")
264        || key.contains("GRAPH")
265        || key.contains("SCHED")
266        || key.contains("BATCH")
267        || key.contains("ATTN")
268        || key.contains("ATTENTION_POLICY")
269        || key.contains("FLASH")
270        || key.contains("CUDA")
271        || key.contains("TRITON")
272        || key.contains("GREEDY")
273        || key.contains("REUSABLE_EXECUTION")
274        || key.contains("FA")
275    {
276        effects.push(RuntimeConfigEffect::Performance);
277    }
278
279    effects.sort();
280    effects.dedup();
281    effects
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    #[test]
289    fn parses_boolean_values() {
290        assert_eq!(parse_bool_env_value("1").unwrap(), true);
291        assert_eq!(parse_bool_env_value("off").unwrap(), false);
292        assert!(parse_bool_env_value("maybe").is_err());
293    }
294
295    #[test]
296    fn parses_integer_values() {
297        assert_eq!(parse_usize_env_value("4096").unwrap(), 4096);
298        assert!(parse_usize_env_value("-1").is_err());
299        assert!(parse_usize_env_value("many").is_err());
300    }
301
302    #[test]
303    fn parses_path_values() {
304        assert_eq!(
305            parse_path_env_value("/tmp/model").unwrap(),
306            PathBuf::from("/tmp/model")
307        );
308        assert!(parse_path_env_value("   ").is_err());
309    }
310
311    #[test]
312    fn parses_tri_state_values() {
313        assert_eq!(
314            parse_tri_state_env_value(None).unwrap(),
315            EnvTriState::Default
316        );
317        assert_eq!(
318            parse_tri_state_env_value(Some("0")).unwrap(),
319            EnvTriState::ForcedOff
320        );
321        assert_eq!(
322            parse_tri_state_env_value(Some("on")).unwrap(),
323            EnvTriState::ForcedOn
324        );
325        assert!(parse_tri_state_env_value(Some("auto")).is_err());
326    }
327
328    #[test]
329    fn attention_policy_is_a_correctness_and_performance_input() {
330        let snapshot =
331            RuntimeConfigSnapshot::from_env_vars([("FERRUM_ATTENTION_POLICY", "native-adaptive")]);
332        let entry = snapshot.entries.first().expect("attention policy entry");
333        assert!(entry.affects.contains(&RuntimeConfigEffect::Correctness));
334        assert!(entry.affects.contains(&RuntimeConfigEffect::Performance));
335    }
336
337    #[test]
338    fn snapshot_is_sorted_and_classified() {
339        let snapshot = RuntimeConfigSnapshot::from_env_vars([
340            ("OTHER_ENV", "ignored"),
341            ("FERRUM_FA2_NATIVE_ARTIFACT", "/tmp/libferrum_native_fa2.a"),
342            ("FERRUM_PREFIX_CACHE", "1"),
343            ("FERRUM_MOE_GRAPH", "1"),
344            ("FERRUM_REUSABLE_EXECUTION", "1"),
345        ]);
346        let keys: Vec<_> = snapshot
347            .entries
348            .iter()
349            .map(|entry| entry.key.as_str())
350            .collect();
351        assert_eq!(
352            keys,
353            vec![
354                "FERRUM_FA2_NATIVE_ARTIFACT",
355                "FERRUM_MOE_GRAPH",
356                "FERRUM_PREFIX_CACHE",
357                "FERRUM_REUSABLE_EXECUTION"
358            ]
359        );
360        assert_eq!(snapshot.entries[0].source, RuntimeConfigSource::Env);
361        assert!(snapshot.entries[0]
362            .affects
363            .contains(&RuntimeConfigEffect::Correctness));
364        assert!(snapshot.entries[0]
365            .affects
366            .contains(&RuntimeConfigEffect::Performance));
367        assert!(snapshot.entries[1]
368            .affects
369            .contains(&RuntimeConfigEffect::Performance));
370        assert!(snapshot.entries[2]
371            .affects
372            .contains(&RuntimeConfigEffect::Correctness));
373        for effect in [
374            RuntimeConfigEffect::Correctness,
375            RuntimeConfigEffect::Performance,
376            RuntimeConfigEffect::Memory,
377        ] {
378            assert!(snapshot.entries[3].affects.contains(&effect));
379        }
380    }
381
382    #[test]
383    fn upsert_preserves_non_env_source_and_stable_order() {
384        let mut snapshot = RuntimeConfigSnapshot::from_env_vars([
385            ("FERRUM_KV_DTYPE", "fp16"),
386            ("FERRUM_MOE_GRAPH", "1"),
387        ]);
388        snapshot.upsert("FERRUM_KV_DTYPE", "int8", RuntimeConfigSource::Cli);
389        snapshot.upsert(
390            "FERRUM_PROFILE_JSONL",
391            "/tmp/profile.jsonl",
392            RuntimeConfigSource::Cli,
393        );
394
395        let keys: Vec<_> = snapshot
396            .entries
397            .iter()
398            .map(|entry| entry.key.as_str())
399            .collect();
400        assert_eq!(
401            keys,
402            [
403                "FERRUM_KV_DTYPE",
404                "FERRUM_MOE_GRAPH",
405                "FERRUM_PROFILE_JSONL"
406            ]
407        );
408        let kv = snapshot
409            .entries
410            .iter()
411            .find(|entry| entry.key == "FERRUM_KV_DTYPE")
412            .unwrap();
413        assert_eq!(kv.effective_value, "int8");
414        assert_eq!(kv.source, RuntimeConfigSource::Cli);
415        assert!(kv.affects.contains(&RuntimeConfigEffect::Correctness));
416
417        let profile = snapshot
418            .entries
419            .iter()
420            .find(|entry| entry.key == "FERRUM_PROFILE_JSONL")
421            .unwrap();
422        assert_eq!(profile.source, RuntimeConfigSource::Cli);
423        assert!(profile.affects.contains(&RuntimeConfigEffect::Diagnostics));
424    }
425}