Skip to main content

Performance Benchmarking

trtmc-bench measures public TRTMC pipeline calls across text, vision-language, diffusion, audio, segmentation, classification, encoder, reranking, speech-transcription, and neural-operator models. The default path is one command:

trtmc-bench run --model distilgpt2

Release performance matrix

trtmc-bench measures one resolved workload. The release performance matrix adds a repository-owned comparison layer around it: tools/perf_matrix.py runs TRTMC through trtmc-bench, runs the reference backend declared by each suite row in a separate Python process, and checks that both sides used the same workload and timing boundary.

The checked-in suite at benchmarks/performance/release.yaml currently covers 105 release-relevant, ready, single-process model-profile comparisons across 76 families and 77 (family, operation) contracts. Short l0 smoke duplicates are excluded by rule; any other omission must appear in excluded_profiles with a reason. Validate coverage and all machine prerequisites without measuring a model:

python3 tools/perf_matrix.py check \
benchmarks/performance/release.yaml \
--environment benchmarks/performance/environments/gb300.yaml

The checked-in GB300 environment requires these repository variables to point at the installed worker, caches, bundles, and runtime libraries:

TRTMC_PERF_WORKER
TRTMC_PERF_BUNDLE_CACHE
TRTMC_PERF_BUNDLE_ROOTS
TRTMC_PERF_RUNTIME_DIRS

Both check and run perform the same preflight: suite coverage, expanded environment, free storage, required executables, candidate Release-build revision, selected trtmc-bench testcases, and candidate/reference timing contracts. Reference-specific upstream checkout paths and prebuilt Python profiles described in benchmarks/performance/README.md are additional operator prerequisites; dependency installation is outside the measured campaign.

Reference precision is resolved from the suite row's explicit baseline.precision, then the selected testcase's reference_precision, then the model manifest's top-level reference_precision, and finally the resolved TRTMC model precision. The chosen value is passed to the reference runner, recorded as resolved_settings.baseline_precision in results.json, and checked against the runner result. A mismatch is a contract mismatch and does not receive a performance light.

Diffusers media references also reject non-finite numeric pixels before image conversion can hide the invalid values. Such output is a reference execution failure, not a completed performance comparison.

Run the complete matrix, one exact row, resume an interrupted run, or regenerate an existing report with task-level preparation evidence:

python3 tools/perf_matrix.py run \
benchmarks/performance/release.yaml \
--environment benchmarks/performance/environments/gb300.yaml
python3 tools/perf_matrix.py run \
benchmarks/performance/release.yaml \
--environment benchmarks/performance/environments/gb300.yaml \
--entry gpt2.generate
python3 tools/perf_matrix.py resume artifacts/perf/example-run
python3 tools/perf_matrix.py report artifacts/perf/example-run \
--preparation-receipt artifacts/perf/bundle-preparation.json

Every new run writes results.json and report.html below the configured results root. The JSON records resolved configuration, provenance, raw samples, exact leaf commands, timing policies, and bundle preparation; the HTML shows candidate/reference p50 values and the traffic light. The report's self-contained controls can filter by text, traffic light, or bundle preparation status without a server.

A separately run bundle-preparation step can be attached with the report command shown above. The receipt must use schema trtmc.perf-bundle-preparation/v1, scope test_task, the run's exact Git commit, and the exact model and bundle paths consumed by that campaign. Revision mismatches, duplicate records, invalid build times, and unused bundle paths are rejected. A matching preparation receipt takes precedence over a later cache hit, so a task-level rebuild remains visible as Built.

Green, yellow, and red are completed comparison results and therefore return zero. Configuration errors, command failures, incomplete measurements, and timing or output-contract mismatches return nonzero and do not receive a performance light.

Controlled Internal CI can run the same matrix and retain the unique run directory as a private artifact. A green documentation build, sanitized premerge status, or host-only matrix check is not target-hardware performance evidence: a release claim requires the retained target-hardware run, reference result, exact revision, and report.

List the model profiles currently supported by the installed benchmark catalog:

trtmc-bench list models

The command lists every profile declared by the canonical MODEL.toml files. STATUS=ready means it can run in the current single-process worker. STATUS=distributed keeps an MPI/TP profile visible but explains why it cannot yet run. Invalid or unknown task contracts are reported explicitly instead of silently disappearing from the list.

Install a packaged build

A TRTMC native wheel is the end-user distribution. It contains the Python orchestrator, trtmc-bench command, native measurement worker, TRTMC runtime libraries, model plugins, and the canonical model catalog snapshot:

python -m pip install /path/to/tensorrt_model_connect-*.whl
trtmc-bench run --model distilgpt2

No separate worker install or CMake build is required. The wheel must match the supported Python, CUDA, TensorRT, and machine platform. Source installation is the development workflow described below.

If no compatible bundle is available, the command invokes the existing trtmc build implementation with settings from the model manifest and stores the bundle in a managed cache. It writes result.json, report.html, resolved inputs, build evidence, all timed observations, worker logs, and optional low-frequency GPU telemetry into one new result directory. Bundle building, model loading, and warmup are excluded from the reported latency. The timed boundary is named public_pipeline_call_wall in the machine-readable result.

A successful case keeps a small, user-facing evidence set:

<result-dir>/
├── result.json
├── report.html
└── 001-<model>-<case>/
├── resolved-case.json
├── observations.jsonl
├── telemetry.json # only when telemetry is enabled
└── worker.log

result.json contains the reduced metrics used by reports. resolved-case.json records the effective request and the source of every field. observations.jsonl keeps one raw timed observation per line. worker.log combines worker stdout and stderr. A failed worker additionally retains its internal worker-request.json and, when produced, worker-result.json protocol files for diagnosis; successful cases remove those redundant intermediates.

Build from source

In a prepared development environment that already contains the repository's Python and TensorRT dependencies, install only the editable Python source. The following minimal build creates the measurement worker, TensorRT backend, and GPT-2 model plugin used by the first example:

python -m pip install --no-deps -e . -C py-only=true
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --target \
trtmc_benchmark_worker trtmc_backend_trt trtmc_model_gpt2 -j

--no-deps prevents pip from replacing the TensorRT stack supplied by the development environment. Do not use it in an empty environment: provision the supported development container or install the required dependencies first.

The commands above use CMake's default generator, normally Unix Makefiles on Linux. Ninja is optional. To use it, install ninja and add -G Ninja during configuration. CMake stores the generator in the build directory, so use a fresh directory when changing generators. For example, if build was already configured for Ninja on a machine without Ninja:

cmake -S . -B build-make -DCMAKE_BUILD_TYPE=Release
cmake --build build-make --target \
trtmc_benchmark_worker trtmc_backend_trt trtmc_model_gpt2 -j

The source wrapper discovers workers in build, build-make, and build-local, so the first benchmark remains one command:

./scripts/trtmc-bench run --model distilgpt2

Replace trtmc_model_gpt2 when benchmarking a different model family. cmake --build build -j is the simpler alternative when all model plugins are wanted. A packaged native wheel installs trtmc-bench on PATH; the source-tree editable workflow uses the explicit ./scripts/trtmc-bench wrapper shown above. The wheel also carries a build-time snapshot of the repository's canonical MODEL.toml and E2E manifest files, so model names and default cases resolve without a source checkout. The snapshot is copied from those files during packaging rather than maintained as a second catalog.

Bundle resolution and automatic builds

Bundle resolution has one predictable order: an explicit --bundle, a match below --bundle-root, a compatible managed-cache entry, and finally an automatic build. The cache key includes the manifest, resolved build settings, TensorRT version, machine architecture, and GPU target. Request-only changes reuse a bundle; changes that affect engine shape, such as a larger diffusion batch, produce a different cache entry.

The default build settings come from the existing model manifest. For example, distilgpt2 resolves to distilbert/distilgpt2, FP16, and a 256-token KV cache. Build logs, structured build timing, and the resolved command are stored next to the cached bundle and referenced from result.json. They are marked as excluded from performance metrics.

Before building, the benchmark compares the TensorRT ABI declared by the runtime backend beside the measurement worker with the Python builder ABI. It uses a compatible installed Python binding when available and creates a separate cache entry. If no compatible binding exists, it fails before the expensive engine build instead of producing a bundle that cannot be loaded.

Use an existing bundle explicitly when required:

trtmc-bench run --model distilgpt2 --bundle /engines/distilgpt2.bundle

Use --no-build for a strict CI run that must fail when no bundle exists, or --rebuild to replace the compatible entry in the managed cache. --dry-run resolves the planned cache path without downloading a model or building an engine.

Additional resolver and execution controls are:

OptionContract
--bundle-cache PATHOverride the managed bundle-cache root used for compatible automatic builds.
--manifest-root PATHResolve MODEL.toml and E2E benchmark profiles from an alternate catalog root. It applies to both run and list models.
--case NAMESelect a literal named case; repeat to select several. Named cases remain independent and never form a Cartesian product.
--runtime-dir PATHRepeatable directory added to both backend and model-plugin runtime search paths.
--worker PATHUse one explicit trtmc_benchmark_worker executable instead of packaged, source-build, or PATH discovery.
`--telemetry autooff`

Architecture

Benchmark orchestration from catalog resolution through an existing bundle path or managed build to the Python service and C++ worker
Python resolves one concrete case and bundle path, building a managed-cache entry only when needed, then starts the native worker; explicit bundle paths are not shown as compatibility-verified.
Benchmark measurement boundary where native and optimized bundles converge on IPipeline before raw observations, metrics, and reports
Both bundle formats load through trtmc::load and converge on the public IPipeline operation; the worker records raw timing while Python computes task-aware metrics and reports.

Python owns configuration, matrix expansion, orchestration, metrics, and reporting. The native worker owns the timed loop and calls the same public C++ pipeline API as an application. It loads the bundle with trtmc::load, which either follows native runtime_strategy dispatch through model and backend DSOs or recognizes optimized_runtime.json and loads the exact embedded implementation path. Both paths return IPipeline, so the task operation and measurement boundary stay the same. Model family, task semantics, runtime implementation, and public operation are separate extension points:

ChangeBenchmark work
New weight/profile in a known family and taskAdd the normal manifest and MODEL.toml.test_manifests entry; no benchmark code
New native family using a known task_strategyAdd its normal runtime model plugin and manifest; no benchmark code
New optimized implementation/profile for a known model and operationAdd the family-owned IMPLEMENTATION.toml, exact profile, qualification evidence, and normal E2E/catalog ownership; no benchmark code
New task using an existing public IPipeline operationAdd one task adapter that translates its testcase contract
New public pipeline capabilityAdd an operation metric contract and one native runner, then map task adapters to it

The benchmark never registers individual families or runtime_strategy values. For example, a new Wan video family using diffusion_media_generation and generate_image is discovered automatically, and a new native or optimized decoder implementation behind generate remains invisible to the benchmark layer. This is the same rule for source checkouts and the catalog snapshot packaged in a wheel.

Run several models

Repeat --model to run a batch. Each missing bundle is built once and then reused from the managed cache:

trtmc-bench run \
--model distilgpt2 \
--model flux-schnell-l0 \
--model chronos-bolt-tiny-official

Use one YAML file when models need different cases or measurement counts:

trtmc-bench run examples/trtmc_bench.yaml -o results/current

An explicit -o/--output is a replaceable result slot. If that directory already exists, the command writes the new run to a sibling staging directory and replaces the complete old result after the new report is ready. An exception while producing the staged run leaves the previous result intact. The command never merges new artifacts with an older run. For safety, it only replaces an empty directory or a directory containing a recognized trtmc-bench result.json; unrelated directories and symlinks are rejected. Omit -o to create a new timestamped result directory for every invocation.

The YAML reuses the repository's existing model names, manifests, task_strategy, runtime_strategy, testcase inputs, and .bundle bundle names. It does not introduce a second model catalog.

Combine separate model runs in one report

Separate CLI invocations can share one collection directory while retaining an independent result directory per model:

trtmc-bench run --model distilgpt2 \
-o result-20260721/distilgpt2
trtmc-bench run --model bart-base \
-o result-20260721/bart-base
trtmc-bench run --model flux-schnell-l0 \
-o result-20260721/flux-schnell-l0

Recursively discover their trtmc.benchmark-run/v1 results and build one collection report in place:

trtmc-bench report result-20260721

This writes result-20260721/report.json and result-20260721/report.html. The per-model result.json files remain the authoritative evidence and are not rewritten or copied. Add another model subdirectory and run the same report command again to atomically rebuild the summary; no append flag or report database is required.

Several result roots can be combined when an explicit report output is given:

trtmc-bench report results/gb300 results/h100 -o reports/combined

Cases, sweeps, and batches

A named case is one complete request. Two cases are two runs; their fields are never combined:

cases:
- name: fast
set:
request.num_inference_steps: 4
- name: standard
set:
request.num_inference_steps: 20

Only --sweep requests a Cartesian product:

trtmc-bench run --model flux-schnell-l0 \
--sweep request.batch_size=1,2 \
--sweep request.num_inference_steps=4,20

Batch behavior follows the public pipeline capability. Diffusion uses generate_image_batch, so request.batch_size measures one batch call and reports generated samples/s. Built-in batch profiles preserve their individual prompts and seeds rather than cloning one scalar request. Operations without a public batch API reject batch sizes above one instead of silently simulating a batch with sequential requests.

Defaults and metrics

The first existing E2E testcase supplies the default workload. Operation defaults are 5 warmups and 50 timed calls for text generation, 1 and 5 for diffusion, and 50 and 500 for encoder/neural-operator workloads. Override them without editing a plan file:

trtmc-bench run --model distilgpt2 \
--warmup 10 --iterations 100 --set request.max_new_tokens=32

Every operation reports wall-latency min/mean/p50/p95/max and request/s. Task-aware reducers additionally report:

OperationAdditional metrics
Text and vision-language generationoutput token/s and runtime-reported prefill/decode stages
Image diffusionimage/s and seconds/image
Video diffusionvideo/s, frame/s, and seconds/video
Audio generationgenerated audio seconds/s, sample/s, and real-time factor
Speech-to-speechconsumed and generated audio seconds/s, plus input real-time factor
Segmentationimage/s, mask/s where applicable, and mask pixel/s
Classification and object detectionimage/s
Rerankingdocument/s
Encoder/embeddingembedding vector/s and element/s
Speech transcriptionaudio seconds/s, real-time factor, output token/s, and streaming first-partial latency
Neural operatorwindow/s and forecast element/s

Text requests preserve the selected testcase's sampling parameters, text generation mode, and chat-template contract. Greedy cases without an explicit seed use the public pipeline default of -1; seeded sampling cases retain their declared seed.

Some metrics cannot share a valid measurement boundary. For example, a long profiler capture perturbs baseline latency, and model loading is not request latency. Keep those as separate runs/artifacts rather than mixing them into the baseline result.

Relationship to reference validation and NVIDIA tools

trtmc-validate answers whether model outputs satisfy a reference-consistency contract using datasets, references, and comparators. trtmc-bench answers how the runtime performs for a resolved workload. A built-in testcase makes a benchmark runnable, but it is not proof of task quality; the result explicitly records task_quality_evaluated: false.

Baseline runs optionally sample nvidia-smi outside the timed call. Use Nsight Systems or existing profiling tools as a separate diagnostic pass when a baseline exposes a bottleneck. Compute Sanitizer/memcheck is a correctness diagnostic and should not be part of a normal performance run.

Add a model, family, or task type

Keep model knowledge in the existing model manifest and runtime plugin. The benchmark reads MODEL.toml.test_manifests; there is no benchmark-owned model allowlist.

For a model or family on an existing task, add the normal model implementation, manifest, and MODEL.toml entry, then verify that trtmc-bench list models shows ready. No import or if family == ... branch belongs in benchmark code.

For a genuinely new task contract, add its testcase translator to benchmark/task_adapters.py. Reuse an existing operation whenever the task can be expressed by an existing public IPipeline call. Only a genuinely new public capability adds a metric declaration in benchmark/operations.py and a timed runner in trtmc_benchmark_worker.cpp. The registry validates task-to- operation references at import time. Unsupported task strategies and malformed default testcases remain visible with a reason in list models and fail closed when selected.

Artifact inputs follow the existing manifest-relative paths. For example, speech transcription resolves test_input_audio once, hashes it into the case identity, decodes WAV outside the timed region, and measures only offline or streaming public pipeline calls. Image-conditioned generation resolves and hashes test_image the same way. Packaged default audio, image, and FP8 scale assets are copied from the canonical E2E model directory with the catalog snapshot.