Autoscale on Queue Depth, Then Delete the Mean-Latency Panel
Utilization-based scaling under-provisions the exact bursts it exists to absorb, and the agent restructuring everyone is prototyping doubles arrival rate into that same mis-instrumented tier.
Why a pinned GPU stays pinned while the queue grows
Prefill and decode fail differently. Prefill runs the whole prompt in parallel and is compute-bound. Decode emits one token at a time and is bound by reading weights out of high-bandwidth memory, so the device reports busy at modest arithmetic intensity. The gauge reads saturated long before throughput is exhausted. It reads the same with zero requests waiting and with four hundred. That is the defect: the autoscaling signal is not a function of backlog. The horizontal pod autoscaler adds replicas late and removes them early.
The latency panel beside it is blind the same way. Continuous batching lets one long prefill stall short decodes. The distribution goes bimodal and the mean sits in the empty middle. It looks healthy while tail users time out. Histograms at p50/p95/p99, on time-to-first-token and inter-token latency, are the only view that shows the stalled request.
The replacement scaling signal is the serving engine's waiting-requests gauge, exposed on /metrics. Verify the exact metric name against your vLLM or SGLang version before wiring it up. These names have moved between releases. A silently absent metric evaluates as zero, and an autoscaler fed zero never scales.
Two more panels that mislead the same way
| Signal in use | What it actually tracks | Failure it hides | Replace with |
|---|---|---|---|
| Requests per second | Arrival rate only | Per-request token growth from agent loops | Tokens per request and steps per task |
| "Prefix caching is enabled" | Configuration state | Workloads whose prefixes never repeat | Per-workload cache hit rate |
The agent change lands in this tier
Conditional replanning is a serving change in an agent costume. One LLM call per step becomes two, which doubles arrival rate against the same replicas. The two roles have opposite shapes. Planner prompts carry state plus prior plans plus action history, so they are prefill-heavy. Executor calls emit one action, so they are decode-heavy. Mixed into one queue, a long planner prefill delays every executor decode sharing that batch.
Two consequences follow. Splitting model routing by role, strong model for the planner and cheap grounded model for the executor, is a cheaper lever than a frontier upgrade, and it lets the two workloads scale as separate pools with separate SLOs. And a per-request token budget that hard-caps steps is the only defense against one pathological task occupying a replica indefinitely.
Prefix caching is where agent loops genuinely pay off. A stable system prompt plus a fixed plan prefix repeats across every step of a task, which is close to the ideal cache shape. That is good engineering, and it deserves the credit. Measure the hit rate per workload rather than assuming a global win. A retrieval-heavy endpoint with unique context per call gets nothing from it, and a blended average will not tell you which one you have.
Utilization tells you the device is busy; only queue depth tells you the users are waiting.
Sequencing matters. Observability precedes optimization. Prefix caching, quantization and speculative decoding all have workload-dependent ceilings that histograms find and averages hide. Tune after the tail is visible, not before.
What to do
Repoint inference autoscaling from GPU/CPU utilization to the engine's waiting-requests gauge this sprint, confirming the exact metric name against your deployed vLLM or SGLang version before rollout.
Replace every mean-latency panel with TTFT and inter-token-latency histograms at p50/p95/p99 before your next load test.
Measure prefix-cache hit rate per workload and set a per-request token budget that hard-caps agent steps before any replanning layer reaches production this quarter.