Visual walkthrough — Kubernetes for ML workloads
This is a deep-dive child of Kubernetes for ML workloads. It leans on the control-loop idea from Control Systems - Feedback Loops and the scaling picture from Horizontal vs Vertical Scaling.
Step 1 — What is a "replica", and what is a "metric"?
WHAT. A replica is one running copy of your model server — an identical worker. If you have 4 replicas, four identical boxes are all answering requests. A metric is one number we measure per replica that tells us how hard it is working — for example its CPU busy-ness as a percentage, or how many requests per second it handles.
WHY. Before we can balance load we need a picture of load. The autoscaler cannot see "the model" — it only sees numbers coming off each box. So the whole derivation is built on: boxes (replicas) + a dial on each box (the metric).
PICTURE. Four identical boxes, each with a little pressure-gauge showing how busy it is.
Step 2 — The key assumption: load splits evenly
WHAT. We assume all incoming work is shared equally across the replicas. If total work is and there are boxes, each box carries .
WHY. A model server sits behind a Service that load-balances round-robin, so this "equal share" is almost true in practice. We need it because it is the one idea that lets us predict what happens when we add boxes: add a box, and each box's share shrinks.
PICTURE. A single pipe of total load splitting into equal streams.
Step 3 — Total load is a conserved quantity
WHAT. Rearrange Step 2. Multiply both sides by :
WHY. This is the trick the whole formula rests on. The total work does not care how many boxes exist — the traffic arriving is set by your users, not by you. So is fixed at the instant we decide to scale. Whatever we do to , the product must stay equal to that same . This is exactly like conservation of a quantity in a feedback system: something is preserved while its pieces trade off.
PICTURE. A see-saw: on one side "number of boxes ", on the other "busy-ness per box ". The see-saw's total weight is a fixed block that never changes — only how it's distributed does.
Step 4 — Write the "now" and the "wish" states
WHAT. We name two moments:
- Now: replicas, showing average metric .
- Wish: replicas, where we want each to show the target .
WHY. The autoscaler is a control loop: it compares observed against desired (the reconcile idea from the parent note). Here the "observed" is and the "desired per-box" is . We introduce because you pick it — e.g. "keep every box around 50% CPU" — as the comfortable operating point.
PICTURE. Two see-saws side by side: the left ("now") tilted heavy (boxes overloaded, high); the right ("wish") level at .
Step 5 — Set the two totals equal and solve
WHAT. Both states describe the same instant of traffic, so both have the same total load (Step 3). Write two ways and equate: Divide both sides by to isolate our unknown:
WHY. We want to know how many boxes make the gauges read instead of . Solving for answers exactly that. The ratio is doing the real work: it says "how many times too busy are we right now?"
PICTURE. Same total block dropped onto a wider base — spread over more boxes so each sits lower, exactly at the line.
Step 6 — Why we must round UP (the ceiling)
WHAT. from Step 5 can come out fractional (like ), but you cannot run of a box. The ceiling means "round up to the next whole number": , .
WHY up and not down? If we rounded down to , those 7 boxes would each carry more than the fair share for target — they'd end up above , i.e. still overloaded. Rounding up guarantees we never under-provision; the extra box just leaves everyone slightly below target, which is the safe side. This is the same "err toward safety" bias the parent note uses.
PICTURE. A number line at : an arrow pointing left to (labelled "boxes stay overloaded — bad") and right to ("everyone safely under target — chosen").
Step 7 — The dead-band: why K8s sometimes does nothing
WHAT. Real gauges jitter. If the computed ratio is very close to (default: within , i.e. between and ), Kubernetes refuses to act and keeps .
WHY. Without this, a metric wobbling from to around a target would make the autoscaler add and remove boxes endlessly — thrashing. A dead-band is hysteresis: exactly what a thermostat does so it doesn't click on/off every second (see Control Systems - Feedback Loops). It trades a little precision for a lot of stability, which also protects your bill (see Cost Optimization in Cloud ML — constant scaling churns machines).
PICTURE. The ratio number line with a shaded "no-action zone" from to around ; outside it, arrows for "scale up" (ratio ) and "scale down" (ratio ).
Step 8 — Edge & degenerate cases (never leave the reader stranded)
WHAT / WHY. Formulas must survive weird inputs. Walk each:
PICTURE. A small map of the ratio axis marking the clamps: minReplicas floor on the left, dead-band in the middle, maxReplicas ceiling on the right — the "safe operating corridor".
The one-picture summary
Everything at once: measured gauges → average → compare to → ratio → ceiling → clamp → new replica count, looped forever.
Recall Feynman retelling — explain the whole walkthrough to a 12-year-old
Imagine 4 identical chefs each with a "tiredness dial". You want every chef about half-tired (that's your target). Right now they read 90% — way too tired, because the total pile of orders is fixed but there aren't enough chefs to share it. Total work stays the same whether you have 4 chefs or 8; only how tired each one gets changes. So you ask: "how many times over comfort are we?" — 90 ÷ 50 = 1.8 times. Multiply your chefs by 1.8 → 7.2 chefs. You can't have 0.2 of a chef, and rounding down leaves everyone still overworked, so you round up to 8. Now the same pile spread over 8 chefs leaves each near half-tired — perfect. And if the dials only wobble a tiny bit (say they read 48 when you wanted 50), you do nothing at all, because hiring and firing chefs over a 2% jiggle would be silly. That "do nothing near the target" rule is the same reason a heater doesn't click on and off every second. That's the whole autoscaler.
Recall Rebuild the formula from memory
Start from load splits evenly ::: , so (total load conserved).
Set now-total = wish-total ::: .
Solve for the unknown ::: .
Fix the "no half a box" problem ::: round up with the ceiling .
Fix the thrashing problem ::: ignore changes inside the ±10% dead-band.
Fix the extremes ::: clamp between minReplicas and maxReplicas.
Connections
- Kubernetes for ML workloads — the parent; this page derives its HPA result.
- Control Systems - Feedback Loops — the dead-band = hysteresis; reconcile = feedback.
- Horizontal vs Vertical Scaling — HPA is horizontal (more boxes), not bigger boxes.
- Model Serving (TF-Serving, TorchServe, Triton) — the servers being replicated.
- Cost Optimization in Cloud ML — why min/max caps and dead-bands protect the bill.