Deployed against real llama.cpp, every scrape failed. The mapping named
llamacpp:kv_cache_usage_ratio; build b10991 serves fifteen metric families
and none is a cache ratio. Because KV usage was mandatory, that one absent
name threw away the two metrics the engine does publish, and the router
scheduled to all three replicas blind while the cluster looked healthy.
A mapping that leaves a metric unnamed now means the engine does not publish
it, and the field stays zero. Naming one the engine does not serve is still a
violation, so this does not become a way to tolerate a typo. Running and
waiting can never be omitted: an engine that reports neither what it is doing
nor what is queued cannot be scheduled against at all. For llama.cpp this
costs the KV half of the saturation signal, which is a real loss softened by
its fixed slot count — queue depth already says when it is full.
The second half is why it took a deployment to notice. A contract violation
was logged per scrape at debug level, so at the default level nothing said
anything. It is now a warning, once, with a matching line when it clears.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
llama.cpp is what makes a cluster with no cards useful: it serves the same
OpenAI API on CPU, so the whole path — placement, supervision, scraping,
scheduling, admission — can be exercised end to end on ordinary machines.
Three things had to give way for it.
A profile may now omit its GPU class, and that is how it says it runs on the
CPU. Only engines that can actually serve without a card may omit it; for
vLLM an omitted class stays an error, because otherwise a typo becomes a
replica placed on a machine with no hardware and diagnosed minutes later
during model loading. Placement spreads CPU replicas instead of bin packing
them, since unlike GPUs there is no allocation to stop two of them sharing a
machine's cores.
Metric mappings are now per engine rather than per cluster. llama.cpp calls
the same three numbers requests_processing, requests_deferred and
kv_cache_usage_ratio, and a single global mapping would leave half a mixed
fleet unmeasurable. An empty mapping still defaults to vLLM's names; one that
names its own Running metric is taken as a complete statement, so the fields
it leaves blank stay blank rather than being filled with names that engine
cannot have.
The adapter refuses multi-node and tensor-parallel profiles. llama.cpp splits
across local GPUs by a different mechanism than this adapter drives, and
accepting either would produce a replica that starts and then serves from one
machine while the catalog believes it has several.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The code behind plan §7.3-7.6:
- breaker: folds cooldown and circuit breaking into one component
(FailureThreshold=1 gives LiteLLM-style cooldown), with exponential backoff
and half-open probes.
- endpoint: live state registry. Inflight counting, staleness detection, drain
rate derived from counters. A scrape failure does not trip the breaker — the
engine may be serving fine while only its metrics endpoint is down.
- scrape: parses the §7.6 metric contract. Errors when any of the mandatory
three is missing; the metric name mapping is overridable, since names drift
between versions (plan §14).
- scheduler: the filter -> score -> pick pipeline. A "strategy" is now just a
set of scorer weights; chat, batch and long-context are the same code with
different weights. Scorers: queue, kv (hard penalty above the ceiling),
inflight, load, weight, session (bounded-load consistent hashing). Pickers:
max-score, p2c, softmax, round-robin.
Decision carries the rationale — which scorer produced what, which filter
dropped how many. Tuning the weights without that is guesswork.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>