LLM inference has two distinct phases: prefill (process the full prompt — compute-bound, fast per token) and decode (generate one token — memory-bound, slow per token). Continuous batching dynamically adds/removes requests from a batch as they finish, rather than waiting for the longest sequence.
Key metrics: TTFT (time to first token — mainly prefill), TPS (tokens per second — decode speed), throughput (total tokens/sec across all requests). Disaggregating prefill and decode to separate GPU pools (prefill cluster + decode cluster) is the latest frontier.
Python — throughput benchmark#
25 batches of 8 requests with output lengths between 20 and 600 tokens. With static batching each batch runs until its longest request finishes, and on average only 59% of the slots are doing useful work. Continuous batching refills a slot as soon as a request ends, approaching 100%.
- Higher throughput can mean worse latency for each user; batch size is a trade-off, not a free gain.
- Prefill (reading the prompt) and decode compete for the same GPU; long prompts stall everyone’s generation unless the two are separated.
- Benchmarks with uniform request lengths overstate real throughput.
rng = np.random.default_rng(3) out = rng.integers(20, 600, size=(25, 8)) # output lengths: 25 batches of 8 requests static_util = (out.sum(1) / (8 * out.max(1))).mean() # each batch runs until its longest request ends continuous_util = 1.0 # finished slots refilled at once (the ideal)
Assumes import numpy as np. Configurations named after real models use their published shapes; other numbers are made up or simulated, as the comments say.
The same idea with a library
import time, asyncio
from openai import AsyncOpenAI
client = AsyncOpenAI(base_url="http://localhost:8000/v1")
async def single_request(prompt):
start = time.monotonic()
resp = await client.completions.create(
model="llama-3.1-8b", prompt=prompt,
max_tokens=256, temperature=0.7)
elapsed = time.monotonic() - start
tokens = resp.usage.completion_tokens
return tokens, elapsed
async def benchmark(n_concurrent=32):
prompts = ["Explain transformers in detail."] * n_concurrent
tasks = [single_request(p) for p in prompts]
results = await asyncio.gather(*tasks)
total_tokens = sum(r[0] for r in results)
wall_time = max(r[1] for r in results)
print(f"Throughput: {total_tokens/wall_time:.0f} tok/s")- G.-I. Yu, J. S. Jeong, G.-W. Kim, S. Kim & B.-G. Chun, “Orca: A Distributed Serving System for Transformer-Based Generative Models”, OSDI, 2022
- W. Kwon et al., “Efficient Memory Management for Large Language Model Serving with PagedAttention”, SOSP, 2023
- Y. Zhong et al., “DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving”, OSDI, 2024
Last reviewed October 2026 · How this site checks its content · Report a mistake