GenAI-Perf in depth: LLM inference profiling with real data

Contents

Notation: amounts in euros (N EUR), decimals with a point. The dollar symbol is not used (on this site it is a formula delimiter).

TL;DR

GenAI-Perf (the genai-perf package, part of NVIDIA’s Triton ecosystem) is the reference LLM inference profiler for OpenAI-compatible endpoints. Installable with pip install genai-perf. A single genai-perf profile command produces a console table with TTFT, ITL, request latency (avg/min/max/P75/P90/P99), output sequence length, output token throughput and request throughput, plus profile_export_genai_perf.json and .csv artefacts. The analyze subcommand automatically sweeps concurrencies from 1 to 256 (powers of 2) and generates a summary analyze_export_genai_perf.csv. GenAI-Perf was declared retired in April 2026 (replaced by AIPerf); it remains the reference benchmark for historical runs and documented comparisons, and the NVIDIA tool with the most public documentation for LLMs before 2026.


What GenAI-Perf is and where it fits

GenAI-Perf is a command-line tool for measuring the throughput and latency of generative AI models served through an inference server. It is part of the triton-inference-server/perf_analyzer GitHub repository and is distributed as a pip package (genai-perf) and inside the Triton SDK container (nvcr.io/nvidia/tritonserver:YY.MM-py3-sdk).

Position in the NVIDIA ecosystem:

Inference engineTriton + TRT-LLM / vLLMEndpointOpenAI-compatibleGenAI-Perfprofiler (until Apr 2026)AIPerfsuccessor (Apr 2026+)GenAI-Perf works against any endpoint compatible with the OpenAI API (vLLM, NIM, Triton, SGLang, TGI…).AIPerf is the official successor from April 2026 (GitHub: ai-dynamo/aiperf).

Relationship with perf_analyzer: GenAI-Perf internally uses Triton’s perf_analyzer binary to generate load and measure latencies. perf_analyzer is the low-level load generator; GenAI-Perf is the high-level layer that adds prompt synthesis, OpenAI payload construction and the LLM-specific metrics (TTFT, ITL, sequence lengths).

Installation:

# Option 1: pip (requires CUDA 12, Ubuntu 24.04, Python 3.10+)
pip install genai-perf

# Option 2: Triton SDK container (recommended for reproducibility)
export RELEASE="25.01"
docker run -it --net=host --gpus=all \
  nvcr.io/nvidia/tritonserver:25.01-py3-sdk
# Inside the container:
genai-perf --help

Metrics GenAI-Perf produces

The metrics cover the full cycle of a streaming generation request. All of them are reported with avg, min, max, P75, P90 and P99 except the throughput ones (avg only).

Metrics glossary

MetricSymbolExact definition (GenAI-Perf)Unit
Time to First TokenTTFTTime from sending the request to receiving the first token (includes queueing, prefill and network)ms
Inter-Token LatencyITL (= TPOT)\(\frac{e2e\_latency - TTFT}{output\_tokens - 1}\) — decode phase only, excludes the first tokenms/token
Request Latencye2e_latency\(e2e = TTFT + Generation\_time\) — from the first request to the last responsems
Output Token ThroughputOTT\(\frac{total\_output\_tokens}{T_y - T_x}\) where \(T_x\) = first request, \(T_y\) = last token receivedtok/s
Request ThroughputRPS\(\frac{total\_completed\_requests}{T_y - T_x}\)req/s
Input Sequence LengthISLMean length in tokens of the input prompttokens
Output Sequence LengthOSLMean length in tokens of the generated responsetokens

Methodological note on ITL versus LLMPerf: GenAI-Perf excludes TTFT from the ITL calculation (pure decode only). LLMPerf includes TTFT in its ITL mean. The numbers are not directly comparable.

Methodological note on TPS versus LLMPerf: GenAI-Perf measures throughput between the first request and the last token received (\(T_y - T_x\)). LLMPerf uses the total duration of the benchmark including prompt generation and response storage; in a concurrency-1 scenario, that difference can amount to up to 33% in the reported throughput figure.

Console output table (real example, TRT-LLM backend)

The table GenAI-Perf prints for a profile with ISL=200, OSL=100, concurrency=1:

                              NVIDIA GenAI-Perf | LLM Metrics
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
┃                         Statistic ┃    avg ┃    min ┃    max ┃    p99 ┃    p90 ┃    p75 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
│          Time to first token (ms) │  13.68 │  11.07 │  21.50 │  18.81 │  14.29 │  13.97 │
│          Inter token latency (ms) │   1.86 │   1.28 │   2.11 │   2.11 │   2.01 │   1.95 │
│              Request latency (ms) │ 203.70 │ 180.33 │ 228.30 │ 225.45 │ 216.48 │ 211.72 │
│            Output sequence length │ 103.46 │  95.00 │ 134.00 │ 122.96 │ 108.00 │ 104.75 │
│             Input sequence length │ 200.00 │ 200.00 │ 200.00 │ 200.00 │ 200.00 │ 200.00 │
│ Output token throughput (per sec) │ 504.02 │    N/A │    N/A │    N/A │    N/A │    N/A │
│      Request throughput (per sec) │   4.87 │    N/A │    N/A │    N/A │    N/A │    N/A │
└───────────────────────────────────┴────────┴────────┴────────┴────────┴────────┴────────┘

(Source: docs.nvidia.com/deeplearning/triton-inference-server — LLM tutorial)


Invocation against OpenAI-compatible endpoints

profile mode: one load point

The profile subcommand measures a fixed operating point (concurrency or request-rate, synthetic ISL/OSL).

Example 1: vLLM endpoint (chat, streaming)

# 1. Bring up the vLLM server
docker run -it --net=host --rm --gpus=all \
  vllm/vllm-openai:latest \
  --model meta-llama/Llama-3.1-70B-Instruct \
  --dtype float16

# 2. Profile with GenAI-Perf
genai-perf profile \
  -m meta-llama/Llama-3.1-70B-Instruct \
  --endpoint-type chat \
  --streaming \
  --concurrency 16 \
  --synthetic-input-tokens-mean 1024 \
  --synthetic-input-tokens-stddev 128 \
  --output-tokens-mean 256 \
  --output-tokens-stddev 0 \
  --output-tokens-mean-deterministic \
  --request-count 200 \
  --warmup-request-count 20 \
  --tokenizer meta-llama/Llama-3.1-70B-Instruct

Example 2: completions endpoint (no streaming)

genai-perf profile \
  -m meta-llama/Llama-3.1-70B-Instruct \
  --endpoint-type completions \
  --concurrency 32 \
  --synthetic-input-tokens-mean 512 \
  --synthetic-input-tokens-stddev 64 \
  --output-tokens-mean 128 \
  --request-count 200 \
  --warmup-request-count 20

Example 3: NVIDIA NIM (TRT-LLM backend endpoint, via Triton)

genai-perf profile \
  -m meta/llama-3.1-70b-instruct \
  --backend tensorrtllm \
  --streaming \
  --concurrency 8 \
  --synthetic-input-tokens-mean 2048 \
  --synthetic-input-tokens-stddev 0 \
  --output-tokens-mean 512 \
  --output-tokens-mean-deterministic \
  --request-count 100 \
  --warmup-request-count 10 \
  --generate-plots

Example 4: request-rate instead of concurrency (Poisson)

genai-perf profile \
  -m meta-llama/Llama-3.1-70B-Instruct \
  --endpoint-type chat \
  --streaming \
  --request-rate 5.0 \
  --synthetic-input-tokens-mean 1024 \
  --output-tokens-mean 256 \
  --request-count 300 \
  --warmup-request-count 30

Main flags and what they do

FlagTypical valuesFunction
--endpoint-typechat, completions, embeddingsType of OpenAI-compatible endpoint
--streaming(boolean)Enables SSE streaming; required to measure real TTFT and ITL
--concurrency1, 8, 16, 32, 64Number of concurrent requests maintained; GenAI-Perf guarantees N active at all times
--request-rate1.0, 5.0, 10.0Constant arrival rate (req/s); does not guarantee N active
--synthetic-input-tokens-mean128–8192Mean of synthetic ISL tokens
--synthetic-input-tokens-stddev0–512Standard deviation of the ISL (0 = fixed)
--output-tokens-mean64–2048Mean of target OSL tokens
--output-tokens-mean-deterministic(boolean)Sets the minimum output tokens = target mean (more precise with TRT-LLM)
--request-count100–2000Number of requests to benchmark
--warmup-request-count10–50Warm-up requests discarded from the metrics
--generate-plots(boolean)Generates PNG plots of TTFT vs ISL, ITL vs token position, etc.
--tokenizerHF model idTokenizer for counting tokens; mandatory when ISL/OSL matter
--backendtensorrtllm, vllmTriton backend (for serving directly via Triton without an OpenAI endpoint)
--extra-inputsignore_eos:trueExtra request parameters; ignore_eos:true guarantees a consistent OSL

Note on --concurrency versus --request-rate: NVIDIA recommends using --concurrency. With --request-rate, if the rate exceeds the engine’s throughput, the queue grows without bound and the metrics stop being stable. With --concurrency, there are always exactly N active requests, which gives a clean latency-throughput curve.

Output artefacts

GenAI-Perf dumps all results into an artifacts/ directory:

ArtefactFormatContent
profile_export_genai_perf.jsonJSONFull metrics (avg, min, max, P75, P90, P99) + CLI arguments used
profile_export_genai_perf.csvCSVExported console tables, ready to import into Excel/pandas
profile_export.jsonJSONRaw perf_analyzer data (per-request traces)
inputs.jsonJSONSynthetic payloads sent (for reproducibility)
PNG plotsPNGTTFT analysis, Request latency, TTFT vs ISL, ITL vs position, ISL vs OSL (with --generate-plots)

The analyze subcommand: automatic concurrency sweep

The analyze subcommand sweeps multiple values of a parameter in a single command and generates a summary CSV.

Concurrency sweep (the most used)

# Sweeps concurrencies 1, 2, 4, 8, 16, 32, 64, 128, 256
genai-perf analyze \
  -m meta-llama/Llama-3.1-70B-Instruct \
  --endpoint-type chat \
  --streaming \
  --sweep-type concurrency \
  --sweep-range 1:256 \
  --synthetic-input-tokens-mean 1024 \
  --output-tokens-mean 256 \
  --request-count 200 \
  --warmup-request-count 20

Request-rate sweep

# Sweeps 2, 4, 6, 8, 10, 12 req/s
genai-perf analyze \
  -m meta-llama/Llama-3.1-70B-Instruct \
  --endpoint-type chat \
  --streaming \
  --sweep-type request_rate \
  --sweep-list 2,4,6,8,10,12 \
  --synthetic-input-tokens-mean 1024 \
  --output-tokens-mean 256 \
  --request-count 200 \
  --warmup-request-count 20

ISL sweep (to study the effect of prefill)

genai-perf analyze \
  -m meta-llama/Llama-3.1-70B-Instruct \
  --endpoint-type chat \
  --streaming \
  --sweep-type input_sequence_length \
  --sweep-list 256,512,1024,2048,4096 \
  --concurrency 16 \
  --output-tokens-mean 256 \
  --request-count 200 \
  --warmup-request-count 20

Artefact structure of analyze

For a sweep with --sweep-type concurrency --sweep-range 1:32:

artifacts/
  llama70b-openai-chat-concurrency1/
    inputs.json
    profile_export.json
    profile_export_genai_perf.json
    profile_export_genai_perf.csv
  llama70b-openai-chat-concurrency2/
    ...
  llama70b-openai-chat-concurrency4/
    ...
  [...]
analyze_export_genai_perf.csv    ← summary of every scenario
checkpoint.json                  ← allows interrupted sweeps to be resumed

Summary CSV format

Config Name,Concurrency,ISL,p99 TTFT (ms),p99 ITL (ms),p99 Request Latency (ms),Avg. OTT (tok/s),RPS
llama70b_run_0,1,1024,45.2,6.8,1823.4,128.3,0.55
llama70b_run_1,4,1024,48.1,7.1,1901.2,510.7,2.18
llama70b_run_2,8,1024,61.3,7.9,2142.5,989.2,4.12
llama70b_run_3,16,1024,98.7,9.4,2891.3,1741.6,6.88
llama70b_run_4,32,1024,284.1,14.2,4320.8,2134.2,7.14
llama70b_run_5,64,1024,892.4,28.7,8741.2,2251.8,7.23

The summary CSV also includes a second table with GPU metrics (P99 power, energy, utilisation, memory) when they are captured via Triton’s telemetry URLs (--server-metrics-urls http://localhost:8002/metrics).


Comparison table: GenAI-Perf versus GuideLLM versus LLMPerf versus vLLM bench

The choice of tool changes the number. They are not comparable with each other without adjustment.

DimensionGenAI-PerfGuideLLMLLMPerfvLLM bench serve
Origin / maintainerNVIDIA / Triton team (retired Apr 2026)Red Hat / vLLM projectAnyscale / RayvLLM project
Classmulti-process load generatormulti-process load generatormulti-process load generatorsingle-process micro-bench
Supported endpointsOpenAI-compatible + KServe + native TritonOpenAI-compatibleOpenAI-compatiblenative vLLM
Main load modefixed concurrency N (recommended) or constant request-ratesynchronous, concurrent, poisson, throughput, automatic sweepbatches of N concurrent (with a draining period at the end)request-rate, concurrency
Draining periodNO — guarantees N active at all timesNO (poisson)YES — at the end of each batch the system empties, concurrency drops to 0N/A
Automatic sweepanalyze (concurrency, request-rate, ISL, OSL)--rate-type sweep (idle to saturation, 10 rounds)manual (several runs)vllm bench sweep serve
LLM metricsTTFT, ITL, e2e latency, OTT, RPS, ISL/OSLTTFT, ITL/TPOT, throughput, goodput under SLOTTFT, ITL (includes TTFT in the mean), TPS (total benchmark duration)TTFT, TPOT, throughput
ITL differenceexcludes TTFT: \((e2e - TTFT) / (N_{tok}-1)\)excludes TTFT (same as GenAI-Perf)includes TTFT in the meanexcludes TTFT
TPS difference\(total\_tokens / (T_y - T_x)\)throughput + goodput under SLO\(total\_tokens / (T_{end} - T_{start})\) — up to 33% lower\(total\_tokens / (T_y - T_x)\)
Goodput / SLONOYES (the key differentiator)NONO
ExportsJSON + CSV + PNG + checkpointJSON + YAML + CSV + interactive HTMLJSONconsole / JSON
Built-in warm-upYES (--warmup-request-count)YES (warm-up requests)NOT nativeYES
Real datasetsOpenOrca, CNN DailyMail, JSONL file, moon_cakefile with a traffic distributionJSONL filesharegpt, random
GPU telemetryYES (via Triton’s server-metrics-urls → summary CSV)NOT native (integrate DCGM separately)NONO
When to choose itfine profiling of one operating point, comparing configs with the same harness, integration with NIM/TritonSLO-driven sweep, finding the knee, sizing replicasquick endpoint validation in the Ray ecosystemiterating on vLLM flags

Fair-comparison rule: never cross numbers from different tools for the same conclusion. If vLLM and TRT-LLM are being compared, use GenAI-Perf against both, with the same ISL/OSL, the same concurrency and the same --warmup-request-count. The tool is the constant; the engine is the variable.


Profiling on 4×H100 SXM 80 GB: a full sweep example

Generic reference hardware: 4×H100 SXM 80 GB NVLink, model Llama-3.1-70B-Instruct FP16, ISL=1024 tokens, OSL=256 tokens, vLLM endpoint.

Illustrative result of a concurrency sweep (1→64)

ConcurrencyTTFT P50 (ms)TTFT P99 (ms)ITL P50 (ms)ITL P99 (ms)OTT (tok/s)RPS
138456.47.11300.51
440516.88.25102.00
846627.39.49803.83
1668988.912.11,7206.72
3214528413.721.42,1308.32
4838074022.338.62,2808.90
648901,82041.278.32,3409.14

How to read it: throughput saturates around concurrency 48–64, while TTFT P99 already exceeds 500 ms from concurrency 32 onwards. The knee (the last point with TTFT P99 < 500 ms) is at concurrency 16 with 1,720 useful tok/s. Anyone reporting “2,340 tok/s” (concurrency 64) is describing the maximum throughput, at a point where the system meets no interactive chat SLO at all.

Formulas for the key metrics

$$ \text{ITL} = \frac{e2e\_latency - TTFT}{N_{output\_tokens} - 1} $$ $$ \text{OTT} = \frac{\sum output\_tokens}{T_y - T_x} $$ $$ \text{RPS} = \frac{N_{requests}}{T_y - T_x} $$

where \(T_x\) is the timestamp of the first request sent and \(T_y\) the timestamp of the last token received from the last request.


Synthetic ISL/OSL profiling: what to configure for each use case

The ISL/OSL parameters determine which engine of the system is stressed: a long ISL stresses prefill (KV-cache, TTFT); a long OSL stresses decode (ITL, memory bandwidth).

Use caseTypical ISLTypical OSL--synthetic-input-tokens-mean--output-tokens-mean
Short interactive chat~300 tok~100 tok300100
Code copilot~800 tok~200 tok800200
Document summarisation~2000 tok~256 tok2000256
RAG with long context~4096 tok~512 tok4096512
Batch without latency~512 tok~1024 tok5121024

The --extra-inputs ignore_eos:true option disables the engine’s EOS token, forcing exactly the requested tokens to be generated. Essential for measuring a consistent OSL on TRT-LLM; optional on vLLM.


Honest methodology: how not to measure badly

1. Warm-up is mandatory

Without warm-up, the first requests include the KV-cache loading latency, the engine’s autotuning and the initialisations. GenAI-Perf applies a sliding window to detect stability (max/min ratio within a margin over the last 3 measurements), but the explicit --warmup-request-count discards the first N requests from the metrics. Recommended minimum value: 10 requests (or 5% of the total if the total is large).

2. The concurrency knee: extend the sweep until you see it

Throughput saturates once the engine’s effective max batch size is reached. If the sweep stops before saturation is reached, the “maximum capacity” reported is the last concurrency tested, not the real knee. The rule: the sweep must reach a point where TTFT P99 has doubled relative to the low-load value (or where OTT stops growing by more than 5% between steps). Only then is the knee visible.

For a sweep with --sweep-range 1:256, GenAI-Perf sweeps 1, 2, 4, 8, 16, 32, 64, 128, 256 (powers of 2). On most 70B models over 4×H100, saturation occurs between concurrency 32 and 64; the range 1:128 covers the knee with room to spare.

3. Concurrency versus request-rate: choosing correctly

ModeBehaviourWhen to use
--concurrency NAlways N active requests; when one finishes, the next is sentClean latency-throughput curve; comparison between configs
--request-rate R1 request is sent every 1/R seconds; the queue can grow without boundSimulating traffic with arrivals at a constant rate

NVIDIA recommends --concurrency for benchmarking. With --request-rate, if R exceeds the engine’s maximum throughput, the queue grows indefinitely and the metrics become unstable.

4. Single-process versus multi-process bias

GenAI-Perf is multi-process: it maintains N concurrent requests using perf_analyzer’s internal manager. There is no risk of client saturation. This sets it apart from vllm bench serve (single-process), which can saturate on the client at high concurrency and give inflated throughput metrics.

5. ignore_eos and an inconsistent OSL

Without --extra-inputs ignore_eos:true, the engine may generate fewer tokens than requested (if the model produces an EOS earlier). This makes the real OSL vary between requests and the ITL become inconsistent between runs. For reproducible benchmarks with a controlled OSL: always enable ignore_eos:true.

6. The correct tokenizer

The --tokenizer flag must point to the model being served. If it is omitted, GenAI-Perf uses a default tokenizer that can give different ISL/OSL in the real model’s tokens, making the throughput numbers in tok/s not comparable between models with different vocabularies.

7. One run = a data point; three or more = a number

Variance between runs under identical conditions can be 5–10% on tail latency metrics (P99). A single profile is not a defensible number; the mean of three runs with the same config is.

1 · Enginepinned config2 · Warm-up≥10 requests3 · Sweep1:256, past the knee4 · Artefactsversioned JSON + CSV5 · CompareP99 at the kneePin: genai-perf version, engine version, model, precision, ISL/OSL, hardware, tokenizer.Without that metadata in the JSON, the number is neither reproducible nor comparable.

Connection with the cost and energy track

GenAI-Perf can capture GPU metrics in the same summary CSV if --server-metrics-urls http://localhost:8002/metrics is passed (Triton’s metrics endpoint, which exposes power and utilisation via DCGM). The analyze summary CSV then includes a second table with P99 power (W), energy (MJ), utilisation (%) and memory (GB) per GPU and per scenario.

With that data, a single sweep yields the three axes of the scorecard:

AxisSourceMetric
PerformanceGenAI-PerfOTT (tok/s), TTFT P99, ITL P99
EnergyDCGM via server-metrics-urlsPower (W) → \(J/token = W / OTT\)
Costnode priceEUR/hour → \(EUR/token = EUR_{hour} / (OTT \times 3600)\)

The formula for cost per million tokens:

$$ CPM = \frac{EUR_{hour}}{OTT \times 3{.}6 \times 10^3} $$

where \(OTT\) is the output token throughput in tok/s and \(CPM\) is the cost per million tokens in EUR.

It connects with GPU observability with DCGM for the power capture, and with anatomy of an LLM request for the breakdown of phases (prefill versus decode) that TTFT and ITL quantify.


Status 2026: GenAI-Perf → AIPerf

NVIDIA announced in April 2026 that GenAI-Perf moves to passive maintenance mode (no new features) and that the active successor is AIPerf (github.com/ai-dynamo/aiperf). AIPerf adds automatic detection of the saturation point (estimatedCapacity) and is integrated into the NVIDIA Dynamo pipeline.

For new benchmarks in production: migrate to AIPerf. To reproduce historical runs documented with GenAI-Perf, or to use NVIDIA’s NIM benchmarking artefacts (which use GenAI-Perf): stay on GenAI-Perf 25.x.

The article LLM benchmark tools, one by one covers the full comparison including AIPerf. The article GuideLLM: SLO validation under load details the SLO-driven sweep that GenAI-Perf does not do natively. For the general methodology context: LLM benchmarking frameworks, state of the art.


See also

Sources