Hardware Profiles
Hardware Profiles#
A profile is a single YAML file describing one hardware configuration: the machines and GPUs it runs on, the serving topology, the load to drive, and the profiler coverage to expect back. The filename stem is the profile name.
Profiles exist because the profiler OTEL suite has to run unchanged against everything from a two-GPU CI machine to a ninety-six-GPU disaggregated cluster. The load, the timeouts and the number of reporting GPUs differ by orders of magnitude between them, and none of that belongs in the test code.
They live in tests/suites/profiler_otel/profiles/.
Which profiles ship here#
Only default — the configuration continuous integration runs, matching
deployments/docker-compose-vllm.yml. It is selected when no profile is named, so nothing has
to pass arguments for the common case.
Other tiers are maintained alongside the environment that runs them, together with that
environment's own deployment configuration, and are supplied at run time with --profile-dir.
Keeping them out of this repository avoids publishing internal cluster topology, and lets each
environment change its own configuration without a change here.
Selecting a profile#
uv run pytest --workload-profile internal --profile-dir /path/to/your/profiles
--profile-dir is repeatable and searched after the built-in directory, so a supplied profile
may also override one shipped here. An unknown name fails immediately and lists the names it
did find.
Paths inside a profile resolve relative to that profile's own directory, so a profile and the deployment configuration it refers to can be moved into another repository together.
Note
Profile selection is a command-line option rather than an environment variable, so a value left set in a shell cannot silently change a later run.
Schema#
| Key | Meaning |
|---|---|
description |
One line, printed at the start of a run |
hardware |
machines, gpus_per_machine, sku. Documentation, and the upper bound coverage is validated against |
serving |
mode (aggregated or disaggregated), model, and the parallelism. A disaggregated profile adds prefill and decode blocks |
deployment |
Exactly one of compose_file (this suite brings the stack up) or external: true (stood up out of band) |
endpoint |
Either host and port, or base_url for a single frontend |
otel_endpoint |
Where profiler telemetry is sent |
coverage |
The hosts, gpus and communicators the profiler must report from |
timeouts |
workload, metrics_available and quiesce, in seconds |
expected_metrics |
Names a metric list defined in the suite's conftest.py |
benchmark_options |
Passed verbatim to the benchmark workload |
Benchmark options#
Keys in benchmark_options become benchmark_serving.py flags by the usual conventions:
num_prompts: 64 becomes --num-prompts 64, ignore_eos: true becomes --ignore-eos, and a
value of None omits the flag entirely. Nothing enumerates the arguments that script accepts,
so any flag it supports can be set from a profile without a code change.
Omitting a flag is how a profile drops a default it does not want. A cluster reached through a
single frontend sets base_url and clears the host and port:
Coverage#
coverage is what the assertions check: how many hosts, GPUs and communicators must actually
report telemetry during a run.
It is declared rather than calculated from hardware. On a disaggregated cluster the prefill
and decode pools can differ in size, and a machine may host only a frontend or a router with no
GPUs doing work at all, so machines multiplied by gpus_per_machine is the wrong answer. The
hardware block stays as documentation and as a sanity bound.
communicators matters because prefill and decode form separate NCCL communicators. A total
summed across every series rises when only one pool is alive, so requiring more than one is what
catches a dead decode pool.
Reading coverage from a running cluster#
Rather than predicting the numbers, run once and read them back. The profiler labels every
series with hostname, gpu_pci_bus_id, gpu_uuid, rank, local_rank and communicator:
sum by (hostname) (nccl_profiler_collective_bytes_total)
sum by (hostname, gpu_pci_bus_id) (nccl_profiler_collective_bytes_total)
sum by (communicator) (nccl_profiler_collective_bytes_total)
Warning
Count GPUs by gpu_pci_bus_id (or gpu_uuid), never by rank. A rank is a position
within one communicator, and a deployment builds several -- a tensor-parallel group
spanning two machines numbers its ranks 0..7 across them while a node-local group numbers
its own 0..3 on each, so one GPU reports under more than one rank. Grouping by
(hostname, rank) on an eight-GPU cluster with three communicators returns 12.
local_rank is no better: the profiler assigns it from the rank for every collective.
Cross-node traffic needs a pool that spans machines#
A worker pool pinned to one machine produces no cross-node NCCL. Every collective stays inside the node, and the only traffic between machines is the key-value cache transfer, which the transfer backend handles outside NCCL and the profiler therefore never observes.
A profile spanning several machines whose pools each sit on one machine will report more hosts and no new NCCL behaviour. Use wide expert parallelism, or a tensor-parallel group that straddles machines, when cross-node traffic is the point of the tier.
Externally deployed clusters#
A profile with external: true names no deployment configuration; the serving cluster and the
observability stack are expected to be running already.
Warning
Every machine in a multi-machine tier must reach the same collector. The address used by the
single-machine configuration is the Docker bridge gateway, which is reachable only from the
host it runs on. A profile that leaves it unchanged reports no telemetry at all, which looks
exactly like a broken profiler. Confirm otel_endpoint is reachable from every machine
before reading anything into a failing run.
Querying profiles from other tooling#
If your environment drives its own deployment, these avoid re-implementing profile parsing and path resolution:
python -m profiler_otel.profiles list [--profile-dir DIR ...]
python -m profiler_otel.profiles compose-file NAME [--profile-dir DIR ...]
compose-file prints an absolute path, or nothing at all with a zero exit status when the
profile is externally deployed — so empty output means there is nothing to bring up. An unknown
profile exits non-zero and writes to standard error, so it cannot be mistaken for that case.
Validating profiles#
Profiles are parsed into pydantic models, so a malformed one fails when it loads rather than part-way through a run. Validation needs no hardware:
uv run pytest profiler_otel/test_profiles.py --profile-dir /path/to/your/profiles
Errors name the profile and the field path, for example
coverage.gpus: 99 exceeds the 16 GPUs the hardware provides. What is checked:
- Unknown keys are rejected. A misspelled key would otherwise be ignored and leave the setting it was meant to change at its default.
- Types and ranges. Counts and timeouts must be positive; a port must be a valid port.
coveragemust be satisfiable by the machines and GPUs inhardware.deploymentnames exactly one source,compose_fileorexternal.endpointis one form or the other, a host and port or abase_url.- Worker pools match the serving mode. A disaggregated profile declares both
prefillanddecode; an aggregated one declares neither. expected_metricsnames a list that exists.- The compose path resolves to a file that is really there, relative to the profile.
Running this against a directory of profiles kept elsewhere gives that directory the same checks, in a repository whose own tests may never run this suite.
tests/suites/profiler_otel/testdata/profiles/ holds synthetic profiles used only by those
tests. They cover shapes this repository supports but no longer ships a profile for, such as
external deployments and multi-machine coverage. They are deliberately not on the default search
path and cannot be selected by an ordinary run.