1use serde::{Deserialize, Serialize};
8use std::ffi::OsString;
9use std::sync::RwLock;
10use std::{collections::BTreeMap, path::PathBuf};
11
12static ACTIVE_SNAPSHOT: RwLock<Option<RuntimeConfigSnapshot>> = RwLock::new(None);
22
23pub fn install_runtime_snapshot(snapshot: RuntimeConfigSnapshot) {
25 *ACTIVE_SNAPSHOT
26 .write()
27 .expect("runtime snapshot lock poisoned") = Some(snapshot);
28}
29
30pub fn active_runtime_snapshot() -> RuntimeConfigSnapshot {
33 ACTIVE_SNAPSHOT
34 .read()
35 .expect("runtime snapshot lock poisoned")
36 .clone()
37 .unwrap_or_default()
38}
39
40#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
42pub struct RuntimeConfigSnapshot {
43 pub entries: Vec<RuntimeConfigEntry>,
45}
46
47impl RuntimeConfigSnapshot {
48 pub fn capture_current() -> Self {
50 Self::from_os_env_vars(std::env::vars_os())
51 }
52
53 fn from_os_env_vars(vars: impl IntoIterator<Item = (OsString, OsString)>) -> Self {
54 Self::from_env_vars(vars.into_iter().filter_map(|(key, value)| {
55 let key = key.into_string().ok()?;
56 if !key.starts_with("FERRUM_") {
57 return None;
58 }
59 let value = value
62 .into_string()
63 .unwrap_or_else(|_| panic!("environment variable {key} must contain Unicode"));
64 Some((key, value))
65 }))
66 }
67
68 pub fn from_env_vars<I, K, V>(vars: I) -> Self
70 where
71 I: IntoIterator<Item = (K, V)>,
72 K: Into<String>,
73 V: Into<String>,
74 {
75 let mut sorted = BTreeMap::new();
76 for (key, value) in vars {
77 let key = key.into();
78 if key.starts_with("FERRUM_") {
79 sorted.insert(key, value.into());
80 }
81 }
82
83 Self {
84 entries: sorted
85 .into_iter()
86 .map(|(key, effective_value)| RuntimeConfigEntry {
87 affects: infer_effects(&key),
88 key,
89 effective_value,
90 source: RuntimeConfigSource::Env,
91 })
92 .collect(),
93 }
94 }
95
96 pub fn from_entries<I>(entries: I) -> Self
99 where
100 I: IntoIterator<Item = RuntimeConfigEntry>,
101 {
102 let mut sorted = BTreeMap::new();
103 for entry in entries {
104 sorted.insert(entry.key.clone(), entry);
105 }
106 Self {
107 entries: sorted.into_values().collect(),
108 }
109 }
110
111 pub fn upsert(
113 &mut self,
114 key: impl Into<String>,
115 effective_value: impl Into<String>,
116 source: RuntimeConfigSource,
117 ) {
118 self.upsert_entry(RuntimeConfigEntry::new(key, effective_value, source));
119 }
120
121 pub fn upsert_entry(&mut self, entry: RuntimeConfigEntry) {
123 let mut entries = std::mem::take(&mut self.entries);
124 entries.retain(|existing| existing.key != entry.key);
125 entries.push(entry);
126 *self = Self::from_entries(entries);
127 }
128
129 pub fn with_entry(
131 mut self,
132 key: impl Into<String>,
133 effective_value: impl Into<String>,
134 source: RuntimeConfigSource,
135 ) -> Self {
136 self.upsert(key, effective_value, source);
137 self
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
143pub struct RuntimeConfigEntry {
144 pub key: String,
145 pub effective_value: String,
146 pub source: RuntimeConfigSource,
147 pub affects: Vec<RuntimeConfigEffect>,
148}
149
150impl RuntimeConfigEntry {
151 pub fn new(
152 key: impl Into<String>,
153 effective_value: impl Into<String>,
154 source: RuntimeConfigSource,
155 ) -> Self {
156 let key = key.into();
157 Self {
158 affects: infer_effects(&key),
159 key,
160 effective_value: effective_value.into(),
161 source,
162 }
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
168#[serde(rename_all = "snake_case")]
169pub enum RuntimeConfigSource {
170 Default,
171 ConfigFile,
172 Cli,
173 Env,
174 ScriptCase,
175 MemoryProfile,
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
180#[serde(rename_all = "snake_case")]
181pub enum RuntimeConfigEffect {
182 Correctness,
183 Performance,
184 Memory,
185 Diagnostics,
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
190#[serde(rename_all = "snake_case")]
191pub enum EnvTriState {
192 Default,
193 ForcedOff,
194 ForcedOn,
195}
196
197pub fn parse_bool_env_value(raw: &str) -> Result<bool, String> {
198 match raw.trim().to_ascii_lowercase().as_str() {
199 "1" | "true" | "yes" | "on" => Ok(true),
200 "0" | "false" | "no" | "off" => Ok(false),
201 other => Err(format!("invalid boolean env value: {other:?}")),
202 }
203}
204
205pub fn parse_usize_env_value(raw: &str) -> Result<usize, String> {
206 raw.trim()
207 .parse::<usize>()
208 .map_err(|_| format!("invalid integer env value: {raw:?}"))
209}
210
211pub fn parse_path_env_value(raw: &str) -> Result<PathBuf, String> {
212 let trimmed = raw.trim();
213 if trimmed.is_empty() {
214 return Err("path env value must not be empty".to_string());
215 }
216 Ok(PathBuf::from(trimmed))
217}
218
219pub fn parse_tri_state_env_value(raw: Option<&str>) -> Result<EnvTriState, String> {
220 let Some(raw) = raw else {
221 return Ok(EnvTriState::Default);
222 };
223 if raw.trim().is_empty() {
224 return Ok(EnvTriState::Default);
225 }
226 Ok(if parse_bool_env_value(raw)? {
227 EnvTriState::ForcedOn
228 } else {
229 EnvTriState::ForcedOff
230 })
231}
232
233fn infer_effects(key: &str) -> Vec<RuntimeConfigEffect> {
234 let mut effects = Vec::new();
235
236 if key.contains("DIAG")
237 || key.contains("PROF")
238 || key.contains("TRACE")
239 || key.contains("DUMP")
240 || key.contains("LOG_CONFIG")
241 || key.contains("CAPTURE")
242 || key.contains("DEBUG")
243 {
244 effects.push(RuntimeConfigEffect::Diagnostics);
245 }
246
247 if key.contains("KV")
248 || key.contains("BATCHED_TOKENS")
249 || key.contains("PAGED_MAX_SEQS")
250 || key.contains("MODEL_LEN")
251 || key.contains("FIT_POLICY")
252 || key.contains("STATE_MAX_SLOTS")
253 || key.contains("REUSABLE_EXECUTION")
254 || key.contains("MEMORY")
255 {
256 effects.push(RuntimeConfigEffect::Memory);
257 }
258
259 if key.contains("PREFIX_CACHE")
260 || key.contains("MODEL_PATH")
261 || key.contains("MODEL_LEN")
262 || key.contains("FIT_POLICY")
263 || key.contains("RUNTIME_MEMORY_BUDGET")
264 || key.contains("NATIVE")
265 || key.contains("ARTIFACT")
266 || key.contains("SPEC_")
267 || key.contains("REF_")
268 || key.contains("DTYPE")
269 || key.contains("ATTENTION_POLICY")
270 || key.contains("REUSABLE_EXECUTION")
271 {
272 effects.push(RuntimeConfigEffect::Correctness);
273 }
274
275 if effects.is_empty()
276 || key.contains("MOE")
277 || key.contains("VLLM")
278 || key.contains("MARLIN")
279 || key.contains("PAGED")
280 || key.contains("GRAPH")
281 || key.contains("SCHED")
282 || key.contains("BATCH")
283 || key.contains("ATTN")
284 || key.contains("ATTENTION_POLICY")
285 || key.contains("FLASH")
286 || key.contains("CUDA")
287 || key.contains("TRITON")
288 || key.contains("GREEDY")
289 || key.contains("REUSABLE_EXECUTION")
290 || key.contains("FA")
291 {
292 effects.push(RuntimeConfigEffect::Performance);
293 }
294
295 effects.sort();
296 effects.dedup();
297 effects
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn parses_boolean_values() {
306 assert_eq!(parse_bool_env_value("1").unwrap(), true);
307 assert_eq!(parse_bool_env_value("off").unwrap(), false);
308 assert!(parse_bool_env_value("maybe").is_err());
309 }
310
311 #[test]
312 fn parses_integer_values() {
313 assert_eq!(parse_usize_env_value("4096").unwrap(), 4096);
314 assert!(parse_usize_env_value("-1").is_err());
315 assert!(parse_usize_env_value("many").is_err());
316 }
317
318 #[test]
319 fn parses_path_values() {
320 assert_eq!(
321 parse_path_env_value("/tmp/model").unwrap(),
322 PathBuf::from("/tmp/model")
323 );
324 assert!(parse_path_env_value(" ").is_err());
325 }
326
327 #[test]
328 fn parses_tri_state_values() {
329 assert_eq!(
330 parse_tri_state_env_value(None).unwrap(),
331 EnvTriState::Default
332 );
333 assert_eq!(
334 parse_tri_state_env_value(Some("0")).unwrap(),
335 EnvTriState::ForcedOff
336 );
337 assert_eq!(
338 parse_tri_state_env_value(Some("on")).unwrap(),
339 EnvTriState::ForcedOn
340 );
341 assert!(parse_tri_state_env_value(Some("auto")).is_err());
342 }
343
344 #[test]
345 fn attention_policy_is_a_correctness_and_performance_input() {
346 let snapshot =
347 RuntimeConfigSnapshot::from_env_vars([("FERRUM_ATTENTION_POLICY", "native-adaptive")]);
348 let entry = snapshot.entries.first().expect("attention policy entry");
349 assert!(entry.affects.contains(&RuntimeConfigEffect::Correctness));
350 assert!(entry.affects.contains(&RuntimeConfigEffect::Performance));
351 }
352
353 #[cfg(unix)]
354 #[test]
355 fn snapshot_ignores_unrelated_non_unicode_environment() {
356 use std::os::unix::ffi::OsStringExt;
357
358 let snapshot = RuntimeConfigSnapshot::from_os_env_vars([
359 (
360 OsString::from("PATH"),
361 OsString::from_vec(b"/bin:\xff".to_vec()),
362 ),
363 (
364 OsString::from_vec(b"OTHER_\xff".to_vec()),
365 OsString::from("ignored"),
366 ),
367 (
368 OsString::from("FERRUM_KV_MAX_BLOCKS"),
369 OsString::from(" 4096 "),
370 ),
371 (
372 OsString::from("FERRUM_ATTENTION_POLICY"),
373 OsString::from("native-adaptive"),
374 ),
375 ]);
376
377 assert_eq!(
378 snapshot,
379 RuntimeConfigSnapshot::from_env_vars([
380 ("FERRUM_ATTENTION_POLICY", "native-adaptive"),
381 ("FERRUM_KV_MAX_BLOCKS", " 4096 "),
382 ])
383 );
384 }
385
386 #[test]
387 fn os_environment_preserves_invalid_override_for_typed_validation() {
388 let snapshot = RuntimeConfigSnapshot::from_os_env_vars([(
389 OsString::from("FERRUM_KV_MAX_BLOCKS"),
390 OsString::from("many"),
391 )]);
392 assert_eq!(snapshot.entries[0].effective_value, "many");
393 let error = crate::EngineConfig::default()
394 .apply_runtime_config_snapshot(&snapshot)
395 .unwrap_err();
396 assert!(error.contains("FERRUM_KV_MAX_BLOCKS"));
397 }
398
399 #[cfg(unix)]
400 #[test]
401 #[should_panic(expected = "environment variable FERRUM_KV_MAX_BLOCKS must contain Unicode")]
402 fn snapshot_rejects_non_unicode_ferrum_override() {
403 use std::os::unix::ffi::OsStringExt;
404
405 RuntimeConfigSnapshot::from_os_env_vars([(
406 OsString::from("FERRUM_KV_MAX_BLOCKS"),
407 OsString::from_vec(vec![0xff]),
408 )]);
409 }
410
411 #[test]
412 fn snapshot_is_sorted_and_classified() {
413 let snapshot = RuntimeConfigSnapshot::from_env_vars([
414 ("OTHER_ENV", "ignored"),
415 ("FERRUM_FA2_NATIVE_ARTIFACT", "/tmp/libferrum_native_fa2.a"),
416 ("FERRUM_PREFIX_CACHE", "1"),
417 ("FERRUM_MOE_GRAPH", "1"),
418 ("FERRUM_REUSABLE_EXECUTION", "1"),
419 ]);
420 let keys: Vec<_> = snapshot
421 .entries
422 .iter()
423 .map(|entry| entry.key.as_str())
424 .collect();
425 assert_eq!(
426 keys,
427 vec![
428 "FERRUM_FA2_NATIVE_ARTIFACT",
429 "FERRUM_MOE_GRAPH",
430 "FERRUM_PREFIX_CACHE",
431 "FERRUM_REUSABLE_EXECUTION"
432 ]
433 );
434 assert_eq!(snapshot.entries[0].source, RuntimeConfigSource::Env);
435 assert!(snapshot.entries[0]
436 .affects
437 .contains(&RuntimeConfigEffect::Correctness));
438 assert!(snapshot.entries[0]
439 .affects
440 .contains(&RuntimeConfigEffect::Performance));
441 assert!(snapshot.entries[1]
442 .affects
443 .contains(&RuntimeConfigEffect::Performance));
444 assert!(snapshot.entries[2]
445 .affects
446 .contains(&RuntimeConfigEffect::Correctness));
447 for effect in [
448 RuntimeConfigEffect::Correctness,
449 RuntimeConfigEffect::Performance,
450 RuntimeConfigEffect::Memory,
451 ] {
452 assert!(snapshot.entries[3].affects.contains(&effect));
453 }
454 }
455
456 #[test]
457 fn upsert_preserves_non_env_source_and_stable_order() {
458 let mut snapshot = RuntimeConfigSnapshot::from_env_vars([
459 ("FERRUM_KV_DTYPE", "fp16"),
460 ("FERRUM_MOE_GRAPH", "1"),
461 ]);
462 snapshot.upsert("FERRUM_KV_DTYPE", "int8", RuntimeConfigSource::Cli);
463 snapshot.upsert(
464 "FERRUM_PROFILE_JSONL",
465 "/tmp/profile.jsonl",
466 RuntimeConfigSource::Cli,
467 );
468
469 let keys: Vec<_> = snapshot
470 .entries
471 .iter()
472 .map(|entry| entry.key.as_str())
473 .collect();
474 assert_eq!(
475 keys,
476 [
477 "FERRUM_KV_DTYPE",
478 "FERRUM_MOE_GRAPH",
479 "FERRUM_PROFILE_JSONL"
480 ]
481 );
482 let kv = snapshot
483 .entries
484 .iter()
485 .find(|entry| entry.key == "FERRUM_KV_DTYPE")
486 .unwrap();
487 assert_eq!(kv.effective_value, "int8");
488 assert_eq!(kv.source, RuntimeConfigSource::Cli);
489 assert!(kv.affects.contains(&RuntimeConfigEffect::Correctness));
490
491 let profile = snapshot
492 .entries
493 .iter()
494 .find(|entry| entry.key == "FERRUM_PROFILE_JSONL")
495 .unwrap();
496 assert_eq!(profile.source, RuntimeConfigSource::Cli);
497 assert!(profile.affects.contains(&RuntimeConfigEffect::Diagnostics));
498 }
499}