AI Infra Interviews logo
LLM Inference & Serving / 06
medium★ EssentialNewTogether AIBasetenCoreWeave

You have eight H100s and Llama 3.1 70B. How many concurrent users can you serve, and what changes the number?

Weights take a fixed slice, the KV cache takes the rest, and the rest divided by context is your user count. Work it in bf16 and fp8, then find out why the answer also depends on how you split the model.

Updated Sep 2026 · Grounded in real AI infrastructure interview loops and written to a senior-engineer editorial bar, with every number worked and every diagram hand-built.

TL;DR: Memory sets the ceiling: 8 × 80 GB with 10% headroom leaves 435 GB after 141 GB of bf16 weights, which is about 324 sequences at 4k context or 40 at 32k; fp8 weights and fp8 KV push that to roughly 750 at 4k. The latency SLO sets the operating point below the ceiling, because a decode step at batch 256 takes about 18 ms. TP8 shares the KV pool across all cards; two TP4 replicas hold two copies of the weights and lose 141 GB of KV budget to gain independent failure domains and half the all-reduce cost.

How to approach it

Ask three things: context length distribution (the number changes 8x between 4k and 32k), the TPOT SLO (it caps batch below the memory ceiling), and whether fp8 is acceptable. Then say the order: weights, then headroom for activations and CUDA graphs, then the KV budget, then the KV per sequence at the stated context, then the division, then a check that the resulting batch meets the SLO. Say the TP question last, because it changes the budget only through weight duplication.

A strong answer

A typical situation: a capacity plan is written from a GPU count and a hope. The answer is four lines of arithmetic, and it changes by a factor of two depending on one flag nobody set deliberately.

Start with Model Memory Footprint and KV Cache Sizing, in that order:

inputs (Llama 3.1 70B, 8 × H100 SXM 80 GB, 3.35 TB/s each)
  weights bf16 = 70.6e9 × 2 B = 141.2 GB
  weights fp8  = 70.6e9 × 1 B =  70.6 GB
  KV per token bf16 = 2 × 80 × 8 × 128 × 2 = 327,680 B ≈ 328 KB
  KV per token fp8  ≈ 164 KB
  usable memory = 8 × 80 × 0.9 = 576 GB   (10% for activations, workspace, graphs)

KV budget bf16 = 576 - 141.2 ≈ 435 GB
KV budget fp8  = 576 -  70.6 ≈ 505 GB

sequences = KV budget ÷ (KV per token × context)
  bf16, 4k:   435 GB ÷ (328 KB × 4,096 = 1.34 GB)  ≈ 324
  bf16, 8k:   435 GB ÷ 2.68 GB                      ≈ 162
  bf16, 32k:  435 GB ÷ 10.7 GB                      ≈ 40
  fp8 W + fp8 KV, 4k:  505 GB ÷ 0.67 GB             ≈ 753
sanity: fp8 roughly doubles the KV budget's reach because it both frees 70 GB and halves KV per token
MEMORY FIT (size a deployment)
weightskv cacheoverhead118 / 80 GB
Weights 65 GB + cache 40.0 GB + overhead against a 80 GB card: does not fit. At this context length the card supports 2 concurrent sequences. The weights are a fixed cost; every extra user pays only the cache.

That is the ceiling, not the operating point. The batch also sets per-token latency, because every decode step reads the weights plus every running sequence's KV:

step time ≈ (W + B × KV per seq) ÷ aggregate bandwidth (8 × 3.35 = 26.8 TB/s)
  bf16, 4k, B = 64:   (141.2 + 85.8) ÷ 26.8 ≈ 8.5 ms   → 7,500 tokens/s
  bf16, 4k, B = 128:  (141.2 + 172)  ÷ 26.8 ≈ 11.7 ms  → 10,900 tokens/s
  bf16, 4k, B = 324:  (141.2 + 434)  ÷ 26.8 ≈ 21.5 ms  → 15,100 tokens/s
  fp8,  4k, B = 256:  (70.6 + 172)   ÷ 26.8 ≈ 9.1 ms   → 28,200 tokens/s
sanity: filling memory (B = 324) costs 21.5 ms per token; with a 15 ms TPOT p95 SLO the
        operating batch is nearer 180, and the spare memory is headroom for long contexts

So the answer to "how many users" is two numbers: the memory ceiling and the SLO-bounded batch. With a 15 ms TPOT SLO in bf16, about 180 concurrent 4k conversations; with fp8 and the same SLO, roughly 400, at which point the batch approaches the compute ridge (295 on H100) and the step stops being bandwidth-bound.

Where TP degree enters. With TP8 there is one copy of the weights and one shared KV pool. With two TP4 replicas there are two weight copies (282 GB) and the KV budget drops to 576 - 282 ≈ 294 GB, so 4k concurrency falls from 324 to about 220. What two replicas buy: an all-reduce over four ranks instead of eight per layer (two per layer, 160 per step), which at NVLink speeds is about 30% less communication time per step; independent failure and rollout domains; and a KV pool that is not shared, so one replica's long-context tenant cannot starve the other. TP8 wins on capacity and on batch-1 latency, TP4 × 2 wins on isolation and the throughput per GPU at moderate batch. Pipeline parallelism is the wrong tool here; it adds bubbles and buys nothing on a node that fits the model.

The reversal condition: fp8 weights and an fp8 KV cache roughly double every number below, so a deployment that does not fit in bf16 often fits comfortably one precision down, and the question becomes an evaluation project rather than a hardware one.

Concurrency in users is more than sequences in flight. If each user sends a request every 30 s and a request lives 6 s (1,000-token prompt at 45 ms plus 300 tokens at 12 ms plus queue), one running slot serves about 5 users, so 180 slots is roughly 900 active chat users.

What interviewers probe next

  • "What if half the traffic is 32k?" Budget in tokens, not sequences: 435 GB ÷ 328 KB ≈ 1.33 M cached tokens shared across mixed contexts; a 32k session costs 8 of the 4k slots, and the scheduler needs a per-tenant cap or the long tail blocks admission.
  • "Would H200s change this?" Yes, twice over: 141 GB per card gives 1,015 GB usable, so the bf16 KV budget rises to 874 GB (about 650 sequences at 4k), and 4.8 TB/s cuts the step time by 30%.
  • "Why leave 10% headroom?" Activations for the prefill chunk, CUDA graph buffers, sampling workspace and NCCL buffers; vLLM's gpu_memory_utilization defaults near 0.9 for this reason, and setting 0.98 produces OOM under a long-prompt burst.

Common mistakes

  • Dividing 640 GB by the weight size and reporting "it fits" as the answer.
  • Forgetting that the KV cache is read every step, so filling memory also fills the step time.
  • Counting attention heads instead of KV heads.
  • Proposing TP16 across two nodes to "get more memory" when the problem was never the weights.

Key takeaways

  • Budget order: usable memory, minus weights, equals KV budget; divide by KV per token × context.
  • 8 H100s in bf16 hold about 324 sequences at 4k and 40 at 32k; fp8 weights and KV roughly double reach.
  • Step time = (weights + batch × KV per sequence) ÷ aggregate bandwidth; the SLO, not memory, sets the batch.
  • TP8 pools KV; two TP4 replicas cost 141 GB of budget for isolation and cheaper all-reduce.
That one was free — and so are 10 answers per topic without an account. Signing in doubles that to 20, opens the Plus lessons in the courses, and remembers which topics you keep getting wrong.no card · Google sign-in · nothing to cancel
HOW DID IT GO?
0
READING SIGNED OUT

Signing in doubles your free answers, from 10 to 20 per topic, and the site starts remembering you: mastery per topic, bookmarks, and a next-focus recommendation. Free, no card.

Sign in free

The concepts behind this question

Ranked by how closely each one overlaps this question's topic, so the first card is the thing to read if the answer above moved too fast.

Foundational
🧮 Napkin Math & Capacity
KV Cache SizingThe KV cache is the memory that decides how many users a serving replica can hold and how long their context can be. Its size per token comes from four numbers in the model's config file (layers, KV heads, head dimension, bytes per element) and one formula; multiplied by context and concurrency it is the number every capacity plan is built on. This page derives it, works it for four models including an MLA one, and shows the two places candidates get it wrong by a factor of eight.
Foundational
🧮 Open Weights & Serving Engines
Reading config.json to Size a Model You Have Never RunEvery Hugging Face model ships a config.json, and it contains enough to compute the weight footprint, the KV cache per token, the parallel degrees that divide cleanly and the minimum GPU count, before downloading a byte. Doing that derivation is a standard whiteboard exercise in serving interviews because it is exactly what an engineer does on the morning a new model lands, and the fields that matter are the same across every recent architecture.
Foundational
🧮 Open Weights & Serving Engines
Capacity Planning for Open-Weights FleetsPlanning a fleet for a sparse open-weights model works differently from planning one for a dense model, because memory follows total parameters and throughput follows active parameters, and those now differ by more than twenty times. The sizing goes in one direction only: from a traffic forecast to tokens per second, to replicas at a measured operating point, to GPUs, to racks and kilowatts. Doing it in the other direction, from an available GPU count, produces a fleet that fits the hardware rather than the demand.
Foundational
🧮 Open Weights & Serving Engines
vLLM Server Arguments That MatterA vLLM deployment is mostly decided by a dozen flags, and the ones that matter fall into four groups: how the model is split across GPUs, how memory is divided between weights and cache, how requests are batched, and which specialized backends the model needs. Getting the first two wrong produces an engine that will not start or that runs out of memory under load. Getting the third wrong produces an engine that starts, serves, and misses its latency target by a wide margin.
UP NEXT ON YOUR JOURNEY
FEDITOR'S NOTE

Scored on doing the budget in the right order (weights, headroom, KV, then batch against the latency SLO) and on knowing where TP degree changes the arithmetic.

DISCUSSION · 0

No comments yet — be the first to share your approach.