, ,

How to autoscale LLM inference on Kubernetes GPUs

The default autoscaler watches CPU, which barely moves when a GPU is full, and a new GPU replica can take ten minutes to start. Which vLLM metric to scale on, how to handle cold starts, and when scale-to-zero makes sense. With a live demo.

How to autoscale LLM inference: a day of traffic with GPU replicas stepping up behind it

AI infrastructure · Kubernetes · vLLM · KEDA · Autoscaling

In the cost post, scaling inference with traffic was the single biggest saving. It is also where teams get burned: the default autoscaler watches the wrong number, and a new GPU replica can take ten minutes to start. This post shows which metric to scale on, why cold starts decide everything, and when scale-to-zero is a good idea. The live demo runs a whole day of chatbot traffic through four scaling policies.

Try it: GPU Autoscaling Lab

A day of chatbot traffic, vLLM on up to 8 GPUs. Switch between four scaling policies, change the model size, add a warm spare and send a traffic spike.

Open the demo →

The resultFour policies, one day of traffic

Same traffic, same 70B model, a spike during the evening peak. GPU cost per day at $3 per GPU-hour, and the share of requests slower than 3 seconds:

Fixed for the peak6 replicas, all day
$4320% slow
HPA on CPUthe default autoscaler
$14477% slow
Scale on vLLM loadKEDA on running + waiting
$2652.4% slow
vLLM load, 8B model, warm sparefaster start, one spare ready
$3310.5% slow

The CPU autoscaler is the cheapest because it never scales up, and three-quarters of users wait. Scaling on vLLM’s own load saves about 40% against fixed sizing, but a spike still hurts while new replicas load the model. The last line is the usual answer: scale on the right metric and make starting a replica faster.

The metricScale on what the model server sees

An LLM server spends its time on the GPU. The CPU only handles requests and tokenising, so it sits at 20 to 30% even when the GPU is full and users are queueing. Scale on the server’s own queue instead.

Use thisvllm:num_requests_running + vllm:num_requests_waiting

Requests in flight per replica. Rises the moment the replicas fall behind. Set a target below the point where latency climbs.

Use as a second signalvllm:gpu_cache_usage_perc

How full the KV cache is. Near 100% means vLLM starts pausing requests. Newer vLLM versions call it vllm:kv_cache_usage_perc.

Do not usecpu

Barely moves. The default HPA on CPU never scales a GPU-bound server.

Do not use aloneDCGM_FI_DEV_GPU_UTIL

Reads close to 100% at moderate load, as explained in part 1. It cannot tell “busy” from “overloaded”.

The catchA new replica takes minutes, not seconds

A web pod starts in seconds. A GPU replica has to find a GPU, pull a large image and load the model into GPU memory. Rough times for a 70B model when no GPU node is free:

new GPU node
pull image
load 70B weights
warm up
New GPU node: 3 to 8 minif no node has a free GPU, the cluster autoscaler or Karpenter has to add one.Fix: keep headroom or a warm node.
Pull the image: 1 to 3 minvLLM images are around 10 GB.Fix: pre-pull on GPU nodes.
Load weights: 2 to 8 min140 GB for a 70B model at 16-bit, from wherever it is stored.Fix: cache on local NVMe, or a smaller model.
Warm up: about 1 minvLLM profiles memory and prepares CUDA graphs.Fix: count it in readiness.

Every minute of cold start is a minute of users waiting during a spike. So shorten it, or start earlier:

Scale earlier

Set the target per replica well below the breaking point, so new replicas start while there is still headroom.

Keep a warm spare

One extra replica always loaded. In the demo it cut lost requests during the spike from 383 to 103 for the 70B model, and to none for the 8B model, for about $66 a day.

Scale on the clock too

Traffic peaks at the same time every day. Add a scheduled rule that adds replicas before the evening rush.

Scale to zeroGreat for some services, wrong for others

Scale to zero when
  • Internal tools used a few times a day
  • Batch or overnight jobs
  • Demos and test environments
  • Users accept a slow first answer
Keep at least one when
  • Customer-facing chat
  • Anything with a latency promise
  • Large models with long cold starts
  • Traffic arrives in sudden bursts

The setupvLLM, Prometheus and KEDA

  1. Let Prometheus scrape vLLM

    vLLM publishes its metrics on /metrics on the same port as the API. Add a ServiceMonitor for the vLLM service, as in part 1.

  2. Install KEDA

    helm repo add kedacore https://kedacore.github.io/charts
    helm repo update
    helm install keda kedacore/keda -n keda --create-namespace
  3. Scale on vLLM load, plus the evening rush

    apiVersion: keda.sh/v1alpha1
    kind: ScaledObject
    metadata:
      name: vllm-chat
    spec:
      scaleTargetRef:
        name: vllm-chat
      minReplicaCount: 2
      maxReplicaCount: 8
      cooldownPeriod: 600
      advanced:
        horizontalPodAutoscalerConfig:
          behavior:
            scaleDown:
              stabilizationWindowSeconds: 600   # wait 10 min before removing a replica
      triggers:
      - type: prometheus
        metadata:
          serverAddress: http://prometheus.monitoring:9090
          query: |
            sum(vllm:num_requests_running{model_name="chat"})
            + sum(vllm:num_requests_waiting{model_name="chat"})
          threshold: "14"                       # in-flight requests per replica
      - type: cron
        metadata:
          timezone: Asia/Singapore
          start: "0 18 * * *"
          end: "0 23 * * *"
          desiredReplicas: "5"                  # be ready before the peak
  4. Make replicas start faster

    Pre-pull the vLLM image on GPU nodes, keep model weights on a local NVMe cache or a fast shared volume, and set a readiness probe on /health so traffic only arrives once the model is loaded.

Before you call it done
  • The autoscaler watches vLLM’s queue, not CPU.
  • You have measured your real cold start, from “scale up” to “first request served”.
  • The scaling target leaves enough headroom to cover that cold start.
  • Scale-down waits long enough that replicas do not flap.
  • Customer-facing models never scale to zero.
  • Predictable peaks are covered by a scheduled rule.
Send the spike yourself

Open GPU Autoscaling Lab, pick “Scale on vLLM load”, and send a spike with the 70B model. Then switch to the 8B model with a warm spare and send it again.

Open the demo →

The series so far: monitoring, logging, node lifecycle, sharing, scheduling, cost and this post on autoscaling. Next: networking for multi-node training.

About The Author


Discover more from Journal of Intelligent Infrastructure

Subscribe to get the latest posts sent to your email.

Leave a Reply

Your email address will not be published. Required fields are marked *

Architect’s Toolkit

About the Author

Dr. Pranay Jha is a Cloud and AI Consultant with 18+ years of experience in hybrid cloud, virtualization, and enterprise infrastructure transformation. He specializes in VMware technologies, multi-cloud strategy, and Generative AI solutions. He holds a PhD in Computer Applications with research focused on Cloud and AI, has published multiple research papers, and has been a VMware vExpert since 2016 and a VMUG Community Leader.

Discover more from Journal of Intelligent Infrastructure

Subscribe now to keep reading and get access to the full archive.

Continue reading