A profile is a role such as chat, worker or
comfy. A target is a place it can run. A profile declares
one variant per target, each with its own command line, environment
and VRAM estimate, because the real unit of difference is not a config flag: the CUDA
and ROCm builds of llama.cpp are different binaries, and the two ComfyUI portables are
different folders on disk. Swapping a card is therefore not a special case, it is just
picking a different variant.
The same idea carries a third kind of target. vulkan is not a card, it is
a span across both of them, used when one model is too large for either. It owns no
memory of its own, so the arbiter admits it against each physical card it touches,
which means a span request can evict on two cards at once and is correctly refused if
either one is full.
Admission runs on declared estimates rather than measurements, because
the decision has to be made before the process exists, when there is nothing to measure
yet. The estimate is a worst case footprint that
includes model residency, the KV allocation for the full accepted context, the parallel
slot multiplier, and an MTP reserve where the model supports it. Measured peaks are
still recorded, but only so the dashboard can show you the gap between what a profile
reserved and what it actually used. Telemetry never feeds an admission decision, so a
card whose driver reports nothing useful still gets supervised correctly.
When a card is full, Warden tries eviction first, and only considers profiles that are
unleased, unpinned and strictly lower priority. If that is not enough it returns
409 with the reason: which profiles are holding the memory, why each
one could not be moved, and which other target could serve the request. It never queues
silently, because a queue turns a capacity problem into a latency mystery, and the
caller is the only party that knows whether waiting is acceptable.
# warden.yaml, abbreviated
profiles:
chat:
kind: llm
priority: 100
default_device: cuda0
default_context: 32768
mtp: true
health: {type: http, path: /v1/models}
# one entry per model, priced per target
models:
- label: Qwen3.6 35B-A3B
supports_mtp: true
hip0: {arg: "...Q5_K_M.gguf", vram_mb: 26000}
cuda0: {arg: qwen_qwen3.6-35b-a3b, vram_mb: 27000}
vulkan: {arg: qwen_qwen3.6-35b-a3b,
split: {cuda0: 14000, hip0: 14000}}
# one launch recipe per target
variants:
cuda0: # LM Studio shim, CUDA engine
port: 1234
cmd: [py, -3.11, -m, warden.lmstudio, ...]
hip0: # ROCm llama.cpp, its own binary
port: 1235
cwd: E:/llama-rocm-lemonade/bin
cmd: [llama-server.exe, -m, "{model}", ...]
The same profile, two vendors, one price list. footprint() reads this and
returns megabytes per physical card, which is the only number the arbiter trusts.