« Phase 05 · Warmup · Track Overview

Lab 01 — Capacity Planning & the Serving Simulator

The problem

Finance asks whether to commit to a year of provisioned capacity. Wholesale asks why the self-hosted model is slower than the managed one. Security asks whether the restricted-data model can run on-shore. Every one of those is the same question — what does serving actually cost, and what determines it — and every one is answerable with arithmetic you can do on a whiteboard.

This lab builds that arithmetic, plus a simulator that shows why continuous batching replaced static batching.

What you build

#ComponentWhat it does
1ModelShape, GPUthe shapes that determine serving cost, including a tensor-parallel group as one aggregate device
2kv_bytes_per_token, usable_kv_bytes, max_concurrent_sequencesthe KV-cache arithmetic that turns concurrency into a memory question
3prefill_ms, decode_ms_per_token, arithmetic_intensity, ridge_pointwhy prefill is compute-bound and decode is memory-bound, with the numbers
4ContinuousBatcherin-flight batching: retire, admit, step — with KV-budget admission on the final length
5StaticBatcherthe baseline it replaced, so the win is measured rather than asserted
6PaygPricing, ProvisionedPricing, break_eventhe PTU-vs-PAYG break-even, as a function of the output mix
7plan_with_spilloverthe floor-plus-spill shape every mature deployment converges on
8SelfHostedPricing, cheapest_optionthe utilization bet, including the engineering line everyone forgets

Key concepts

ConceptWhereWhy it matters
KV bytes = 2·L·H·d·bkv_bytes_per_tokenthe one formula to memorize; every concurrency question reduces to it
GQA is the biggest leverthe kv_heads tests64 → 8 KV heads is 8× the concurrency, same model
Working-space reserveusable_kv_bytesassuming 100% of free memory is KV over-admits and OOMs the batch
Weights amortize, KV does notdecode_ms_per_tokenthe entire argument for batching, in one expression
Ridge pointarithmetic_intensitydecode at batch 1 wastes almost all of a GPU's FLOPs
Admit on the final length_projectedadmitting on the prompt OOMs mid-flight and kills half-served requests
Retire before admitContinuousBatcher.runreusing freed capacity in the same step is continuous batching
Never-fits ⇒ rejectsamea request larger than the whole budget must not queue forever
Break-even moves with the mixbreak_evenoutput costs several times input; never quote one number
Spilloverplan_with_spilloverdegrade in cost, not in availability
Utilization betself_hosted_cost_per_1k_tokensan idle GPU costs the same as a busy one

Files

FileRole
lab.pyyour implementation
solution.pyreference; python solution.py runs six worked sections
test_lab.py55 tests
requirements.txtpytest

Run

pip install -r requirements.txt
pytest test_lab.py -v
LAB_MODULE=solution pytest test_lab.py -v
python solution.py

Success criteria

  • All 55 tests green against your lab.py.
  • A 70B fp16 model on one 80 GiB card yields a KV budget of 0 — and a positive one on an 8-way group.
  • With zero context, doubling the batch exactly halves per-token decode time; with a very long context, it does not.
  • Batch-1 decode sits far below the GPU's ridge point.
  • Continuous batching finishes an uneven workload in fewer ticks than static, and the short request finishes first.
  • A request whose prompt fits but whose prompt+output does not is rejected.
  • peak_kv_bytes never exceeds the budget.
  • A heavier output mix reaches the PTU break-even at a lower volume.
  • Ties in cheapest_option go to PAYG.

How this maps to the real stack

This labThe real thingWhat we simplified
ContinuousBatchervLLM's scheduler, TGI's continuous batching, TensorRT-LLM's in-flight batchingreal schedulers also do chunked prefill, preemption/swapping and priority; ours never preempts
KV budgetvLLM's PagedAttention block allocatorours reserves the final length contiguously; paging allocates in blocks on demand, which is why real systems can over-commit safely and we cannot
max_concurrent_sequencesgpu_memory_utilization and the profiling step vLLM runs at startupours uses a flat overhead fraction; real systems measure
decode_ms_per_tokena roofline estimateignores kernel efficiency, TP communication, and scheduling gaps
ProvisionedPricingAzure OpenAI PTUs, AWS Bedrock Provisioned Throughputreal capacity has minimums, regional availability and reservation terms
plan_with_spilloverAzure's PTU + standard "spillover" deployments; a gateway routing ruleours is monthly arithmetic; production spills per-request
SelfHostedPricingGPU node pools on AKS/EKS, plus the platform team that runs themthe engineering line is a guess; make it your own

Honest limits. No chunked prefill (so a long prompt blocks a step), no preemption, no paging (so fragmentation is invisible), no speculative decoding, no quantization modelling, and a decode model that ignores tensor-parallel communication. Each of those changes the numbers; none changes the reasoning.

Extensions

  1. Paged KV. Allocate in fixed blocks on demand rather than reserving the final length. Then over-commit and add preemption when the budget is exceeded — and watch the goodput/latency trade-off appear.
  2. Chunked prefill. Split a long prompt across steps so it does not stall decode. Measure TTFT for short requests arriving behind a long one, before and after.
  3. Quantization. Add bytes_per_param=1 and a KV-cache quantization option, and recompute the concurrency table. The result usually surprises people.
  4. Speculative decoding. Model a draft model that proposes k tokens with acceptance rate a, and find where it stops paying.
  5. A real workload trace. Replace the synthetic arrivals with a sampled distribution of prompt and output lengths and compute p50/p95 TTFT and end-to-end latency.
  6. Reserved-capacity risk. Add a one-year commitment with a discount, then price the option of the model being deprecated mid-term. That is the conversation finance actually needs.

Interview / resume bullets

  • "Built the platform's capacity model: KV-cache arithmetic that made maximum concurrency a memory calculation rather than a guess, and showed that moving from MHA to 8-way GQA multiplied servable concurrency by eight on the same hardware."
  • "Simulated continuous versus static batching on our real output-length distribution and quantified the win — 2.8× shorter wall-clock on an uneven workload — which turned a serving-stack choice into an evidenced decision."
  • "Made the PTU-versus-pay-as-you-go decision a function of measured traffic mix rather than a vendor datasheet, and adopted a floor-plus-spillover topology so peak demand degrades in cost rather than in availability."
  • "Included GPU-hours and platform-engineering cost in the self-hosting business case, and showed the crossover point as a function of monthly token volume."