Part 1 — feasibility
TP-8, fp16:
weights per GPU = 141.2 / 8 = 17.65 GB
KV per token per GPU (8 KV heads, TP-8, so 1 head each):
= 2 x 80 x 1 x 128 x 2 = 40,960 B = 40 KiB
workspace ~ 10 GB per GPU
KV budget/GPU = 80 x 0.95 - 17.65 - 10 = 48.35 GB
node KV budget = 48.35 x 8 = 386.8 GB
Per sequence (using the full-model figure of 320 KiB/token, since the cache is split across GPUs and the node total is what matters):
1,500 tokens: 327,680 x 1,500 = 0.49 GB -> 386.8 / 0.49 = 787 sequences
24,000 tokens: 327,680 x 24,000 = 7.86 GB -> 386.8 / 7.86 = 49 sequences
Plenty of headroom on paper. Memory is not the binding constraint here — which is unusual and worth noticing.
Part 2 — the latency budget
Effective bandwidth: 26,800 × 0.70 = 18,760 GB/s.
a) Batch 32, 2,000-token context:
bytes = 141.2 GB (weights) + 327,680 x 2,000 x 32 = 141.2 + 21.0 = 162.2 GB
TPOT = 162.2 / 18,760 = 8.6 ms
Comfortably inside the 40 ms budget.
b) Solve for batch at 40 ms:
bytes_at_40ms = 0.040 x 18,760 = 750.4 GB
750.4 = 141.2 + batch x 0.655 GB (327,680 x 2,000 = 0.655 GB per sequence)
batch = (750.4 - 141.2) / 0.655 = 930
But memory caps us at 386.8 / 0.655 = 590 sequences. Memory binds before latency — at maximum batch you would be at (141.2 + 386.8)/18,760 = 28 ms, still inside the SLO.
c) Prefill, 8 GPUs at 989 TFLOP/s × 70% = 5,538 TFLOP/s aggregate:
1,500 tokens: 2 x 70.6e9 x 1,500 = 2.118e14 / 5.538e15 = 38 ms
24,000 tokens: 2 x 70.6e9 x 24,000 = 3.389e15 / 5.538e15 = 612 ms
d) A 612 ms unchunked prefill against Product A's 800 ms TTFT budget: it technically fits for the request itself. But that is the wrong question. The problem is what it does to everyone else: for 612 ms, every decoding Product A sequence produces nothing. Their TPOT for that step is 612 ms against a 40 ms SLO — a 15× violation — and at ~500 concurrent sequences that is roughly 300 seconds of aggregate stall from a single Product B request.
At 0.4 req/s, a Product B request arrives every 2.5 seconds, so roughly 25% of all wall-clock time would be spent in a state where Product A is completely stalled. That is fatal, and it is the central finding of this exercise.
Part 3 — capacity via Little's Law
Product A: latency ~ TTFT + 300 x TPOT = 0.04 + 300 x 0.0086 = 2.62 s
concurrency = 12 req/s x 2.62 s = 31 sequences
Product B: latency ~ 0.61 + 600 x 0.0086 = 5.8 s
concurrency = 0.4 x 5.8 = 2.3 -> 3 sequences
Total concurrency needed: about 34 sequences, against capacity for hundreds. You are not capacity-constrained at all — you are interference-constrained. That reframes the entire design problem, and it is why doing Part 1 and Part 3 before Part 4 matters.
Part 4 — the design
a) Two pools, disaggregated by product. Not by prefill/decode — by product, which is the sharper split here. Product B's 24k prefills are the entire problem and they must not touch Product A's decode.
A reasonable allocation given the tiny concurrency requirements:
6 GPUs -> Product A (TP-6 is awkward; use 2 replicas of TP-4? see (b))
2 GPUs -> Product B
But 2 GPUs cannot hold 141.2 GB of fp16 weights. So either quantize Product B's copy to fp8 (70.6 GB, fits in 2×80 GB with ~80 GB left for its 3-sequence KV requirement of 24 GB — workable), or use a 4/4 split.
Recommended: 4 GPUs each, TP-4, fp8 weights on both. fp8 halves weights to 70.6 GB, so TP-4 gives 17.65 GB/GPU and leaves ample KV room. This also doubles decode speed.
b) TP-4 per pool, two pools. TP-8 in one pool would give lower latency per request, but we established we are not latency-constrained — Product A's TPOT floor is 8.6 ms against a 40 ms budget. Spending GPUs on latency we do not need, at the cost of leaving Product B co-located, is the wrong trade. TP-4 also keeps both pools within NVLink and preserves the 8-KV-head split cleanly.
c) Chunked prefill: on for both, token budget 2,048.
For Product A it bounds the p99 TTFT spike from the occasional 8,000-token prompt:
8,000-token prefill unchunked at TP-4: 2 x 70.6e9 x 8000 / 2,769e12 = 408 ms
chunked at 2,048: 4 iterations, ~104 ms each -> worst ITL ~104 ms
Still above the 40 ms TPOT SLO, so drop the budget to 1,024 for Product A: ~52 ms worst ITL, close enough that with real overlap it lands inside budget. Measure and tune.
For Product B, chunking barely matters since it has its own pool, but leave it on at 2,048 to keep its own concurrent requests from blocking each other.
d) KV precision: fp16 for both. We have enormous memory headroom — 386.8 GB against a need of about 25 GB. There is no reason to spend quality on memory we are not short of. This is the discipline from Module 6: do not reach for lossy optimizations to solve a problem you do not have. Revisit if load grows 10×.
e) Speculative decoding: on for Product A, off for Product B.
Product A runs at concurrency ~31 per pool — well below the ridge point, so there is idle arithmetic to spend. With EAGLE-style self-speculation (c ≈ 0.1, α ≈ 0.7) at γ = 4, expect around 1.9×, taking TPOT from 8.6 ms to roughly 4.5 ms. Not needed for the SLO, but it buys headroom for load growth.
Product B has concurrency 3 and is dominated by prefill; speculation would help its 600 decode tokens marginally and adds complexity. Leave it off.
Summary of the design:
Pool A: 4x H100, TP-4, fp8 weights, fp16 KV, chunked prefill @1024,
EAGLE speculation, target batch ~32
Pool B: 4x H100, TP-4, fp8 weights, fp16 KV, chunked prefill @2048,
no speculation, target batch ~3
The single most important decision was separating the pools, and it came from Part 2(d) — not from a memory calculation or a throughput calculation, but from noticing that one product's prefill destroys the other product's SLO.
Part 5 — the benchmark plan
LOAD MODEL
open-loop, Poisson arrivals, two independent streams
Product A: sweep 4 -> 24 req/s
Product B: fixed 0.4 req/s, then sweep to 1.5 to find the breaking point
run both streams SIMULTANEOUSLY -- testing them separately would
miss the interference this design exists to prevent
DISTRIBUTIONS
A: prompt lognormal median 1500 p99 8000; output lognormal median 300 p99 1500
B: prompt normal around 24000 +/- 6000; output around 600
ideally replayed from a real trace instead
PROCEDURE
5 min warmup, discarded
20 min steady state
3 repetitions, report variance
METRICS
per product: TTFT p50/p95/p99, TPOT p50/p95/p99, end-to-end p95
GOODPUT: A-requests/s meeting (TTFT<800ms AND TPOT p95<40ms)
B-requests/s meeting (e2e < 60 s)
GPU utilization and KV occupancy per pool
realized speculation acceptance rate on Pool A
WHAT WOULD FALSIFY THE DESIGN
- A's TPOT p95 exceeds 40 ms at 12 req/s
-> chunk budget too high, or batch too large; lower budget, re-measure
- A's TPOT p99 spikes correlate in time with B's arrivals
-> the pools are not actually isolated (shared NVLink? shared host?)
- B's e2e p95 exceeds 60 s
-> Pool B is under-provisioned; rebalance 5/3
- KV occupancy above ~70% on either pool
-> the fp16-KV decision was wrong; revisit fp8
- speculation acceptance below 0.5 on real traffic
-> speculation is near break-even; measure whether it still pays
CONTROL
same model, same precision, same hardware, stated explicitly
compare against a single-pool co-located baseline -- that comparison
is the whole justification for the design
That last line matters. A benchmark that only measures your chosen design tells you whether it meets the SLO, not whether it was the right design. Always measure the alternative you rejected.