Tooling

Benchmarking: tooling and profiling

Benchmarking LLM inference is surprisingly easy to do wrong. The tools exist, but knowing what to measure, and what the numbers actually mean, is the hard part.

I've seen teams benchmark their inference stack by running 10 requests sequentially and reporting the average latency. That number is almost meaningless. It tells you nothing about how the system behaves under concurrent load, nothing about tail latency, and nothing about throughput. Real benchmarking is about understanding the system's behavior across its operating range, and it requires both the right tools and the right methodology.

What to measure

LLM inference has more performance dimensions than a typical web service. The key metrics:

Always report percentiles (p50, p90, p99), not averages. Average latency hides the tail, and the tail is where users feel pain.

The tools

Several open-source tools have emerged specifically for LLM inference benchmarking:

GenAI-Perf (NVIDIA). Part of the Triton Inference Server ecosystem. It generates load against an OpenAI-compatible API and reports TTFT, ITL, throughput, and latency percentiles. It understands streaming responses and can parse SSE token streams. This is my default for quick benchmarks.

# GenAI-Perf: benchmark an OpenAI-compatible endpoint
genai-perf profile \
  -m llama-3.1-70b \
  --endpoint-type chat \
  --url http://localhost:8000 \
  --streaming \
  --concurrency 32 \
  --input-tokens-mean 512 \
  --output-tokens-mean 256 \
  --measurement-interval 60000

vLLM's benchmark_serving.py. Ships with vLLM and is tightly integrated with its metrics. It supports configurable request rates (Poisson arrival), input/output length distributions, and multi-model scenarios. The output includes TTFT and ITL distributions.

# vLLM's built-in benchmark
python benchmark_serving.py \
  --backend openai \
  --base-url http://localhost:8000 \
  --model meta-llama/Llama-3.1-70B-Instruct \
  --dataset-name sharegpt \
  --request-rate 10 \
  --num-prompts 500

Locust. Not LLM-specific, but I use it when I need custom load patterns or want to simulate realistic user behavior (e.g., users who read tokens as they stream and then send a follow-up). Locust's Python-based test scripts make it easy to write LLM-aware load generators.

Nsight Systems. For GPU-level profiling. When you need to know why a kernel is slow, not just that it is slow. Nsight traces CUDA kernels, memory copies, and CPU activity on a timeline. It's the difference between "inference is slow" and "the attention kernel takes 3.2 ms because it's reading 48 MB of KV cache from HBM."

Methodology: how to not fool yourself

The most common benchmarking mistakes I've seen:

The saturation sweep

The most informative benchmark is a request rate sweep: fix the input/output distribution, then increase the request rate from low to beyond saturation. Plot TTFT p50 and p99 against request rate. You'll see a hockey stick: flat at low rates, then a sharp inflection where the system saturates. The request rate just before the inflection is your practical capacity.

Profiling: going deeper

Benchmarking tells you what is slow. Profiling tells you why. For LLM inference, the profiling stack looks like:

# Profile with torch.profiler
import torch
from torch.profiler import profile, ProfilerActivity

with profile(
    activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
    schedule=torch.profiler.schedule(wait=2, warmup=3, active=5),
    on_trace_ready=torch.profiler.tensorboard_trace_handler("./logs"),
    record_shapes=True,
    profile_memory=True,
    with_stack=True,
) as prof:
    for step in range(10):
        output = model.generate(input_ids, max_new_tokens=64)
        prof.step()

What good numbers look like

As a rough calibration for a 70B model on a single 8xH100 node in FP16 with tensor parallelism across all 8 GPUs:

These numbers shift significantly with quantization (INT8 roughly doubles throughput), KV cache compression, speculative decoding, and the specific serving engine. The point is not to memorize them but to have a baseline so you can tell when something is wrong.

Benchmarking is a skill, not a task. The tools are straightforward; the discipline of controlling variables, reporting distributions, and questioning your own results is what separates useful benchmarks from misleading ones.

Next: client code for streaming and async, because the server is only half the story.