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.
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.
python3 lab.py
python3 -m unittest -v test_lab.pyRead lab.py
"""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
{
"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
}
}
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
"""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.
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.