cuML Benchmark Suite#
This document describes how to run the cuML benchmark suite. The tools support two execution modes:
Full mode:
python -m cuml.benchmark— requires cuML and GPU dependencies; runs GPU + CPU benchmarks.Standalone mode:
python run_benchmarks.py— works without cuML installed (e.g. from the repo); runs CPU-only benchmarks when cuML is not available.
The benchmark runner also supports YAML manifests. A manifest is the declarative source of truth for a benchmark suite, while CLI flags can be used to filter or override selected fields at runtime.
Contents#
Running the benchmarks#
Full mode (cuML installed)#
When cuML is installed, use the module entry point:
python -m cuml.benchmark --dataset classification LogisticRegression --csv results.csv
If a GPU is available, this runs both GPU (cuML) and CPU implementations and reports speedup.
Use --output to write the canonical JSON benchmark artifact:
python -m cuml.benchmark --dataset classification LogisticRegression --output results.json
To run a YAML-defined suite:
python -m cuml.benchmark \
--config python/cuml/cuml/benchmark/configs/single_gpu.yaml \
--profile default \
--backends gpu \
--output results.json
To run the tiny harness-validation manifest:
python -m cuml.benchmark \
--config python/cuml/cuml/benchmark/configs/test.yaml \
--profile default \
--backends cpu
Standalone mode (from the repository)#
From the python/cuml/cuml/benchmark/ directory, you can run without installing cuML:
cd python/cuml/cuml/benchmark/
python run_benchmarks.py --dataset classification LogisticRegression
When cuML is not installed, only CPU (e.g. scikit-learn) benchmarks run. The same script works with cuML installed for full GPU+CPU runs.
Standalone mode also supports YAML manifests:
cd python/cuml/cuml/benchmark/
python run_benchmarks.py \
--config configs/test.yaml \
--profile default \
--backends cpu
The following Python packages are required for standalone mode:
pip install numpy pandas scikit-learn scipy
YAML manifests require PyYAML and msgspec. If either is not installed, the benchmark CLI will print install instructions. You can install them with:
conda install -c conda-forge pyyaml msgspec
# or
python -m pip install pyyaml msgspec
CPU-only mode#
To run only CPU benchmarks (e.g. when no GPU or to compare CPU implementations only):
python -m cuml.benchmark --skip-gpu --dataset classification LogisticRegression
# or: python run_benchmarks.py --skip-gpu --dataset classification LogisticRegression
GPU-only mode#
To run only GPU (cuML) benchmarks:
python -m cuml.benchmark --skip-cpu --dataset classification LogisticRegression
Note: Do not use both --skip-gpu and --skip-cpu; that would run no benchmarks. The script will report an error.
Common options#
| Option | Description |
|---|---|
--dataset |
Dataset name: e.g. blobs, classification, regression, higgs. Use --print-datasets to list all. |
--config |
Path to a YAML benchmark manifest. |
--profile |
Named profile to select from a YAML benchmark manifest. |
--backends |
Comma-separated backends to run (cpu, gpu). |
--skip-gpu |
Skip GPU/cuML benchmarks (CPU only); compatibility shortcut. |
--skip-cpu |
Skip CPU benchmarks (GPU/cuML only); compatibility shortcut. |
--output FILE |
Save the canonical JSON artifact with metadata and grouped benchmark results. |
--csv [FILE] |
Save a legacy flat CSV export. |
--min-rows, --max-rows, --num-sizes |
Control sample sizes for scaling benchmarks. |
--input-dimensions |
Feature dimensions to test (e.g. 16 256). |
--metadata-override KEY=VALUE |
Override a JSON metadata field using dotted paths, e.g. hardware.label=test-node. May be repeated. |
--print-algorithms |
List available algorithms and exit. |
--print-datasets |
List available datasets and exit. |
--print-status |
Print GPU/cuML availability and exit. |
Examples#
Run a single algorithm at default sizes:
python -m cuml.benchmark --dataset classification LogisticRegression
Run with parameter sweeps and save a JSON artifact:
python -m cuml.benchmark --dataset classification \
--max-rows 100000 --min-rows 10000 \
--dataset-param-sweep n_classes=[2,4] \
--cuml-param-sweep n_estimators=[10,100] \
--output results.json \
RandomForestClassifier
Run multiple algorithms (use -- before algorithm names when passing --input-dimensions):
python -m cuml.benchmark --dataset blobs --num-sizes 1 \
--input-dimensions 16 256 -- \
DBSCAN KMeans PCA UMAP
Run with a real dataset at its default size:
python -m cuml.benchmark --dataset higgs --default-size \
RandomForestClassifier LogisticRegression
Run a manifest but restrict execution to one algorithm:
python -m cuml.benchmark \
--config python/cuml/cuml/benchmark/configs/single_gpu.yaml \
--profile default \
LogisticRegression
Run a manifest and override only the row count from the CLI:
python -m cuml.benchmark \
--config python/cuml/cuml/benchmark/configs/test.yaml \
--profile default \
--num-rows 500 \
--backends cpu
In config mode, only explicitly provided CLI flags override the manifest. Parser defaults do not silently replace YAML values.
Output#
The recommended output artifact is a single JSON file written with --output:
python -m cuml.benchmark \
--config python/cuml/cuml/benchmark/configs/single_gpu.yaml \
--profile default \
--backends gpu \
--output results.json
The JSON file contains:
metadata: run context, command, Python/platform info, cuML/git identity, GPU availability, hardware details, and config/profile provenanceresults: grouped benchmark results, with raw backend timings and statuses under each logical benchmark variation
For lab or CI machines where detected hardware names need a stable display label, use repeated metadata overrides:
python -m cuml.benchmark \
--config python/cuml/cuml/benchmark/configs/single_gpu.yaml \
--profile default \
--backends gpu \
--output results.json \
--metadata-override hardware.label=dgx-ci-01 \
--metadata-override hardware.gpu.effective.name=H100
Example shape:
{
"metadata": {
"result_schema_version": 1,
"config": {
"path": "python/cuml/cuml/benchmark/configs/single_gpu.yaml",
"profile": "default"
}
},
"results": [
{
"benchmark_id": "logreg_fit_narrow_default",
"algorithm": "LogisticRegression",
"shape": {
"rows": 84000000,
"features": 16,
"estimated_input_size_gb": 5.376
},
"params": {
"declared": {},
"effective": {
"gpu": {},
"cpu": null
}
},
"backends": {
"gpu": {
"status": "success",
"time_sec": 0.91,
"accuracy": 0.995
}
}
}
]
}
Derived comparisons such as GPU speedup are intentionally not stored in JSON. They are computed from raw backend timings for terminal display and downstream analysis.
JSON result schema#
The JSON artifact is versioned separately from the YAML manifest schema. The current output schema is identified by:
{
"metadata": {
"result_schema_version": 1
}
}
Top-level fields:
results: list of benchmark result recordsmetadata: run-level provenance and environment information
Each entry in results represents one logical benchmark variation:
benchmark_id: stable ID from YAML configs, ornullfor ad hoc CLI runsalgorithm: benchmark algorithm namedataset: dataset generator nameoperation: operation name when defined by YAML, otherwisenullshape: row/feature dimensions and estimated dense input sizedata: input type, dtype, and repetition countparams.declared: parameters explicitly supplied through YAML or CLI sweepsparams.effective: estimator parameters reported byget_params()for each backend when availablebackends: backend-specific result records keyed bycpuand/orgpu
Backend result records use these fields:
status:successorskippedtime_sec: elapsed benchmark time in seconds, present for successful runsaccuracy: accuracy or score metric when availablereason: explanation for skipped backends, such asGPU unavailable
Run metadata includes:
command: argv and current working directoryhost: hostname captured withplatform.node()python: Python executable, version, and platformcuml: cuML version, current Git repo path, SHA, and dirty-state flag when availableruntime: GPU/cuML availability statusconfig: selected config path, profile, and backend overridehardware: detected CPU/GPU/OS/memory metadata plus optional effective labels from metadata overridesenvironment: package snapshot fromconda listorpip list
The environment package snapshot is intentionally compact. Conda entries include only name, version, build, and channel; pip entries include only name and version.
Terminal output is a concise progress table, for example:
progress algorithm shape data gpu_time cpu_time details
--------------------------------------------------------------------------------------------
[1/96] LogisticRegression 84.0M x 16 ~5.38 GB 0.91s - acc=0.9950
When multiple backends are present, timings are grouped on one row:
[1/96] LogisticRegression 50.0K x 16 ~0.00 GB 12.3ms 18.5ms gpu_speedup=1.50x acc=0.9944
CSV output remains available through --csv, but it is a flat compatibility export. Prefer JSON for regression tracking and reproducibility.
Adding algorithm coverage#
New algorithms should include benchmark coverage for every applicable
implementation layer. Add a Python estimator to the registry in
python/cuml/cuml/benchmark/algorithms.py and add or update the appropriate
manifest. For a new C++ algorithm, add a Google Benchmark case under
cpp/bench/sg and list its source in cpp/bench/CMakeLists.txt.
Use benchmarks and profiling for performance-sensitive changes to establish baselines and investigate bottlenecks, regressions, and unexpected memory behavior.
YAML manifests#
A manifest defines a benchmark suite, default settings for the suite, and the individual benchmark entries to run.
The repository currently includes:
python/cuml/cuml/benchmark/configs/test.yaml: tiny smoke/harness-validation suitepython/cuml/cuml/benchmark/configs/single_gpu.yaml: canonical single-GPU regression suite with multiple profiles
Top-level schema#
Each manifest is a YAML mapping with these top-level fields:
version: integer schema version. Currently1.suite: metadata about the suite.profiles: optional named selectors that include benchmarks by tag.defaults: optional fields applied to every benchmark unless overridden.benchmarks: list of benchmark entries.
Example:
version: 1
suite:
name: single_gpu
tier: single_gpu
description: Canonical single-GPU regression manifest
profiles:
default:
include_tags: [default]
defaults:
input_type: cupy
dtype: fp32
n_reps: 3
random_state: 42
test_split: 0.1
backends: [cpu, gpu]
raise_on_error: true
benchmarks:
- id: logreg_fit_small
algorithm: LogisticRegression
dataset: classification
operation: fit
rows: [100000]
features: [16]
tags: [default, linear, classification]
suite#
suite must contain:
name: short suite identifiertier: suite category such astestorsingle_gpudescription: human-readable description
profiles#
Profiles allow a single manifest to define multiple run surfaces such as default or nightly.
In single_gpu.yaml, the manifest is organized along two axes:
width class:
narrow,medium,wideruntime tier:
default,nightly
The width class changes the feature shape of the workload. The runtime tier changes only the row count. Every algorithm should therefore appear as a full width x tier grid.
Each profile currently has:
include_tags: list of tags; a benchmark is included when it shares at least one tag with the profile
Example:
profiles:
default:
include_tags: [default]
nightly:
include_tags: [nightly]
This means:
defaultincludesnarrow,medium, andwideworkloads for every algorithm at the smallest row countsnightlyincludes the samenarrow,medium, andwideworkloads with the largest row counts
Example:
- id: logreg_fit_narrow_default
algorithm: LogisticRegression
dataset: classification
operation: fit
rows: [500000]
features: [16]
tags: [default, narrow, linear]
- id: logreg_fit_narrow_nightly
algorithm: LogisticRegression
dataset: classification
operation: fit
rows: [1500000]
features: [16]
tags: [nightly, narrow, linear]
- id: logreg_fit_wide_default
algorithm: LogisticRegression
dataset: classification
operation: fit
rows: [150000]
features: [512]
tags: [default, wide, linear]
Compact variants#
To avoid repeating the same benchmark definition for every width x tier combination, a benchmark entry can define variants. This expands one compact entry into multiple resolved benchmark entries while preserving the same runtime behavior.
Example:
- id: logreg_fit
algorithm: LogisticRegression
dataset: classification
operation: fit
tags: [linear, classification]
variants:
narrow:
features: [16]
tiers:
default:
rows: [500000]
nightly:
rows: [1500000]
wide:
features: [512]
tiers:
default:
rows: [150000]
How this works:
each key under
variantsbecomes a width-class suffix and tag, such asnarroworwideeach key under
tiersbecomes a runtime-tier suffix and tag, such asdefaultornightlythe example above expands to benchmark IDs like
logreg_fit_narrow_default,logreg_fit_narrow_nightly, andlogreg_fit_wide_defaultfields from the outer benchmark entry are shared across all expanded entries
width-specific fields such as
featureslive under the varianttier-specific fields such as
rowslive under the tier
Flat benchmark entries using explicit rows, features, or shapes are still supported. variants is just a more compact way to describe repeated width/tier grids.
defaults#
defaults can provide common values for benchmark entries, including:
datasetinput_typedtypen_repsrandom_statetest_splitbackends: execution backends to run, selected fromcpuandgpuraise_on_errorenabledtagsparams,cuml_params,cpu_params,dataset_paramsparam_grid,cuml_param_grid,cpu_param_grid,dataset_param_gridcomparisonmetadata
Benchmark entries override scalar defaults and merge dictionary-style fields.
For GPU-oriented suites such as single_gpu.yaml, prefer a GPU-native input_type such as cupy or cudf. This avoids benchmarking large host/device or cuDF-to-NumPy conversion costs when the goal is to measure the estimator itself on GPU inputs.
Benchmark entry schema#
Each entry in benchmarks must define:
algorithm: algorithm name from the benchmark registry
It can also define:
id: stable benchmark identifier; defaults toalgorithmif omitteddataset: dataset generator name such asclassification,regression, orblobsoperation: benchmarked operation such asfit,predict,transform,fit_transform,fit_predict,fit_kneighbors, orkneighborsrowsandfeatures: lists used as a Cartesian productshapes: explicit paired(rows, features)combinations when you do not want a Cartesian productdefault_size: use the dataset’s natural default size instead of explicit dimensionsvariants: compact expansion for repeated width/tier familiesparams: shared estimator parameterscuml_params: GPU-only estimator parameterscpu_params: CPU-only estimator parametersdataset_params: dataset generator parametersparam_grid,cuml_param_grid,cpu_param_grid,dataset_param_grid: parameter sweeps expanded as Cartesian productsn_reps,input_type,dtype,random_state,test_split,backends,raise_on_errortags: labels used by profilesenabled,skip_reasoncomparison,metadata
Choosing dimensions#
Use rows plus features when you want the Cartesian product:
- id: pca_fit_small
algorithm: PCA
dataset: blobs
operation: fit
rows: [100000]
features: [32, 256]
This runs (100000, 32) and (100000, 256).
Use shapes when you want explicit pairs instead:
- id: paired_shapes_example
algorithm: LogisticRegression
dataset: classification
operation: fit
shapes:
- rows: 10000
features: 32
- rows: 50000
features: 256
This runs only (10000, 32) and (50000, 256).
Creating your own manifest#
Start from test.yaml if you want a tiny suite, or from single_gpu.yaml if you want a richer example with profiles.
This minimal custom manifest is a good starting point:
version: 1
suite:
name: my_benchmarks
tier: custom
description: Example custom benchmark suite
profiles:
default:
include_tags: [default]
nightly:
include_tags: [nightly]
defaults:
input_type: cupy
dtype: fp32
n_reps: 2
random_state: 42
test_split: 0.1
backends: [cpu, gpu]
raise_on_error: true
benchmarks:
- id: my_logreg_fit
algorithm: LogisticRegression
dataset: classification
operation: fit
tags: [linear]
variants:
narrow:
features: [16]
tiers:
default:
rows: [500000]
nightly:
rows: [1500000]
wide:
features: [512]
tiers:
default:
rows: [150000]
- id: my_kmeans_fitpredict
algorithm: KMeans
dataset: blobs
operation: fit_predict
dataset_params:
centers: 8
tags: [clustering]
variants:
medium:
features: [128]
tiers:
default:
rows: [50000]
Save it anywhere and run it with:
python -m cuml.benchmark --config /path/to/my_benchmarks.yaml --profile default
If you also define a nightly profile, keep the same algorithm and width-class entries, and increase only rows for the companion nightly tiers.
CLI overrides in config mode#
When --config is provided, the manifest remains the primary definition of the benchmark suite. CLI flags can still override selected fields for ad hoc runs.
Common useful overrides are:
--profilepositional algorithm names to restrict which manifest entries run
--backends,--skip-gpu, or--skip-cpu--num-rows,--num-features,--input-dimensions,--default-size--n-reps,--dtype,--input-type,--test-split--param-sweep,--cuml-param-sweep,--cpu-param-sweep,--dataset-param-sweep
Example:
python -m cuml.benchmark \
--config python/cuml/cuml/benchmark/configs/single_gpu.yaml \
--profile default \
--backends cpu \
--num-rows 50000 \
LogisticRegression
This keeps the manifest-selected benchmark entry but overrides the row count and execution mode for that run.
Input types#
With GPU/cuML you can use --input-type such as numpy, pandas, cupy, or cudf. Without GPU, only numpy and pandas are valid; the script will warn and switch to numpy if needed.
Manifest structural validation#
The benchmark manifest structure is validated by typed msgspec models in
python/cuml/cuml/benchmark/config.py. The prose in this document explains the
fields and gives examples, while the msgspec models provide the structural
contract for parsing YAML manifests.
To generate a JSON Schema from the msgspec manifest model:
python - <<'PY'
import json
from cuml.benchmark.config import benchmark_manifest_json_schema
print(json.dumps(benchmark_manifest_json_schema(), indent=2))
PY
Some semantic checks, such as post-default required fields, unknown algorithm
names, and compact variants conflicts, are still enforced by custom Python
validation after the structural conversion step.