At a glance#
| Number | Value | What it drives |
|---|---|---|
| Users · messages | 1.2B weekly · 4.3B / day (design) | ~50,000 msg/s average, ~100,000/s at the 2× peak |
| Output tokens | 25M / s avg · 50M / s peak | 500 tokens a reply (assumption); decode GPUs |
| Input tokens | 100M / s · half from prefix cache | 2,000 tokens of context (assumption); prefill GPUs |
| KV cache (70B, bf16) | 0.33 MB / token | 2,000-token chat ≈ 655 MB; ~225 seqs per 4 × H100 replica |
| Per GPU | ~1,500 out tok/s · ~3,500 prefill tok/s | decode is memory-bound, prefill is compute-bound |
| Streams in flight | ~1.8 million at peak | 100,000/s × ~18 s a reply (Little's law) |
| Fleet (one 70B model, illustrative) | ~75,000 H100s · ~115 MW | ~$4.5M a day at $2.50/GPU-h (assumption) |
| Latency targets | TTFT p95 < 1 s paid, < 2 s free · TPOT < 50 ms | lanes by tier; people read ~5.6 tok/s |
Architecture#
DiagramLLM serving on one page: everything keeps the GPUs busy and fair
%% title: LLM serving on one page: everything keeps the GPUs busy and fair
flowchart TD
APP@{ icon: "sd:phone", label: "ChatGPT apps · web, mobile, desktop<br/>API clients" } ==> GW@{ icon: "sd:gateway", label: "Edge + API gateway<br/>TLS · SSE streams · auth<br/>token quotas per user per model" }
GW ==>|"message"| CONV["Conversation service<br/>context in cache-friendly order<br/>count tokens"]:::service
CONV --> HIST@{ icon: "sd:database", label: "Conversation store<br/>~10 TB a day · active cache" }
CONV ==>|"context"| SAFE@{ icon: "sd:shield", label: "Safety classifiers<br/>input before · output per sentence" }
SAFE ==>|"generate"| ROUTE["Router<br/>model · region · prefix affinity + overflow"]:::core
ROUTE --> MINI["Mini-model pool<br/>fallback past limits"]:::service
ROUTE ==> LANES@{ icon: "sd:queue", label: "Tier lanes<br/>paid · free · background" }
LANES ==> REP["Model replicas · 4 × H100<br/>continuous batching · paged KV<br/>prefix cache"]:::hot
REP ==>|"tokens"| TLOG@{ icon: "sd:cache", label: "Relay + token log<br/>numbered events · kept minutes<br/>streams back through the edge" }
REP --> USE@{ icon: "sd:stream", label: "Usage + telemetry<br/>limits · billing · TTFT · cost" }
USE --> FLEET@{ icon: "tech:kubernetes", label: "Fleet manager · Kubernetes<br/>forecast capacity · spot<br/>adds, drains replicas" }
class APP client
class GW edge
class HIST store
class SAFE,TLOG cache
class LANES,USE queue
class FLEET service- The GPU replicas are the only scarce, expensive tier. The router and the replica schedulers are the core; the rest is an ordinary web tier.
- While it streams, a reply lives twice: in the replica's KV cache (to write the next token) and in the token log (to resend after a reconnect).
- Context is assembled in a fixed order (instructions, tools, memory, history, new message) so everything before the new part is a reusable prefix.
DiagramInside one replica: one step for the whole batch
%% title: Inside one replica: one step for the whole batch
flowchart TD
W[["Waiting · from the tier lanes"]]:::queue --> ADM{"Prompt blocks fit,<br/>above the reserve?"}:::service
ADM -->|"yes · reuse cached prefix blocks"| RUN["Running batch · ~225 seqs<br/>KV in 16-token blocks"]:::core
ADM -->|"no"| W
RUN --> STEP["One forward step<br/>prefill arrivals · 1 token for the rest<br/>reads all weights once"]:::hot
STEP --> END{"Stop token, max tokens<br/>or cancelled?"}:::service
END -->|"no · append K, V"| RUN
END -->|"yes"| FREE(["Leave next step<br/>blocks freed, prefix kept"]):::ok
RUN -.->|"out of blocks"| PRE(["Preempt the newest<br/>requeue, recompute later"]):::badKey flows#
DiagramOne message, keyboard to screen
%% title: One message, keyboard to screen
sequenceDiagram
autonumber
participant A as Meera's app
participant E as Edge + gateway
participant C as Conversation svc
participant S as Router
participant M as Replica r17
A->>E: send (conversation c42, key m9)
E->>E: auth · token quota ok (paid)
E->>C: forward · open SSE stream
C->>C: input check · 2,000-token context
C->>S: generate (model, tier, prefix hash)
S->>M: admit · prefix already cached here
M->>M: prefill only ~300 new tokens
M-)A: token 1 at ~0.42 s (via relay, edge)
loop every ~35 ms until a stop token
M-)A: next token · numbered event
end
M->>M: free KV blocks
C->>C: output check · save reply
C-)A: done
Note over M: r17 writes ~224 other replies in the same stepsDiagramWi-Fi drops at token 212, then Stop at 260
%% title: Wi-Fi drops at token 212, then Stop at 260
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
R->>G: generate r88
loop each token
G-)R: token n, appended to r88's log
R-)A: event id n
end
Note over A,E: Wi-Fi drops at token 212
A->>E: reopen · Last-Event-ID 212 (another edge)
E->>R: subscribe to r88 after 212
R-)A: 213 onwards from the log, then live
Note over G: GPU never noticed
alt user taps Stop at 260
A->>E: stop
E->>R: cancel r88
R->>G: abort · free KV blocks next step
R->>R: save 260 tokens, marked stopped
else no reconnect within ~30 s
R->>G: cancel · nobody is listening
endDiagramA replica dies with 225 replies in flight
%% title: A replica dies with 225 replies in flight sequenceDiagram autonumber participant R as Relay + token log participant X as Replica r17 participant S as Router participant N as Replica r42 participant A as App X--xR: GPU fails · tokens stop Note over R: no tokens for ~2 s · health check confirms R->>S: resubmit each as continuation Note over S: context + tokens already sent · front of its lane S->>N: admit N->>N: prefill ~2,000 tokens (~0.15 s) N-)R: next token onwards R-)A: reply carries on after a short pause S->>S: stop routing to r17 · fleet replaces it
Data model#
DiagramCore entities
%% title: Core entities
classDiagram
class Conversation {
+conversationId : string · partition key
+userId : string
+tier : free / paid / business
+model : string
}
class Message {
+messageId : string
+clientKey : string · idempotency
+role : user / assistant
+text : string · ~2.4 KB a turn
+status : complete / stopped / flagged
}
class Reply {
+replyId : string
+replicaId : string
+lastEventId : int
+state : ReplyState
}
class TokenLogEntry {
+replyId : string
+n : int · SSE event id
+token : string
}
class Sequence {
+replyId : string
+length : int · tokens so far
+cachedTokens : int · prefix hits
+arrived : timestamp · newest preempted first
}
class KVBlock {
+blockId : int · 16 tokens
+prefixHash : hash · full blocks only
+refs : int
}
Conversation "1" --> "many" Message : turns
Message "1" --> "1" Reply : answered by
Reply "1" --> "many" TokenLogEntry : streamed as
Reply "1" --> "1" Sequence : runs as
Sequence "many" --> "many" KVBlock : shares prefix blocks- Conversations partition by conversation id; ~2.4 KB of text a turn ≈ 10 TB a day, ~3.8 PB a year before compression.
- KV blocks live in GPU memory: ~450,000 tokens of cache per replica after weights; a prefix hash covers a block and everything before it.
Lifecycle#
DiagramReply state machine
%% title: Reply state machine stateDiagram-v2 [*] --> QUEUED: admitted to a tier lane QUEUED --> SHED: free tier at capacity QUEUED --> RUNNING: blocks fit, prefill RUNNING --> PREEMPTED: out of blocks, newest PREEMPTED --> QUEUED: requeued, recompute RUNNING --> RESUMING: replica failed RESUMING --> QUEUED: continuation, front of lane RUNNING --> FINISHED: stop token or max tokens RUNNING --> STOPPED: user Stop or nobody for 30 s RUNNING --> FLAGGED: output check stops it FINISHED --> [*] STOPPED --> [*] FLAGGED --> [*] SHED --> [*] class FINISHED ok class SHED,FLAGGED bad class PREEMPTED,RESUMING queue
Decisions#
| Decision | Chose | Over | Because |
|---|---|---|---|
| Batching | Continuous, per step (Orca) | Static batches | short replies leave, new ones join every step |
| KV memory | 16-token blocks on demand (PagedAttention) | Reserve the max per request | old systems used only 20–38 % of KV memory |
| Routing | Prefix affinity with overflow | Round-robin or pure affinity | skip prefill, but no hot replica |
| Queues | Tier lanes, round-robin per user | One queue | free surge can't break paid TTFT |
| Quotas | Tokens per user per model | Requests | requests vary 1,000× in size |
| Capacity | Forecast the daily wave | React to the queue | a replica takes 3–10 min to serve |
| Streaming | SSE with event ids + token log | Resend the message | a reconnect never repays the GPU |
When it breaks#
| Failure | What users see | Contain it with |
|---|---|---|
| GPU dies mid-reply | a 1–2 s pause, then it carries on | token log; resubmit as continuation |
| Phone switches network | nothing | Last-Event-ID; any edge resumes from the log |
| Launch at 5× demand | fast mode, allowance, honest ETA | pause background; separate pools; paid first |
| A region of four fails | others at 100 %, cold prefixes | run ≤ 75 % at peak; degradation ladder |
| Control plane overwhelmed (11 Dec 2024) | all services degraded for hours | serving keeps working on what it knows; phased rollouts, break-glass |
| Evening free-tier surge | mini model or a clear limit | lanes by tier; never a spinner |
Say it in the interview#
- "Requests are the wrong unit." Admission, quotas, routing and autoscaling all count tokens and KV blocks.
- Decode is memory-bound: one user leaves 99.7 % of compute idle; 225 users get ~46 tok/s each and the replica writes 110× more.
- After the weights, KV memory sets the batch: 0.33 MB a token; paging and prefix caching fit more people in the same bytes.
- Prefix hit rate is money: keep per-request content last and route a conversation to the replica holding its history, with overflow.
- Never pay the GPU twice: numbered SSE events plus a token log make reconnects and replica failures resumable; Stop frees blocks.
- Scale on a forecast: cold start is minutes, so place capacity ahead of the wave; the ladder covers the gap.
- Fairness is a choice: pause background, then shorten, then mini model, then an honest ETA, then shed free work with a clear message.