Skip to content
nonconvexcard

Autoscaling and Cold Starts

Why a GPU replica takes minutes to become useful, what that does to a scaling policy, and the four techniques that shorten it.

AI EngineeringAdvanced⏱ 3 hours7 min readautoscalingcold startwarm poolscale to zeromodel loading

The budget

DefinitionWhat the cold start is made of
StageTypicalAttack
scheduling onto a node with a free GPU5–60 spre-provisioned node pool, or reservations
container image pull30–300 sslim images, layer caching, pre-pulled on the node
weight load into device memory60–600 slocal NVMe cache, tensor streaming, snapshots
CUDA context + kernel compilation10–90 sAOT compilation, cached compilation artefacts
warm-up generations5–30 srun before joining the router, not after
total2–10 min

The dominant term is almost always the weights, and it has a hard floor.

PropositionA lower bound on weight loading

A model of NN parameters at bb bytes each cannot be loaded into device memory faster than

tload  ≥  NbBeff,t_{\text{load}} \;\ge\; \frac{N b}{B_{\text{eff}}} ,

where BeffB_{\text{eff}} is the narrowest bandwidth on the path from where the weights are stored to device memory.

Key idea

Put numbers on it. A 70-billion-parameter model in bf16 is 140140 GB.

PathBandwidthFloor
object storage over a shared network1 GB/s140 s
local NVMe5 GB/s28 s
host RAM → device over PCIe 4 ×16~25 GB/s6 s

The remedy is visible directly in the table: move the weights closer before you need them. A node-local NVMe cache turns a 140-second load into 28; keeping the weights resident in host RAM turns it into 6. Both are pre-work, and both are what "warm pool" actually means in practice.

Should you scale to zero?

PropositionWhen scale-to-zero pays

Over a period TT, let ff be the fraction of time no request is in flight, nn the number of idle-to-active transitions, dd the cold start and cc the cost per replica-second. Keeping one replica warm costs cTcT. Scaling to zero costs

c (1−f) T  +  c n d,c\,(1-f)\,T \;+\; c\,n\,d ,

since each cold start burns dd replica-seconds before serving anything. Scale-to-zero is cheaper exactly when

 f T  >  n d \boxed{\ f\,T \;>\; n\,d\ }

— the total idle time must exceed the total time spent starting up. Separately, nn requests per period wait dd for their first token, which is a latency cost the inequality does not price.

Key idea

Put numbers in. With d=5d = 5 minutes and a service idle 20 hours a day, scale-to-zero pays while n<fT/d=(20×60)/5=240n < fT/d = (20 \times 60)/5 = 240 transitions a day. That sounds generous until you notice that a service probed by a health checker every minute never goes idle at all, and one used sporadically by twenty people can easily transition a few hundred times.

So the rule is: scale to zero when the traffic is clustered — long genuine idle periods, few transitions — and never when it is sparse but spread out, which is the pattern that looks idle on a utilisation graph and thrashes on a cold-start graph. Interactive diurnal traffic is the worst case: one transition, at exactly the moment every user arrives, all of them paying dd together.

Scaling that arrives in time

HeuristicScale on queued work, with a lead

Heuristic — a reasoning shortcut that predicts the right answer in the regimes discussed here. It is not a proof and can fail outside them.

Two properties the signal and the policy must have:

The signal must not saturate. GPU utilisation reads near 100% at both "comfortably busy" and "catastrophically overloaded", so it cannot distinguish them. Queued tokens, queue depth and time-to-first-token all keep growing past saturation and can.

The policy must lead the traffic by dd. A reactive controller starts the replica when the queue is already long, and by the recovery-time result the system stays behind for d(μ′−μ)/(μ′−λ1)d(\mu'-\mu)/(\mu'-\lambda_1). Leading requires prediction: schedule-based (traffic is diurnal and you know the shape), derivative-based (the queue is growing at a rate that will breach in dd seconds), or simply a standing headroom of 1/g1/g from the step-absorption corollary.

Assumptions

Prediction is only as good as the regularity of the traffic. For a genuinely unpredictable spike — a viral post, a batch job someone launched — no lead time is available, and the only defences are standing headroom and graceful degradation. The prediction is for the 95% of load that is predictable; the headroom is for the rest.

def desired_replicas(now, metrics, policy):
    # reactive term: what the current queue needs
    need_now = ceil(metrics.queued_tokens / policy.tokens_per_replica_per_sec / policy.target_ttft)

    # predictive term: what we will need once a new replica is ready
    growth = metrics.queued_tokens_derivative                 # tokens/s^2
    need_then = ceil((metrics.queued_tokens + growth * policy.cold_start_seconds)
                     / policy.tokens_per_replica_per_sec / policy.target_ttft)

    # schedule term: known diurnal shape, if we have one
    need_sched = policy.forecast(now + policy.cold_start_seconds)

    target = max(need_now, need_then, need_sched, policy.floor)
    return min(target, policy.ceiling)

The four techniques

  1. Warm pools — keep kk replicas loaded and idle, sized from the step-absorption bound. The simplest and most reliable; it costs exactly what it looks like it costs.
  2. Node-local weight cache — keep checkpoints on NVMe so a restart reads at 5 GB/s rather than pulling from object storage. Cheap, and the largest single win in the table above.
  3. Snapshot and restore — checkpoint the initialised process, including device memory, and restore it. Skips CUDA init and compilation as well as the load.
  4. Over-provision during known windows — if traffic is diurnal, scale on the clock and let the reactive controller handle only the residual.

What this does not say

Cold starts interact with the adapter routing in GPU Routing and Cache Affinity: a fleet serving many per-tenant adapters has a second, much smaller cold start per adapter, and a scaling event throws away every resident adapter along with the KV cache. Systems that scale frequently and multiplex adapters heavily pay this repeatedly, and it is invisible in a cold-start metric that measures only the base model.

References

These works are cited as sources to read, not reproduced. All exposition, examples and problems on this site are written independently; where an argument is standard, the citation points to a canonical treatment.

Results stated on this page

  • DefinitionWhat the cold start is made ofScheduling, image pull, weight load, CUDA initialisation, compilation and warm-up, each with a different remedy.
  • PropositionA lower bound on weight loadingproof includedLoading N parameters at b bytes each over a link of bandwidth B cannot take less than Nb/B seconds, which for a large model over object storage is minutes.
  • PropositionWhen scale-to-zero paysproof includedScaling to zero is cheaper than a warm replica only when the idle fraction exceeds the cold-start cost amortised over the requests it delays.
  • HeuristicScale on queued work, with a leadThe controller must act on a signal that grows past saturation and must act early enough to cover the cold start, which means predicting rather than reacting.