Skip to main content

ferrum_cli/
runtime_env.rs

1//! CLI runtime environment compatibility bridge.
2//!
3//! Product control-plane values should be represented as typed
4//! `RuntimeConfigEntry` values first. This module contains the small remaining
5//! bridge that materializes those entries into process env only for backend
6//! paths that have not yet been converted to typed config.
7
8use ferrum_types::{RuntimeConfigEntry, RuntimeConfigSnapshot, RuntimeConfigSource};
9
10pub fn push_cli_runtime_entry(
11    entries: &mut Vec<RuntimeConfigEntry>,
12    key: &str,
13    value: Option<&str>,
14) {
15    if let Some(value) = value.filter(|value| !value.trim().is_empty()) {
16        entries.push(RuntimeConfigEntry::new(
17            key,
18            value.to_string(),
19            RuntimeConfigSource::Cli,
20        ));
21    }
22}
23
24pub fn push_cli_runtime_usize(
25    entries: &mut Vec<RuntimeConfigEntry>,
26    key: &str,
27    value: Option<usize>,
28) {
29    if let Some(value) = value {
30        entries.push(RuntimeConfigEntry::new(
31            key,
32            value.to_string(),
33            RuntimeConfigSource::Cli,
34        ));
35    }
36}
37
38pub fn materialize_runtime_env_defaults(entries: &[RuntimeConfigEntry]) -> Vec<String> {
39    let mut materialized = Vec::new();
40    for entry in entries {
41        if std::env::var_os(&entry.key).is_none() {
42            std::env::set_var(&entry.key, &entry.effective_value);
43            materialized.push(entry.key.clone());
44        }
45    }
46    materialized
47}
48
49pub fn materialize_runtime_env_effective(snapshot: &RuntimeConfigSnapshot) -> Vec<String> {
50    let mut materialized = Vec::new();
51    for entry in &snapshot.entries {
52        if entry.source != RuntimeConfigSource::Env {
53            std::env::set_var(&entry.key, &entry.effective_value);
54            materialized.push(entry.key.clone());
55        }
56    }
57    materialized
58}
59
60/// Default-OFF MoE CUDA graph policy for Qwen3-MoE startup profiles.
61///
62/// The returned entries are only values absent from `current`. This preserves
63/// explicit env/config overrides and lets callers keep source attribution when
64/// they later merge a full startup snapshot.
65pub fn moe_graph_default_entries(
66    current: &RuntimeConfigSnapshot,
67    source: RuntimeConfigSource,
68) -> Vec<RuntimeConfigEntry> {
69    let mut entries = Vec::new();
70    let moe_graph_enabled = match runtime_snapshot_value(current, "FERRUM_MOE_GRAPH") {
71        Some(value) => value == "1",
72        None => {
73            entries.push(RuntimeConfigEntry::new("FERRUM_MOE_GRAPH", "0", source));
74            false
75        }
76    };
77
78    if !moe_graph_enabled {
79        return entries;
80    }
81
82    #[cfg(feature = "vllm-moe-marlin")]
83    {
84        if runtime_snapshot_value(current, "FERRUM_VLLM_MOE").is_none() {
85            entries.push(RuntimeConfigEntry::new("FERRUM_VLLM_MOE", "1", source));
86        }
87        if runtime_snapshot_value(current, "FERRUM_VLLM_MOE_PAIR_IDS").is_none() {
88            entries.push(RuntimeConfigEntry::new(
89                "FERRUM_VLLM_MOE_PAIR_IDS",
90                "1",
91                source,
92            ));
93        }
94    }
95
96    entries
97}
98
99pub fn warn_if_moe_graph_needs_unbuilt_vllm_moe(_snapshot: &RuntimeConfigSnapshot) {
100    #[cfg(not(feature = "vllm-moe-marlin"))]
101    if runtime_snapshot_value(_snapshot, "FERRUM_MOE_GRAPH") == Some("1")
102        && runtime_snapshot_value(_snapshot, "FERRUM_VLLM_MOE") != Some("1")
103    {
104        eprintln!(
105            "[auto-size] MOE_GRAPH=1 requested, but vllm-moe-marlin is not built; graph capture requires FERRUM_VLLM_MOE=1"
106        );
107    }
108}
109
110pub fn runtime_snapshot_value<'a>(
111    snapshot: &'a RuntimeConfigSnapshot,
112    key: &str,
113) -> Option<&'a str> {
114    snapshot
115        .entries
116        .iter()
117        .find(|entry| entry.key == key)
118        .map(|entry| entry.effective_value.as_str())
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    fn snapshot(entries: &[(&str, &str, RuntimeConfigSource)]) -> RuntimeConfigSnapshot {
126        RuntimeConfigSnapshot::from_entries(
127            entries
128                .iter()
129                .map(|(key, value, source)| RuntimeConfigEntry::new(*key, *value, *source)),
130        )
131    }
132
133    #[test]
134    fn moe_graph_defaults_add_missing_graph_default() {
135        let entries = moe_graph_default_entries(
136            &RuntimeConfigSnapshot::default(),
137            RuntimeConfigSource::Default,
138        );
139        let resolved = RuntimeConfigSnapshot::from_entries(entries);
140        let graph = runtime_snapshot_value(&resolved, "FERRUM_MOE_GRAPH");
141
142        assert_eq!(graph, Some("0"));
143        assert_eq!(resolved.entries.len(), 1);
144    }
145
146    #[test]
147    fn moe_graph_defaults_respect_forced_off_graph() {
148        let current = snapshot(&[("FERRUM_MOE_GRAPH", "0", RuntimeConfigSource::Env)]);
149        let entries = moe_graph_default_entries(&current, RuntimeConfigSource::Default);
150
151        assert!(entries.is_empty());
152    }
153
154    #[test]
155    fn moe_graph_defaults_complete_graph_enabled_snapshot() {
156        let current = snapshot(&[("FERRUM_MOE_GRAPH", "1", RuntimeConfigSource::ConfigFile)]);
157        let entries = moe_graph_default_entries(&current, RuntimeConfigSource::Default);
158        let resolved = RuntimeConfigSnapshot::from_entries(entries);
159
160        assert_eq!(runtime_snapshot_value(&resolved, "FERRUM_MOE_GRAPH"), None);
161        #[cfg(feature = "vllm-moe-marlin")]
162        assert_eq!(
163            runtime_snapshot_value(&resolved, "FERRUM_VLLM_MOE"),
164            Some("1")
165        );
166        #[cfg(not(feature = "vllm-moe-marlin"))]
167        assert!(resolved.entries.is_empty());
168    }
169}