AINexus
Tutorial2 min read20d ago·2 comments·2375 views

Hands-on · step by step

Tutorial: scale GPU inference workers on queue depth with KEDA

by @diego — Platform SRE for AI workloads

Four steps to GPU autoscaling on Kubernetes with Karpenter and DCGM exporter — checkpoints, tuning knobs and the failure to guard first.

Before you start — about 45 minutes · Karpenter · DCGM exporter · KEDA

We rebuilt GPU autoscaling on Kubernetes three times before it held. The version below is the one that survived a bad Tuesday and a worse Friday.

Step 1 — Decide what "working" means

Before touching code, write down three numbers: worst acceptable latency, cost per request, and what should happen when the upstream is unavailable. Everything after this is implementation.

Step 2 — Wire Karpenter

The whole flow is one path. No special branches for "large" inputs — express those as different limits, not different code.

# scale the worker deployment on queue depth, not on CPU
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata: { name: inference-workers }
spec:
  scaleTargetRef: { name: inference-workers }
  minReplicaCount: 1
  maxReplicaCount: 12
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus:9090
        query: sum(vllm:num_requests_waiting)

Why this works

The limit is enforced before the expensive call, not after it. Reserving budget up front means a burst degrades the caller that caused it instead of everyone downstream.

Step 3 — Guard the failure path

CPU-based HPA never triggers for GPU pods, because the model is not CPU-bound

Write that guard before you write the feature. It costs an hour and saves a postmortem.

Step 4 — Verify it

kubectl get hpa,scaledobject -n ai && kubectl top pods -n ai
  • Lower the limit and confirm the command fails loudly
  • Kill the upstream and confirm you get a typed error, not a timeout
  • Check the metric you emit per outcome: ok, throttled, upstream_error

Tuning knobs

KnobDefaultWhat we runWhy
concurrency824Most of the wait is network, not compute
retry budgetinfinite2, with jitterUnbounded retries turn an outage into an avalanche
timeout30s8s, inherited from the callerNobody waits 30s for a chat reply

None of this was clever. It was just the work nobody wanted to do first.

Comments (2)

Log in to join the discussion.

@tomas·2h ago

Bookmarked. The verification step is the bit most write-ups skip.

@nadia·2h ago

This matches what we saw, especially the part about defaults nobody questioned.