Track D — ML and Inference Infrastructure
The reported onsite design round was "design ChatGPT", with the interviewer caring about GPU allocation, autoscaling under non-stationary traffic, and distributed coordination (
../../research/source-report.mdrows 28–32). The reported advice was to abstract the model-serving layer unless told otherwise.This is the round most senior generalists lose. It is also the one where your background transfers further than you would guess — see below.
→ Study guide: WARMUP.md — inference from zero: the KV cache, the roofline derivation, the memory budget, every batching technique, autoscaling, and a complete worked “design ChatGPT” answer at both altitudes.
Table of Contents
- Why This Is Closer to Your Background Than It Looks
- The Two Altitudes
- The One Fact Everything Follows From
- Concept Inventory
- Numbers to Quote Cold
- The Eight Worked Designs
- The Calculator
- Drill Set
- Failure Modes
- Self-Assessment Rubric
- References
Why This Is Closer to Your Background Than It Looks
A decade on multilingual search and recommendation means you have shipped: a serving tier with a hard latency budget, an index that does not fit on one box, ranking under a compute constraint, cache hierarchies where hit rate is the whole economics, and traffic that is non-stationary by time zone.
Serving an LLM is the same problem class with three substitutions:
| Search/ranking | LLM serving |
|---|---|
| Index shards that must fit in RAM | Model weights + KV cache that must fit in HBM |
| Cache hit rate drives cost | Prefix-cache hit rate drives cost |
| Fan-out then merge, bounded by the slowest shard | Prefill then decode, bounded by memory bandwidth |
| QPS-based autoscaling works | QPS-based autoscaling fails — see D4 |
| Tail latency from stragglers | Tail latency from queueing behind long prefills |
The gap is vocabulary and the memory-bandwidth constraint, not concepts. That is roughly six weeks of focused work, not six months — which is why this track gets 20% of hours rather than 40%, despite being the round you are weakest in today.
The Two Altitudes
The reported advice — abstract the model-serving layer unless told otherwise — is a
scoping test, not a hint about depth. This is inference I2 in
../../research/findings.md.
The interviewer wants to see whether you can identify which component is load-bearing for this conversation and hold the rest at a stable interface. Candidates who dive straight into PagedAttention are demonstrating knowledge while failing the actual signal, which is judgement.
Altitude 1 — abstracted (your default). Name the abstraction explicitly and move on:
"I'll treat the inference engine as a service with three properties: it exposes tokens-per- second capacity rather than requests-per-second, it has an admission interface I can apply backpressure to, and it streams. I'll spend my time on traffic, coordination, and failure — tell me if you want me to open it up."
That sentence does three things at once: it proves you know the engine is special, it hands the interviewer the steering wheel, and it buys you the time for the parts they asked about.
Altitude 2 — opened. When they say "open it up," you have seconds, not minutes, to get into KV cache math, batching policy, and scheduling. Hesitating there undoes the credibility the abstraction bought.
Drill both. Default to altitude 1.
The One Fact Everything Follows From
If you internalize one thing in this track, this is it.
Autoregressive decode is memory-bandwidth-bound, not compute-bound.
The derivation, which you should be able to do on a whiteboard in ninety seconds:
To generate one token, the GPU must read every weight in the model. For a 70B model at FP16 that is 140 GB of reads per token, per sequence in the batch — except that a batch shares the weight read. So:
time per decode step ≈ bytes_of_weights / memory_bandwidth
70B FP16 on an H100: 140 GB / 3.35 TB/s ≈ 42 ms (weights alone, one step)
Meanwhile the FLOPs for one token are ~2 × 70e9 = 140 GFLOP. An H100 does ~989.5 TFLOP/s dense dense BF16, so the compute takes ~0.07 ms. Three orders of magnitude apart. The GPU is idle waiting on HBM.
Three consequences, and every technique in this track is one of them:
- Batching is nearly free on the compute axis — you amortize the same weight read across more sequences. This is why continuous batching is the single biggest throughput lever.
- Prefill is the opposite. Processing a 2,000-token prompt is a big matrix multiply: compute-bound, high arithmetic intensity. So prefill and decode want different scheduling, which is the entire reason chunked prefill exists.
- The KV cache, not the weights, is what limits your batch size. It grows linearly with batch size and sequence length, and it is why memory management is the hard part.
The clean empirical proof: the H200 has identical compute to the H100 — same 989.5 TFLOP/s BF16 dense, same 1,979 TFLOP/s FP8 dense — and 43% more memory bandwidth (4.8 vs 3.35 TB/s). It is materially faster at decode. If decode were compute-bound, it would be exactly as fast.
Being able to state this, derive it, and cite the H100/H200 comparison as the evidence is worth more in this round than knowing the name of every serving framework.
Concept Inventory
D1. Request Lifecycle
| Concept | The question it answers |
|---|---|
| Gateway, auth, quota, routing | Where does a request get rejected before it costs a GPU? |
| Model registry and version pinning | How does a conversation stay on one model version? |
| Context assembly: system prompt, history, tools, RAG | What actually gets tokenized, and how big is it? |
| Tokenization, and where it runs | CPU work on the critical path — batch it or move it |
| Prefill vs decode | Two different workloads sharing one accelerator |
| TTFT vs TPOT vs end-to-end | Three SLOs that trade against each other |
| Streaming transport: SSE vs WebSocket | Why SSE usually wins for one-way token streams |
| Abort handling | User closes the tab — how fast do you stop paying for it? |
| Conversation state | Stateless serving with client-supplied history, or server-side sessions? |
D2. GPU Memory and Economics
| Concept | The question |
|---|---|
| Weights + KV cache + activations | The memory budget, and what is left for batch |
| KV cache size formula | See the calculator |
| GQA / MQA and their effect on KV size | The architectural lever that makes long context affordable |
| Quantization: FP16 / FP8 / INT8 / INT4 | What you trade and where quality actually breaks |
| Tensor parallelism | Split a layer across GPUs; needs fast interconnect every layer |
| Pipeline parallelism | Split layers across GPUs; introduces bubbles |
| Expert parallelism (MoE) | Sparse activation; all-to-all becomes the bottleneck |
| Multi-tenancy and fragmentation | Why a 60%-full GPU can refuse a request |
| Cold start and weight loading | Minutes, not seconds — which is why warm pools exist |
| Spot vs reserved capacity | Preemption on a stateful decode is expensive |
| Cost per million tokens | The number the business actually runs on |
D3. Throughput Techniques
Each with what it buys, what it costs, and when it loses.
| Technique | Buys | Costs | Loses when |
|---|---|---|---|
| Continuous batching (Orca) | Huge throughput; no idle slots | Scheduler complexity | Almost never — this is table stakes |
| PagedAttention (vLLM) | Near-zero KV fragmentation; higher batch | Indirection per attention op | Almost never |
| Prefix caching | Skips prefill for shared prefixes | Cache memory; eviction policy | Prefixes are not shared |
| Chunked prefill (Sarathi) | Much better TTFT tail | Slightly lower prefill throughput | Throughput matters more than tail |
| Speculative decoding | Lower latency at low batch | Wasted compute on rejects; a draft model to maintain | High batch — you have no spare compute |
| Quantization | More batch, more speed | Quality, and it is workload-specific | Quality is the product |
| Disaggregated prefill/decode | Each phase scales independently | KV transfer across the network | The transfer cost exceeds the win |
The framing that makes this an answer rather than a list: these are not a stack of wins. They are points on a throughput-versus-tail-latency curve, and different traffic classes want different points. An interactive chat turn, a long agentic tool loop, and a batch API are three different curves. Saying that — and then asking whether they run separate pools or one priority-aware scheduler — is the staff-level move.
D4. Autoscaling Under Non-Stationary Traffic
Explicitly named by the interviewer in the source report. This is where your search background transfers and where it misleads you.
Why request-count autoscaling fails for LLMs. In a search tier, requests are roughly interchangeable, so QPS is a good proxy for load. In LLM serving, one request can be a 20-token prompt with a 5-token answer and another can be a 100k-token prompt with a 4,000-token answer. Their costs differ by four orders of magnitude. QPS-based HPA is measuring the wrong thing, and it will scale up on a burst of cheap requests and fail to scale on a handful of expensive ones.
| Signal | Quality | Why |
|---|---|---|
| Requests/sec | Bad | Cost variance is 10,000x |
| GPU utilization | Misleading | Decode is bandwidth-bound; utilization can read high while throughput is poor |
| Queue depth / waiting time | Good | Directly measures unmet demand |
| Tokens/sec (prefill + decode separately) | Good | The actual unit of work |
| KV cache occupancy | Good | The real capacity constraint; predicts admission failure before it happens |
| TTFT p95 | Good as an SLO trigger | The thing users feel |
Also required:
- Predictive vs reactive. GPU scale-up is minutes (allocation + weight load), so purely reactive scaling is always late. Forecast from historical diurnal patterns and pre-warm. The SageServe and ENOVA lines of work are exactly this.
- Warm pools. Pay for idle capacity to hide cold start. Size it from the forecast error, not from average load.
- Admission control and load shedding. When you cannot scale further, refuse work rather than accepting it and missing SLO for everyone. Little's law and the utilization knee are the argument.
- SLO classes and fairness. Interactive, batch, and free-tier want different queues. Per-tenant fairness so one heavy user cannot starve the rest. Weighted fair queueing on tokens, not on requests.
D5. Distributed Coordination
| Concept | The question |
|---|---|
| Scheduler placement | Which replica gets this request, given its KV state and prefix cache? |
| Prefix-aware routing | Route to the replica that already has this prefix cached — a huge win, and it turns the load balancer into a cache-affinity problem you have solved before |
| Health, drain, and graceful shutdown | Decode sequences in flight for minutes — you cannot just SIGTERM |
| Rolling model rollouts | Two model versions live at once; conversations pinned to one |
| Canaries and shadow traffic | Evaluating a new model without exposing users |
| Config propagation | Rate limits and routing rules updated without a restart |
| Global rate limiting | Per-tenant quotas across regions; the same problem as rate-limiter gate 4 |
| Multi-region | Where does conversation state live? |
D6. Surrounding Systems
| Concept | The question |
|---|---|
| Conversation storage | Append-only, sharded by conversation, hot/cold tiering |
| Retrieval augmentation | Your home turf — embedding, ANN index, chunking, freshness |
| Safety/moderation in the path | It costs latency; is it inline, parallel, or on the output stream? |
| Tool-calling loops | One user turn becomes N model calls — the cost story changes completely |
| Evaluation and telemetry | Offline evals, online metrics, and why token-level logging is expensive |
| Abuse detection | Rate limits, cost caps, prompt-injection monitoring |
Numbers to Quote Cold
Every one verified and attributed. Prices are volatile — always attach a date.
Hardware
| GPU | Memory | Bandwidth | Dense compute |
|---|---|---|---|
| H100 SXM | 80 GB HBM3 | 3.35 TB/s | 989.5 TFLOP/s BF16 · 1,979 FP8 — dense; datasheet doubles these for 2:4 sparsity, which inference never uses |
| H200 SXM | 141 GB HBM3e | 4.8 TB/s | identical to H100 |
| B200 | 192 GB HBM3e | ~8 TB/s | up to ~9,000 TFLOP/s FP4 |
The H100→H200 comparison is your evidence sentence: same compute, 43% more bandwidth, materially faster decode. That is the proof decode is bandwidth-bound.
Cost anchors (2026-reported, order of magnitude only)
| GPU | Cloud hourly |
|---|---|
| H100 | ~$1.50–3.00/hr |
| H200 | ~$3.80/hr |
| B200 | ~$6.50/hr |
Derived rules
| Rule | Value |
|---|---|
| Model weights | ~2 bytes/param at FP16 → 70B ≈ 140 GB |
| KV cache per token | 2 × layers × kv_heads × head_dim × bytes |
| Decode step floor | weight_bytes / bandwidth |
| Prefill FLOPs | ≈ 2 × params × prompt_tokens |
| Decode FLOPs per token | ≈ 2 × params |
Run python3 gpu_math.py for all of these against a model you name.
The Eight Worked Designs
Track C has twelve worked designs; this track has eight of its own, in the same shape — nine sections, six hostile critiques, six revisions each. They are where the concept inventory above becomes a design round.
| # | Design | The calculation that decides it |
|---|---|---|
| m01 | Multi-tenant LLM API platform | one 128k request = 23% of a 4×H100 replica → fairness is KV·seconds, not requests |
| m02 | KV / prefix cache tier | break-even 9.3 GB/s → NVMe is slower than recomputing |
| m03 | GPU cluster scheduler | at 50% free, 0.5 expected fully-free nodes → fragmentation, not capacity |
| m04 | Pretraining data pipeline | all-pairs dedup = 3.5M core-years → MinHash + LSH |
| m05 | Evaluation harness | 500 items resolves only > 6.7 pp → most reported gains are noise |
| m06 | Retrieval-augmented serving | retrieval 85 ms, prefilling it 207 ms → optimize k, not the index |
| m07 | Multi-adapter (LoRA) serving | +6% vs +151% by adapter shape → constrain it at registration |
| m08 | Training fault tolerance | restart is 5.4% of the run, invariant in the checkpoint interval |
Attempt each before reading it. The index also carries what generalizes: which Track C primitives transfer, which distributed-systems instincts actively fail here, and the defect taxonomy across all 48 critiques.
The Calculator
cd tracks/ml-infra
python3 gpu_math.py --model llama-70b --gpu h100
python3 gpu_math.py --model llama-70b --gpu h100 --seq-len 8192 --batch 64
python3 gpu_math.py --list
It prints the memory budget, the maximum batch that fits, the decode-step floor, the roofline verdict, and the cost per million tokens. The output is the script for what you say out loud in the round — derive, do not recite.
Drill Set
| Drill | Cadence | Trains |
|---|---|---|
| Design ChatGPT, altitude 1 | Weekly | The default answer. 45 min, abstracted engine, traffic and coordination |
| Design ChatGPT, altitude 2 | Weekly | The "open it up" answer. Same clock, engine internals |
| Altitude switch, mid-round | Weekly | I interrupt at minute 20 with "open up the serving layer." Trains the transition |
| Memory math from memory | Daily, 5 min | A model and a GPU, no calculator. Weights, KV, max batch, decode floor |
| Technique tradeoff, 60s | Daily | Pick one technique; state what it buys, costs, and when it loses |
| Autoscaling signal defence | Weekly | "Why not just scale on GPU utilization?" Answer in 90 seconds with numbers |
| Paper read + one question | Weekly | Read one paper from the references; write the one question you would ask its authors |
| Benchmark a claim | Biweekly | Take a claimed number, measure it, log the delta. Feeds "numbers I measured" |
Failure Modes
| Failure | Symptom | Fix |
|---|---|---|
| Wrong altitude | 20 minutes on PagedAttention when they asked about traffic | Altitude-1 default; say the abstraction sentence out loud |
| Can't open up on request | "Open the serving layer" → hesitation | Altitude-2 drill |
| Compute-bound reasoning | Sizing decode by FLOPs | Derive the bandwidth floor every day until it is automatic |
| QPS autoscaling | Proposes an HPA on request count | The D4 table |
| Framework name-dropping | "We'd use vLLM" with no mechanism | For every named system, state the mechanism and the tradeoff |
| Claiming internal knowledge | "OpenAI does X internally" | Say what is public and what you are inferring. Nothing about their stack is public at that detail |
| Unattributed numbers | "vLLM gets 3–5x" | Either measure it or attribute it. Both are fine; asserting is not |
| Ignoring cost | A design with no $/token | It is the business. Say the number |
| Forgetting the KV cache | Sizes memory by weights only | It is the constraint, not the weights |
Self-Assessment Rubric
| Level | Standard |
|---|---|
| L0 | Cannot size a model's memory; treats the GPU as a black box |
| L1 | Knows the vocabulary; recites techniques without tradeoffs; would scale on QPS |
| L2 | Derives the memory budget and decode floor; names correct autoscaling signals; holds altitude 1 and can open to altitude 2 |
| L3 | Above, plus frames techniques as points on a throughput/latency curve, reasons about per-tenant fairness and cost per token unprompted, and states clearly what is public versus inferred |
Hire-bar translation
| Verdict | What it looks like |
|---|---|
| No hire | Designs it like a stateless web service |
| Hire (senior) | Correct architecture; abstracts the engine; some memory math |
| Strong hire (senior) | Above, plus correct autoscaling signals with the reason QPS fails |
| Hire (staff) | Above, plus the throughput/latency curve framing and traffic-class separation |
| Strong hire (staff) | Above, plus a cost model, a fairness policy, and an explicitly accepted failure mode |
References
Serving systems and papers
- Kwon et al. Efficient Memory Management for LLM Serving with PagedAttention. SOSP 2023. https://arxiv.org/abs/2309.06180
- Yu et al. Orca: A Distributed Serving System for Transformer-Based Generative Models. OSDI 2022 — origin of continuous batching
- Agrawal et al. Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve. https://arxiv.org/abs/2403.02310 — chunked prefill
- Leviathan et al. Fast Inference from Transformers via Speculative Decoding. ICML 2023. https://arxiv.org/abs/2211.17192
- Ainslie et al. GQA: Training Generalized Multi-Query Transformer Models. https://arxiv.org/abs/2305.13245
- Pope et al. Efficiently Scaling Transformer Inference. MLSys 2023. https://arxiv.org/abs/2211.05102
- Zhong et al. DistServe: Disaggregating Prefill and Decoding. OSDI 2024. https://arxiv.org/abs/2401.09670
- SageServe: Forecast Aware Auto-Scaling for LLM Serving. https://arxiv.org/pdf/2502.14617
- ENOVA: Autoscaling towards Cost-effective and Stable Serverless LLM Serving. https://arxiv.org/abs/2407.09486
Implementations to read
- vLLM — https://github.com/vllm-project/vllm · docs https://docs.vllm.ai/
- vLLM Blog. Inside vLLM: Anatomy of a High-Throughput LLM Inference System (2025-09-05). https://vllm.ai/blog/2025-09-05-anatomy-of-vllm
- NVIDIA TensorRT-LLM — https://github.com/NVIDIA/TensorRT-LLM
- NVIDIA Triton Inference Server — https://github.com/triton-inference-server/server
- Ray Serve — https://docs.ray.io/en/latest/serve/index.html
- SGLang — https://github.com/sgl-project/sglang (RadixAttention / prefix caching)
Operational grounding
- OpenAI. Scaling Kubernetes to 7,500 nodes. https://openai.com/index/scaling-kubernetes-to-7500-nodes/
- NVIDIA. H100 Tensor Core GPU. https://www.nvidia.com/en-us/data-center/h100/
- Hello Interview. Design ChatGPT. https://www.hellointerview.com/learn/system-design/problem-breakdowns/chatgpt
- Related tracks in this repo: llm-inference-engineer · Senior AI Engineer · pretraining-lead