{ "uid": "llm-fleet", "title": "LLM fleet \u2014 capacity and reuse", "description": "The signals that explain the LLM endpoints under load, from the 2026-09-16 capacity work. The panels read llama.cpp's own counters: speculative acceptance, queue depth, prefix-cache reuse, decode step rate and throughput. frank-medium (Dankserver, both R9700s, layer split) and frank-small (Dankclaw, RTX 3090) both export them; frank-large runs the halogen engine, which publishes /health, /cache and /v1/* and no metrics endpoint at all, so it cannot appear here. See serverAppConfigs-docs/LLM/frank-medium-llamacpp-dflash2.md for the numbers these panels are read against.", "tags": [ "llm", "frank-medium", "capacity" ], "timezone": "browser", "schemaVersion": 39, "version": 1, "refresh": "30s", "time": { "from": "now-6h", "to": "now" }, "templating": { "list": [ { "name": "job", "label": "endpoint", "type": "query", "datasource": { "type": "prometheus", "uid": "prometheus" }, "query": { "query": "label_values({__name__=~\"vllm:kv_cache_usage_perc|llamacpp:.*\"}, job)", "refId": "A" }, "includeAll": true, "multi": true, "current": { "text": "All", "value": "$__all" } } ] }, "panels": [ { "id": 1, "type": "timeseries", "title": "Speculative decoding (DFlash2 acceptance)", "description": "How full each card's KV pool is. One card holds ~122,636 tokens; sustained high values with a cold prefix cache is the thrash this fleet measured.", "gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 }, "datasource": { "type": "prometheus", "uid": "prometheus" }, "fieldConfig": { "defaults": { "unit": "percentunit", "min": 0, "max": 1, "custom": { "fillOpacity": 10, "lineWidth": 1 } }, "overrides": [] }, "targets": [ { "refId": "A", "expr": "rate(llamacpp:spec_decode_num_accepted_tokens_total{job=~\"$job\"}[5m]) / clamp_min(rate(llamacpp:spec_decode_num_drafts_total{job=~\"$job\"}[5m]), 0.001)", "legendFormat": "{{job}} accepted per draft step" }, { "refId": "B", "expr": "rate(llamacpp:spec_decode_num_draft_tokens_total{job=~\"$job\"}[5m])", "legendFormat": "{{job}} draft tokens/s" } ] }, { "id": 2, "type": "timeseries", "title": "Requests waiting (queue depth)", "description": "Anything above zero means the engine is serialising: with MAXSEQS=4 the fifth concurrent request waits.", "gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 }, "datasource": { "type": "prometheus", "uid": "prometheus" }, "fieldConfig": { "defaults": { "unit": "short", "min": 0, "custom": { "fillOpacity": 10, "lineWidth": 1 } }, "overrides": [] }, "targets": [ { "refId": "A", "expr": "llamacpp:requests_deferred{job=~\"$job\"}", "legendFormat": "{{job}} deferred" }, { "refId": "B", "expr": "llamacpp:requests_processing{job=~\"$job\"}", "legendFormat": "{{job}} processing" } ] }, { "id": 3, "type": "timeseries", "title": "Prefix cache reuse rate", "description": "rate(hits)/rate(queries). High means pinned conversations are being served from cache (measured: 22.3 s cold vs 1.4 s warm on a ~50k-token prefix). A collapse here with three concurrent large contexts is the capacity wall, not a routing failure.", "gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 }, "datasource": { "type": "prometheus", "uid": "prometheus" }, "fieldConfig": { "defaults": { "unit": "percentunit", "min": 0, "max": 1, "custom": { "fillOpacity": 10, "lineWidth": 1 } }, "overrides": [] }, "targets": [ { "refId": "A", "expr": "rate(llamacpp:prompt_tokens_cached_total{job=~\"$job\"}[5m]) / clamp_min(rate(llamacpp:prompt_tokens_total{job=~\"$job\"}[5m]), 1)", "legendFormat": "{{job}} reuse" } ] }, { "id": 4, "type": "timeseries", "title": "Decode steps and batch size", "description": "Wall-clock per request, prefill included \u2014 the number an agent-loop turn feels.", "gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 }, "datasource": { "type": "prometheus", "uid": "prometheus" }, "fieldConfig": { "defaults": { "unit": "s", "custom": { "fillOpacity": 10, "lineWidth": 1 } }, "overrides": [] }, "targets": [ { "refId": "A", "expr": "rate(llamacpp:n_decode_total{job=~\"$job\"}[5m])", "legendFormat": "{{job}} decode steps/s" }, { "refId": "B", "expr": "llamacpp:n_busy_slots_per_decode{job=~\"$job\"}", "legendFormat": "{{job}} sequences per decode step" } ] }, { "id": 5, "type": "timeseries", "title": "Decode and prefill throughput", "description": "vLLM generation tokens, plus llama.cpp's counters for frank-small: llamacpp:predicted_tokens_seconds is decode and llamacpp:prompt_tokens_seconds is prompt throughput. The llama.cpp series carry the job label of their scrape, so the endpoint picker selects between the runtimes.", "gridPos": { "h": 8, "w": 12, "x": 0, "y": 16 }, "datasource": { "type": "prometheus", "uid": "prometheus" }, "fieldConfig": { "defaults": { "unit": "tokens/sec", "custom": { "fillOpacity": 10, "lineWidth": 1 } }, "overrides": [] }, "targets": [ { "refId": "C", "expr": "llamacpp:predicted_tokens_seconds{job=~\"$job\"}", "legendFormat": "{{job}} llama.cpp decode tok/s" }, { "refId": "D", "expr": "llamacpp:prompt_tokens_seconds{job=~\"$job\"}", "legendFormat": "{{job}} llama.cpp prefill tok/s" } ] }, { "id": 6, "type": "timeseries", "title": "Alerts firing (ALERTS)", "description": "Prometheus rules for the LLM fleet (shared/prometheus/rules/llm-fleet.yml): pool saturation, queueing, prefix-cache collapse, endpoint down, p95 latency. A firing alert appears here, on Prometheus' /alerts page, and nowhere else \u2014 this stack has no Alertmanager, so nothing is notified.", "gridPos": { "h": 8, "w": 12, "x": 12, "y": 16 }, "datasource": { "type": "prometheus", "uid": "prometheus" }, "fieldConfig": { "defaults": { "unit": "short", "min": 0, "custom": { "fillOpacity": 10, "lineWidth": 1 } }, "overrides": [] }, "targets": [ { "refId": "A", "expr": "ALERTS{alertstate=\"firing\"}", "legendFormat": "{{alertname}} {{job}}" } ] } ] }