The one idea this stage is built on
FramingEvery compute service any cloud sells is a position on a single dial: how much undifferentiated heavy lifting you hand the provider. Turn the dial one way and you get control — kernel modules, GPU drivers, a filesystem you own. Turn it the other way and you get less toil — no patching, no capacity planning, no 3 a.m. node replacement — but you accept the platform's rules and a completely different cost curve.
Work your users would never pay for and your competitors also have to do: racking servers, patching kernels, replacing dead disks, installing NVIDIA drivers, writing a scale-out script. It is real work; it is just not your work. Every rung up the ladder is a decision to stop doing some of it.
There is no "right" rung. There is only a rung that matches the shape of your workload. That is the second idea, and it is the one that shows up on your bill.
The abstraction ladder
Click any rungTwo more ideas you'll carry into every tab
FramingSteady baseline → reserved or committed VMs. You know you need it; buy it cheaper.
Bursty with a known floor → an autoscaling group or a container platform. A warm minimum plus elastic headroom.
Event-driven and spiky, with real idle gaps → serverless. Pay per invocation, pay nothing at 3 a.m.
The villain of this stage is paying for idle: an always-on fleet sized for a peak that happens twice a day. Its quieter twin is choosing too much control — running a Kubernetes cluster to serve one stateless API the platform would have run for free.
Autoscaling only works if a replica can be created and destroyed without anyone caring which one they got. That requires the application to keep no durable state in the process. Domain state goes to a database; session state travels in the request.
Say it plainly: state belongs in the storage tier — Stage 5 — not on the instance. Without that discipline, every rung above "single VM" quietly degrades into a single VM with extra steps: sticky sessions, replicas that can't be killed, a scale-in event that loses a shopping cart.
How the four sessions build
Map · click to jumpEach session runs the same five beats: why it exists → core concepts (three passes, vendor-neutral first, then the three clouds) → reality check (numbers on paper plus cited current sources) → apply it (your GenAI context, one optional lab) → bridge.
What you already have, and what this stage adds
ContinuityCarried forward from Stages 1–2
- The shared-responsibility line. Stage 1 taught it as IaaS / PaaS / SaaS. Compute is where that line becomes a purchase decision — the ladder above is the shared-responsibility line, drawn as a dial.
- Regions, zones, resource hierarchy. Every fleet you build in this stage is placed against zones; a group that lives in one zone is a group that dies with one zone.
- Identity. Every compute resource here runs as an identity — an execution role, a managed identity, a service account. When a tab says "attach the role," that is Stage 2 machinery, not new material.
- The Rosetta-Stone habit. Keep translating. By the end of S4 you should be able to say "ASG ⇄ VMSS ⇄ MIG" without pausing.
What this stage is not
- Not a Kubernetes course. You already run KServe. The tabs teach the cloud-specific wrapper — node pools, Autopilot, Auto Mode, Fargate profiles — not pods and deployments.
- Not storage. Where state goes is Stage 5. This stage only insists, repeatedly, that it goes somewhere else.
- Not networking. Load balancers appear as the thing an autoscaler registers instances with; their internals come later.
- Not a price list. Prices in the reality checks are worked examples with dates attached. The method is the durable part; re-check the rate card before you commit money.
Pick a compute abstraction from workload shape and defend the cost trade-off. Map EC2 / ASG / ECS / EKS / Fargate / Lambda to their Azure and GCP equivalents instantly. Reason about autoscaling policy, cold starts, spot interruption, and GPU selection well enough to argue with a colleague — and to answer the exam question.
Why this session exists
10%Somebody sized a server for the worst Tuesday of the year, and then paid for that Tuesday 365 days running.
That is the oldest cost mistake in infrastructure and the cloud did not fix it — it just made it easier to commit at scale. Before you can pick between a VM, a container, and a function, you need to see the gap the cloud is actually selling you: the difference between capacity you provisioned and capacity you used.
The cloud's answer is a third option — make the capacity line follow the demand curve — and the whole rest of this stage is machinery for doing that. But the machinery only works if your application will tolerate being replicated and killed at will. So we start there.
Core concepts
50% · vendor-neutral first1 · The ladder, properly
Think about getting a meal. Bare metal is a farm: you own the land and the animals, and dinner is entirely your problem. A VM is a rented kitchen: someone else maintains the building and the gas line, you still cook. A managed VM group is a rented kitchen with staff who notice when a burner dies and light a second stove when the queue grows. Managed Kubernetes is a commercial kitchen with a head chef's system — you still hire the line cooks. Serverless containers is a ghost kitchen: you hand over the recipe in a box, they cook it when orders arrive. Functions is a vending machine: you supply one snack, the machine handles everything else and refuses to sell anything bigger.
The analogy holds where it matters: as you go up, you lose the ability to do unusual things, and you stop paying for an empty kitchen.
Mechanically, each rung is a different answer to who owns the boundary. Below the boundary the provider automates; above it, you do.
| Rung | The provider's unit of work | Your unit of work | What "scaling" means here |
|---|---|---|---|
| Bare metal | a rack, a power feed | hypervisor, OS, everything | a purchase order |
| VM | a virtualised machine, booted | OS patching, runtime, app, capacity | you resize or clone it by hand |
| Managed VM group | identical VMs from a template, kept alive | the image, the health endpoint, the policy | a number goes up; new VMs boot |
| Managed Kubernetes | the control plane; often the nodes too | manifests, resource requests, node pool shape | pods scale, then nodes scale under them |
| Serverless containers | everything but your image | a container that listens on a port | instances appear per unit of concurrency |
| Functions | everything but your handler | one function signature | invisible — one environment per concurrent event |
The mechanism, in one config fragment — this is what "scaling" is, everywhere:
min_instances: 2 # the floor you pay for even at 3am
max_instances: 20 # the ceiling that stops a runaway bill
metric: cpu_utilization # the signal the controller watches
target: 60 # the number it steers toward
Every autoscaler on every cloud is those four lines plus timing knobs. Learn them once.
Going up costs you control in specific, predictable places. Kernel modules and custom drivers stop being possible around the serverless-container rung. Long-running processes stop being possible at the function rung (AWS caps a single Lambda invocation at 15 minutes; Cloud Run caps a request at 60 minutes). GPUs are available on some rungs and flatly absent on others — AWS Fargate has no GPU support at all, which is why a serving stack ends up on EC2 or EKS nodes rather than "serverless containers" on AWS.
Going up also changes the cost curve's shape, not just its height. A VM has a high fixed cost and a near-zero marginal cost per request. A function has zero fixed cost and a meaningful marginal cost per request. Two lines with different slopes cross somewhere — and that crossing point, not a vendor's marketing, is the real decision. We compute one in the reality check below.
The ladder is bending. The clean story — "VMs are cheap at scale, functions are cheap when idle" — is being deliberately eroded by the vendors. AWS shipped Lambda Managed Instances in December 2025: Lambda functions running on EC2 capacity you specify, so you can apply EC2 pricing models to a serverless programming model. Google shipped Cloud Run worker pools and GPU support. Expect the middle of the ladder to keep filling in.
2 · Stateless applications — why this is the hinge
Data that outlives a single interaction and is shared by everyone: an account balance, a product record, a model registry entry.
Data that only makes sense for one client across a series of requests: who they are, what's in the cart, which step of the wizard they're on.
A stateless application is not an application with no state. Every program more complicated than a calculator has state. A stateless application is one that keeps its state somewhere else — domain state in a database, session state passed in with each request.
The analogy: a good hotel concierge is stateless. They don't remember you; your key card tells them your room, and the property management system tells them the materialing. That's exactly why any concierge on shift can serve you, why the hotel can add a second one at Christmas, and why one going home mid-shift breaks nothing.
Inside a request the application is free to hold data — it loads what it needs from the database using the context in the request parameters, does the work, writes back, and returns. Each call to the API defines one logical transaction, and at the end of it the process flushes everything. Concurrent requests each get their own thread with their own temporary variables, so no two clients can see each other's data.
The refactor, in two lines of pseudocode — the whole change is one keyword:
class ProductManager:
self.cache = {} # ← instance variable: STATEFUL, breaks replication
def get(name): return self.cache.setdefault(name, db.get(name))
class ProductManager:
def get(name): return db.get(name) # ← temporary only: STATELESS, replicates freely
Holding domain data in memory does make individual reads faster. Statelessness gives that up, and it buys five things back:
- Startup time. No prefetch, so a new replica is useful in seconds instead of minutes. This is exactly why autoscaling feels responsive on stateless services and sluggish on stateful ones.
- Memory ceiling. Replicas stop each hoarding their own copy of the same rows, so you fit more concurrency per gigabyte.
- Consistency. Nothing can go stale, because nothing is duplicated.
- Graceful shutdown. A stateless process is always safe to kill between transactions. That is what makes spot instances and rolling deploys viable.
- Recovery point. A crash loses only in-flight transactions. A stateful process that persists hourly has an effective recovery point objective of one hour — every crash silently deletes up to an hour of user work.
Where it breaks: re-reading the same rows on every request can saturate the network path to the database. The fix is not to cache in the process — it is an external cache (Redis, Memcached), which keeps the application stateless while restoring the read speed. Similarly, if session state grows large, passing it on every request becomes the bottleneck; the answer is to design an API with few, small parameters — pass an ID, not an object graph.
Sticky sessions are the tell. If your load balancer must route a user back to the same instance, you have not externalised session state, and every rung above "single VM" will fight you. The twelve-factor formulation is blunt about it: sticky sessions violate the model and should never be relied on; session data belongs in a store with time expiry.
3 · Replicable applications — running reliably on unreliable parts
Traditional IT made systems reliable by buying reliable hardware. That gets expensive fast — and it never reaches zero outages, because there are also planned ones. The cloud takes the opposite bet: assume everything fails, and make the application redundant rather than the machine. One vendor who took this seriously deliberately bought cheaper RAM with a higher failure rate; if a batch turned out too reliable, they assumed they were being overcharged.
A replicable application is one the platform can deploy many times over from the same package, where the copies don't know about each other and don't interfere. The platform's deployer runs repeatedly; each run produces an equivalent replica. Running many replicas instead of one big one solves both problems at once — scalability, because you get the capacity of several machines, and reliability, because when one machine dies only its replicas die.
The replicas coordinate only through shared backend services. That's the whole architecture.
Teams new to this usually say "our app won't work that way." The useful move is to hunt for the specific assumption that only one copy would ever exist. The recurring offenders:
- Singletons. An object designed to have exactly one instance shared globally. Two replicas each build their own, which defeats the purpose. Electing one replica to own it works right up until that replica crashes holding the lock — then nothing works, including the replacement.
- Block storage. A volume typically cannot be attached to more than one workload. Each replica creates its own, each knows only its own data, and when a replica dies its data is effectively lost. Use a shared database service instead.
- Fixed IP addresses or hostnames. The first replica claims it; the rest can't start.
- In-process locks and semaphores. They coordinate threads inside one process and are invisible to the process next door.
Note the theme: replicas almost always work fine internally. The breakage is in how they use external resources, under an assumption of exclusivity that was true on one server and is false on twelve.
Two connections worth holding onto. First, replicas are usually copies of a VM image or a container image that wraps the application package — which is why "containerise it" and "make it replicable" feel like the same project. Second, statelessness makes replication dramatically easier: when replicas are stateless they are equivalent throughout their lifetimes, so routing is trivial and, on scale-in, any replica is an equally valid one to kill.
4 · The autoscaling control loop
Every autoscaler is a thermostat. Walk the loop:
A metric crosses a threshold
The controller samples an aggregate signal — average CPU across the group, requests per instance, queue depth, or a custom metric. Aggregate is the key word: the target is the average across all healthy members, not any single one. A traffic spike arrives; average CPU climbs from 45% to 78%.
Sampling interval matters. Five-minute metric granularity means up to five minutes of blindness before the loop even starts.
The policy converts the signal into a number
Three policy families, everywhere: target tracking ("hold average CPU at 60%" — the controller does the arithmetic), step or rule based ("above 80% for 5 minutes, add 2"), and scheduled ("at 08:00 weekdays, floor of 10"). A fourth, predictive, forecasts from history and acts before the load arrives — it needs about a week of data and only pays off for cyclical traffic.
If several metrics are configured, the controller computes a recommendation per metric and takes the maximum — so the worst signal always wins.
New instances are created from a template
The group boots new members from an immutable template — machine type, image, disk, network, startup script, and the identity the instance runs as. This is why the template is the real artifact: replicas are only equivalent because they came from the same one.
Boot to "OS ready" is often under a minute. Boot to "my application is warm and serving" can be five, and for a model server loading weights, much longer.
Health checks gate entry to the load balancer
A new instance does not receive traffic until it passes a health check a configured number of consecutive times. Two dials define "aggressive": the check interval and the healthy/unhealthy thresholds. A shallow check (TCP connect succeeded) proves the VM booted; a deep check (HTTP 200 from /ready) proves the application works.
Health checks do double duty: they also detect an instance that is running but wedged, and trigger its replacement.
Warmup and cooldown stop the loop oscillating
Two distinct timers, routinely confused. Warmup excludes a brand-new instance from the aggregate metric until it has settled — otherwise a booting instance at 100% CPU makes the group look overloaded and triggers more scale-out. Cooldown blocks any further scaling action for a fixed window so the metric can respond to what you just did.
Set cooldown too short and you scale again before the new capacity is even serving. That is how a spike becomes a stampede.
Capacity is removed — carefully, and asymmetrically
Scale-in is deliberately more conservative than scale-out, because being slightly over-provisioned costs money while being under-provisioned costs users. Controllers add a stabilisation window (only shrink if the metric has stayed low across a trailing period) and a maximum reduction (never remove more than N instances or N% at once).
Then a termination policy picks which instance dies. With stateless replicas this is a free choice — which is the payoff for all that discipline earlier.
5 · Decision tree — VM, container, or serverless?
single instance, or a managed group if you need more than one
functions are capped; containers are not
functions for events, serverless containers for HTTP
you are paying for the ecosystem, not the scheduler
Note what is not in this tree: cost. Cost is a consequence of the shape, not an input to it — and once the shape is fixed, the purchasing model (on-demand vs spot vs committed) is where the real money is. That is a separate decision, taken per session below.
6 · The same ladder in three dialects
Virtual machines. IaaS, the VM rung.
EC2 Auto Scaling GroupA managed fleet of identical EC2 instances. The managed-VM-group rung.
ECSAWS's own container orchestrator. Container rung, AWS-native flavour.
EKSManaged Kubernetes. Container rung, portable flavour.
FargateServerless compute under ECS or EKS — not an orchestrator itself.
LambdaFunctions. The top rung.
Virtual machines. IaaS, the VM rung.
Virtual Machine Scale Sets (VMSS)A managed fleet of identical VMs. The managed-VM-group rung.
Azure Container Instances (ACI)Single containers, no orchestrator. A rung of its own — simplest possible container.
Azure Kubernetes Service (AKS)Managed Kubernetes.
Container Apps & App ServiceServerless containers, and managed web-app hosting.
Azure FunctionsFunctions. The top rung.
Virtual machines. IaaS, the VM rung.
Managed Instance Group (MIG)A managed fleet from an instance template. The managed-VM-group rung.
Google Kubernetes Engine (GKE)Managed Kubernetes, in two modes — Standard and Autopilot.
Cloud RunServerless containers. Services, jobs, and worker pools.
Cloud Run functionsFunctions, built on Cloud Run. Formerly Cloud Functions.
App EngineThe original PaaS; still supported, rarely the new choice.
ASG ⇄ VMSS ⇄ MIG. ECS/EKS ⇄ AKS ⇄ GKE. Fargate ⇄ Container Apps ⇄ Cloud Run. Lambda ⇄ Azure Functions ⇄ Cloud Run functions. If you can produce those four lines cold, you can navigate any of the three consoles. Everything in S2–S4 is detail hanging off them.
Reality check
25% · numbers on paperWorked example — the same API, three shapes
The workload. A stateless HTTP API. 50 million requests per month. Average 200 ms of work per request. Diurnal traffic: mean 19 req/s, peak 77 req/s, trough 5 req/s. One 2-vCPU instance safely serves about 20 req/s of this workload. Reference instance price $0.10/hour (a general-purpose 2-vCPU class, us-east-1, mid-2026), 730 hours in a month, one load balancer at about $22/month.
| Shape | Sizing | Compute | + LB | Monthly | What you gave up |
|---|---|---|---|---|---|
| Always-on, peak-sized | ceil(77 ÷ 20) = 4, +1 spare = 5 instances, 24/7 | 5 × 730 × $0.10 = $365 | $22 | $387 | nothing operationally — you just overpaid |
| Autoscaling group | min 2, max 6, target tracking; time-weighted average ≈ 3.2 instances | 3.2 × 730 × $0.10 = $234 | $22 | $256 | ~3 minutes of lag when a spike starts |
| Function, 1 GB | no instances; 50M × 0.2s × 1GB = 10M GB-seconds | (10M − 0.4M free) × $0.0000166667 = $160 + (50M − 1M) × $0.0000002 = $9.80 | $0* | $170 | cold starts, a 15-minute ceiling, no GPU |
Read the result honestly. The always-on fleet is the clear loser and that is the lesson of the session. But the interesting finding is the near-tie between the other two. Serverless has no floor and a higher slope; the fleet has a $168/month floor and a lower slope. Below roughly 50M requests/month, serverless wins outright. Above it, the fleet pulls ahead — but only if you keep it busy. The break-even is not a request count, it is a utilisation number: at this configuration the fleet stops winning once its average utilisation drops below about two-thirds.
The $0 in the LB column assumes a direct function URL or an application load balancer at roughly $0.10 per million requests. Put an API gateway in front instead at a typical $3.50 per million and those 50M requests add $175 — more than doubling the total and flipping the comparison. On every cloud, the serverless bill is dominated by the things around the function: gateway, logging, egress, NAT. Price the whole path, not the compute line.
Three current sources worth reading in full
Apply it
10% · your context + one labWhere you have already done this without calling it this
- Your Modal deployment of Gemma via vLLM is the top of the ladder with a GPU bolted on. Scale-to-zero, per-second billing, no instances you name — that is the serverless-container rung. What you learned there transfers directly to Cloud Run GPU and Azure Container Apps serverless GPU; those are the same product shape from the hyperscalers. The thing to notice is what Modal hid from you: the cold-start cost of pulling an image and loading weights. In S4 we put a number on it.
- Your Vertex AI script — T4 GPUs, autoscaling 1 to 3, spot instances — is the managed-VM-group rung. "Min 1, max 3" is exactly the
min_instances / max_instancespair from the config fragment above. The min 1 is the important choice: you decided this endpoint keeps a warm floor rather than scaling to zero, which is the correct instinct for inference serving and the wrong one for a training job. - Spot for training, warm for serving is the workload-shape rule applied to GPUs, and it falls straight out of the statelessness discussion. A training job checkpoints — it tolerates being killed, so interruptible capacity is nearly free money. An inference endpoint holds no state either, but it holds a latency promise, and a two-minute eviction notice mid-request breaks that promise. Same statelessness, different constraint.
- KServe already taught you the hard half. Readiness probes, replica counts, resource requests — that is steps 2 through 5 of the control loop. S2–S4 mostly teach you what each cloud calls those knobs.
Optional lab — model the crossover before you touch a console
This session's lab is deliberately paper-only, because the concept is arithmetic and the console teaches nothing here.
- Take one service you have actually run — the vLLM Gemma endpoint is ideal. Write down: requests per day, average seconds of compute per request, memory needed, and the longest idle gap in a 24-hour window.
- Compute the three columns from the table above for your numbers. Use $0.0000166667 per GB-second and $0.20 per million requests for the function column (x86 rates, us-east-1, verified July 2026; Arm is about 20% cheaper).
- Find your crossover: solve
floor + slope_fleet × x = slope_serverless × xfor x in millions of requests. - Then check whether your longest idle gap is long enough for scale-to-zero to matter. If your traffic never drops below 30% of peak, serverless is buying you very little and you should be shopping for a committed-use discount instead.
If you want to touch something anyway: open your cloud's pricing calculator and set a budget alert on your account before the next session — GCP budgets, AWS Budgets, Azure budgets. Every subsequent lab in this stage assumes one is already in place. Set it at a number that would annoy you, not a number that would ruin you — the point is to find out early.
You now have the ladder, the hinge that makes it work, and the arithmetic that decides between rungs. Next: AWS puts more rungs on that ladder than anyone — and names them all confusingly.
S2 turns "VM, container, serverless" into EC2, ASG, ECS, EKS, Fargate, and Lambda, and answers the question your GPU work actually needs: why vLLM serving is an EC2 instance and never a Lambda.
Why this session exists
10%AWS sells six different ways to run the same container, and the difference between the cheapest and most expensive is roughly 5×.
AWS built its compute portfolio by accretion over twenty years, so the names encode history rather than logic. ECS and EKS are orchestrators; Fargate is not an orchestrator at all, it is a compute engine that runs underneath either one. Getting that one relationship wrong is the most expensive mistake teams make on AWS containers. This session gives you the map and, more importantly, the purchasing decision that sits on top of it.
§5 of the companion AWS reference is a broad services survey: §5.1 core services, §5.2 compute, §5.3 storage, §5.4 databases, §5.5 security, §5.6 workload selection, §5.7 deployment strategies. The compute portion is §5.2 only, and it is about three paragraphs per service — EC2, Lambda, ECS and EKS get a definition and a use-case list each. It contains no instance families, no purchasing options, no Auto Scaling Group mechanics, no Fargate, and no GPU coverage. The material is not wrong; it simply is not a compute section. Everything below the "what each service is" line is supplemented from current AWS documentation, and each such claim carries a link.
Core concepts
50%1 · EC2 — reading the name is half the skill
Virtual servers you launch, size, patch, and pay for by the second. The foundation everything else on this page is built on — including, quietly, most of the "serverless" products.
An EC2 instance type name is a compressed spec sheet. g6e.12xlarge is not a random string: it says G family (GPU, graphics and inference), 6th generation, e for enhanced memory, at the 12xlarge size point. Once you can read it, browsing 800 instance types becomes browsing about a dozen families.
Two attributes matter more than the rest for your work. g means Graviton — AWS's Arm processors, generally cheaper per unit of work and now the default recommendation for anything that compiles cleanly on Arm. d means local NVMe attached to the host, which matters enormously for a model server that wants to cache weights on fast local disk instead of pulling from object storage on every cold start.
This is the part of any cert guide that ages fastest. Current state:
| Family | Accelerator | Where it fits |
|---|---|---|
| G4dn | NVIDIA T4, 16 GB | Still available. Cheapest real inference GPU on AWS; fine for small models and for learning CUDA mechanics. |
| G5 | NVIDIA A10G, 24 GB | The previous inference workhorse; largely superseded by G6. |
| G6 | NVIDIA L4, 24 GB | The current default for single-GPU inference. g6.xlarge = 1× L4, 4 vCPU, 16 GiB. |
| G6e | NVIDIA L40S, 48 GB | When 24 GB of VRAM is not enough but you don't need an H100. g6e.xlarge gives ~45 GB usable. |
| P4d / P4de | NVIDIA A100, 40/80 GB | Previous-generation training. Still widely used, increasingly a spot-capacity story. |
| P5 / P5e / P5en | H100 (P5), H200 (P5e, P5en) | Serious training and large-model inference, with Elastic Fabric Adapter networking for multi-node. |
| P6-B200 / P6-B300 | NVIDIA Blackwell B200 / B300 | P6-B200 went GA May 2025: 8 Blackwell GPUs, 1,440 GB of HBM, roughly 2× P5en for training and inference. |
| P6e-GB200 / GB300 | Grace Blackwell NVL72 UltraServers | Frontier scale — up to 72 GPUs as one unit. Reserved through EC2 Capacity Blocks, not launched on demand. |
| Inf1 / Inf2 · Trn1 / Trn2 / Trn3 | AWS Inferentia and Trainium | AWS's own silicon. Cheaper per token if your stack tolerates the Neuron SDK; a real port for a vLLM shop, not a flag. |
Sources: EC2 P5 instances P6 and P6e UltraServers Deep Learning AMI recommended GPU instances. Availability and pricing move monthly; check before you budget.
2 · Purchasing options — where the money actually is
Same instance, same performance, up to a 90% price difference. This is the highest-leverage decision on the page.
Per-second billing with a 60-second minimum, no commitment. Every other option is quoted as a discount from this. Correct for genuinely unpredictable workloads, for the first four weeks of any new service (you cannot commit sensibly without data), and for the 20–30% of your fleet you want to keep uncommitted.
Compute Savings Plans commit you to a dollar amount of hourly compute spend for 1 or 3 years and apply automatically across EC2, Fargate, and Lambda, across families and regions. Discount runs up to about 66%. This is the flexible option — it survives you migrating from x86 to Graviton, or from EC2 to Fargate.
EC2 Instance Savings Plans narrow the commitment to one instance family in one region. You keep flexibility on size, OS, and tenancy; the discount rises to about 72%. Roughly six percentage points deeper than the Compute plan — but it breaks the moment you switch families.
AWS generally steers customers to Savings Plans over Reserved Instances now.
Commit to a specific instance configuration for 1 or 3 years, up to about 72% off. Two reasons they still exist: Savings Plans do not reserve capacity, so a zonal Reserved Instance is how you guarantee a machine will be there; and Savings Plans do not cover RDS, Aurora, ElastiCache, or Redshift, which still need RIs.
Spare EC2 capacity at a discount of up to 90% versus on-demand. Prices move gradually with supply and demand rather than by auction. When AWS needs the capacity back, your instance gets a two-minute interruption notice and is then terminated.
That two minutes is the whole design constraint. It is generous for a training job that checkpoints every ten minutes. It is not enough for a stateful database, and it is uncomfortable for a latency-SLA inference endpoint unless you have warm capacity elsewhere. Spot instances also cannot receive Savings Plan or RI discounts — the two models are mutually exclusive.
Reserve GPU instances in advance for a defined block of time. This is how the frontier-scale hardware is actually consumed — P6e-GB200 UltraServers, for instance, are accessed through Capacity Blocks rather than launched on demand. If you have ever failed to get A100 capacity in a region, this is the answer AWS built.
A whole physical EC2 server for your exclusive use, purchasable on-demand or through Savings Plans. The reason is almost never performance; it is bring-your-own server-bound software licences, or a compliance requirement for physical isolation. This is the bottom rung of the ladder from Tab 0.
A commitment at discount d breaks even when your duty cycle exceeds (1 − d). A 1-year commit at 30% off pays for itself if you run the resource more than 70% of the hours — about 511 hours a month. A 3-year commit at 50% off needs only 50%. Everything else — payment options, coverage percentages, layering strategies — is refinement on that line.
3 · Auto Scaling Groups, end to end
A logical fleet of EC2 instances that AWS keeps at a desired count, replaces when unhealthy, and resizes according to policy. Free itself — you pay only for the instances, EBS volumes, and load balancer.
Target tracking, the only policy most services need — this fragment is the mechanism:
TargetTrackingConfiguration:
PredefinedMetricSpecification: { PredefinedMetricType: ASGAverageCPUUtilization }
TargetValue: 60 # AWS creates and manages the CloudWatch alarms for you
EstimatedInstanceWarmup: 180 # seconds before a new instance counts toward the average
The timing knobs that decide whether it works
- Default instance warmup. How long after reaching InService before an instance contributes to the aggregated metric. Not enabled by default — AWS explicitly recommends configuring it. Without it, a booting instance pegged at 100% CPU drags the group average up and triggers more scale-out.
- Cooldown. A 300-second default pause after a scaling action. Note that target tracking ignores the static cooldown and uses warmup instead; step scaling honours it.
- Detailed monitoring. One-minute metric granularity instead of five. Without it, you have added up to four minutes of blindness to every scaling decision.
The lifecycle features worth knowing by name
- Lifecycle hooks pause an instance in
Pending:WaitorTerminating:Waitso external automation can bootstrap or drain it. - Instance refresh rolls a new launch-template version through the group with a minimum-healthy-percentage target and rollback. This is how you deploy a new AMI.
- Warm pools hold pre-initialised, stopped instances beside the group. They can hibernate, preserving memory state. You pay EBS storage for stopped instances but not compute.
- Termination policies choose which instance dies on scale-in — default, oldest, newest, oldest launch template, or an allocation strategy for spot.
4 · Containers — the relationship that trips everyone up
ECS and EKS are orchestrators. Fargate is a launch type. The real question is two-dimensional: which orchestrator (ECS or EKS) and which compute underneath (EC2 instances you manage, or Fargate). "ECS vs EKS vs Fargate" is a malformed question, and it is why a 0.5-vCPU service can cost $18 or $91 a month depending on which boxes you tick.
| Service | What it is | Control plane cost | The catch |
|---|---|---|---|
| Amazon ECS | AWS's proprietary container orchestrator. Task definitions, services, tight ALB/IAM/CloudWatch integration. No Kubernetes API. | $0 | AWS-only. Small ecosystem — no Helm, no operators, no Istio. |
| Amazon EKS | Managed upstream Kubernetes. Your manifests, Helm charts, operators, and GitOps tooling work unchanged. | $0.10/cluster-hour ≈ $73/mo $0.60/hr on extended support | You inherit Kubernetes. The fee is trivial at scale and absurd for one small service. |
| AWS Fargate | Serverless compute engine under either orchestrator. Each task or pod runs in its own Firecracker micro-VM. | n/a — per vCPU-second and GB-second | No GPU support. No Arm on EKS Fargate. EKS pods capped at 4 vCPU / 30 GB. ALB only, private subnets only, no DaemonSets. |
| EKS Auto Mode | GA since December 2024. AWS selects instance types, scales, patches the OS, and manages add-ons — Karpenter-style provisioning with an AWS-managed lifecycle. | EKS fee + compute | Newer, less escape-hatch than raw node groups. Closes most of the gap to ECS Fargate on operational effort. |
Node scaling inside EKS is its own layer: the Horizontal Pod Autoscaler scales pods, the Vertical Pod Autoscaler resizes them, and underneath, Karpenter (now generally preferred over the older Cluster Autoscaler) provisions right-sized nodes in seconds and consolidates underutilised ones. KEDA adds event-driven scaling on queue depth. If you know KServe, you already know this layer — what is new is only that the nodes are EC2 instances in an ASG.
5 · Lambda — and its hard edges
You supply a handler; AWS runs it in response to an event and bills per millisecond of execution. No instances exist from your point of view.
The limits that decide architecture
- 15 minutes maximum per invocation. Hard limit.
- 10,240 MB maximum memory. Hard limit. CPU scales with memory — this is the only performance dial.
- 1,000 concurrent executions by default per region, raisable to tens of thousands. New accounts start lower.
- 10,000 requests/second account limit, separate from concurrency and easy to hit first.
- Burst of 500 concurrency per 10 seconds, or 5,000 requests/second per 10 seconds, whichever comes first.
- 6 MB synchronous payload · 10 GB container image · 10 GB
/tmp. - No GPU. None. There is no GPU resource type, no CUDA drivers, and no public roadmap commitment.
What changed at re:Invent 2025
- Lambda Managed Instances. You define a capacity provider naming EC2 instance types, subnets, and scaling bounds; Lambda provisions, scales, patches, and routes to them. It exists because at steady high volume teams were re-architecting away from Lambda to reach bigger machines and Savings Plans. This is the ladder bending — serverless programming model, EC2 economics.
- Durable Functions. Multi-step workflows with automatic checkpointing that can pause for up to a year waiting on an event or a human, with no compute charge while waiting. This deletes the main reason to reach for an external orchestrator on long AI workflows.
- ECS Express Mode deploys a containerised app with one command, provisioning load balancer, autoscaling, networking, and domain.
6 · Decision tree — which AWS compute for this workload?
Fargate and Lambda are both out — neither offers GPU
or Lambda Durable Functions if it is a workflow that mostly waits
price the gateway and logs too, not just the function
Auto Mode unless you have a reason to manage node groups
7 · Three-cloud comparison — AWS-anchored
| Concept | AWS | Azure | Google Cloud |
|---|---|---|---|
| Virtual machine | EC2 instance | Azure Virtual Machine | Compute Engine instance |
| VM template | Launch template (versioned) | Scale set model / ARM template | Instance template (immutable) |
| Managed VM fleet | Auto Scaling Group | Virtual Machine Scale Set | Managed Instance Group |
| Discount for commitment | Savings Plans (66% compute / 72% instance) · RIs 72% | Reserved VM Instances, up to 72% · Hybrid Benefit | CUDs — resource-based to 55% (70% memory-opt), Flex 28%/46% |
| Automatic usage discount | none | none | Sustained use discounts, up to 30% — no commitment |
| Interruptible capacity | Spot, up to 90% off, 2-minute notice | Spot VMs, ~70–82% off, 30-second notice | Spot VMs, up to 91% off, ~30-second notice |
| Managed Kubernetes | EKS ($0.10/cluster-hr) · Auto Mode | AKS | GKE — Standard and Autopilot |
| Native orchestrator | ECS (free control plane) | none — ACI is unorchestrated single containers | none |
| Serverless containers | Fargate (under ECS/EKS) | Azure Container Apps | Cloud Run |
| Serverless GPU | not available — Fargate has no GPU, Lambda has no GPU | Container Apps serverless GPU — T4 and A100 | Cloud Run GPU — L4 and RTX PRO 6000 |
| Functions | Lambda — 15 min, 10 GB | Azure Functions — plan-dependent timeout | Cloud Run functions — on Cloud Run, 60 min |
| Single-L4 inference VM | g6.xlarge, 4 vCPU / 16 GiB | NVads A10 v5 family (A10, not L4) | g2-standard-4, 4 vCPU / 16 GB |
Reality check
25%Worked example — one L4 GPU, three ways to pay for it
Reference: g6.xlarge — 1× NVIDIA L4 24 GB, 4 vCPU, 16 GiB — at $0.8048/hour on-demand in us-east-1 (July 2026; regions and rates vary, verify before committing).
| Scenario | Arithmetic | Result | When this is right |
|---|---|---|---|
| Always on, on-demand | 730 h × $0.8048 | $587 / month | Never, if the workload is steady. This is the number every other option is trying to beat. |
| Training run, on-demand | 100 GPU-hours × $0.8048 | $80.48 | Short, urgent, can't tolerate interruption. |
| Training run, spot at 70% off | 100 h × $0.24 = $24 + rework: 17 interruptions × 7.5 min avg lost = 2.1 h ≈ +5% | ≈ $25 | Almost always, for training. 3.2× cheaper. Assumes you checkpoint every 15 minutes — the checkpoint interval is the risk control. |
| Serving, 24/7, 1-year commit | break-even duty cycle = 1 − d | commit if you run >(1−d) of hours | At d = 30%, you need 511+ hours/month. A 24/7 endpoint clears that trivially — so commit. |
A single g6.xlarge forgotten over a weekend: 62 hours × $0.8048 = $50. A p5.48xlarge (8× H100) forgotten the same weekend is roughly $2,500–4,000, depending on the rate you're paying. GPU instances do not idle cheaply — they idle at full price, because you are renting the silicon, not the utilisation. Every GPU lab in this stage ends with a teardown step, and you should treat that step as the most important line in the lab.
Why spot works for training and not for serving — the same fact, two conclusions
Both workloads are stateless in the Tab-1 sense. The difference is what they promise. A training job promises a result, eventually — so a two-minute eviction notice costs you the time since your last checkpoint and nothing else. An inference endpoint promises a latency percentile, continuously — and a correlated capacity reclaim can take several spot replicas at once, so "run two spot instances in two AZs" is weaker protection than it looks.
The production pattern that falls out: a committed or on-demand warm floor sized to your p50 traffic, with spot capacity layered on top for the peak. That is exactly the min/max pair from Tab 1, with a purchasing decision attached to each end.
Three current sources
Apply it
10%Why your vLLM serving is an EC2 instance and never a Lambda
Four independent disqualifications, any one of which is fatal:
- No GPU. Lambda has no GPU resource type, no CUDA drivers, and no billing dimension for one. This is not a limit you can raise.
- Memory ceiling. 10,240 MB is host memory, and it is less than the weights of most models you'd bother serving — before you consider that there is no VRAM at all.
- The 15-minute wall. Fine for a request; useless for a persistent server holding a KV cache and batching across requests. Continuous batching — the entire reason vLLM is fast — requires a long-lived process that sees many requests at once.
- Cold-start economics. Weight loading would land in the init phase and re-run on every cold start. Python ML functions already hit 3,000–4,500 ms of cold start without a model; add weights and the number becomes unbearable.
The AWS-native shape for your workload is therefore: a GPU instance in an Auto Scaling Group behind an application load balancer, with min = 1 (your warm floor — the same choice you already made on Vertex), a target-tracking policy on a custom metric (queue depth or tokens-in-flight, not CPU — CPU is meaningless on a GPU server), a generous instance warmup covering model load time, and a warm pool so scale-out doesn't pay the weight-loading cost twice. If you want Kubernetes semantics, the same thing on EKS with GPU node groups and KServe — which is closest to what you already know.
The Modal comparison is instructive. Modal gave you scale-to-zero GPU serving, which AWS does not sell: Fargate has no GPU, Lambda has no GPU. On AWS the closest equivalents are SageMaker asynchronous or serverless inference endpoints, or building it yourself with an ASG that scales to zero and eating the cold start. This is a genuine capability gap, and it is why Cloud Run GPU (S4) and Container Apps serverless GPU (S3) will feel more familiar to you than anything in this tab.
Optional lab — build the mechanism on a CPU instance
Do not use a GPU instance for this. The mechanics of an ASG are identical on a $0.0104/hour t3.micro and a $0.80/hour g6.xlarge, and you learn nothing extra from the expensive one. GPU instances are not free-tier eligible and cost several dollars per hour.
- Before anything: confirm your AWS Budget from S1 exists and is set low. AWS Budgets
- Create a launch template with
t3.micro(ort4g.microfor Arm), Amazon Linux 2023, and user-data that installs a one-line HTTP server returning the instance ID. - Create an ASG: min 1, desired 1, max 3, across two subnets in different AZs. Attach an application load balancer target group.
- Add a target-tracking policy at 40% average CPU, and set
EstimatedInstanceWarmupto 120 seconds. Enable one-minute detailed monitoring. - Drive load —
ab,hey, or a shell loop withstress-ngon the instance. Watch the group scale out, then watch how long the new instance takes to appear in the load balancer as healthy. That gap is the number that matters; it is what a warm pool exists to shrink. - Stop the load. Time the scale-in. Notice it is deliberately slower.
- Teardown, in this order: delete the ASG (this terminates the instances — deleting instances directly just makes the ASG replace them), then the load balancer, then the target group, then the launch template. Finally check the EC2 console for orphaned EBS volumes and Elastic IPs, which survive instance termination and keep billing.
AWS gave you the richest set of rungs and the least helpful naming. Azure inverts that: fewer compute primitives, clearer names, and one genuinely hard decision.
S3 covers VMs and Scale Sets — including the cooldown and flapping arithmetic AWS glosses over — and then the choice that dominates Azure architecture reviews: App Service, Functions, or Container Apps.
Why this session exists
10%Azure will happily host your web app in four different services, and three of them are defensible.
Azure's compute story is cleaner than AWS's at the VM layer and messier at the PaaS layer. There is exactly one VM service and one scale-set service — no ECS/EKS-style fork. But above that sit App Service, Functions, Container Apps, and Container Instances, all of which will run an HTTP workload, and choosing between them is the single most common Azure architecture question. This session teaches the VM machinery properly (the Scale Set autoscaling model is the best-documented of the three clouds) and then resolves the PaaS choice with a decision tree.
1. The GPU VM table is a decade out of date. §5's Table 5.1 illustrates the ND family with Standard_ND12s and Standard_ND24s — those are NVIDIA P40-era SKUs. The current lineup is NC A100 v4 (A100 PCIe 80 GB), NCads H100 v5 (up to 2× H100 NVL 94 GB), NCCads H100 v5 (confidential computing with GPU), ND H100 v5 (8× H100 SXM with InfiniBand), ND H200 v5, and announced GB200-class ND v6. Treat the material's Table 5.2 pricing as illustrative of shape only.
2. Flex Consumption is not in preview. The material's hosting-plan table marks it "In Preview at the time of writing." Flex Consumption went generally available in December 2024. It is now the plan you should reach for by default on new Functions projects.
3. NCv3 is gone. The V100-based NCv3 series retired on 30 September 2025; the migration guidance points at NVads A10 v5 or NCads H100 v5. NCv2 (P100) retired back in 2023. If a practice exam offers you an NCv3 answer, it is testing an old syllabus.
What the material gets right and is worth reading closely: the VMSS cooldown, flapping, orchestration-mode, and scale-in-policy material in §5 is the most precise treatment of autoscaling timing in any of your four sources.
Core concepts
50%1 · Azure VMs and the SKU-name decoder
On-demand virtual CPU, RAM, disks, and NICs. Same rung as EC2 and Compute Engine; different naming convention, and Azure's is the most information-dense of the three.
Azure SKU names look worse than they are. Standard_DC8ads_v4 reads like line noise until you learn that it is a sentence with a fixed grammar — and once you have the grammar, you can read a SKU you have never seen and know roughly what it costs and why.
[Family][Sub-family]*[#vCPUs][Constrained vCPUs]*[Additive features][Accelerator type]*[Version]. Starred parts are optional. Azure VM sizes overviewConstrained vCPUs is the one piece with no AWS or GCP equivalent worth knowing: Azure lets you buy a large VM and disable some of its cores, so you keep the memory and I/O bandwidth of the big SKU while paying less in per-core software licensing. It exists almost entirely for Oracle and SQL Server licensing, and it is a favourite exam question.
| Model | Shape | Notes and traps |
|---|---|---|
| Pay-as-you-go | Hourly, no commitment | The reference price. Short-term or unpredictable workloads. |
| Reserved Instances | 1 or 3 years, up to 72% off | You pay whether or not you use it. The material states this bluntly and it is worth internalising — unlike AWS Savings Plans, which are dollar-denominated and flow to whatever you run, an Azure reservation is closer to a classic RI. Same break-even formula: commit when duty cycle > (1 − discount). |
| Spot VMs | Unused capacity, roughly 70–82% off | 30 seconds of eviction notice, not AWS's two minutes. That is a quarter of the warning, and it changes what "checkpoint frequently" means in practice. |
| Hybrid Benefit | Apply existing Windows Server / SQL Server licences | No AWS or GCP equivalent at this scale. If your organisation is already a Microsoft licensee, this can dominate every other lever. |
The current GPU lineup, which the material's table predates: NC-family for applied AI training and batch inference — NC A100 v4 (up to 4× A100 PCIe 80 GB), NCads H100 v5 (up to 2× H100 NVL 94 GB), NCasT4_v3 (T4). ND-family for large distributed training — ND A100 v4, ND H100 v5 (8× H100 SXM, NVLink and InfiniBand), ND H200 v5. NV-family for visualisation and lighter inference — NVads A10 v5, and L40S-based sizes. One operational fact that catches everyone: every Azure subscription starts with a GPU vCPU quota of zero in every region, and approval for popular series can take days.
2 · Virtual Machine Scale Sets — the autoscaling model, properly
A group of identical VMs that Azure creates, monitors, and resizes automatically. Direct equivalent of an AWS Auto Scaling Group and a GCP Managed Instance Group.
Orchestration mode — pick this correctly at creation, it is not changeable later
| Uniform mode | Flexible mode (recommended) | |
|---|---|---|
| Best for | Large fleets of identical, stateless VMs | High availability, mixed VM types, most new work |
| VM homogeneity | All identical, from one profile | Identical or mixed — different sizes, spot and on-demand together |
| Individual VM control | Limited; managed as a group via Scale Set VM APIs. Not compatible with standard Azure IaaS VM APIs. | Full control using the standard IaaS VM APIs, like regular VMs |
| Visibility in the portal | Abstracted inside the scale set | VMs appear as individual resources |
| Instance ceiling | 100 | 1,000 |
Why Flexible matters for GPU work: mixed VM types in one scale set means you can express "one on-demand instance as my warm floor, up to five spot instances on top" as a single resource — the exact serving pattern from S2, but native rather than assembled.
Scaling policies and the timing knobs
- Metric-based. Thresholds on CPU, memory, network I/O, or custom metrics. "Scale out by one when average CPU exceeds 70% for a duration."
- Schedule-based. Fixed instance counts at fixed times, for known patterns.
- Predictive autoscale. Machine-learned forecast of CPU load from history, scaling out ahead of a predicted spike. Needs a minimum of seven days of history and only pays off for cyclical patterns.
- Scale-in policy — which VM dies.
Defaultbalances across availability zones and fault domains, then deletes the highest instance ID;NewestVMandOldestVMdo what they say.
- Cooldown. Minimum wait after any scaling action before another can occur — in either direction. "If cooldown is 10 minutes and the set just scaled out, it will not scale again for 10 minutes." Set it too low and you scale again before new VMs are operational.
- Threshold margin. Azure's own guidance: leave adequate space between scale-out and scale-in thresholds — for example out at 80%, in at 60%.
- Same metric both directions. Using one metric to drive both rules avoids the conflicts that produce flapping.
- Multi-instance steps. When scaling by more than one instance, autoscale may adjust the actual count to avoid a flap.
3 · Flapping — derive the rule instead of memorising it
Flapping is when opposing scale events trigger each other in a loop: the group scales in, which raises load on the survivors, which triggers a scale-out, which lowers load, which triggers a scale-in. Nothing is broken and nothing is stable. The system spends its life booting and killing VMs, and your users get the latency of a permanent deployment.
The rule, derived: a scale-in is safe only if the surviving instances stay below the scale-out threshold. That gives
scale_in_threshold < scale_out_threshold × (N − 1) / N
N = 4, scale_out = 80% → scale_in must be < 60% ← Azure's own recommendation, explained
N = 2, scale_out = 80% → scale_in must be < 40% ← small groups need enormous gaps
N = 10, scale_out = 80% → scale_in must be < 72% ← large groups can be tuned tightly
Read the third line again. Small groups cannot be tuned efficiently. A two-instance scale set with an 80% scale-out threshold must not scale in above 40% — which means it spends most of its life running two instances at low utilisation, because removing one would immediately overload the other. The efficiency gains from autoscaling are structurally unavailable below roughly four instances.
This is a genuinely useful architectural finding, and it generalises to AWS and GCP unchanged — the arithmetic is not vendor-specific. If your fleet is two or three VMs, autoscaling is buying you availability, not savings. Price it accordingly, and consider whether a serverless platform that scales in units of one request would serve you better.
Now add the lag. Cooldown (say 10 minutes) plus VM boot and application initialisation (say 4 minutes) means up to 14 minutes between "load arrives" and "capacity is serving it." Against a 4× spike, you have three options and no fourth: over-provision the floor, accept 14 minutes of degradation, or use predictive autoscale to start before the spike. Most teams discover this during the incident rather than during design.
4 · AKS, and the PaaS layer above it
Managed Kubernetes. Azure runs the control plane; you manage node pools — or let cluster autoscaler and node auto-provisioning do it.
Reach for it when: you already have Kubernetes manifests, need the CNCF ecosystem, or need GPU node pools. Note: Azure does not charge for the AKS control plane on the free tier — unlike EKS's $0.10/hour.
Azure Container Instances (ACI)A single container, run directly, with no orchestrator at all. Its own small rung.
Reach for it when: you need one container for a burst job or a build agent and an orchestrator would be absurd. Also serves as burst capacity for AKS via virtual nodes.
Azure BatchManaged job scheduling across a pool of VMs for large parallel and HPC workloads.
Reach for it when: the work is embarrassingly parallel and finite — rendering, simulation, bulk transcoding.
Managed hosting for web apps and APIs. You deploy code or a container; Azure runs the web server, TLS, scaling, and deployment slots. PaaS, above the container rung.
Reach for it when: it is a conventional web application, you want deployment slots and built-in auth, and you don't want to think about containers at all. Scales up (bigger SKU) and out (more instances).
Azure Container AppsServerless containers with built-in service discovery, event-driven scaling (KEDA under the hood), and scale-to-zero. Direct peer of Cloud Run.
Reach for it when: you have containers and microservices but do not want a Kubernetes cluster. Consumption workload profiles scale to zero; Dedicated profiles run on reserved VMs you choose.
Azure Container Apps — serverless GPUGA. NVIDIA A100 and T4, per-second billing, scale to zero when idle.
Reach for it when: you want Modal-style GPU serving inside Azure. Quota must be requested via a support case, and it runs on Consumption workload profiles only.
Functions. Triggered by HTTP, timers, queues, blobs, Event Hubs, and more; native support for C#, Java, JavaScript, PowerShell, and Python.
Reach for it when: the unit of work is genuinely an event. The hosting plan you pick matters more than the code.
Azure Logic AppsLow-code workflow orchestration across connectors. Not really compute — it is glue.
Reach for it when: the job is integration between SaaS systems, not computation.
Azure Event GridEvent routing. The thing that delivers events to the compute above.
Reach for it when: you need pub/sub fan-out between Azure services.
Functions hosting plans — this is the actual decision
| Plan | Scale | Cold start | Billing | When |
|---|---|---|---|---|
| Consumption | to 200 instances, event-driven | Yes, on every idle gap | Per execution + GB-second; scales to zero | Sporadic work where latency on the first request doesn't matter. No VNet integration. |
| Flex Consumption GA Dec 2024 | to 1,000 instances, concurrency-based; 0→~1,000 in under a minute | Avoidable via always-ready instances | On-demand instances billed only while executing; always-ready instances billed for idle memory too | The default for new work. VNet integration at no extra cost, per-function scaling, 2,048 or 4,096 MB instance sizes, Azure Files mounts for large model binaries. |
| Premium | typically 20–100 instances | Pre-warmed instances | Per-VM, always at least one | Legacy answer to the same problems Flex now solves. Existing estates. |
| Dedicated (App Service plan) | App Service scaling | None with Always On | Per App Service plan | You already pay for an App Service plan and want to fill it. |
| On Container Apps | Container Apps scaling | Container cold start | Container Apps billing | Functions that need a custom container — and the only route to GPU-enabled Functions, via ACA serverless GPU. |
The 230-second wall. The Azure load balancer has a 230-second idle timeout. Even if your function timeout is higher, an HTTP client gets a timeout error at 230 seconds. For anything longer, return 202 Accepted immediately and let the client poll. This catches every team that assumes the function timeout is the only ceiling.
"Always ready" is idle billing wearing a different hat. Flex Consumption bills always-ready instances for their provisioned memory while idle, plus execution time, plus executions. Setting always-ready to 2 gives you a warm floor and a monthly bill that no longer scales to zero — which is fine, as long as you decided it deliberately rather than discovering it. Also note the reported behaviour where the always-ready metric can exceed your configured count during load balancing and health checks, and you are billed for all active always-ready instances.
5 · Decision tree — hosting an application on Azure
with GPU node pools if the workload is inference or training
add always-ready only if cold start hurts a user
Consumption profile for scale-to-zero; add serverless GPU if you're serving a model
scale up for power, scale out for traffic
6 · Three-cloud comparison — Azure-anchored
| Azure service | What it is | AWS equivalent | GCP equivalent | Key quirk |
|---|---|---|---|---|
| Virtual Machines | IaaS VMs | EC2 | Compute Engine | Constrained-vCPU SKUs for licence savings — unique to Azure |
| VM Scale Sets | Managed identical VM fleet | Auto Scaling Group | Managed Instance Group | Uniform (100 VMs) vs Flexible (1,000, mixed types) — chosen at creation, permanent |
| Spot VMs | Interruptible capacity | Spot (2-min notice) | Spot VMs (~30-sec notice) | 30-second eviction notice — the shortest of the three |
| AKS | Managed Kubernetes | EKS | GKE | No control-plane charge on the free tier; EKS charges $0.10/hour |
| Container Instances | One container, no orchestrator | no clean peer | no clean peer | Genuinely useful for burst jobs; also backs AKS virtual nodes |
| Container Apps | Serverless containers | Fargate (under ECS/EKS) | Cloud Run | KEDA-based event scaling built in; Consumption vs Dedicated workload profiles |
| Container Apps GPU | Serverless GPU containers | none | Cloud Run GPU | A100 and T4; quota by support case; West US 3, Australia East, Sweden Central at GA |
| App Service | Managed web-app PaaS | Elastic Beanstalk / App Runner | App Engine | Deployment slots and built-in auth are the differentiators |
| Functions | Functions | Lambda | Cloud Run functions | The hosting plan, not the code, determines scale and cold-start behaviour |
| Hybrid Benefit | Reuse owned Windows/SQL licences | nothing comparable | nothing comparable | Can outweigh every other discount for a Microsoft shop |
| Predictive autoscale | ML forecast of CPU load | Predictive scaling | Predictive autoscaling | All three exist; all three need ~7 days of history and cyclical traffic |
Reality check
25%Worked example — sizing the lag budget for a 4× spike
Setup. A VMSS behind a load balancer. Scale-out at 80% CPU, scale-in at 60% (which the formula above tells us is correct for N ≥ 4). Cooldown 10 minutes. VM boot plus application initialisation: 4 minutes. Baseline 4 VMs at 50% CPU. At 09:00 traffic goes 4×.
| t | What happens | User impact |
|---|---|---|
| 09:00 | Load 4×. Four VMs go from 50% to 200% demand — they saturate at 100% and start queuing. | Latency climbing |
| 09:00–09:05 | Metric aggregation and the configured duration window elapse before the rule fires. | Degraded |
| 09:05 | Scale-out triggers. New VMs begin booting. | Degraded |
| 09:09 | New VMs boot, initialise, pass health checks, and join the load balancer. | Recovering |
| 09:09–09:15 | Cooldown blocks further scaling — even if four VMs were not enough, no more can be added yet. | Partially served |
| 09:15 | Second scaling round if still needed. | Served |
The three real options, and there is no fourth: (1) raise the floor so the baseline absorbs the spike — reliable, and you pay for four idle VMs every night; (2) accept nine minutes of degradation and communicate it as an SLO; (3) use predictive autoscale so the scale-out starts at 08:52 instead of 09:05 — free, but only works if the spike is genuinely cyclical and you have seven days of history proving it. Note that option 3 is the only one that improves both cost and latency, which is why it is worth the setup cost for any traffic pattern with a daily rhythm.
Three current sources
Apply it
10%Mapping your existing stack onto Azure
- Your Modal Gemma deployment → Azure Container Apps with serverless GPU. This is the closest thing Azure sells to what you already run: a container, an A100 or T4, per-second billing, scale to zero. The differences you'd feel immediately are the quota-by-support-case friction and the narrow regional footprint at GA. The architectural shape is identical — which means your Modal container image is close to portable, and the interesting work is in the scaling rules and the cold-start budget, not the model code.
- Your Vertex AI script (T4, autoscale 1–3, spot) → a Flexible-mode VMSS with mixed VM types. Flexible mode is genuinely better than the AWS equivalent for this: one scale set expressing "one on-demand NCasT4_v3 as the warm floor, up to two spot instances above it." On AWS you assemble that from a mixed-instances policy; on Azure it is the mode's native purpose. But note the eviction notice is 30 seconds, not 120. If your Vertex spot strategy assumed a two-minute drain window, it does not transfer.
- Your GKE/KServe knowledge → AKS. Almost everything transfers unchanged. What is new is the wrapper: node pools with GPU SKUs, the cluster autoscaler configuration, and the fact that you must request GPU quota before the first pod will ever schedule.
- The identity link back to C2: where AWS gives an instance an IAM instance profile and GCP gives it a service account, Azure gives it a managed identity. Same idea, same purpose, different noun. Don't re-learn it — just add the third word to the Rosetta Stone.
Optional lab — a scale set that flaps, then doesn't
Use Standard_B1s or Standard_B2als_v2 — burstable, cheapest, and eligible for the Azure free account's 750 free hours in the first 12 months. Do not use an N-series VM: GPU SKUs are not free-tier eligible, start at several dollars per hour, and require a quota request that will take longer than this lab.
- Before anything: create a budget with an alert at a low threshold. Azure budgets. Put everything in one new resource group — that is your teardown handle.
- Create a VMSS in Flexible orchestration mode: Ubuntu,
Standard_B1s, instance count 2, min 2 / max 5, across availability zones, with a load balancer. - Add a metric-based autoscale rule deliberately badly: scale out above 70% CPU, scale in below 65%, cooldown 5 minutes. Note that 65% violates the formula for N = 2 (which demands below 35%).
- Drive CPU with
stress-ng --cpu 1 --timeout 900son the instances. Watch the autoscale history blade. You are looking for the oscillation, not a graph that looks good. - Fix it: scale in below 30%, cooldown 10 minutes. Repeat. Compare the two autoscale histories side by side — that comparison is the entire lesson.
- Optional: switch the scale set to include one spot instance and watch the eviction policy behave.
- Teardown: delete the resource group. This is Azure's one genuine ergonomic advantage over AWS — one delete removes the scale set, load balancer, public IPs, disks, and NICs together. Then confirm in Cost Management that daily spend returns to zero within 24 hours.
Azure gave you the clearest autoscaling model and the messiest PaaS menu. Google gives you the opposite: a tidy four-service ladder, and the most opinionated managed Kubernetes of the three.
S4 is also where this stops being theoretical — it maps directly onto the Vertex AI T4 deployment and the GKE footprint you already run.
Why this session exists
10%This is the cloud you already run GPUs on — and the one whose pricing rules will surprise you most.
Google's compute ladder is the tidiest of the three: Compute Engine → Managed Instance Group → GKE → Cloud Run → Cloud Run functions, with no ECS/EKS-style fork and no four-way PaaS ambiguity. The complexity has been moved somewhere else — into the discount system. GCP is the only cloud with an automatic no-commitment discount, and it is also the cloud where that discount pointedly excludes the machine families you care about. Your Vertex AI T4 script sits exactly on that fault line.
§1's machine-series table stops at the third generation (M3, C3, H3, C3D). Current Compute Engine adds C4, C4A, C4D, N4, and N4A for general purpose and compute, plus a completely rebuilt accelerator line — A3 (H100/H200), A4 (B200), A4X (GB200), A4X Max (GB300), and G4 (RTX PRO 6000 Blackwell) alongside the older A2 (A100) and G2 (L4).
§2's MIG quota figures are self-dated — the material says "as of writing this material in late 2023 / early 2024," 2,000 VMs regional and 1,000 zonal. Treat the relationship (regional quota > zonal quota) as durable and the numbers as needing verification in the console.
§6's Autopilot-vs-Standard framing predates the hybrid model. Since 2025 you can run Autopilot-mode workloads inside a Standard cluster via Autopilot ComputeClasses — the choice is no longer all-or-nothing at cluster creation.
What the material gets right and ahead of the curve: §7 already uses the current "Cloud Run functions" naming rather than "Cloud Functions," and §2's treatment of MIG health checks is the best in any of your four sources — see below.
Core concepts
50%1 · Compute Engine — machine families and the naming grammar
Google's virtual machines. Per-second billing with a one-minute minimum, and a naming scheme that is the most readable of the three clouds.
Three nested terms, and the material defines them precisely: a machine family is a curated set of hardware configurations for a workload class; a machine series is a generation within that family (N2 is newer than N1; M3 newer than M2); a machine type is a specific vCPU/memory point within a series — or a custom machine type, which some series support and which has no clean AWS or Azure equivalent.
g2 - standard - 4 a3 - highgpu - 8g
│ │ │ │ │ │
│ │ └ 4 vCPU │ │ └ 8 GPUs
│ └ configuration │ └ high GPU density
└ series (G2 = L4) └ series (A3 = H100)
n1-standard-8 + 1× nvidia-tesla-t4 ← N1 is the exception: GPUs attach separately,
so you tune the CPU-to-GPU ratio yourself
| Series | Accelerator | On-demand, us-central1 (Jul 2026) | Where it fits |
|---|---|---|---|
| N1 + T4 | NVIDIA T4, 16 GB | ~$0.35/hr GPU add-on, plus the N1 rate | Still current. The cheapest real inference GPU on GCP, and the one your Vertex script uses. Also the only GPU family that earns sustained use discounts. |
| G2 | NVIDIA L4, 24 GB | $0.70/hr (g2-standard-4) | The default single-GPU inference VM. Models up to roughly 13B at FP16. |
| A2 | A100 40 GB / 80 GB | $3.67 / $5.07 per hour | Fine-tuning and medium training. |
| A3 | H100 80 GB (also H200 on A3 Ultra) | $88.48/hr for a3-highgpu-8g (~$11.06/GPU) | Note: 1-, 2-, and 4-GPU A3 sizes must be created as Spot or Flex-start VMs. Only the 8-GPU size is available on demand. |
| A4 / A4X / A4X Max | B200 / GB200 NVL72 / GB300 | A4 ≈ $4.28/GPU/hr | Frontier scale. Requires reserved capacity, no self-service CUD, and the Compute Engine SLA does not apply to A4X. |
| G4 | RTX PRO 6000 Blackwell, 96 GB | $4.50/hr (g4-standard-48) | Newest family. Visualisation, rendering, and inference that needs a lot of VRAM without H100 pricing. |
| N1 + P100 | NVIDIA P100 | — | End of support 15 September 2026. Confirms the correction carried forward from earlier stages: do not target P100. |
Sources: Compute Engine GPU machine types Accelerator-optimized machine family VM instance pricing. Rates move; re-check before budgeting.
The discount system — this is where GCP differs most
| Mechanism | Discount | The catch that matters |
|---|---|---|
| Sustained use discounts (SUD) | up to 30%, automatic, no commitment | Only N1 and other eligible general-purpose series. A2, A3, G2, G4 and the accelerator-optimized families get nothing. For GPU devices the discount is computed per GPU model — 30% off one GPU running a full month. |
| Resource-based CUD | up to 55% (70% memory-optimized), 1 or 3 years | Region- and family-specific. A us-central1 A100 commitment does not cover europe-west4. |
| Compute Flexible CUD | 28% (1-yr) / 46% (3-yr) | Spend-based, not region-specific, and since 2024 it covers Compute Engine + GKE (including most Autopilot pods) + Cloud Run in one commitment. GPU instances are not eligible. |
| Spot VMs | up to 91% off | ~30 seconds of preemption notice. Spot VMs cannot receive CUDs or SUDs — the models are mutually exclusive. |
| Dynamic Workload Scheduler | varies | Queues batch AI/HPC jobs and starts them when capacity frees up at a reduced rate. Non-time-critical training without preemption risk. |
Commitment discounts on GPU instances are shallow. A g2-standard-4 saves roughly 8% on a 1-year CUD and 11% on a 3-year — against 37% and 55% for a comparable n2-standard-4. The reason is structural: the accelerator dominates the instance price and Google prices GPU commitments conservatively.
Apply the S2 formula and the conclusion is stark. Break-even duty cycle = 1 − d; at d = 0.08 you must run the instance 92% of the year before a 1-year GPU commitment pays for itself. For GPU workloads, Spot beats CUDs almost every time the workload can tolerate interruption, and driving utilisation above 80% delivers more saving than any commitment.
2 · Managed Instance Groups — and the two-health-check rule
A group of identical VMs created from an instance template, kept at a target size, auto-healed when unhealthy, and autoscaled by policy. Equivalent to an AWS ASG and an Azure VMSS.
A MIG does two jobs that people conflate: it keeps the right number of VMs alive (auto-healing) and it changes that number (autoscaling). Auto-healing is about correctness; autoscaling is about capacity. They use the same signal — health checks — for different purposes, and that is exactly where the interesting subtlety lives.
This is the single best idea in the GCP source and it is missing from the AWS and Azure treatments. When a MIG is a load-balancer backend, configure two separate health checks. The LB check should be aggressive because its only consequence is routing traffic away — cheap, reversible. The MIG check should be conservative because its consequence is destroying and recreating a VM — expensive, and on a GPU node, minutes of model reloading.
Health-check tuning is four dials: check interval, timeout, healthy threshold (consecutive passes to mark a sick VM healthy), and unhealthy threshold (consecutive failures to mark a healthy VM sick). "Aggressive" simply means low values on all four. Health checks are a Google-managed service running inside Google's network, so your VPC firewall must allow 130.211.0.0/22 and 35.191.0.0/16 — the single most common reason a new MIG reports every instance as unhealthy.
One more rule with real operational bite: if the MIG intentionally stops a VM — during autoscaling, for instance — it will not then "repair" it. But it will repair a VM lost to hardware failure, a Spot VM preemption, a maintenance event where the VM isn't set to live-migrate, or a manual deletion. That is how a MIG of spot instances self-heals.
The MIG autoscaler exposes more timing control than either competitor, and each knob maps to something you already met:
- Utilization-based policy on average CPU, HTTP load-balancing capacity, or Cloud Monitoring metrics. If several metrics are configured, the autoscaler computes a recommendation for each and scales to the maximum — the worst signal wins.
- Schedule-based policy — up to 128 schedules per MIG, each with a capacity floor, start time, duration, and recurrence.
- Initialization period — how long a VM takes to become fully useful, application included. This is AWS's instance warmup.
- Stabilization period — used only for scale-in. Gives a noisy metric time to settle before VMs are deleted.
- Maximum allowed reduction and trailing time window — a hard cap on how many VMs (absolute or percentage) can be removed over an observation window. AWS and Azure have nothing this explicit.
- Predictive autoscaling — forecasts from history and scales out in advance. Google's own guidance: it works best when your initialization period exceeds two minutes and load varies on daily or weekly cycles. That is a precise, actionable trigger condition — and a model server loading weights clears the two-minute bar easily.
Regional vs zonal MIGs. A zonal MIG puts every VM in one zone; a regional MIG spreads them across zones in the region. Regional gives higher availability against a zonal failure, and — a detail worth remembering because it is counter-intuitive — a higher effective VM ceiling, because regional quotas are larger than zonal ones.
Stateful MIGs exist for workloads that genuinely cannot be stateless — they preserve disks and instance names across recreation. Read them as an escape hatch, not a pattern. Everything Tab 1 said about the cost of statefulness still applies; a stateful MIG just makes it survivable.
3 · GKE — the wrapper, not Kubernetes
You know Kubernetes. Here is only what GKE adds on top.
| Autopilot mode | Standard mode | |
|---|---|---|
| You manage | Pods. That is the whole list. | Node pools, machine types, upgrades, OS images, node security |
| Billing | Per pod resource request — vCPU, memory, storage. Hardware-specific workloads shift to node-based billing. | Per node VM, regardless of how full it is |
| Cheaper when | Utilisation is low or variable — you never pay for empty node capacity | Nodes run consistently hot, roughly 70–80% |
| GPUs | Supported — request nvidia.com/gpu in the pod spec via the Accelerator compute class; GKE provisions and manages drivers | Full range of GPU types, node-level access, custom drivers |
| Escape hatches | No SSH to nodes, no privileged containers, no hostPath. DaemonSets and Confidential Nodes do work in 2026 — older comparisons say otherwise. | Everything |
The 2026 update that makes this less of a fork: you can now run Autopilot-mode workloads inside a Standard cluster using Autopilot ComputeClasses. GKE provisions and manages those nodes while the rest of the cluster stays under your control with your own node pools. You can even set an Autopilot ComputeClass as the cluster or namespace default. Requirements are real — Rapid release channel, GKE 1.33.1-gke.1107000 or later — but the architectural point stands: Autopilot vs Standard is now a per-workload decision, not a per-cluster one.
The three autoscalers, which you already know from KServe but under different names: HPA adds pods on CPU, memory, or custom metrics; VPA right-sizes each pod's requests; Cluster Autoscaler adds and removes nodes under them. Node auto-provisioning goes one step further and creates new node pools of shapes you never defined. For your work the important interaction is that HPA scales pods in seconds while the Cluster Autoscaler must boot a GPU node in minutes — so a GPU deployment's real scale-out latency is the node-provisioning time, not the pod-scheduling time.
4 · Cloud Run — serverless containers, and the GPU story
You give Google a container that listens on a port. Google runs it, scales it by concurrency, and bills per second. Three shapes: services (request-driven), jobs (run to completion), and worker pools (GA — long-running consumers with no HTTP endpoint, for queue and stream processing).
The configuration surface
- Concurrency — requests handled simultaneously per instance. Default 80, maximum 1,000. This is the dial that has no AWS Lambda equivalent, and it is the main reason Cloud Run is cheaper than Lambda for HTTP work: one instance amortises across many requests.
- Request timeout — default 300 s, maximum 3,600 s (60 minutes). Google recommends idempotency and resumability past 15 minutes, because long connections drop.
- Memory — 512 MiB default, 32 GiB maximum, with minimum-CPU requirements that scale alongside it.
- Max instances — default 100 per revision. Set it thoughtfully: 100 instances all opening database connections will destroy a 20-connection pool.
- Min instances — default 0. Set to 1+ to eliminate cold starts, and accept that you are now paying for idle.
- Billing mode — request-based (per request plus a higher per-second rate only while processing) or instance-based (no per-request fee, lower per-second rates, billed for the whole instance lifetime).
Cloud Run GPU — the thing you actually want
- GA since June 2025, covered by the Cloud Run SLA. NVIDIA L4 (24 GB) and NVIDIA RTX PRO 6000 Blackwell (96 GB).
- Scales to zero. Per-second billing. No reservation needed and no quota request — projects using L4 in a region for the first time are automatically granted 3 GPUs.
- One GPU per instance. L4 requires at least 4 CPU and 16 GiB; RTX PRO 6000 requires at least 20 CPU and 80 GiB.
- Instance-based billing is mandatory for GPU, and min-instances are charged at the full rate even when idle. There are no per-request fees.
- Start-up: an instance with the GPU and drivers ready in approximately 5 seconds. Google's own measured cold-start time-to-first-token for
gemma3:4b— including startup, model load, and inference — was about 19 seconds. - Zonal redundancy on by default; turning it off lowers the price in exchange for best-effort failover.
Formerly Cloud Functions. Single-purpose event-driven handlers. Gen 2 is deployed as services on Cloud Run and uses Eventarc for triggers — which means the function rung and the serverless-container rung on GCP are now the same runtime with two front doors. That is a cleaner story than either competitor offers.
5 · Decision tree — which GCP compute?
plus Spot or Dynamic Workload Scheduler for the capacity
Cloud Run's request ceiling is 60 minutes and it allocates one GPU per instance
Autopilot mode unless a workload specifically needs node control — and you can now mix both in one cluster
gen 2, which is Cloud Run underneath anyway
min-instances=0 for anything internal or batch; set it to 1 when a human is waiting on the first request.6 · Master three-cloud comparison
| Rung / concept | AWS | Azure | Google Cloud |
|---|---|---|---|
| VM | EC2 | Virtual Machines | Compute Engine |
| VM template | Launch template | Scale set model | Instance template |
| Managed VM fleet | Auto Scaling Group | VM Scale Set — Uniform or Flexible | Managed Instance Group — zonal or regional |
| Warm-up knob | Default instance warmup | Cooldown period | Initialization period |
| Anti-flap knob | Cooldown (ignored by target tracking) | Cooldown + threshold margin | Stabilization period + max allowed reduction |
| Pre-warmed capacity | Warm pools (stopped or hibernated) | Premium plan pre-warmed instances (Functions) | min-instances (Cloud Run); MIG has no direct peer |
| Managed Kubernetes | EKS + Auto Mode · $0.10/cluster-hr | AKS · free control plane tier | GKE Standard + Autopilot · mixable per workload |
| Node right-sizing | Karpenter | Cluster autoscaler + node auto-provisioning | Cluster autoscaler + node auto-provisioning + ComputeClasses |
| Serverless containers | Fargate (under ECS/EKS) | Container Apps | Cloud Run — services, jobs, worker pools |
| Serverless GPU | none | Container Apps — A100, T4 (quota via support case) | Cloud Run — L4, RTX PRO 6000 (auto quota of 3) |
| Functions | Lambda — 15 min hard cap, 10 GB | Azure Functions — plan-dependent | Cloud Run functions — 60 min, runs on Cloud Run |
| Long-running workflow | Lambda Durable Functions — up to 1 year, no charge while waiting | Durable Functions / Logic Apps | Workflows + Cloud Run jobs |
| Automatic discount | none | none | Sustained use, up to 30% — not on A2/A3/G2/G4 |
| Flexible commitment | Compute Savings Plan, to 66% — covers EC2, Fargate, Lambda | Reserved Instances, to 72% — VM-scoped | Flexible CUD, 28%/46% — covers CE, GKE, Cloud Run; not GPUs |
| Deepest commitment | EC2 Instance SP / Standard RI, to 72% | 3-yr Reserved, to 72% (+ Hybrid Benefit) | Resource CUD, to 55% (70% memory-opt) |
| Interruptible | Spot — to 90%, 2-min notice | Spot — ~70–82%, 30-sec notice | Spot — to 91%, ~30-sec notice |
| Reserve future GPU capacity | Capacity Blocks for ML | Reserved VM Instances + quota request | Future reservations / AI Hypercomputer; DWS for batch |
| Single-L4 VM | g6.xlarge — $0.8048/hr | closest is NVads A10 v5 (A10, not L4) | g2-standard-4 — $0.70/hr |
| Compute identity | IAM role via instance profile | Managed identity | Service account |
Reality check
25%Worked example — Cloud Run GPU vs a dedicated L4 VM
This is the exact decision behind your Modal-vs-Vertex instinct, with numbers on it. Workload: an L4-sized inference endpoint — a 7B-to-9B model at FP16, comfortably inside 24 GB of VRAM.
| Option | Rate | Monthly if always on | Cost when idle |
|---|---|---|---|
g2-standard-4 Compute Engine VM | $0.70/hr on-demand | 730 × $0.70 = $511 | $0.70/hr — full price, forever |
| Cloud Run with 1× L4, instance-based billing | GPU $0.0001867/s ≈ $0.672/GPU-hr, plus the mandatory 4 vCPU and 16 GiB ≈ $1.05/hr active (verify current rates) | 730 × $1.05 = $767 | $0 at min-instances=0 |
Break-even is a duty cycle, not a request count:
break_even_hours = VM_monthly ÷ serverless_hourly
= $511 ÷ $1.05
≈ 487 active hours per month
= 487 / 730
≈ 67% duty cycle
Read it as a rule you can state out loud: below roughly 1.3 sustained requests per second, serverless GPU is cheaper; above it, the dedicated VM wins. And crucially, the crossover is high — 67% is a busy endpoint. Most internal tools, demos, batch scoring jobs, and pre-launch products sit far below it, which is why scale-to-zero GPU serving is genuinely the right default for that whole class of work.
Nineteen seconds. If a human is waiting on the first token, scale-to-zero is unacceptable — and the fix is min-instances=1, which puts you back at roughly $767/month with none of the savings. The moment you set min-instances above zero on a GPU service, the dedicated VM is cheaper, because Cloud Run charges min-instances at the full rate while idle and its hourly rate is above the VM's.
So the honest decision rule is: interactive and busy → Compute Engine or GKE with a committed L4. Bursty, internal, or batch → Cloud Run GPU at min-instances=0. There is no configuration that gives you both, and recognising that is the whole skill.
Persistent disks bill while the VM is stopped. Stopping a VM stops compute charges and not storage — only deleting the disk stops those. A 500 GB SSD dataset is a real monthly line item before a single GPU-hour.
Vertex AI adds a premium over raw Compute Engine rates. Treat the managed-ML layer as a service with its own margin, not as a thin wrapper over the same price list — which matters directly when you compare your Vertex endpoint against the same GPU on GKE.
Three current sources
Apply it
10%Your Vertex AI script, re-read as architecture
You wrote: T4 GPUs, autoscaling 1 to 3, spot instances. Four deliberate decisions, and the vocabulary from this stage now names all of them.
| Your choice | What it is, in this stage's terms | Verdict |
|---|---|---|
| T4 GPUs | N1 with an attached accelerator — the cheapest inference GPU, ~$0.35/hr add-on, and the only GPU family eligible for sustained use discounts | Well chosen, partly by accident. An equivalent G2/L4 deployment would be faster but would forfeit up to 30% of automatic discount. |
| min = 1 | A warm floor. You decided against scale-to-zero. | Correct for an endpoint with a latency promise — the 19-second cold-start figure above is the justification you didn't have at the time. |
| max = 3 | A ceiling that bounds the bill during a runaway. | Correct, and the habit most people skip. Three T4s is a knowable worst case. |
| spot instances | Interruptible capacity, ~30 seconds of preemption notice, ineligible for CUDs and SUDs | Question this one. Spot on the serving floor means your warm minimum can vanish with 30 seconds' notice. Spot for the burst instances above the floor is fine. Spot for the min-1 replica is trading your latency promise for a discount you may not have priced. |
The refinement worth making: keep min = 1 on standard (non-spot) capacity so the floor is real, and allow instances 2 and 3 to be spot. That is the pattern from every session in this stage — a committed or on-demand floor, interruptible headroom — and on GCP the MIG will auto-heal a preempted spot VM for you, since preemption counts as a repairable failure.
Your other two workloads
- Modal's scale-to-zero GPU serving → Cloud Run GPU, almost exactly. Container in, per-second billing, scale to zero, ~5-second instance start with drivers ready, no quota request for your first three L4s. The one real difference is concurrency: Cloud Run gives you an explicit dial (default 80, max 1,000), and for LLM serving you want it low — a value like 4 or 8 matched to what your vLLM instance can batch without blowing the latency budget. Getting that number right is worth more than any instance-type decision.
- Your GKE footprint. Two things to go and check today. First, whether your GPU workloads are in Standard node pools that could move to an Autopilot ComputeClass — you would stop paying for empty node capacity. Second, whether your cluster is covered by a Compute Flexible CUD: since 2024 one commitment spans Compute Engine, GKE (including most Autopilot pods), and Cloud Run, and it is not region-specific. If you are running steady CPU-side workloads across all three, a single Flex CUD at 28% or 46% is the highest-leverage thing on this page — on the CPU side. It will not touch your GPU spend.
Optional lab — a MIG with two health checks
Use e2-micro in us-central1, us-west1, or us-east1 — Google's Always Free tier covers one e2-micro-equivalent for the full month. Do not attach a GPU for this lab. GPU instances are not free-tier eligible, run from about $0.35/hour for a T4 to over $88/hour for an 8-GPU A3, and an idle GPU VM is the single biggest silent money leak in cloud. If you want to see a GPU boot, use one short Spot VM run and delete it in the same sitting.
- Before anything: confirm a budget with alerts at 50/90/100% on the billing account, and work in a dedicated project — deleting the project is your nuclear teardown.
- Create an instance template:
e2-micro, Debian, and a startup script that installs a tiny HTTP server returning the hostname on/and200on/healthz. - Open the firewall to
130.211.0.0/22and35.191.0.0/16on your serving port. Do this deliberately and notice it — skipping it is why most first MIGs report every instance unhealthy. - Create two health checks:
hc-lb-fast(interval 5 s, timeout 5 s, unhealthy threshold 2) andhc-mig-slow(interval 30 s, timeout 10 s, unhealthy threshold 3). - Create a regional MIG, size 2, with
hc-mig-slowas the auto-healing policy and an initial delay of 120 seconds. Attach it to an HTTP load balancer usinghc-lb-fast. - The experiment: SSH into one instance and make
/healthzreturn 500. Watch the load balancer drain it within seconds while the MIG waits. Restore it before the MIG's threshold trips — that gap is the entire point of two health checks. Then break it permanently and watch the MIG recreate the VM. - Add a utilisation autoscaling policy (target 60% CPU, min 2, max 4) and set a stabilization period. Drive load and watch scale-out, then note how much more slowly scale-in happens.
- Teardown, in order: delete the forwarding rule and load balancer components, then the MIG (which deletes its VMs), then the health checks, then the instance template. Check Compute Engine → Disks for orphaned persistent disks and VPC network → IP addresses for reserved static IPs — both keep billing after the VMs are gone. If in doubt, delete the whole project.
You can now place any workload on the ladder, name its service on all three clouds, and defend the purchase with a duty-cycle calculation. The gap you have been stepping around all stage is state: every rung above "single VM" assumed the data lives somewhere else.
C5 is that somewhere else — object, block, and file storage, and the databases your stateless replicas share. C4 sits between: the networking that puts a load balancer in front of everything you built here.