Want it harder? Hide the model answer and work each phase in a scratchpad against its time budget, then reveal and score yourself. Or simply read on: the checkpoints below work either way.
How Do You Stream a Reply One Token at a Time to Hundreds of Millions of People Without Melting the GPUs?
It is 8:40 pm in Bengaluru. Meera types "Plan a three-day trip to Hampi with a toddler" and presses enter. Within a second the first words appear, and the rest of the reply flows in faster than she can read it.
In that second her message was checked, joined to the earlier turns of her conversation and squeezed onto a GPU alongside a couple of hundred other people's replies. Every few tens of milliseconds the GPU writes one more token for each of them; her reply is a few hundred such steps.
More servers can't make one reply faster, because the model writes one token after another. The design has to make each GPU serve as many people at once as it can, without anyone noticing they are sharing.
- 1.2 billion — weekly users in September 2026, shown at OpenAI's DevDay; up from 900 million in February 2026, when OpenAI reported 50 million paying subscribers
- 2.5 billion — prompts a day in July 2025, about 330 million of them from the US (OpenAI, via Axios)
- 6 billion+ — tokens a minute through OpenAI's API in October 2025 (Sam Altman, DevDay)
- 1 million — new users in one hour after image generation reached everyone in March 2025; the original launch took five days to reach 1 million (Sam Altman)
- "Our GPUs are melting" — Sam Altman, March 2025, announcing temporary rate limits on image generation
For context:
A token is a piece of a word: OpenAI's rule of thumb is 1 token ≈ 4 characters ≈ ¾ of an English word
OpenAI has published little about how ChatGPT is served; everything not sourced here is how you would design it, not OpenAI's internals
The sizing uses an open 70-billion-parameter model of known shape, not any OpenAI model
This is the system we'll design today.
ChatGPT serves 1.2 billion weekly users: ~100,000 messages a second at peak is 50 million output tokens a second, and a 4-GPU replica fits ~225 conversations because KV cache, not weights, fills memory. Messages pass the edge, context assembly and a router to a GPU replica, and tokens stream back through a resumable token log. The deep dives cover memory-bound decode, continuous batching, paged KV and prefix caching, tier lanes and slow cold starts, then a dead replica, the degradation ladder and a 5x launch day.
The Interview Begins#
You're interviewing at a company that runs a popular AI assistant. The interviewer opens a graph of GPU utilisation across the fleet: a wave that follows the sun, with a sharp spike on the day a new feature launched.
"We have the model; training is someone else's problem. Your job is serving it: a message in, a reply streaming back, for over a billion people a week, without the GPU bill or the latency getting out of hand."
Users send messages in conversations from web, mobile and desktop apps. Each reply is generated by a large model on GPUs and streamed back as it is written. Free and paid users share the fleet; paid users get more and faster.
Requirements
- Show the first words of a reply within about a second, and keep them coming faster than anyone reads
- Use the GPUs well: they are the cost of the whole business
- Be fair: paid tiers keep their promises under load, free users get an honest answer, not a spinner
- Check inputs and outputs for safety without making every reply wait
- Survive a GPU failing mid-reply, a region going down and a launch at 5× demand
"Assume the weights exist. Everything from the keyboard to the GPU and back is yours."
Phase 1: Requirements Clarification (5 minutes)#
"I want to pin down what 'fast' and 'cheap' mean for LLM serving."
Your Questions#
"How many users and messages?"
"ChatGPT showed 1.2 billion weekly users at OpenAI's DevDay in September 2026, and reported 2.5 billion prompts a day in July 2025, when it was approaching 700 million weekly users. Design for 1.2 billion weekly users."
"What does fast mean for a reply that takes twenty seconds to write?"
"Two numbers. Time to first token: how long until the first word appears. Then time per output token: how fast the rest arrives. People forgive a long reply; they don't forgive a blank screen."
"Are there tiers?"
"Free, paid and business plans. OpenAI has said that once usage limits are reached, a mini version of each model handles the remaining queries, and describes GPT-5 as a fast model and a reasoning model behind a real-time router."
"Where does safety sit?"
"In the path: inputs before generation, outputs while they stream. OpenAI's API documentation warns that streaming makes moderation harder, because partial completions are harder to judge."
Functional Requirements#
- Chat: send a message and get a streamed reply; stop it; regenerate
- Conversations: history on every device; each reply sees the earlier turns
- Models and tiers: several models (fast, reasoning, mini); per-tier limits; fallback to a smaller model past a limit
- Safety: input checked before generation, output during streaming; a reply can be stopped and replaced
- Accounting: tokens per user and per model, for limits, billing and cost
Non-Functional Requirements#
Latency (design targets, at our edge, excluding visible "thinking" time)
- Time to first token (TTFT): p95 < 1 s for paid tiers, < 2 s for free
- Time per output token (TPOT): p95 < 50 ms, i.e. at least 20 tokens a second per reply
- No stalls: 99.9% of streams never pause for more than 2 s mid-reply
Availability and fairness
- 99.9% of messages get a complete reply, or an honest, actionable error, per 30 days
- Under overload, paid tiers keep their latency targets; free users get a clear limit, a smaller model or an honest wait
Cost
- GPU cost per million output tokens and per message are first-class metrics; the fleet is sized for the peak, so utilisation is money
"Why 20 tokens a second? Say people read 250 words a minute: at ¾ of a word per token, 250 ÷ 60 ÷ 0.75 ≈ 5.6 tokens a second. Twenty leaves room to skim; much faster mostly buys nothing."
Phase 2: Back of the Envelope Estimation (5 minutes)#
"Requests are the wrong unit. A GPU's work is tokens read in (prefill) and tokens written out (decode). Tokens a second first, then GPUs, memory and power."
Before the numbers: how many tokens a second?
Design for 4.3 billion messages a day. If an average reply is 500 tokens and the evening peak is twice the daily average, how many output tokens a second does the fleet write at peak?
One strong answer
- Messages: 4.3 billion ÷ 86,400 s ≈ 49,800 a second on average, call it 50,000; at 2× peak, ~100,000 a second.
- Output tokens: 50,000 × 500 = 25 million tokens a second on average; ~50 million a second at peak.
- Input tokens: if each message carries ~2,000 tokens of context (instructions, history, the new message), that's 100 million tokens a second read on average.
Each written token is one step of the model through all its weights.
Traffic Estimation#
| Average | Evening peak (2×, assumption) | |
|---|---|---|
| Messages a day (design: 1.2 billion weekly users, scaled from 2.5 billion at ~700 million) | 4.3 billion | — |
| Messages a second | ~50,000 | ~100,000 |
| Output tokens a second (500 a reply, assumption, including any hidden reasoning) | 25 million | 50 million |
| Input tokens a second (2,000 a message, assumption) | 100 million | 200 million |
| Input tokens computed, if 50% are served from prefix caches (assumption) | 50 million | 100 million |
- Conversations a day at ~4 messages each (assumption): 4.3 billion ÷ 4 ≈ 1.1 billion
- Sanity check: 100 + 25 = 125 million tokens a second; OpenAI's API alone reported 6 billion a minute (100 million a second) in October 2025, the same order of magnitude
GPU Memory: Weights vs KV Cache#
"OpenAI doesn't publish its models' sizes, so I'll size with an open model, Llama 3.3 70B: 80 layers, 64 query heads but only 8 key-value heads (grouped-query attention), head dimension 128, 128K-token context."
- Weights: 70 billion × 2 bytes (bf16) = 140 GB; in fp8, 70 GB. Neither leaves room on one 80 GB GPU, so the model is split across GPUs (tensor parallelism)
- KV cache per token = 2 (K and V) × layers × kv_heads × head_dim × bytes = 2 × 80 × 8 × 128 × 2 = 327,680 bytes ≈ 0.33 MB in bf16 (0.16 MB in fp8)
- Per sequence: a 2,000-token conversation = 2,000 × 327,680 ≈ 655 MB; a full 128K-token context (131,072 tokens) ≈ 43 GB, more than half a GPU for one user
- Without grouped-query attention (64 KV heads, not 8) the cache would be 8× bigger: 2.6 MB a token
| Replica (illustrative) | 4 × H100, bf16 | 2 × H100, fp8 weights + fp8 KV |
|---|---|---|
| GPU memory | 320 GB | 160 GB |
| Weights | 140 GB | 70 GB |
| Reserved for activations, runtime (~10%) | 32 GB | 16 GB |
| Left for KV cache | 148 GB | 74 GB |
| Tokens of KV cache | 148 GB ÷ 327,680 B ≈ 450,000 | 74 GB ÷ 163,840 B ≈ 450,000 |
| Sequences of 2,000 tokens | ~225 (~56 a GPU) | ~225 (~112 a GPU) |
Conclusion: after the weights, KV cache memory decides how many people a replica serves at once; fp8 serves the same 225 on half the GPUs, if quality holds.
Throughput, GPUs and Power#
- One decode step reads all the weights once, plus every sequence's KV cache: 140 GB + 225 × 655 MB ≈ 288 GB per step on the 4-GPU replica
- At 4 × 3.35 TB/s = 13.4 TB/s: 288 GB ÷ 13.4 TB/s ≈ 21.5 ms a step at best → ~46 tokens a second per stream, ~10,500 tokens a second per replica
- At a realistic ~60% of peak bandwidth (assumption): ~36 ms a step, ~28 tokens a second per stream, ~1,500 tokens a second per GPU
- Prefill is compute: 2 FLOPs per parameter per token = 1.4 × 10¹¹ FLOPs a token. H100 dense bf16 is ~989 TFLOPS; at 50% (assumption) ≈ 3,500 prompt tokens a second per GPU. A 2,000-token prompt on 4 GPUs: ~140 ms
- Decode at peak: 50 million ÷ 1,500 ≈ 33,300 GPUs
- Prefill at peak: 100 million uncached tokens ÷ 3,500 ≈ 28,600 GPUs
- Together ≈ 62,000; with ~20% headroom for failures and spikes, ~75,000 H100-class GPUs
- Cross-check with Little's law: a reply of 500 tokens at ~28 a second takes ~18 s; 100,000 a second × 18 s ≈ 1.8 million streams in flight. At 225 per 4-GPU replica that's ~8,000 replicas ≈ 32,000 GPUs for decode, close to the 33,300 above
- Power: 75,000 GPUs ≈ 9,400 eight-GPU servers × 10.2 kW (DGX H100 maximum) ≈ 96 MW; at a PUE of 1.2 (assumption) ≈ 115 MW, a small power station
- Cost: at $2.50 per GPU-hour (assumption), 75,000 × $2.50 × 24 = $4.5 million a day, ≈ $0.001 a message; per million output tokens, a GPU at 1,500 tokens a second writes 5.4 million an hour, ≈ $0.46 at full use, but the fleet averages ~40% busy (31,000 GPUs of work against 75,000), so all-in it is nearer $2
Key Metrics Summary#
| Metric | Value |
|---|---|
| Messages a second, average / peak (design) | ~50,000 / ~100,000 |
| Output tokens a second, average / peak | 25 million / 50 million |
| Sequences per 4 × H100 replica | ~225 |
| Streams in flight at peak | ~1.8 million |
| GPUs (illustrative, one 70B model for everything) | ~75,000 H100-class |
| Power | ~115 MW |
Requirements and estimation
Q1/3 A 70B model has 80 layers, 8 key-value heads and a head dimension of 128. How big is its KV cache per token in bf16?
Answer & explanation
Answer: B
2 × 80 × 8 × 128 × 2 bytes = 327,680 bytes. With 64 KV heads instead of 8 it would be 2.6 MB.
Q2/3 Why is time to first token measured separately from time per output token?
Answer & explanation
Answer: B
TTFT covers admission, queueing and reading the whole prompt; TPOT is the steady pace of decode steps. They fail for different reasons.
Q3/3 After the weights are loaded, what mostly limits how many replies one replica can generate at once?
Answer & explanation
Answer: C
Every sequence in the batch keeps its keys and values in GPU memory; 148 GB holds ~450,000 tokens, about 225 conversations of 2,000 tokens.
Phase 3: High-Level Design (10 minutes)#
Sketch it first: one message, keyboard to screen
Trace Meera's message from the app to the GPU and back. Which parts are ordinary web services, which part is scarce and expensive, and where does the reply live while it is being written?
One strong answer
- Ordinary and cheap: edge, auth, quotas, conversations, storage: any web tier.
- Scarce and expensive: GPU replicas. Everything in front of them keeps them full of useful work and away from work they can't finish in time.
- The router and schedulers pick the model, region and replica for each request, and the order.
- While it streams, the reply lives twice: in the replica's KV cache (to write the next token) and in a short-lived token log (to resend tokens after a reconnect). It reaches the conversation store when it ends.
- Safety is in the path: input before generation, output while it streams.
"The GPU tier is the only part that is hard to scale and expensive to waste. Everything else is designed around keeping it busy and fair."
System Architecture#
%% title: LLM serving architecture
flowchart TD
subgraph L1["Layer 1 · Clients"]
direction LR
APP["ChatGPT apps<br/>web, mobile, desktop"]:::client
DEV["API clients<br/>another front door"]:::client
end
subgraph L2["Layer 2 · Edge"]
direction LR
EDGE["Edge + TLS<br/>long-lived SSE streams"]:::edge
GW["API gateway<br/>auth, quotas, limits"]:::edge
end
subgraph L3["Layer 3 · Conversation"]
direction LR
CONV["Conversation service<br/>context assembly"]:::service
SAFE["Safety<br/>input + output checks"]:::service
HIST[("Conversation store<br/>replicated")]:::store
end
subgraph L4["Layer 4 · Scheduling"]
direction LR
ROUTE["Router<br/>model, region, prefix"]:::core
LANES[["Tier lanes<br/>paid, free, background"]]:::queue
end
subgraph L5["Layer 5 · Inference"]
direction LR
REP["Model replicas<br/>4 GPUs, batch + KV cache"]:::hot
MINI["Small-model pool<br/>fallback, routing"]:::service
TLOG[("Token log<br/>replies in flight")]:::cache
end
subgraph L6["Layer 6 · Operations"]
direction LR
TEL["Telemetry<br/>TTFT, TPOT, cost"]:::service
FLEET["Fleet manager<br/>capacity, spot, drains"]:::service
BILL[["Usage events<br/>limits, billing"]]:::queue
end
L1 ==> L2
L2 ==>|"message"| L3
L3 ==>|"generate"| L4
L4 ==>|"admit to a replica"| L5
L5 -->|"tokens, usage, health"| L6KEY INSIGHTS:
- The router and the replicas' schedulers are the core; the rest is a well-known web tier
- Replies stream back up the same path (relay, conversation service, edge) over long-lived connections: ~1.8 million open at peak
- Tokens pass through a relay with a short log, so a dropped connection doesn't lose work the GPU already paid for
Data Flow: One Message at 20:40#
%% title: One message, end to end
sequenceDiagram
autonumber
participant A as Meera's app
participant E as Edge + gateway
participant C as Conversation svc
participant S as Scheduler
participant M as Replica r17
Note over A: 20:40:03.000 · "Plan a 3-day trip to Hampi"
A->>E: Send message (conversation c42, key m9)
E->>E: Auth · quota ok (paid tier)
E->>C: Forward · open the stream back
C->>C: Input check · build 2,000-token context
C->>S: Generate (model, tier, prefix hash)
S->>M: Admit · prefix already cached here
M->>M: Prefill the ~300 new tokens
M-)C: Token 1 at 20:40:03.420
C-)A: First words on screen (through the edge)
loop every ~35 ms until a stop token
M-)C: Next token
C-)A: Stream event, numbered
end
M->>M: Free the KV blocks
C->>C: Output check · save the reply
C-)A: DoneMEANWHILE, IN PARALLEL:
- Replica r17 is writing ~224 other replies; each decode step adds a token to all of them
- The output check runs on each finished sentence, alongside the stream
High-level design
Q1/3 Which tier of the design is scarce and expensive enough that everything else is built around it?
Answer & explanation
Answer: C
The rest are ordinary web services. Every design choice in front of the GPUs is about keeping them full of useful work.
Q2/3 Why do replies pass through a relay with a short token log?
Answer & explanation
Answer: B
The GPU already paid for those tokens. A log lets a reconnecting client ask for "everything after token 212".
Q3/3 A reply takes ~18 s and 100,000 replies start every second at peak. Roughly how many streams are open?
Answer & explanation
Answer: C
Little's law: in flight = rate × time = 100,000 × 18 s ≈ 1.8 million.
Phase 4: Deep Dives (20 minutes)#
Deep Dive 1: Why LLM Serving Is Different (Week 0 Concepts: Back of the Envelope, Operating Systems)#
"A web request does a fixed amount of work. A model's reply is a loop: one step per token, each depending on the one before. That changes where time and memory go."
One user on a whole replica
A 4-GPU replica serves exactly one user. The 70B model is in bf16 and the conversation is 2,000 tokens. How fast does that user get tokens, and how busy are the GPUs' compute units? Now put 225 users on it. What changes?
One strong answer
- One user: each step reads 140 GB of weights + 0.66 GB of KV cache at 13.4 TB/s ≈ 10.5 ms: ~95 tokens a second. 1.4 × 10¹¹ FLOPs in 10.5 ms is ~13 TFLOPS of ~3,956 available: 0.3% busy.
- The reason: decode does about 1 FLOP per byte read (2 FLOPs per 2-byte weight), while an H100 can do ~295 FLOPs per byte its memory delivers. Decode at batch 1 is memory-bound.
- 225 users: the weights are read once per step for everyone. The step grows to ~21.5 ms (each sequence's KV adds reads), so each user gets ~46 tokens a second, but the replica writes ~10,500 a second, 110× more, and compute is ~37% busy.
- Batching is the whole economics: each user loses a little speed; the GPU's output multiplies.
The Challenge:
- Work isn't known in advance: a reply may stop after 5 tokens or 5,000, so neither its duration nor its memory is known when it starts
- Two phases with opposite needs: reading the prompt is compute-heavy; writing the reply is memory-bandwidth-heavy
- State grows while it runs: every token adds 0.33 MB to the sequence's KV cache, on the GPU, until the reply ends
- Steps are sequential: token 101 can't start before token 100, so a slow step slows every reply in the batch
The Solution:
%% title: Prefill and decode
flowchart TD
P(["Prompt · ~2,000 tokens"]):::client
subgraph PF["Prefill · once per request"]
PF1["All prompt tokens at once<br/>large matrix multiplies"]:::core
PF2["Compute-bound<br/>~140 ms on 4 GPUs"]:::service
end
KV[("KV cache<br/>0.33 MB per token")]:::cache
subgraph DC["Decode · once per output token"]
D1["One token for each sequence<br/>reads all weights + its KV"]:::hot
D2{"Stop token or<br/>max tokens?"}:::service
end
OUT(["Token streamed to the user"]):::ok
DONE(["Finished · KV blocks freed"]):::ok
P --> PF1 --> PF2 -->|"writes K, V for every prompt token"| KV
KV --> D1 --> OUT
D1 --> D2
D2 -->|"no · append this token's K, V"| KV
D2 -->|"yes"| DONE- Prefill reads the whole prompt in parallel and writes its keys and values into the KV cache. It sets the time to first token.
- Decode produces one token per step for every sequence in the batch, reading all the weights and each sequence's cache. It sets the time per output token.
- The KV cache is why decode doesn't recompute the prompt every step, and why memory runs out.
"Two optimisations follow. fp8 weights and KV cache halve the bytes per step and per token. And since the phases want different things, they can be separated: DistServe (OSDI 2024) runs prefill and decode on different GPUs, while Sarathi-Serve (OSDI 2024) cuts long prompts into chunks that ride along with decode steps."
Deep Dive 2: Batching, Paged KV Memory and Prefix Caching (Week 4 Concepts: Caching, Week 0: Operating Systems)#
"So batching is the economics. The question is how to batch requests that start at different times, stop at unknown times and grow as they go."
A batch of eight, one long reply
A replica batches 8 requests and runs them until all finish. Seven replies are 50 tokens long; one is 2,000. The KV cache is reserved per request for the 4,096-token maximum. What does that cost, and how would you fix it?
One strong answer
- Static batching wastes time: the 7 short replies finish at step 50, but their slots sit empty until the long one finishes at step 2,000. For 97.5% of the batch's life it runs 1 of 8.
- New requests wait for the whole batch, so their time to first token includes someone else's long reply.
- Reserving for the maximum wastes memory: 4,096 tokens × 0.33 MB ≈ 1.3 GB held per request, when most use a fraction.
- Fix 1, continuous batching: schedule per step, not per batch. A finished sequence leaves after its step and a waiting one joins at the next. This is Orca's iteration-level scheduling (OSDI 2022).
- Fix 2, paged KV memory: allocate the cache in small blocks (16 tokens) on demand, like virtual-memory pages, so a sequence holds only what it has used. This is vLLM's PagedAttention (SOSP 2023).
The Solution:
- 36.9× — Orca's throughput over NVIDIA FasterTransformer at the same latency, on GPT-3 175B (OSDI 2022)
- 20.4%–38.2% — share of KV cache memory holding actual tokens in the systems vLLM's authors measured; the rest lost to reservation and fragmentation
- 2–4× — vLLM's throughput over FasterTransformer and Orca at the same latency (SOSP 2023)
- Up to 6.4× — SGLang's throughput, helped by RadixAttention reusing KV cache across shared prefixes
In a static batch of 8, seven 50-token replies finish at step 50 and their slots idle until the 2,000-token reply ends: 1 of 8 runs for 97.5% of the batch, and new requests wait. Continuous batching schedules per step, so finished sequences leave and waiting ones join at the next step. Paged KV memory replaces a 4,096-token reservation (~1.3 GB a request) with 16-token blocks on demand, and prefix caching shares full blocks of the same instructions.
%% title: A request inside one replica stateDiagram-v2 [*] --> WAITING: routed to this replica WAITING --> RUNNING: blocks free for its prompt RUNNING --> PREEMPTED: out of blocks, newest evicted PREEMPTED --> WAITING: requeued, prefix recomputed RUNNING --> FINISHED: stop token or max tokens RUNNING --> CANCELLED: user stopped or left WAITING --> CANCELLED: user left before it started FINISHED --> [*] CANCELLED --> [*] WAITING: WAITING · in a tier lane RUNNING: RUNNING · one token per step PREEMPTED: PREEMPTED · blocks taken back class RUNNING core class WAITING,PREEMPTED queue class FINISHED ok class CANCELLED bad
Prefix caching is the third lever. Many prompts share a beginning:
- Shared instructions: every conversation starts with the same system instructions and tool descriptions
- The conversation so far: turn 6 of Meera's chat repeats turns 1 to 5
- Keep those blocks: hash each full block with everything before it; a request whose prefix hashes match shares those blocks (reference-counted) and prefills only the new part
- Published behaviour: OpenAI's API documentation describes cached prefixes lasting minutes (typically 5 to 10 of inactivity on earlier models, 30 after the last use on its newest) and discounts cached input tokens
# engine/scheduler.py
"""
Continuous batching with paged KV memory and a prefix cache (illustrative,
in the style of vLLM). One call = one step for the whole running batch.
Only FULL prompt blocks are shared, so nobody writes into a shared block;
a freed block keeps its cached prefix until it is handed out again.
"""
from collections import OrderedDict, deque
BLOCK = 16 # tokens per block
class BlockPool:
def __init__(self, n_blocks: int):
self.free = OrderedDict.fromkeys(range(n_blocks)) # least recently freed first
self.refs, self.by_hash, self.hash_of = {}, {}, {}
def take(self, h=None) -> int:
b, _ = self.free.popitem(last=False) # evicts the oldest cached prefix
old = self.hash_of.pop(b, None)
if old is not None:
del self.by_hash[old]
if h is not None:
self.by_hash[h], self.hash_of[b] = b, h
self.refs[b] = 1
return b
def reuse(self, h):
b = self.by_hash.get(h)
if b is not None:
if self.refs.get(b, 0) == 0:
del self.free[b] # revive a cached, freed block
self.refs[b] = self.refs.get(b, 0) + 1
return b
def release(self, b: int) -> None:
self.refs[b] -= 1
if self.refs[b] == 0:
self.free[b] = None
class Scheduler:
def __init__(self, pool: BlockPool, max_batch: int = 256, reserve: int = 64):
self.pool, self.max_batch, self.reserve = pool, max_batch, reserve
self.waiting, self.running = deque(), [] # waiting: filled by the router, by lane
def _blocks_for(self, seq):
full = len(seq.prompt) // BLOCK
blocks = []
for i in range(-(-seq.length // BLOCK)): # a preempted seq re-prefills its output too
h = seq.prefix_hash(i) if i < full else None
b = self.pool.reuse(h) if h is not None else None
if b is not None:
seq.cached_tokens += BLOCK # no prefill for this block
elif len(self.pool.free) > self.reserve:
b = self.pool.take(h) # keep a reserve for running seqs to grow
else:
for b2 in blocks:
self.pool.release(b2)
seq.cached_tokens = 0
return None
blocks.append(b)
return blocks
def step(self, model) -> None:
for seq in [s for s in self.running if s.done or s.cancelled]:
self._free(seq) # 1. finished and cancelled leave first
while self.waiting and len(self.running) < self.max_batch:
blocks = self._blocks_for(self.waiting[0])
if blocks is None:
break # 2. admit until memory is tight
seq = self.waiting.popleft()
seq.blocks = blocks
self.running.append(seq)
for seq in list(self.running): # 3. room for each sequence's next token
if seq not in self.running or seq.length < len(seq.blocks) * BLOCK:
continue
while not self.pool.free and len(self.running) > 1:
self._preempt(max(self.running, key=lambda s: s.arrived)) # newest first
if seq in self.running:
if self.pool.free:
seq.blocks.append(self.pool.take())
else:
self._preempt(seq)
model.forward(self.running) # 4. prefill arrivals, one token for the rest
def _free(self, seq) -> None:
for b in seq.blocks:
self.pool.release(b)
seq.blocks = []
self.running.remove(seq)
def _preempt(self, victim) -> None:
self._free(victim)
victim.cached_tokens = 0
self.waiting.appendleft(victim) # recomputed when blocks free upA few details carry the design:
- Stop admitting before memory runs out: preemption throws work away, so a reserve of blocks is kept for running sequences to grow into.
- Preempt the newest: it has the least to redo. vLLM's paper evicts whole sequences and swaps their blocks to CPU memory or recomputes them later.
- A cancelled sequence frees its blocks at the next step, so "Stop" saves money, not just attention.
- Real engines also cache full blocks written during decode, so turn 5's reply is cached for turn 6; the sketch hashes prompt blocks only.
Prefill, decode and batching
Q1/4 Why does a replica's throughput rise ~110× when it goes from 1 to 225 sequences?
Answer & explanation
Answer: C
Decode is memory-bound. One read of 140 GB of weights serves every sequence in the batch, so each extra sequence costs only its own KV reads.
Q2/4 In static batching, seven 50-token replies share a batch with one 2,000-token reply. What happens to the seven slots after step 50?
Answer & explanation
Answer: A
That idle time is what continuous (iteration-level) batching removes: slots are refilled every step.
Q3/4 What does PagedAttention change about KV cache memory?
Answer & explanation
Answer: C
Blocks of 16 tokens, mapped like virtual-memory pages, cut waste to under one block per sequence and let shared prefixes share blocks.
Q4/4 Turn 6 of a conversation arrives at the replica that served turn 5 two minutes ago. What can prefix caching skip?
Answer & explanation
Answer: B
The earlier turns' KV blocks are still cached, so only the new message has to be prefilled. Arriving at a different replica would lose this.
Deep Dive 3: Scheduling and Routing Across the Fleet (Week 1 Concepts: Rate Limiting, Week 9: Noisy Neighbours, Week 10: Capacity Planning)#
"Inside a replica, the scheduler fills a batch. Across the fleet, the router picks the replica and the fleet manager decides how many there are, both respecting tiers and warm caches."
The evening peak and the free tier
It's 21:00 in India and 16:30 in Europe. One region's replicas are 95% full. Free users send about 85% of the messages. A paid user's message and three free users' messages arrive in the same millisecond. Who goes first, where does each go, and what does a free user see if the region stays full for twenty minutes?
One strong answer
- Lanes by tier: paid interactive, then free interactive, then background (batch jobs, evaluations, flex-tier API traffic). Within a lane, round-robin by user so one heavy user can't fill it.
- Admission by memory: admit only if the prompt's blocks fit and the TTFT estimate is inside the lane's target.
- Route for the cache: to the replica holding the conversation's prefix, unless its queue is over a threshold; then the least-loaded one, paying the prefill.
- Spill across regions: a neighbouring region costs ~100 ms of network but saves seconds of queueing.
- Twenty minutes full: free users past their limit get the smaller model (as ChatGPT does); if that is full too, "high demand, try again in about 10 minutes", not a spinner.
The Challenge:
- Round-robin routing scatters a conversation's turns across replicas, so every turn re-prefills its whole history
- Pure cache affinity turns one popular prefix into one hot replica
- One queue for everyone lets a free-tier surge push paid TTFT past its target
- Reactive autoscaling asks for GPUs when the queue is long; a new replica takes minutes to be useful
The Solution:
%% title: Routing one request
flowchart TD
REQ(["Generate request"]):::client
TIER{"Within the tier's<br/>limits?"}:::service
FALL(["Mini model<br/>or a clear limit message"]):::bad
MODEL["Choose the model<br/>fast, reasoning or mini"]:::core
REGION["Choose the region<br/>nearest with headroom"]:::edge
AFF{"A replica holds<br/>this prefix?"}:::cache
LOAD{"Its queue under<br/>the threshold?"}:::service
HOT(["Send there<br/>skip most prefill"]):::ok
LEAST(["Least-loaded replica<br/>prefill from scratch"]):::service
subgraph Q["Lanes on every replica"]
direction LR
Q1[["1 · Paid interactive"]]:::queue
Q2[["2 · Free interactive"]]:::queue
Q3[["3 · Background, flex"]]:::queue
end
REQ --> TIER
TIER -->|"over"| FALL
TIER -->|"within"| MODEL --> REGION --> AFF
AFF -->|"yes"| LOAD
AFF -->|"no"| LEAST
LOAD -->|"yes"| HOT
LOAD -->|"no · overflow"| LEAST
HOT --> Q
LEAST --> Q- Quotas in tokens, not requests: a token bucket per user per model, charged with the prompt plus the requested maximum output. OpenAI's API limits requests and tokens per minute (RPM, TPM), counts the larger of
max_tokensand an estimate of the request against TPM, and returns 429 with rate-limit headers. - Prefix affinity with overflow, the published shape of OpenAI's prompt-cache routing: a hash of the first tokens picks the machine; heavy prefixes spill to more machines.
- Lanes, not one queue: paid work is admitted first; background work fills the gaps, so idle GPU-seconds become cheap batch output.
Autoscaling GPUs That Take Minutes to Start#
- Get a machine: seconds from a warm pool, minutes from a cloud provider, never if it is out of capacity
- Pull the serving image: ~1 minute
- Load 140 GB of weights: at ~2 GB/s from network storage ≈ 70 s; from local NVMe at ~10 GB/s ≈ 14 s
- Warm up (compile kernels, capture CUDA graphs, health checks): ~1 minute
- Total: ~3 to 10 minutes before it serves its first token, and its prefix cache starts cold
"So the fleet scales on a forecast, not a queue. The daily wave follows the sun and repeats, so capacity is placed ahead of it; reactive scaling covers forecast error, and the degradation ladder covers the minutes in between."
Reserved or owned
- The baseline, busy every hour of the day
- Cheapest per hour when used; paid for when not
On-demand and spot
- The evening peak and launch days
- Spot is cheaper but can be taken back: AWS gives a two-minute interruption notice
- On notice: stop admitting, let short replies finish, resubmit the rest elsewhere
Background work
- Batch, evaluations, flex-tier traffic in the troughs
- First to pause when interactive load rises
Deep Dive 4: Streaming Tokens Reliably, and Assembling the Context (Week 3 Concepts: Backpressure, Week 2: Idempotency)#
"Each reply is a few hundred small events over 10 to 30 seconds, to phones that change networks and users who press Stop. The stream must never make the GPU repeat work, or keep it working for nobody."
Wi-Fi to 4G at token 212
Meera walks out of a café while her reply is streaming. Her phone drops Wi-Fi at token 212 and reconnects on mobile data two seconds later, to a different edge server. Then she taps Stop at token 260. What does she see, what does the GPU do, and what is saved in her conversation?
One strong answer
- Number every event: each token goes out as a server-sent event with an
id; on reconnect the app sendsLast-Event-ID: 212, which any edge server can forward. - Keep a short log per reply (in memory, replicated, kept for minutes): the new connection gets tokens 213 onwards from the log, then goes live. The GPU never noticed.
- Stop means stop: the cancel aborts the sequence and the replica frees its KV blocks at the next step. No reconnect within ~30 s counts as a cancel too.
- Partial replies are kept: the 260 tokens are stored, marked "stopped", so the next turn sees what she saw.
- Retries are idempotent: the message carries a client key, so a resent "send" attaches to the reply already being written.
The Challenge:
- A dropped connection makes the app resend the message, and the GPU writes the whole reply again
- A closed tab leaves a sequence generating 2,000 tokens for nobody, holding 0.66 GB of KV cache
- A slow client's unsent events pile up in a server's memory
- Safety checks that wait for the full reply destroy time to first token
The Solution:
OpenAI's public API shows a practical shape: responses stream as server-sent events with typed events (response.created, response.output_text.delta, response.completed, error), and its "background mode" lets a client cancel a response or resume a stream from the last event's sequence number (starting_after).
%% title: Resume and cancel a stream
sequenceDiagram
autonumber
participant A as App
participant E as Edge server
participant R as Relay + token log
participant G as Replica
A->>E: Send message, open stream
E->>R: Subscribe to reply r88 from the start
R->>G: Generate r88
loop each token
G-)R: Token n, appended to r88's log
R-)A: Event id n, through the edge
end
Note over A,E: Wi-Fi drops at token 212
A->>E: Reopen, Last-Event-ID 212 (another server)
E->>R: Subscribe to r88 after 212
R-)A: Tokens 213 to 240 from the log, then live
A->>E: Tap Stop at token 260
E->>R: Cancel r88
R->>G: Abort r88, free its KV blocks
R->>R: Save 260 tokens, marked stopped# relay/stream.py
"""SSE for one reply, with resume and cancel (illustrative, asyncio). The
token log is per reply, replicated in memory for minutes; any edge can serve it."""
import asyncio
IDLE_CANCEL_S = 30 # no client attached this long: stop generating
SEND_TIMEOUT_S = 10 # a client this slow is dropped; it can resume
def sse(event_id: int, data: str, event: str = "delta") -> bytes:
lines = "".join(f"data: {line}\n" for line in data.split("\n"))
return f"id: {event_id}\nevent: {event}\n{lines}\n".encode()
async def stream_reply(reply_id: str, last_event_id: int, log, conn) -> None:
"""Serve tokens after last_event_id, from the log, then live."""
log.attach(reply_id)
try:
cursor = last_event_id
async for n, token, final in log.read_from(reply_id, after=cursor):
try:
await asyncio.wait_for(conn.send(sse(n, token)), SEND_TIMEOUT_S)
except asyncio.TimeoutError:
return # slow reader: it reconnects with Last-Event-ID
cursor = n
if final:
await conn.send(sse(n + 1, final, event="done")) # finished, stopped or failed
return
finally:
log.detach(reply_id) # if nobody re-attaches within IDLE_CANCEL_S, cancel()
async def cancel(reply_id: str, log, engine, store, reason: str) -> None:
"""User pressed Stop, or nobody is listening. Safe to call twice."""
if not log.mark_final(reply_id, reason):
return # already finished or cancelled
await engine.abort(reply_id) # blocks freed at the next step
text = log.text(reply_id)
await store.save_partial(reply_id, text, status=reason) # the next turn sees what the user sawConversation Storage and Context Assembly#
- Per exchange: ~100 tokens in + ~500 out ≈ 600 tokens × 4 bytes ≈ 2.4 KB of text
- 4.3 billion a day × 2.4 KB ≈ 10 TB a day, ~3.8 PB a year before compression and replication
- Reads: each message loads its conversation's recent turns, ~50,000 a second, from a cache of active conversations
Context assembly happens in the conversation service, before the router:
- In a fixed order, for the cache: system instructions, tools, saved user memory, the conversation, then the new message. Whatever changes per request goes last, so everything before it is a reusable prefix.
- Within a budget: past the model's context, drop or summarise the oldest turns, never the system instructions.
- Count tokens with the model's tokenizer before routing: quotas, routing and admission are all in tokens.
Safety in the stream:
- Input: a fast classifier on the new message, before generation, on its own small-model GPUs; it adds tens of milliseconds to time to first token
- Output: a classifier on each sentence or ~100-token window, alongside the stream. If it flags, the relay stops the reply and the app replaces it with a notice; the log knows which tokens were shown.
Routing and streaming
Q1/4 A paid user's turn 6 arrives. The replica holding turns 1 to 5 has a queue 3× its normal length. What should the router do?
Answer & explanation
Answer: B
Cache affinity is a preference, not a rule. When the warm replica would break the TTFT target, recomputing the prefix elsewhere is cheaper than the wait.
Q2/4 Why are quotas charged in tokens rather than requests?
Answer & explanation
Answer: C
A 100-token question and a 100,000-token document are both "one request" but differ enormously in prefill, KV cache and cost.
Q3/4 Why must a new GPU replica be planned for ahead of the evening peak?
Answer & explanation
Answer: A
Machine, image, 140 GB of weights and warm-up add up to several minutes; forecasting the daily wave beats reacting to the queue.
Q4/4 A user closes the tab mid-reply and never reconnects. What should happen?
Answer & explanation
Answer: C
Generating for nobody wastes GPU steps and holds memory other users need. Save the partial reply and stop.
Deep Dive 5: Overload and Failure (Week 2 Concepts: Failure-First Design, Week 10: Incident Management)#
"Three bad days: more demand than GPUs, a GPU dying mid-reply, a region going away. Each time the system decides who gets served at what quality, and tells people the truth."
A replica dies with 225 replies in flight
At 21:12 one GPU in a 4-GPU replica fails. All 225 replies on it stop mid-sentence, some at token 3, some at token 480. How do you recover them so users barely notice?
One strong answer
- Detect fast: no tokens for ~2 s on every stream from that replica, and a failed health check. The router stops sending it work; the fleet manager replaces it.
- Nothing sent is lost: each reply's context and tokens so far are in the token log.
- Resubmit as continuations: context + tokens already streamed, at the front of its lane. Another replica prefills that (~0.15 s for 2,000 tokens) and continues from the next token.
- What the user sees: a pause of a second or two, then the reply carries on. What follows the pause is a fresh sample, which is fine because nobody has seen it.
- Capacity: 225 resubmissions are a small prefill burst. A rack or power failure is not; that needs the overload ladder.
%% title: Recovering replies from a failed replica
flowchart TD
FAIL(["Replica r17 fails<br/>225 replies mid-stream"]):::bad
DET["Relay: no tokens for 2 s<br/>health check confirms"]:::service
LOG[("Token log<br/>context + tokens sent")]:::store
RES["Resubmit each as a continuation<br/>context + partial reply"]:::core
LANE[["Front of its tier lane"]]:::queue
NEW["Another replica<br/>prefill, then decode on"]:::service
USER(["User sees a short pause<br/>then the reply carries on"]):::ok
DRAIN["Fleet manager<br/>drain and replace r17"]:::service
FAIL --> DET --> RES
LOG --> RES --> LANE --> NEW --> USER
DET -.-> DRAINA Region Goes Down#
- Run each region at most 75% full at peak: four carry the load of three, so losing one leaves three at 100%
- In-flight replies are lost with the region unless the token log is replicated out of it; the honest default is "Connection lost. Regenerate?" with the partial text kept
- Cold prefix caches: if half the input tokens were cached, the moved traffic needs about 2× the prefill until caches warm, and that capacity comes from the degradation ladder, not spare GPUs
The Degradation Ladder#
- Pause background work
- Batch, evaluations and flex traffic stop admitting; their GPUs go to interactive lanes in seconds
- Shorten what is optional
- Lower free-tier maximum output; send borderline requests to the fast model, not the reasoning one
- Fall back to the smaller model
- Free users past their limit, then all free users, get the mini model, at a fraction of the GPUs per token, and are told so
- Queue with an honest ETA
- A bounded free-tier queue showing a time ("about 40 seconds") from queue depth and throughput (Little's law)
- Shed free-tier work with a clear message
- "ChatGPT is at capacity for free accounts. Try again in about 10 minutes." Paid targets hold throughout
- Image launch, March 2025: Sam Altman posted "our GPUs are melting", announcing temporary image rate limits and three generations a day for free users; days later ChatGPT added a million users in an hour
- 11 December 2024: from 3:16 to 7:38 pm PST all OpenAI services were degraded or down. A new telemetry service made every node run Kubernetes API operations whose cost grew with cluster size, overwhelming the control plane of large clusters; cached DNS records hid the damage at first, and rolling back needed the overwhelmed control plane. Follow-ups included phased rollouts, break-glass access and decoupling the data plane from the control plane
- The lesson: the serving path must keep working on what it already knows when the control plane is unavailable
Overload and failure
Q1/4 A replica fails with replies in flight. Why can they continue on another replica without regenerating what users saw?
Answer & explanation
Answer: B
A new replica prefills context + partial reply and continues from the next token; what the user already read never changes.
Q2/4 Four regions each run at 75% of capacity at peak. One fails. What load do the other three face?
Answer & explanation
Answer: C
Total load is 4 × 75% = 300% of one region; spread over three regions, that is 100% each, before the cold-cache prefill.
Q3/4 Under overload, which step comes first on the degradation ladder?
Answer & explanation
Answer: A
Background work is the cheapest to delay; its GPUs return to interactive lanes in seconds, before any user notices.
Q4/4 What made the December 2024 outage slow to fix?
Answer & explanation
Answer: D
OpenAI's postmortem describes being locked out of the Kubernetes API servers that the faulty telemetry service was overloading.
Phase 5: Scaling and Edge Cases (5 minutes)#
"Walk me through a day on the fleet, then the cases that bite."
A Day on the Fleet#
- 00:00-06:00 · Asia's day, the Americas' night
- American regions run background work in the trough
- 06:00-12:00 · Europe wakes up
- Capacity placed ahead of the European morning; spot added for the peak
- 13:00-17:00 · The overlap
- Europe's afternoon, the Americas' morning, India's evening: the day's peak; background work paused
- 17:00-23:00 · The Americas' afternoon
- Asian capacity back to background work; rollouts start in the quietest region
Edge Cases#
Edge case 1: A 100,000-token document
Problem: One prompt needs ~33 GB of KV cache in bf16 and at least 7 seconds of a whole replica's compute to prefill (2 × 70 billion × 100,000 FLOPs, before attention), delaying everyone behind it.
Solution: Chunked prefill so decode keeps running; a pool sized for long contexts; quotas in tokens.
Edge case 2: "Regenerate" pressed five times
Problem: Each press is a full new reply.
Solution: The prefix is cached, so it costs decode only; one reply in flight per conversation cancels the previous attempt.
Edge case 3: A model upgrade
Problem: A new version must replace the old on thousands of replicas without dropping replies.
Solution: Drain (no new admissions, let replies finish), reload, warm, and shift traffic by percentage, one region first.
Scaling and edge cases
Q1/3 A 100,000-token prompt arrives. Which measure protects the other users on that replica?
Answer & explanation
Answer: B
Splitting the prefill into chunks keeps other replies streaming instead of stalling for the whole prompt.
Q2/3 Why does pressing "Regenerate" cost mostly decode, not prefill?
Answer & explanation
Answer: B
The same context was just prefilled on that replica; only the new reply's tokens need computing.
Q3/3 How do you roll out a new model version without dropping replies?
Answer & explanation
Answer: B
Draining lets in-flight replies finish; shifting by percentage limits the blast radius of a bad version.
Phase 6: Monitoring and Operations#
"Users feel two latencies and a stall; finance feels cost per token. The dashboard shows both, per tier and region."
Monitoring Dashboard#
Latency, by tier
| Metric | Paid | Free |
|---|---|---|
| Time to first token p50 / p95 | 0.42 / 0.88 s | 0.61 / 1.70 s |
| Time per output token p50 / p95 | 31 / 44 ms | 33 / 48 ms |
| Streams that stalled > 2 s, last hour | 0.03% | 0.05% |
| Queue depth / est. wait | 0 / 0 s | 2,300 / 0.04 s |
GPUs and caches
- Messages 68,000/s; output tokens 34 million/s; streams open 1.2 million
- HBM bandwidth in use 61%; KV cache occupancy 88%; preemptions 140/s
- Prefix cache hit rate 52% of input tokens; cancellations freeing blocks 3,900/s
- Cost $1.90 per million output tokens today; background lane paused since 15:10
Alerts (today)
- 15:10 (amber) — Europe-West KV occupancy over 90% for 5 minutes, background lane paused
- 15:42 (amber) — Replica eu-w-2210 failed, 218 replies resumed elsewhere, median pause 1.4 s
SLOs for the Platform#
SLO 1: First token
- Target: TTFT p95 < 1 s for paid and < 2 s for free, in every 5-minute window, per region
SLO 2: Smooth streaming
- Target: TPOT p95 < 50 ms; 99.9% of streams with no pause over 2 s
SLO 3: Replies delivered
- Target: 99.9% of messages end in a complete reply or an actionable error per 30 days; the error budget is 0.1%, about 43 minutes of total outage equivalent
SLO 4: Cost
- Target: GPU cost per million output tokens tracked daily, beside the utilisation and prefix hit rate behind it
Reading the dashboard
Q1/3 The free tier shows 2,300 queued requests and an estimated wait of 0.04 s. Which law gives that wait?
Answer & explanation
Answer: A
Free users send ~85% of 68,000 messages a second, about 58,000; 2,300 queued ÷ 58,000 a second ≈ 40 ms of waiting.
Q2/3 Prefix cache hit rate falls from 52% to 20% after a deploy. What do you expect next?
Answer & explanation
Answer: C
Missed prefixes must be recomputed; a change that reorders the system instructions can do this.
Q3/3 Why is "streams that stalled more than 2 s" on the dashboard when p95 TPOT is fine?
Answer & explanation
Answer: B
A replica failure or a long prefill can freeze replies for seconds while per-token percentiles barely move.
Final Challenge: Launch Day, and Demand Is 5× Capacity#
The interviewer opens a launch calendar. "One more."
"Tomorrow at 17:00 UTC a new image feature launches to every user, on a pool of 8,000 GPUs. It goes viral: demand runs at five times what the pool can serve, while chat is still near its daily peak. Decide who gets served, at what quality, and what you tell people."
- Image pool: 8,000 GPUs; one full-quality image takes 16 GPU-seconds (assumption)
- A "fast" mode at lower resolution takes 8 GPU-seconds (assumption)
- Demand: 5× capacity; paid users send 20% of image requests
- Up to 4,000 more GPUs can be freed from background work (batch, evaluations, flex) in about 10 minutes
- Chat is near its daily peak; its latency targets still apply
Who gets an image, at what quality, and what do they see?
Work out capacity and demand, what the extra GPUs and fast mode buy, the paid/free split, and what each user sees.
One strong answer
- Capacity: 8,000 GPUs ÷ 16 GPU-s = 500 images a second. Demand at 5× = 2,500 a second: 500 paid, 2,000 free.
- A queue can't absorb it: 2,000 a second of excess is 7.2 million waiting after an hour.
- Add what is cheap: pause background work, freeing 4,000 GPUs in ~10 minutes → 12,000 GPUs. Not chat's GPUs: it is near its peak.
- Paid at full quality: 500 a second × 16 GPU-s = 8,000 GPUs, with one image in flight per user.
- Free in fast mode: the remaining 4,000 GPUs ÷ 8 GPU-s = 500 a second, a quarter of free demand. Free accounts get a small daily allowance (OpenAI announced three a day in March 2025) and a bounded queue with an honest ETA; past that, "at capacity, back at 19:00 UTC".
- Tell people before they wait: a banner ("Very high demand: free accounts get 3 images today, in fast mode"), an ETA on each request, the remaining allowance. Never "something went wrong".
- Protect chat: separate pools and lanes, so image demand can't touch chat's time to first token.
Final challenge · Quest check
Q1/4 An image pool has 8,000 GPUs and each image takes 16 GPU-seconds. How many images a second can it make?
Answer & explanation
Answer: B
8,000 GPU-seconds a second ÷ 16 GPU-seconds an image = 500 images a second.
Q2/4 Which extra GPUs should launch day take first?
Answer & explanation
Answer: A
Background work can wait hours without breaking a promise; chat's latency targets can't.
Q3/4 Free demand is four times what is left for it. What should free users get?
Answer & explanation
Answer: D
A smaller cost per image, a quota and a truthful wait keep the queue bounded and expectations right.
Q4/4 Why keep image and chat on separate pools and lanes on launch day?
Answer & explanation
Answer: B
Isolation keeps a launch's surge from breaking the core product for everyone else.
Interview Conclusion#
"Why not give every user their own GPU slot, so nobody waits for anyone?"
"Decode is memory-bound. One user on a replica gets ~95 tokens a second and leaves 99.7% of the compute idle; 225 users get ~46 each and the replica writes 110 times more. Sharing is the only way the price works; the craft is making it invisible."
"Where would you spend your next engineering quarter?"
"On the prefix cache hit rate. Every point of it is prefill we don't buy: per-request content at the end of the prompt, conversations routed to the replica holding their history, blocks kept for minutes. Then a weekly overload drill: replay a launch-day curve against a staging region, kill a replica mid-stream, and measure time to first token and resumed streams."
"The single most important decision?"
"Scheduling by tokens and memory, not requests. Once admission, quotas, routing and autoscaling count tokens and KV blocks, overload becomes a choice about who gets which model and when, instead of a spinner for everyone."
Summary: Concepts Applied from 10-Week Course#
- Week 0: Foundations
- Back of the envelope — tokens a second, KV bytes per token, GPUs and megawatts
- Operating systems — paged KV memory, like virtual-memory pages
- Week 1: Data at scale
- Rate limiting — token buckets in tokens, per user and per model
- Hot keys — a popular prefix overflowing beyond one replica
- Week 2: Failure-first design
- Idempotency — a resent message attaches to the reply already being written; a silent stream resumes elsewhere
- Week 3: Messaging and async processing
- Backpressure — bounded queues per lane, slow readers dropped and resumed
- Week 4: Caching
- Prefix caching — shared instructions and conversation history reused across turns
- Week 9: Multi-tenancy
- Noisy neighbours — tier lanes and per-user fairness inside a lane
- Week 10: Production readiness
- Capacity planning — forecast the daily wave, N+1 regions, a degradation ladder
Why ChatGPT Matters#
The unit of work is a token, not a request
- A reply's length, memory and cost are unknown until it ends, so every layer schedules work it can't measure in advance
The scarce resource is GPU memory
- After the weights, the KV cache decides how many people one replica serves; batching, paging and prefix caching fit more people into the same bytes
Fairness is a product decision
- When demand beats capacity, someone gets a smaller model, a wait or a "no"; a good system chooses deliberately, and says so
"A reply is a few hundred steps on a shared GPU. The craft is making each step serve as many people as possible, and making sure none of them can tell."
Self-Assessment Checklist#
After studying this case study, you should be able to:
Estimation:
- Turn messages a day into tokens a second, and tokens a second into GPUs and power
- Compute KV cache bytes per token from a model's layers, KV heads and head dimension
Inference:
- Explain why decode is memory-bound and why batching multiplies throughput
- Compare static and continuous batching, and explain paged KV memory and prefix caching
Fleet:
- Route by model, region and prefix affinity with overflow
- Design tier lanes, token-based quotas and admission control
- Plan GPU capacity around minutes-long cold starts and spot interruptions
Reliability:
- Stream with resumable server-sent events and cancellation that frees GPU memory
- Recover replies from a failed replica, size regions for N+1, and write the degradation ladder
Sources#
OpenAI announcements and statements:
- Talk
Engadget — OpenAI DevDay 2026 live blog (keynote graphic: ChatGPT has 1.2 billion weekly users), September 2026: https://www.engadget.com/2271985/openai-dev-day-live-blog-chatgpt-news/
- Book
OpenAI — Scaling AI for everyone (900 million weekly active users, 50 million paying subscribers), February 2026, as reported by TechCrunch: https://techcrunch.com/2026/02/27/chatgpt-reaches-900m-weekly-active-users/
- Read
TechCrunch — ChatGPT users send 2.5 billion prompts a day (OpenAI to Axios; 330 million from the US), July 2025: https://techcrunch.com/2025/07/21/chatgpt-users-send-2-5-billion-prompts-a-day/
- Docs
TechCrunch — Sam Altman says ChatGPT has hit 800M weekly active users (and the API processes over 6 billion tokens a minute), DevDay, October 2025: https://techcrunch.com/2025/10/06/sam-altman-says-chatgpt-has-hit-800m-weekly-active-users/
- Read
Fortune — Sam Altman: "our GPUs are melting" (temporary image rate limits; free tier three a day), March 2025: https://fortune.com/2025/03/28/sam-altman-chatgpt-gpus-melting-ai-images/
- Read
The National — ChatGPT adds one million users in an hour (Sam Altman; one million in five days at launch), March 2025: https://www.thenationalnews.com/future/technology/2025/03/31/chatgpt-users-sam-altman-one-million/
- Read
OpenAI — Introducing GPT-5 (real-time router; a mini version of each model after usage limits), August 2025: https://openai.com/index/introducing-gpt-5/
- Read
OpenAI — Scaling Kubernetes to 7,500 nodes, January 2021: https://openai.com/index/scaling-kubernetes-to-7500-nodes/
- Docs
OpenAI status — API, ChatGPT and Sora facing issues, write-up of the 11 December 2024 incident: https://status.openai.com/incidents/ctrsv3lwd797/write-up
OpenAI API documentation:
- Docs
Streaming API responses (server-sent events, typed events, moderation caveat): https://developers.openai.com/api/docs/guides/streaming-responses
- Docs
Background mode (cancel; resume a stream with
starting_after): https://developers.openai.com/api/docs/guides/background - Book
Prompt caching (routing by a hash of the initial tokens, overflow above ~15 requests a minute, cache lifetimes): https://developers.openai.com/api/docs/guides/prompt-caching
- Docs
Rate limits (RPM, TPM, headers, 429,
max_tokenscounting): https://developers.openai.com/api/docs/guides/rate-limits - Docs
Flex processing ("429 Resource Unavailable", batch-rate pricing): https://developers.openai.com/api/docs/guides/flex-processing
- Docs
Batch API (50% discount, 24-hour window, separate limits): https://developers.openai.com/api/docs/guides/batch
- Blog
What are tokens and how to count them (1 token ≈ 4 characters ≈ ¾ word): https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them
Papers:
- Paper
Yu et al. — Orca: A Distributed Serving System for Transformer-Based Generative Models (iteration-level scheduling, selective batching), OSDI 2022: https://www.usenix.org/conference/osdi22/presentation/yu
- Paper
Kwon et al. — Efficient Memory Management for Large Language Model Serving with PagedAttention (vLLM), SOSP 2023: https://arxiv.org/abs/2309.06180
- Paper
Zheng et al. — SGLang: Efficient Execution of Structured Language Model Programs (RadixAttention prefix reuse), 2023/2024: https://arxiv.org/abs/2312.07104
- Paper
Agrawal et al. — Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve (chunked prefills), OSDI 2024: https://arxiv.org/abs/2403.02310
- Paper
Zhong et al. — DistServe: Disaggregating Prefill and Decoding for Goodput-optimized LLM Serving, OSDI 2024: https://arxiv.org/abs/2401.09670
Hardware, models and cloud:
- Docs
NVIDIA — H100 Tensor Core GPU specifications (80 GB, 3.35 TB/s, up to 700 W): https://www.nvidia.com/en-us/data-center/h100/
- Docs
NVIDIA — DGX H100 datasheet (8 × H100, 640 GB, 10.2 kW max): https://www.nvidia.com/en-us/data-center/dgx-h100/
- Read
Meta — Llama 3.3 70B model card and configuration (80 layers, 8 KV heads, head dimension 128, 128K context): https://huggingface.co/meta-llama/Llama-3.3-70B-Instruct
- Docs
AWS — Spot Instance interruption notices (two-minute warning): https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-instance-termination-notices.html
Design note: the architecture, code, thresholds, rates and dashboards here are an illustrative design, sized with an open 70B model on H100 GPUs, not a description of how OpenAI serves ChatGPT beyond what OpenAI has published; the real fleet's models and hardware differ.
Further Reading#
Related systems to study:
- Read
IRCTC Tatkal (Bonus Problem 11): admission control when demand far exceeds supply
- Read
Netflix (Bonus Problem 6): streaming bytes to millions at once
- Read
Zerodha Kite (Bonus Problem 13): honest states and a fixed degradation ladder under a spike
Foundational concepts (this course):
- Read
Week 0, Part 3 — Back of the Envelope Estimation
- Read
Week 1, Day 3 — Rate Limiting at Scale
- Read
Week 3, Day 3 — Backpressure and Flow Control
- Read
Week 4, Day 3 — Thundering Herd
- Read
Week 9, Day 2 — Noisy Neighbour
- Read
Week 10, Day 4 — Capacity Planning
"Every reply is a few hundred steps on a GPU somebody else is also using. Serve it well and nobody notices the sharing; serve it badly and everybody notices the wait."
- 0/7stages cleared
Go deeper · reading trail
all reading →Real write-ups from the teams that built these systems. Each opens on the publisher's site in a new tab.
Efficient Memory Management for Large Language Model Serving with PagedAttention
Woosuk Kwon, Zhuohan Li et al. · 2023- What
- The vLLM paper from UC Berkeley, which introduced PagedAttention.
- Key idea
- The KV cache is stored in fixed-size blocks mapped like virtual memory pages, which cuts fragmentation and lets requests share prefixes, raising throughput.
- Why here
- The paged KV memory and prefix caching deep dive of this quest at its source.
Orca: A Distributed Serving System for Transformer-Based Generative Models
Gyeong-In Yu, Joo Seong Jeong et al. · 2022- What
- The paper that introduced iteration-level scheduling for LLM serving.
- Key idea
- Instead of waiting for a whole batch to finish, the scheduler adds and removes requests at every decoding step, so short answers do not wait for long ones.
- Why here
- Where this quest's batching deep dive comes from: continuous batching is iteration-level scheduling.
How continuous batching enables 23x throughput in LLM inference while reducing p50 latency
Cade Daniel, Chen Shen et al. · 2023- What
- A benchmark-driven explainer of LLM inference batching.
- Key idea
- LLM serving is memory-bound, so batching is the main lever; continuous batching and paged memory raised throughput up to 23x in their tests while lowering median latency.
- Why here
- The back-of-the-envelope GPU maths of this quest checked against real measurements.
New lessons by email
New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.
Discussion · …
Public: everyone reading this lesson sees comments. Be kind and keep it on the material.