Files
compute/src/config.rs
T
Karti Tripathi a4490ec80e
ci / rust (push) Successful in 2m26s
Lumbridge Compute
Governed compute for unified-memory AI hardware — machines where CPU and GPU
share one pool and there is no separate VRAM allocation to bounce off.
Over-commit that pool and the box thrashes and wedges, SSH and ping included,
before the OOM killer gets a turn.

Compute does not run inference. It supervises the servers that do: admission
control against both a declared budget and what the machine actually has free,
a 1 Hz watchdog that stops the newest model before thrash, Scenes activated as
one transactional unit with rollback, process ownership bound to
(boot_id, pid, start_time_ticks, pgid) so a reused PID can never be
group-killed, a protocol-transparent gateway, an MCP server, and a read-only
HTTP API for dashboards.

Registry footprints in this release are measured on a live node rather than
estimated.

One binary, six direct dependencies. Apache-2.0.

Generated by scripts/publish-compute.sh, which refuses to publish a tree it
cannot prove clean.
2026-08-03 22:23:56 -07:00

152 lines
5.1 KiB
Rust

//! Config types + loaders for the model registry and scenes.
//!
//! The registry (`registry/models.yaml`) resolves stable model *ids* to weights +
//! launch commands, and evolves as models requantize. Scenes (`scenes/*.scene.yaml`)
//! reference those ids and stay stable — the public, shareable contract.
//!
//! Several fields below are deserialized but never read by the binary
//! (`api_version`, `channel`, `entry`, `app`, `app_dir`, `tags`, `author`).
//! That is deliberate: they are the published `lumbridge/v1` manifest surface,
//! and declaring them is what makes a manifest carrying them parse rather than
//! fail. Deleting them to satisfy the lint would silently narrow the contract.
#![allow(dead_code)]
use anyhow::{Context, Result};
use serde::Deserialize;
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;
#[derive(Debug, Deserialize)]
pub struct Registry {
#[serde(rename = "apiVersion")]
pub api_version: String,
pub models: BTreeMap<String, Model>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Model {
pub name: String,
/// Worst-case unified memory held once serving (weights + KV/cache + encoder).
pub footprint_gb: f64,
/// `stable` pins the exact weights; `latest` may resolve a newer quant.
#[serde(default)]
pub channel: Option<String>,
#[serde(default)]
pub health: Option<String>,
/// Optional marker that must appear in the health response. This
/// distinguishes mutually-exclusive models that intentionally share a port.
#[serde(default)]
pub health_contains: Option<String>,
pub serve: Serve,
}
/// How to launch a model. Fields are permissive across runtimes (vllm / diffusers /
/// python / uvicorn); only the ones a given `kind` needs are populated.
#[derive(Debug, Clone, Deserialize)]
pub struct Serve {
pub kind: String,
/// Optional runtime executable for kind-based builders (for example a
/// model-specific vLLM virtualenv). Defaults to the kind's command on PATH.
#[serde(default)]
pub executable: Option<String>,
#[serde(default)]
pub port: Option<u16>,
/// Explicit launch argv. If set, it is used verbatim (argv[0] = program) and
/// takes precedence over any `kind`-based builder. This is where launch commands
/// live — never in a scene — so downloaded scenes can't smuggle code.
#[serde(default)]
pub command: Vec<String>,
#[serde(default)]
pub weights: Option<String>,
#[serde(default)]
pub entry: Option<String>,
#[serde(default)]
pub app: Option<String>,
#[serde(default)]
pub app_dir: Option<String>,
#[serde(default)]
pub served_name: Vec<String>,
#[serde(default)]
pub args: BTreeMap<String, serde_yaml::Value>,
#[serde(default)]
pub env: BTreeMap<String, String>,
}
#[derive(Debug, Deserialize)]
pub struct Scene {
#[serde(rename = "apiVersion")]
pub api_version: String,
pub metadata: SceneMeta,
/// Stable model ids resolved via the registry.
pub models: Vec<String>,
/// Optional per-scene budget override (GB); defaults to the Governor's global budget.
#[serde(default)]
pub budget_gb: Option<f64>,
#[serde(default)]
pub activation: Option<Activation>,
}
#[derive(Debug, Deserialize)]
pub struct SceneMeta {
pub name: String,
#[serde(default)]
pub version: u32,
#[serde(default)]
pub description: String,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub author: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct Activation {
/// `footprint-asc` (default) | `listed`.
#[serde(default)]
pub order: Option<String>,
#[serde(default)]
pub wait_healthy: Option<bool>,
}
impl Registry {
pub fn load(root: &Path) -> Result<Registry> {
let p = root.join("registry/models.yaml");
let s =
fs::read_to_string(&p).with_context(|| format!("reading registry {}", p.display()))?;
serde_yaml::from_str(&s).with_context(|| format!("parsing registry {}", p.display()))
}
}
/// Load every `*.scene.yaml` under `<root>/scenes`, sorted by name.
pub fn load_scenes(root: &Path) -> Result<Vec<Scene>> {
let dir = root.join("scenes");
let mut out = Vec::new();
if !dir.exists() {
return Ok(out);
}
for entry in fs::read_dir(&dir).with_context(|| format!("reading {}", dir.display()))? {
let path = entry?.path();
let is_scene = path
.file_name()
.and_then(|n| n.to_str())
.map(|n| n.ends_with(".scene.yaml"))
.unwrap_or(false);
if is_scene {
let s = fs::read_to_string(&path)?;
let scene: Scene =
serde_yaml::from_str(&s).with_context(|| format!("parsing {}", path.display()))?;
out.push(scene);
}
}
out.sort_by(|a, b| a.metadata.name.cmp(&b.metadata.name));
Ok(out)
}
pub fn find_scene(root: &Path, name: &str) -> Result<Scene> {
load_scenes(root)?
.into_iter()
.find(|s| s.metadata.name == name)
.with_context(|| format!("no scene named '{name}' in {}/scenes", root.display()))
}