aiml
SearchMy learning
Menu

Lesson 18 of 20Module 6

Plan cold starts and gate model rollouts

Model replica startup and finite standby capacity, then evaluate canary gates. Distinguish requested capacity from ready capacity and compare tradeoffs.

Prerequisites

Python, basic causal attention, and the preceding inference lessons.

By the end, you can

  • Plan cold starts and gate model rollouts.
  • Explain the bottleneck, measurement boundary, and tradeoffs in this implementation.
  • Distinguish a controller decision from completed readiness or traffic rollback.
Runtime
Python 3.12+
Dependencies
Standard library only

Lab code, tests and fixtures (ZIP, 103 KB)Quiz check your understanding

Progress is saved in this browser.

When you need this. A new model replica takes time to become ready, while a release can change both latency and output quality. A controller must distinguish requested capacity from ready capacity and evaluate a canary before promotion.

What you run. Executable controller policy and startup simulation. Replica starts, real traffic routing, canary quality evaluation, and rollback deployment were not executed.

This lesson builds on protect interactive requests from noisy neighbors.

Distinguish requested from ready capacity

An autoscaler’s action has delay: provisioning, loading weights, allocating caches, and warmup happen before the replica can accept work. Queue growth and active-request load can complement utilization, but the useful signal depends on the workload.

A canary rollout exposes a candidate to limited traffic and compares explicit acceptance criteria. A policy can recommend promotion or rollback; actually routing traffic and draining in-flight requests are separate orchestration actions. The supplied controller returns decisions and does not operate a cluster. Its two scenarios change startup delay, standby capacity, and control signal together; their comparison does not isolate which signal is better.

Load andcanary dataController policyScale orrollout plansamplesdecisionLoad and canary dataController policyScale or rollout plansamplesdecision
Follow the named data through the downloadable program. The article identifies whether the result is measured execution, a controlled fixture, or a planning estimate.

simulate_autoscaler() tracks booting and ready replicas on a controlled traffic timeline. CanaryGate evaluates supplied latency, error, and quality samples and stops its sequence after a rejected stage. These quality samples are fixtures, not scored model outputs.

Exercise the controller decisions

Download the lab from this page, extract it, and run these commands from its directory. The default command reads saved run records or runs the explicitly described local algorithm; it never downloads a model or calls a paid API.

Run the local analysis and checksTerminal · bash
python3 lab.py
python3 -m unittest -v test_lab.py
Read lab.py
lab.pySource · pythonDownload
"""Simulate replica startup and evaluate supplied canary samples; no live fleet is controlled."""

from __future__ import annotations

from dataclasses import asdict, dataclass
import json
import math
from typing import Literal

ScalingMode = Literal["reactive_gpu_util", "leading_kv_and_velocity"]


@dataclass(frozen=True)
class AutoscalerConfig:
    mode: ScalingMode
    min_replicas: int = 4
    max_replicas: int = 16
    target_kv_utilization: float = 0.68
    reactive_gpu_util_threshold: float = 0.88
    tokens_per_replica_capacity: int = 18_000
    cold_start_seconds: int = 180
    warm_standby_replicas: int = 0
    fast_weight_stream_seconds: int = 22
    ttft_slo_ms: float = 450.0

    def __post_init__(self) -> None:
        if self.min_replicas <= 0 or self.max_replicas < self.min_replicas:
            raise ValueError("Invalid replica bounds")
        if not (0.1 <= self.target_kv_utilization <= 0.95):
            raise ValueError("target_kv_utilization must be in [0.1, 0.95]")


@dataclass(frozen=True)
class CanaryStageSample:
    stage_percent: int
    requests_evaluated: int
    baseline_p99_tpot_ms: float
    canary_p99_tpot_ms: float
    baseline_exact_match: float
    canary_exact_match: float
    canary_error_rate: float

    def __post_init__(self):
        if not 1 <= self.stage_percent <= 100 or self.requests_evaluated < 1:
            raise ValueError("Canary sample needs traffic and observations")
        rates = [
            self.baseline_exact_match,
            self.canary_exact_match,
            self.canary_error_rate,
        ]
        if any(not math.isfinite(v) or not 0 <= v <= 1 for v in rates):
            raise ValueError("Invalid canary rate")
        if any(
            not math.isfinite(v) or v < 0
            for v in [self.baseline_p99_tpot_ms, self.canary_p99_tpot_ms]
        ):
            raise ValueError("Invalid canary latency")


@dataclass(frozen=True)
class CanaryGate:
    max_tpot_regression_ms: float = 6.0
    max_tpot_ceiling_ms: float = 45.0
    max_quality_drop: float = 0.015
    max_error_rate: float = 0.005

    def evaluate_stage(self, sample: CanaryStageSample) -> dict[str, object]:
        tpot_delta = sample.canary_p99_tpot_ms - sample.baseline_p99_tpot_ms
        quality_drop = sample.baseline_exact_match - sample.canary_exact_match
        reasons: list[str] = []
        if (
            sample.canary_p99_tpot_ms > self.max_tpot_ceiling_ms
            or tpot_delta > self.max_tpot_regression_ms
        ):
            reasons.append("tpot_slo_regression")
        if quality_drop > self.max_quality_drop:
            reasons.append("quality_score_regression")
        if sample.canary_error_rate > self.max_error_rate:
            reasons.append("error_rate_breach")
        passed = len(reasons) == 0
        return {
            "stage_percent": sample.stage_percent,
            "requests_evaluated": sample.requests_evaluated,
            "decision": "promote" if passed else "rollback_to_0_percent",
            "passed": passed,
            "tpot_delta_ms": tpot_delta,
            "quality_drop": quality_drop,
            "canary_error_rate": sample.canary_error_rate,
            "reasons": reasons,
        }

    def run_rollout(self, stages: list[CanaryStageSample]) -> dict[str, object]:
        percents = [s.stage_percent for s in stages]
        if percents != sorted(set(percents)):
            raise ValueError("Canary stages must increase")
        history: list[dict[str, object]] = []
        active_traffic_percent = 0
        rolled_back = False
        rollback_reason: list[str] = []
        for stage in stages:
            verdict = self.evaluate_stage(stage)
            history.append(verdict)
            if not verdict["passed"]:
                rolled_back = True
                rollback_reason = list(verdict["reasons"])
                active_traffic_percent = 0
                break
            active_traffic_percent = stage.stage_percent
        return {
            "completed_100_percent": (
                not rolled_back and active_traffic_percent == 100
            ),
            "rolled_back": rolled_back,
            "final_canary_traffic_percent": active_traffic_percent,
            "rollback_reasons": rollback_reason,
            "stages": history,
        }


def build_traffic_spike_timeline() -> list[tuple[int, int]]:
    """Returns (second, inflight_token_demand) across a 300-second traffic surge."""
    return [
        (0, 48_000),
        (20, 52_000),
        (40, 74_000),
        (60, 108_000),
        (80, 142_000),
        (100, 168_000),
        (120, 176_000),
        (140, 172_000),
        (160, 160_000),
        (180, 144_000),
        (200, 124_000),
        (220, 102_000),
        (240, 84_000),
        (260, 68_000),
        (280, 56_000),
        (300, 50_000),
    ]


def simulate_autoscaler(
    config: AutoscalerConfig,
    timeline: list[tuple[int, int]] | None = None,
) -> dict[str, object]:
    series = timeline if timeline is not None else build_traffic_spike_timeline()
    ready_replicas = config.min_replicas
    warm_pool = config.warm_standby_replicas
    pending_boots: list[tuple[int, int]] = []  # (ready_at_sec, count)
    prev_demand = series[0][1]
    prev_sec = series[0][0]
    steps: list[dict[str, object]] = []
    slo_breach_windows = 0

    for sec, demand_tokens in series:
        # Promote any replicas whose weight loading completed by `sec`.
        newly_ready = sum(count for ready_at, count in pending_boots if ready_at <= sec)
        pending_boots = [
            (ready_at, count) for ready_at, count in pending_boots if ready_at > sec
        ]
        ready_replicas += newly_ready
        inflight_booting = sum(count for _, count in pending_boots)

        total_kv_capacity = ready_replicas * config.tokens_per_replica_capacity
        kv_util = demand_tokens / max(1, total_kv_capacity)
        # GPU compute utilization saturates early under continuous batching, hiding queue growth.
        gpu_compute_util = min(0.99, 0.52 + 0.38 * min(1.25, kv_util))

        dt = max(1, sec - prev_sec)
        velocity_tokens_per_sec = max(0.0, (demand_tokens - prev_demand) / dt)
        prev_demand = demand_tokens
        prev_sec = sec

        if config.mode == "reactive_gpu_util":
            # Reactive policy waits until GPU utilization exceeds 88% and pays the full 180s cold start.
            if gpu_compute_util >= config.reactive_gpu_util_threshold:
                desired = math.ceil(
                    demand_tokens / (config.tokens_per_replica_capacity * 0.80)
                )
            else:
                desired = ready_replicas
            desired = min(config.max_replicas, max(config.min_replicas, desired))
            needed = max(0, desired - (ready_replicas + inflight_booting))
            if needed > 0:
                pending_boots.append((sec + config.cold_start_seconds, needed))
        else:
            # Leading-indicator policy projects demand across the fast weight-streaming window (22s).
            projected_demand = (
                demand_tokens
                + velocity_tokens_per_sec * config.fast_weight_stream_seconds
            )
            desired = math.ceil(
                projected_demand
                / (config.tokens_per_replica_capacity * config.target_kv_utilization)
            )
            desired = min(config.max_replicas, max(config.min_replicas, desired))
            needed = max(0, desired - (ready_replicas + inflight_booting))
            if needed > 0:
                from_warm = min(warm_pool, needed)
                if from_warm > 0:
                    warm_pool -= from_warm
                    pending_boots.append((sec + 20, from_warm))
                remaining = needed - from_warm
                if remaining > 0:
                    pending_boots.append(
                        (sec + config.fast_weight_stream_seconds, remaining)
                    )
                # This finite initial standby pool is not replenished in the simulation.

        # Recompute effective KV utilization and queue delay at `sec`.
        overload_ratio = max(0.0, kv_util - 0.85)
        p99_ttft_ms = round(
            140.0 + 190.0 * min(kv_util, 0.85) + 2600.0 * (overload_ratio**1.35), 2
        )
        if p99_ttft_ms > config.ttft_slo_ms:
            slo_breach_windows += 1

        steps.append(
            {
                "sec": sec,
                "demand_tokens": demand_tokens,
                "ready_replicas": ready_replicas,
                "booting_replicas": sum(count for _, count in pending_boots),
                "demand_to_kv_capacity_ratio": round(kv_util, 4),
                "kv_utilization": round(min(1.0, kv_util), 4),
                "warm_standby_remaining": warm_pool,
                "gpu_compute_utilization": round(gpu_compute_util, 4),
                "p99_ttft_ms": p99_ttft_ms,
                "slo_breached": p99_ttft_ms > config.ttft_slo_ms,
            }
        )

    return {
        "mode": config.mode,
        "cold_start_seconds": (
            config.cold_start_seconds
            if config.mode == "reactive_gpu_util"
            else config.fast_weight_stream_seconds
        ),
        "peak_kv_utilization": max(s["demand_to_kv_capacity_ratio"] for s in steps),
        "peak_p99_ttft_ms": max(s["p99_ttft_ms"] for s in steps),
        "max_ready_replicas": max(s["ready_replicas"] for s in steps),
        "slo_breach_windows": slo_breach_windows,
        "timeline": steps,
    }


def simulate_cold_start_and_rollout() -> dict[str, object]:
    reactive_cfg = AutoscalerConfig(
        mode="reactive_gpu_util",
        cold_start_seconds=180,
        warm_standby_replicas=0,
    )
    leading_cfg = AutoscalerConfig(
        mode="leading_kv_and_velocity",
        target_kv_utilization=0.68,
        warm_standby_replicas=2,
        fast_weight_stream_seconds=22,
    )
    reactive_run = simulate_autoscaler(reactive_cfg)
    leading_run = simulate_autoscaler(leading_cfg)

    gate = CanaryGate(
        max_tpot_regression_ms=6.0,
        max_tpot_ceiling_ms=45.0,
        max_quality_drop=0.015,
        max_error_rate=0.005,
    )
    # Rollout 1: Defective INT4 calibration that passes 5% traffic but regresses p99 TPOT & quality at 25% load.
    defective_canary_stages = [
        CanaryStageSample(
            stage_percent=5,
            requests_evaluated=250,
            baseline_p99_tpot_ms=28.4,
            canary_p99_tpot_ms=31.2,
            baseline_exact_match=0.842,
            canary_exact_match=0.834,
            canary_error_rate=0.001,
        ),
        CanaryStageSample(
            stage_percent=25,
            requests_evaluated=1200,
            baseline_p99_tpot_ms=29.0,
            canary_p99_tpot_ms=48.6,
            baseline_exact_match=0.842,
            canary_exact_match=0.814,
            canary_error_rate=0.002,
        ),
        CanaryStageSample(
            stage_percent=100,
            requests_evaluated=5000,
            baseline_p99_tpot_ms=29.1,
            canary_p99_tpot_ms=52.0,
            baseline_exact_match=0.842,
            canary_exact_match=0.811,
            canary_error_rate=0.003,
        ),
    ]
    # Rollout 2: Validated FP8 build that passes all 3 stages (5% -> 25% -> 100%).
    healthy_canary_stages = [
        CanaryStageSample(
            stage_percent=5,
            requests_evaluated=250,
            baseline_p99_tpot_ms=28.4,
            canary_p99_tpot_ms=24.8,
            baseline_exact_match=0.842,
            canary_exact_match=0.839,
            canary_error_rate=0.001,
        ),
        CanaryStageSample(
            stage_percent=25,
            requests_evaluated=1200,
            baseline_p99_tpot_ms=29.0,
            canary_p99_tpot_ms=25.6,
            baseline_exact_match=0.842,
            canary_exact_match=0.838,
            canary_error_rate=0.001,
        ),
        CanaryStageSample(
            stage_percent=100,
            requests_evaluated=5000,
            baseline_p99_tpot_ms=29.1,
            canary_p99_tpot_ms=26.1,
            baseline_exact_match=0.842,
            canary_exact_match=0.838,
            canary_error_rate=0.001,
        ),
    ]

    return {
        "experiment": "gpu-autoscaling-cold-starts-and-canary-rollout",
        "scenario_scope": "Combined signal, startup-delay and initial-standby scenarios; does not isolate signal choice",
        "cold_start_breakdown_seconds": {
            "unoptimized_container_and_safetensors_init": {
                "node_alloc_and_image_pull": 75,
                "cuda_context_and_nccl_init": 35,
                "remote_weight_download_and_mmap": 70,
                "total_seconds": 180,
            },
            "pre_pulled_image_plus_object_memory_tensor_stream": {
                "warm_container_init": 4,
                "cuda_graph_cache_restore": 6,
                "direct_nvme_or_rdma_tensor_stream": 12,
                "total_seconds": 22,
            },
        },
        "autoscaling_comparison": {
            "reactive_gpu_util": reactive_run,
            "leading_kv_and_velocity": leading_run,
        },
        "canary_rollouts": {
            "defective_int4_build": gate.run_rollout(defective_canary_stages),
            "validated_fp8_build": gate.run_rollout(healthy_canary_stages),
        },
        "canary_gate_policy": asdict(gate),
    }


def main() -> None:
    print(
        json.dumps(
            {
                "execution_mode": "CPU algorithm with controlled inputs; any latency/capacity coefficients are assumptions",
                **simulate_cold_start_and_rollout(),
            },
            indent=2,
        )
    )


if __name__ == "__main__":
    main()

The controller returns a plan from supplied measurements and fixture samples. A scale request is not ready capacity, and a rollback decision does not show that live traffic has been drained or rerouted.

Inspect the complete local result
Recorded local resultRecorded output · jsonDownload
{
  "execution_mode": "CPU algorithm with controlled inputs; any latency/capacity coefficients are assumptions",
  "experiment": "gpu-autoscaling-cold-starts-and-canary-rollout",
  "scenario_scope": "Combined signal, startup-delay and initial-standby scenarios; does not isolate signal choice",
  "cold_start_breakdown_seconds": {
    "unoptimized_container_and_safetensors_init": {
      "node_alloc_and_image_pull": 75,
      "cuda_context_and_nccl_init": 35,
      "remote_weight_download_and_mmap": 70,
      "total_seconds": 180
    },
    "pre_pulled_image_plus_object_memory_tensor_stream": {
      "warm_container_init": 4,
      "cuda_graph_cache_restore": 6,
      "direct_nvme_or_rdma_tensor_stream": 12,
      "total_seconds": 22
    }
  },
  "autoscaling_comparison": {
    "reactive_gpu_util": {
      "mode": "reactive_gpu_util",
      "cold_start_seconds": 180,
      "peak_kv_utilization": 2.4444,
      "peak_p99_ttft_ms": 5182.36,
      "max_ready_replicas": 13,
      "slo_breach_windows": 9,
      "timeline": [
        {
          "sec": 0,
          "demand_tokens": 48000,
          "ready_replicas": 4,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.6667,
          "kv_utilization": 0.6667,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.7733,
          "p99_ttft_ms": 266.67,
          "slo_breached": false
        },
        {
          "sec": 20,
          "demand_tokens": 52000,
          "ready_replicas": 4,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.7222,
          "kv_utilization": 0.7222,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.7944,
          "p99_ttft_ms": 277.22,
          "slo_breached": false
        },
        {
          "sec": 40,
          "demand_tokens": 74000,
          "ready_replicas": 4,
          "booting_replicas": 2,
          "demand_to_kv_capacity_ratio": 1.0278,
          "kv_utilization": 1.0,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.9106,
          "p99_ttft_ms": 554.03,
          "slo_breached": true
        },
        {
          "sec": 60,
          "demand_tokens": 108000,
          "ready_replicas": 4,
          "booting_replicas": 4,
          "demand_to_kv_capacity_ratio": 1.5,
          "kv_utilization": 1.0,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.99,
          "p99_ttft_ms": 1754.97,
          "slo_breached": true
        },
        {
          "sec": 80,
          "demand_tokens": 142000,
          "ready_replicas": 4,
          "booting_replicas": 6,
          "demand_to_kv_capacity_ratio": 1.9722,
          "kv_utilization": 1.0,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.99,
          "p99_ttft_ms": 3339.44,
          "slo_breached": true
        },
        {
          "sec": 100,
          "demand_tokens": 168000,
          "ready_replicas": 4,
          "booting_replicas": 8,
          "demand_to_kv_capacity_ratio": 2.3333,
          "kv_utilization": 1.0,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.99,
          "p99_ttft_ms": 4728.87,
          "slo_breached": true
        },
        {
          "sec": 120,
          "demand_tokens": 176000,
          "ready_replicas": 4,
          "booting_replicas": 9,
          "demand_to_kv_capacity_ratio": 2.4444,
          "kv_utilization": 1.0,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.99,
          "p99_ttft_ms": 5182.36,
          "slo_breached": true
        },
        {
          "sec": 140,
          "demand_tokens": 172000,
          "ready_replicas": 4,
          "booting_replicas": 9,
          "demand_to_kv_capacity_ratio": 2.3889,
          "kv_utilization": 1.0,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.99,
          "p99_ttft_ms": 4954.18,
          "slo_breached": true
        },
        {
          "sec": 160,
          "demand_tokens": 160000,
          "ready_replicas": 4,
          "booting_replicas": 9,
          "demand_to_kv_capacity_ratio": 2.2222,
          "kv_utilization": 1.0,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.99,
          "p99_ttft_ms": 4287.12,
          "slo_breached": true
        },
        {
          "sec": 180,
          "demand_tokens": 144000,
          "ready_replicas": 4,
          "booting_replicas": 9,
          "demand_to_kv_capacity_ratio": 2.0,
          "kv_utilization": 1.0,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.99,
          "p99_ttft_ms": 3441.4,
          "slo_breached": true
        },
        {
          "sec": 200,
          "demand_tokens": 124000,
          "ready_replicas": 4,
          "booting_replicas": 9,
          "demand_to_kv_capacity_ratio": 1.7222,
          "kv_utilization": 1.0,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.99,
          "p99_ttft_ms": 2463.32,
          "slo_breached": true
        },
        {
          "sec": 220,
          "demand_tokens": 102000,
          "ready_replicas": 6,
          "booting_replicas": 7,
          "demand_to_kv_capacity_ratio": 0.9444,
          "kv_utilization": 0.9444,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.8789,
          "p99_ttft_ms": 409.01,
          "slo_breached": false
        },
        {
          "sec": 240,
          "demand_tokens": 84000,
          "ready_replicas": 8,
          "booting_replicas": 5,
          "demand_to_kv_capacity_ratio": 0.5833,
          "kv_utilization": 0.5833,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.7417,
          "p99_ttft_ms": 250.83,
          "slo_breached": false
        },
        {
          "sec": 260,
          "demand_tokens": 68000,
          "ready_replicas": 10,
          "booting_replicas": 3,
          "demand_to_kv_capacity_ratio": 0.3778,
          "kv_utilization": 0.3778,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.6636,
          "p99_ttft_ms": 211.78,
          "slo_breached": false
        },
        {
          "sec": 280,
          "demand_tokens": 56000,
          "ready_replicas": 12,
          "booting_replicas": 1,
          "demand_to_kv_capacity_ratio": 0.2593,
          "kv_utilization": 0.2593,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.6185,
          "p99_ttft_ms": 189.26,
          "slo_breached": false
        },
        {
          "sec": 300,
          "demand_tokens": 50000,
          "ready_replicas": 13,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.2137,
          "kv_utilization": 0.2137,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.6012,
          "p99_ttft_ms": 180.6,
          "slo_breached": false
        }
      ]
    },
    "leading_kv_and_velocity": {
      "mode": "leading_kv_and_velocity",
      "cold_start_seconds": 22,
      "peak_kv_utilization": 1.0,
      "peak_p99_ttft_ms": 502.27,
      "max_ready_replicas": 16,
      "slo_breach_windows": 1,
      "timeline": [
        {
          "sec": 0,
          "demand_tokens": 48000,
          "ready_replicas": 4,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.6667,
          "kv_utilization": 0.6667,
          "warm_standby_remaining": 2,
          "gpu_compute_utilization": 0.7733,
          "p99_ttft_ms": 266.67,
          "slo_breached": false
        },
        {
          "sec": 20,
          "demand_tokens": 52000,
          "ready_replicas": 4,
          "booting_replicas": 1,
          "demand_to_kv_capacity_ratio": 0.7222,
          "kv_utilization": 0.7222,
          "warm_standby_remaining": 1,
          "gpu_compute_utilization": 0.7944,
          "p99_ttft_ms": 277.22,
          "slo_breached": false
        },
        {
          "sec": 40,
          "demand_tokens": 74000,
          "ready_replicas": 5,
          "booting_replicas": 4,
          "demand_to_kv_capacity_ratio": 0.8222,
          "kv_utilization": 0.8222,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.8324,
          "p99_ttft_ms": 296.22,
          "slo_breached": false
        },
        {
          "sec": 60,
          "demand_tokens": 108000,
          "ready_replicas": 6,
          "booting_replicas": 6,
          "demand_to_kv_capacity_ratio": 1.0,
          "kv_utilization": 1.0,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.9,
          "p99_ttft_ms": 502.27,
          "slo_breached": true
        },
        {
          "sec": 80,
          "demand_tokens": 142000,
          "ready_replicas": 9,
          "booting_replicas": 6,
          "demand_to_kv_capacity_ratio": 0.8765,
          "kv_utilization": 0.8765,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.8531,
          "p99_ttft_ms": 320.88,
          "slo_breached": false
        },
        {
          "sec": 100,
          "demand_tokens": 168000,
          "ready_replicas": 12,
          "booting_replicas": 4,
          "demand_to_kv_capacity_ratio": 0.7778,
          "kv_utilization": 0.7778,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.8156,
          "p99_ttft_ms": 287.78,
          "slo_breached": false
        },
        {
          "sec": 120,
          "demand_tokens": 176000,
          "ready_replicas": 15,
          "booting_replicas": 1,
          "demand_to_kv_capacity_ratio": 0.6519,
          "kv_utilization": 0.6519,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.7677,
          "p99_ttft_ms": 263.85,
          "slo_breached": false
        },
        {
          "sec": 140,
          "demand_tokens": 172000,
          "ready_replicas": 16,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.5972,
          "kv_utilization": 0.5972,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.7469,
          "p99_ttft_ms": 253.47,
          "slo_breached": false
        },
        {
          "sec": 160,
          "demand_tokens": 160000,
          "ready_replicas": 16,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.5556,
          "kv_utilization": 0.5556,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.7311,
          "p99_ttft_ms": 245.56,
          "slo_breached": false
        },
        {
          "sec": 180,
          "demand_tokens": 144000,
          "ready_replicas": 16,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.5,
          "kv_utilization": 0.5,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.71,
          "p99_ttft_ms": 235.0,
          "slo_breached": false
        },
        {
          "sec": 200,
          "demand_tokens": 124000,
          "ready_replicas": 16,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.4306,
          "kv_utilization": 0.4306,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.6836,
          "p99_ttft_ms": 221.81,
          "slo_breached": false
        },
        {
          "sec": 220,
          "demand_tokens": 102000,
          "ready_replicas": 16,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.3542,
          "kv_utilization": 0.3542,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.6546,
          "p99_ttft_ms": 207.29,
          "slo_breached": false
        },
        {
          "sec": 240,
          "demand_tokens": 84000,
          "ready_replicas": 16,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.2917,
          "kv_utilization": 0.2917,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.6308,
          "p99_ttft_ms": 195.42,
          "slo_breached": false
        },
        {
          "sec": 260,
          "demand_tokens": 68000,
          "ready_replicas": 16,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.2361,
          "kv_utilization": 0.2361,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.6097,
          "p99_ttft_ms": 184.86,
          "slo_breached": false
        },
        {
          "sec": 280,
          "demand_tokens": 56000,
          "ready_replicas": 16,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.1944,
          "kv_utilization": 0.1944,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.5939,
          "p99_ttft_ms": 176.94,
          "slo_breached": false
        },
        {
          "sec": 300,
          "demand_tokens": 50000,
          "ready_replicas": 16,
          "booting_replicas": 0,
          "demand_to_kv_capacity_ratio": 0.1736,
          "kv_utilization": 0.1736,
          "warm_standby_remaining": 0,
          "gpu_compute_utilization": 0.586,
          "p99_ttft_ms": 172.99,
          "slo_breached": false
        }
      ]
    }
  },
  "canary_rollouts": {
    "defective_int4_build": {
      "completed_100_percent": false,
      "rolled_back": true,
      "final_canary_traffic_percent": 0,
      "rollback_reasons": [
        "tpot_slo_regression",
        "quality_score_regression"
      ],
      "stages": [
        {
          "stage_percent": 5,
          "requests_evaluated": 250,
          "decision": "promote",
          "passed": true,
          "tpot_delta_ms": 2.8000000000000007,
          "quality_drop": 0.008000000000000007,
          "canary_error_rate": 0.001,
          "reasons": []
        },
        {
          "stage_percent": 25,
          "requests_evaluated": 1200,
          "decision": "rollback_to_0_percent",
          "passed": false,
          "tpot_delta_ms": 19.6,
          "quality_drop": 0.028000000000000025,
          "canary_error_rate": 0.002,
          "reasons": [
            "tpot_slo_regression",
            "quality_score_regression"
          ]
        }
      ]
    },
    "validated_fp8_build": {
      "completed_100_percent": true,
      "rolled_back": false,
      "final_canary_traffic_percent": 100,
      "rollback_reasons": [],
      "stages": [
        {
          "stage_percent": 5,
          "requests_evaluated": 250,
          "decision": "promote",
          "passed": true,
          "tpot_delta_ms": -3.599999999999998,
          "quality_drop": 0.0030000000000000027,
          "canary_error_rate": 0.001,
          "reasons": []
        },
        {
          "stage_percent": 25,
          "requests_evaluated": 1200,
          "decision": "promote",
          "passed": true,
          "tpot_delta_ms": -3.3999999999999986,
          "quality_drop": 0.0040000000000000036,
          "canary_error_rate": 0.001,
          "reasons": []
        },
        {
          "stage_percent": 100,
          "requests_evaluated": 5000,
          "decision": "promote",
          "passed": true,
          "tpot_delta_ms": -3.0,
          "quality_drop": 0.0040000000000000036,
          "canary_error_rate": 0.001,
          "reasons": []
        }
      ]
    }
  },
  "canary_gate_policy": {
    "max_tpot_regression_ms": 6.0,
    "max_tpot_ceiling_ms": 45.0,
    "max_quality_drop": 0.015,
    "max_error_rate": 0.005
  }
}
Saved output report for plan cold starts and gate model rollouts; execution mode and values come from the linked raw result.

This image is an application-generated report of the saved program result, not a new inference run. Open the full-resolution report or read the raw result.

Budget warm capacity and observation time

Warm capacity costs money while idle; waiting for demand can miss the latency target during startup. Faster startup and better steady-state performance may require different compilation choices. A canary with too few or unrepresentative requests can miss regressions even when its gates pass.

Cold-start delay. Requested replicas remain booting until their ready time. The initial warm pool is finite and is not replenished instantaneously; this scenario still has a latency breach.

Canary rejection. A supplied quality or latency regression stops the planned promotion sequence and recommends rollback. No live deployment is rolled back by this policy function.

Read the executable checks
Executable checksSource · pythonDownload
"""Unit and failure-mode tests for GPU autoscaling with slow cold starts and canary model rollouts."""

from __future__ import annotations

import unittest

from lab import (
    AutoscalerConfig,
    CanaryGate,
    CanaryStageSample,
    simulate_autoscaler,
    simulate_cold_start_and_rollout,
)


class AutoscalingAndRolloutTest(unittest.TestCase):
    def test_threshold_breaches_are_not_rounded_away(self):
        gate = CanaryGate()
        for sample in [
            CanaryStageSample(5, 200, 28, 34.001, 0.8, 0.8, 0),
            CanaryStageSample(5, 200, 28, 28, 0.8, 0.78499, 0),
        ]:
            self.assertFalse(gate.evaluate_stage(sample)["passed"])

    def test_combined_fast_start_scenario_keeps_finite_standby_accounting(
        self,
    ) -> None:
        report = simulate_cold_start_and_rollout()
        leading = report["autoscaling_comparison"]["leading_kv_and_velocity"]
        self.assertEqual(leading["slo_breach_windows"], 1)
        self.assertGreater(leading["peak_p99_ttft_ms"], 450.0)
        remaining = [s["warm_standby_remaining"] for s in leading["timeline"]]
        self.assertEqual(remaining, sorted(remaining, reverse=True))
        self.assertEqual(remaining[-1], 0)
        self.assertGreaterEqual(leading["max_ready_replicas"], 10)

    def test_scaling_on_gpu_utilization_reacts_after_slo_breach(self) -> None:
        reactive = simulate_autoscaler(
            AutoscalerConfig(
                mode="reactive_gpu_util",
                cold_start_seconds=180,
                warm_standby_replicas=0,
            )
        )
        leading = simulate_autoscaler(
            AutoscalerConfig(
                mode="leading_kv_and_velocity",
                target_kv_utilization=0.68,
                warm_standby_replicas=2,
                fast_weight_stream_seconds=22,
            )
        )
        # Reactive scaling triggers late and waits 180s for weights, breaching the 450ms TTFT SLO across the surge.
        self.assertGreaterEqual(reactive["slo_breach_windows"], 6)
        self.assertGreater(reactive["peak_p99_ttft_ms"], 1800.0)
        self.assertEqual(leading["slo_breach_windows"], 1)

    def test_canary_recommends_rollback_on_quality_or_tpot_regression(
        self,
    ) -> None:
        report = simulate_cold_start_and_rollout()
        defective = report["canary_rollouts"]["defective_int4_build"]
        healthy = report["canary_rollouts"]["validated_fp8_build"]

        self.assertTrue(defective["rolled_back"])
        self.assertFalse(defective["completed_100_percent"])
        self.assertEqual(defective["final_canary_traffic_percent"], 0)
        self.assertIn("tpot_slo_regression", defective["rollback_reasons"])
        self.assertIn("quality_score_regression", defective["rollback_reasons"])
        # Stage 100% never runs after Stage 25% trips the rollback gate.
        self.assertEqual(len(defective["stages"]), 2)

        self.assertFalse(healthy["rolled_back"])
        self.assertTrue(healthy["completed_100_percent"])
        self.assertEqual(healthy["final_canary_traffic_percent"], 100)

    def test_canary_gate_rejects_elevated_error_rate_even_when_fast(self) -> None:
        gate = CanaryGate()
        verdict = gate.evaluate_stage(
            CanaryStageSample(
                stage_percent=5,
                requests_evaluated=200,
                baseline_p99_tpot_ms=28.0,
                canary_p99_tpot_ms=25.0,
                baseline_exact_match=0.840,
                canary_exact_match=0.839,
                canary_error_rate=0.018,
            )
        )
        self.assertFalse(verdict["passed"])
        self.assertEqual(verdict["decision"], "rollback_to_0_percent")
        self.assertIn("error_rate_breach", verdict["reasons"])


class CanaryValidationTests(unittest.TestCase):
    def test_nonfinite_sample_cannot_pass_gates(self):
        with self.assertRaises(ValueError):
            CanaryStageSample(5, 200, 20, float("nan"), 0.8, 0.8, 0)

    def test_empty_observation_cannot_be_promoted(self):
        with self.assertRaises(ValueError):
            CanaryStageSample(5, 0, 20, 20, 0.8, 0.8, 0)


if __name__ == "__main__":
    unittest.main()


class GuardBoundaryTests(unittest.TestCase):
    def test_rejects_invalid_boundary_inputs_with_specific_reason(self):
        import lab
        cases = [('AutoscalerConfig("reactive_gpu_util",min_replicas=0)', 'replica bounds'), ('AutoscalerConfig("reactive_gpu_util",target_kv_utilization=0)', 'target_kv'), ('CanaryStageSample(5,1,1,1,2,1,0)', 'canary rate'), ('a=CanaryStageSample(5,1,1,1,1,1,0)\nCanaryGate().run_rollout([a,a])', 'stages must')]
        for code, reason in cases:
            with self.subTest(code=code):
                with self.assertRaisesRegex((ValueError, MemoryError, KeyError, RuntimeError), reason):
                    exec(code, vars(lab).copy())

Try a change and explain the result

Increase startup delay and inspect how long requested replicas remain unavailable. Then supply a canary sample that breaches one criterion. Explain which decision the policy makes and which real orchestration steps remain.

An autoscaler requests a new replica. Can traffic immediately rely on it?

Choose one answer, then check your reasoning.

Read the explanation

Requested, booting, and ready capacity are distinct states. Startup delay belongs in the plan.

Connect decisions to orchestration

Measure startup on the deployed image and storage path. Include readiness, draining, timeout, and rollback tests in the real adapter. Use representative task-quality evaluation and a minimum observation plan; fixture scores do not validate a release.

References

Sources checked October 24, 2026. Runtime-specific commands and results apply to the recorded versions; linked rolling documentation can change. The application scenarios are teaching examples, not claims about an author’s production incident.

Written by Arjun Prabhulal.

Module 6

Run at production scale