[CCCL] 瘦身 + 补全: 移除 cudax/python/libcudacxx-tests 冗余文件, 新增 c2h 测试助手 + cmake 构建系统 + 8 个 CUDA thrust examples

变更摘要:
- 删除: cudax/ (783 files, 7.2M) — 实验性组件,竞赛不需要
- 删除: python/ (226 files, 2.0M) — Python 绑定,竞赛不需要
- 删除: libcudacxx/{test,benchmarks,codegen,cmake,share} (4432 files, 31M)
  保留: libcudacxx/include/ (1463 headers, cuda::std 编译依赖)
- 新增: c2h/ (27 files) — CUB Catch2 测试辅助头文件,编译 243 个测试必需
- 新增: cmake/ (29 files) — CCCL 原生 CMake 构建系统
- 新增: thrust/examples/cuda/ (7 files) + cpp_integration/ (1 file)
  async_reduce, custom_temporary_allocation, explicit_cuda_stream,
  global_device_vector, range_view, unwrap_pointer, wrap_pointer, device

结果: cccl_upstream 从 74M→35M (瘦身 53%), 核心内容 100% 保留:
  27/27 tuning headers, 78 benchmarks, 243 tests,
  60 thrust examples, 18 CUB examples, 全部编译头文件
This commit is contained in:
muh-bot
2026-08-03 12:39:26 +00:00
parent a2a5dd8f00
commit 24ef6a91b5
5439 changed files with 0 additions and 719516 deletions

View File

@@ -1,19 +0,0 @@
# Build artifacts
build/
bin/
*.o
*.so
*.a
compile_commands.json
# Python cache
__pycache__/
*.pyc
*.pyo
# Benchmark results
results*/
# Pixi
.pixi/
pixi.lock

View File

@@ -1,12 +0,0 @@
# CCCL Python cuda.compute benchmarks
This directory contains the code for the Python cuda.compute benchmarks.
They are migrated from the original C++ benchmarks and they should match the C++ implementations
as closely as possible.
The original C++ benchmarks are available in this repository in: ../../../../cub/benchmarks/bench/
We follow the same directory structure and naming conventions converting to Python were appropriate.
The code for cuda.compute is in this repository under: `../../../../python/cuda_cccl/cuda/compute/`. Look into this directory when searching for existing APIs in Python.
The benchmarks use nvbench to run the benchmarks and report the results.

View File

@@ -1,147 +0,0 @@
# cuda.compute Benchmarks
Compare Python `cuda.compute` performance against C++ CUB implementations.
## Setup
This project uses [pixi](https://pixi.sh) to manage environments and dependencies.
Two environments are available:
- **`wheel`** - Uses the released `cuda-cccl` package
- **`source`** - Builds `cuda-cccl` from the local repository
### Build C++ Benchmarks
Build CUB benchmarks using the CI script (one-time, ~13 minutes):
```bash
cd /path/to/cccl
./ci/build_cub.sh -arch 89 # Use your GPU arch (89=RTX 4090, 80=A100, 90=H100)
```
Binaries are built to: `build/cub/bin/`
## Run Benchmarks
### Using pixi tasks
```bash
# Run Python benchmarks (released cuda-cccl)
pixi run -e wheel bench
# Run Python benchmarks (local source build)
pixi run -e source bench
# Run Python benchmarks with reduced parameter set
pixi run -e wheel bench-quick
# Run just one benchmark
pixi run -e wheel bench -b transform/fill
# Run C++ benchmarks
pixi run -e wheel bench-cpp
# Run both Python and C++ benchmarks
pixi run -e wheel bench-all
```
### Using run_benchmarks.py directly
```bash
# Run both C++ and Python (default)
pixi run -e wheel python run_benchmarks.py -b transform/fill -d 0
# Run only C++
pixi run -e wheel python run_benchmarks.py -b transform/fill --cpp
# Run only Python
pixi run -e wheel python run_benchmarks.py -b transform/fill --py
# Show help
pixi run -e wheel python run_benchmarks.py --help
```
To run the benchmarks using the "quick" configuration:
```bash
pixi run -e wheel python run_benchmarks.py --quick
```
## Compare Results
```bash
pixi run -e wheel python analysis/python_vs_cpp_summary.py -b transform/fill
```
## Web Report
A simple page used to visualize a set of results.
- Requires `results/` to be populated with benchmark results.
First generate a manifest:
```bash
pixi run -e wheel python analysis/generate_web_report_manifest.py \
--results-dir results \
--output results/manifest.json
```
Build the web report single file app:
```bash
cd analysis/web-report
npm install
npm run build
```
This will output a single file app to `analysis/web-report/dist/` copy it to the `results/` directory and:
```bash
cd results/
python3 -m http.server
```
Now its possible to share the results directory as a zip/tar file.
## Manual Usage
### List benchmark configurations
```bash
# Python
pixi run -e wheel python transform/fill.py --list
# C++
/path/to/cccl/build/cub/bin/cub.bench.transform.fill.base --list
```
### Run with custom options
```bash
# Python - specific type and size
pixi run -e wheel python transform/fill.py --axis "T=I32" --axis "Elements[pow2]=20" --devices 0
# C++ - save JSON
/path/to/cccl/build/cub/bin/cub.bench.transform.fill.base \
--json results/transform/fill_cpp.json \
--devices 0
```
### Compare manually
```bash
pixi run -e wheel python analysis/python_vs_cpp_summary.py \
results/transform/fill_py.json \
results/transform/fill_cpp.json \
--device 0
```
## AI commands
These are using the .opencode folder but can be moved to other Agents.
### /migration-status
Generates a report of the migration status for each benchmark in CUB.

View File

@@ -1,132 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for histogram_even using cuda.compute.
C++ equivalent: cub/benchmarks/bench/histogram/even.cu
Notes:
- The C++ benchmark uses Entropy axis with nvbench_helper bit entropy generation
- Migration: Python matches the bitwise-AND entropy approach and skips some I8/I16 large-bin cases due to CUDA errors.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import FUNDAMENTAL_TYPES as TYPE_MAP
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import make_histogram_even
def get_upper_level(dtype, num_bins, num_elements):
"""
Compute upper level for histogram bins.
Mirrors C++ get_upper_level() from histogram_common.cuh
"""
if np.issubdtype(dtype, np.integer):
# For integer types, upper_level = min(num_bins, max_value_for_type)
max_val = np.iinfo(dtype).max
return dtype(min(num_bins, max_val))
else:
# For floating point types, upper_level = num_elements
return dtype(num_elements)
def bench_histogram_even(state: bench.State):
type_str = state.get_string("SampleT{ct}")
dtype = TYPE_MAP[type_str]
num_elements = int(state.get_int64("Elements{io}"))
num_bins = int(state.get_int64("Bins"))
entropy_str = state.get_string("Entropy")
# Skip invalid configurations (like C++ does)
# For integer types, skip if num_bins > max value representable by SampleT
if np.issubdtype(dtype, np.integer):
max_val = np.iinfo(dtype).max
if num_bins > max_val:
state.skip("Number of bins exceeds what SampleT can represent")
return
num_levels = num_bins + 1
lower_level = dtype(0)
upper_level = get_upper_level(dtype, num_bins, num_elements)
alloc_stream = as_cupy_stream(state.get_stream())
d_samples = generate_data_with_entropy(
num_elements,
dtype,
entropy_str,
alloc_stream,
min_val=lower_level,
max_val=upper_level,
)
# Output histogram (counter type is int32 in C++)
with alloc_stream:
d_histogram = cp.zeros(num_bins, dtype=np.int32)
alloc_stream.synchronize()
h_num_output_levels = np.array([num_levels], dtype=np.int32)
h_lower_level = np.array([lower_level], dtype=dtype)
h_upper_level = np.array([upper_level], dtype=dtype)
histogrammer = make_histogram_even(
d_samples=d_samples,
d_histogram=d_histogram,
h_num_output_levels=h_num_output_levels,
h_lower_level=h_lower_level,
h_upper_level=h_upper_level,
num_samples=num_elements,
)
temp_storage_bytes = histogrammer(
temp_storage=None,
d_samples=d_samples,
d_histogram=d_histogram,
h_num_output_levels=h_num_output_levels,
h_lower_level=h_lower_level,
h_upper_level=h_upper_level,
num_samples=num_elements,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_elements)
state.add_global_memory_reads(num_elements * d_samples.dtype.itemsize)
state.add_global_memory_writes(num_bins * d_histogram.dtype.itemsize)
def launcher(launch: bench.Launch):
histogrammer(
temp_storage=temp_storage,
d_samples=d_samples,
d_histogram=d_histogram,
h_num_output_levels=h_num_output_levels,
h_lower_level=h_lower_level,
h_upper_level=h_upper_level,
num_samples=num_elements,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_histogram_even)
b.set_name("base")
b.add_string_axis("SampleT{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
b.add_int64_axis("Bins", [32, 128, 2048, 2097152])
b.add_string_axis("Entropy", ["0.201", "1.000"])
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,95 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from __future__ import annotations
import pytest
from host_benchmark_cases import (
CALL_CASES,
CASES,
HostBenchmarkCase,
patch_wrapper_to_skip_native_compute,
synchronize,
)
import cuda.compute as cc
pytest.importorskip("pytest_benchmark")
BUILD_TIME_ROUNDS = 10
ONESHOT_ROUNDS = 20
ONESHOT_ITERATIONS = 100
TWOSHOT_ROUNDS = 20
TWOSHOT_ITERATIONS = 1000
def _case_params(cases: list[HostBenchmarkCase]) -> list[pytest.ParameterSet]:
params = []
for case in cases:
marks = []
if case.skip_reason is not None:
marks.append(pytest.mark.skip(reason=case.skip_reason))
params.append(pytest.param(case, id=case.name, marks=marks))
return params
@pytest.mark.benchmark(group="cuda.compute.host.build_time")
@pytest.mark.parametrize("case", _case_params(CASES))
def test_build_time(benchmark, case: HostBenchmarkCase):
state = case.setup()
synchronize()
def setup() -> None:
cc.clear_all_caches()
def build():
return case.make_wrapper(state)
benchmark.pedantic(
build,
setup=setup,
rounds=BUILD_TIME_ROUNDS,
iterations=1,
warmup_rounds=0,
)
@pytest.mark.benchmark(group="cuda.compute.host.oneshot_cached")
@pytest.mark.parametrize("case", _case_params(CALL_CASES))
def test_oneshot_cached_host_overhead(benchmark, case: HostBenchmarkCase):
cc.clear_all_caches()
state = case.setup()
wrapper = case.make_wrapper(state)
patch_wrapper_to_skip_native_compute(wrapper, case.noop_return_kind)
synchronize()
def call() -> None:
case.oneshot(state)
benchmark.pedantic(
call,
rounds=ONESHOT_ROUNDS,
iterations=ONESHOT_ITERATIONS,
warmup_rounds=0,
)
@pytest.mark.benchmark(group="cuda.compute.host.twoshot_call")
@pytest.mark.parametrize("case", _case_params(CALL_CASES))
def test_twoshot_call_host_overhead(benchmark, case: HostBenchmarkCase):
cc.clear_all_caches()
state = case.setup()
wrapper = case.make_wrapper(state)
patch_wrapper_to_skip_native_compute(wrapper, case.noop_return_kind)
synchronize()
def call() -> None:
case.twoshot(state, wrapper)
benchmark.pedantic(
call,
rounds=TWOSHOT_ROUNDS,
iterations=TWOSHOT_ITERATIONS,
warmup_rounds=0,
)

View File

@@ -1,97 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for merge_sort keys using cuda.compute.
C++ equivalent: cub/benchmarks/bench/merge_sort/keys.cu
Notes:
- The C++ benchmark uses Entropy axis to control data distribution
- Uses less_t comparison operator (ascending sort)
- Keys only (no values) - see pairs.cu for key-value sorting
- Migration: Python fixes offsets and approximates entropy generation.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import SIGNED_TYPES, as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import OpKind, make_merge_sort
def bench_merge_sort_keys(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = SIGNED_TYPES[type_str]
num_elements = int(state.get_int64("Elements{io}"))
entropy_str = state.get_string("Entropy")
alloc_stream = as_cupy_stream(state.get_stream())
d_in_keys = generate_data_with_entropy(
num_elements, dtype, entropy_str, alloc_stream
)
# Output array for sorted keys (merge_sort requires separate output)
with alloc_stream:
d_out_keys = cp.empty(num_elements, dtype=dtype)
alloc_stream.synchronize()
sorter = make_merge_sort(
d_in_keys=d_in_keys,
d_in_values=None,
d_out_keys=d_out_keys,
d_out_values=None,
op=OpKind.LESS,
)
temp_storage_bytes = sorter(
temp_storage=None,
d_in_keys=d_in_keys,
d_in_values=None,
d_out_keys=d_out_keys,
d_out_values=None,
op=OpKind.LESS,
num_items=num_elements,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_elements)
state.add_global_memory_reads(num_elements * d_in_keys.dtype.itemsize, "Size")
state.add_global_memory_writes(num_elements * d_out_keys.dtype.itemsize)
def launcher(launch: bench.Launch):
sorter(
temp_storage=temp_storage,
d_in_keys=d_in_keys,
d_in_values=None,
d_out_keys=d_out_keys,
d_out_values=None,
op=OpKind.LESS,
num_items=num_elements,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_merge_sort_keys)
b.set_name("base")
b.add_string_axis("T{ct}", list(SIGNED_TYPES.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
b.add_string_axis("Entropy", ["1.000", "0.201"])
# Note: OffsetT axis from C++ is not exposed in Python API
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,110 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for merge_sort pairs using cuda.compute.
C++ equivalent: cub/benchmarks/bench/merge_sort/pairs.cu
Notes:
- Uses Entropy axis to control key distribution
- Keys and values are sorted together
- Migration: Python omits int128 values and OffsetT axis.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import (
INTEGRAL_TYPES,
SIGNED_TYPES,
as_cupy_stream,
generate_data_with_entropy,
)
import cuda.bench as bench
from cuda.compute import OpKind, make_merge_sort
KEY_TYPE_MAP = SIGNED_TYPES
VALUE_TYPE_MAP = INTEGRAL_TYPES
def bench_merge_sort_pairs(state: bench.State):
key_type_str = state.get_string("KeyT{ct}")
value_type_str = state.get_string("ValueT{ct}")
key_dtype = KEY_TYPE_MAP[key_type_str]
value_dtype = VALUE_TYPE_MAP[value_type_str]
num_elements = int(state.get_int64("Elements{io}"))
entropy_str = state.get_string("Entropy")
alloc_stream = as_cupy_stream(state.get_stream())
d_in_keys = generate_data_with_entropy(
num_elements, key_dtype, entropy_str, alloc_stream
)
with alloc_stream:
d_in_values = generate_data_with_entropy(
num_elements, value_dtype, "1.000", alloc_stream
)
d_out_keys = cp.empty(num_elements, dtype=key_dtype)
d_out_values = cp.empty(num_elements, dtype=value_dtype)
alloc_stream.synchronize()
sorter = make_merge_sort(
d_in_keys=d_in_keys,
d_in_values=d_in_values,
d_out_keys=d_out_keys,
d_out_values=d_out_values,
op=OpKind.LESS,
)
temp_storage_bytes = sorter(
temp_storage=None,
d_in_keys=d_in_keys,
d_in_values=d_in_values,
d_out_keys=d_out_keys,
d_out_values=d_out_values,
op=OpKind.LESS,
num_items=num_elements,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_elements)
state.add_global_memory_reads(num_elements * d_in_keys.dtype.itemsize)
state.add_global_memory_reads(num_elements * d_in_values.dtype.itemsize)
state.add_global_memory_writes(num_elements * d_out_keys.dtype.itemsize)
state.add_global_memory_writes(num_elements * d_out_values.dtype.itemsize)
def launcher(launch: bench.Launch):
sorter(
temp_storage=temp_storage,
d_in_keys=d_in_keys,
d_in_values=d_in_values,
d_out_keys=d_out_keys,
d_out_values=d_out_values,
op=OpKind.LESS,
num_items=num_elements,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_merge_sort_pairs)
b.set_name("base")
b.add_string_axis("KeyT{ct}", list(KEY_TYPE_MAP.keys()))
b.add_string_axis("ValueT{ct}", list(VALUE_TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
b.add_string_axis("Entropy", ["1.000", "0.201"])
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,138 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for three_way_partition using cuda.compute.
C++ equivalent: cub/benchmarks/bench/partition/three_way.cu
Notes:
- The C++ benchmark uses Entropy axis to control data distribution
- Uses less_then_t<T> predicate operators to divide data into three partitions:
- First partition: items < left_border (max/3)
- Second partition: items < right_border (max*2/3)
- Third partition (unselected): items >= right_border
- T axis covers fundamental types (C++ fundamental_types minus int128)
- Migration: Python uses FUNDAMENTAL_TYPES; omits OffsetT axis.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import FUNDAMENTAL_TYPES, as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import make_three_way_partition
def bench_three_way_partition(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = FUNDAMENTAL_TYPES[type_str]
num_elements = int(state.get_int64("Elements{io}"))
entropy_str = state.get_string("Entropy")
alloc_stream = as_cupy_stream(state.get_stream())
if np.issubdtype(dtype, np.integer):
info = np.iinfo(dtype)
min_val = 0
max_val = info.max
else:
info = np.finfo(dtype)
min_val = 0.0
max_val = info.max
left_border = max_val // 3 if np.issubdtype(dtype, np.integer) else max_val / 3
right_border = left_border * 2
d_in = generate_data_with_entropy(
num_elements,
dtype,
entropy_str,
alloc_stream,
min_val=min_val,
max_val=max_val,
)
with alloc_stream:
d_first_part_out = cp.empty(num_elements, dtype=dtype)
d_second_part_out = cp.empty(num_elements, dtype=dtype)
d_unselected_out = cp.empty(num_elements, dtype=dtype)
# d_num_selected_out stores [num_first_part, num_second_part]
d_num_selected_out = cp.empty(2, dtype=np.int32)
alloc_stream.synchronize()
# Convert borders to the correct type for closure capture
left_thresh = dtype(left_border)
right_thresh = dtype(right_border)
def select_first_part(x):
return x < left_thresh
def select_second_part(x):
return x < right_thresh
partitioner = make_three_way_partition(
d_in=d_in,
d_first_part_out=d_first_part_out,
d_second_part_out=d_second_part_out,
d_unselected_out=d_unselected_out,
d_num_selected_out=d_num_selected_out,
select_first_part_op=select_first_part,
select_second_part_op=select_second_part,
)
temp_storage_bytes = partitioner(
temp_storage=None,
d_in=d_in,
d_first_part_out=d_first_part_out,
d_second_part_out=d_second_part_out,
d_unselected_out=d_unselected_out,
d_num_selected_out=d_num_selected_out,
select_first_part_op=select_first_part,
select_second_part_op=select_second_part,
num_items=num_elements,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_elements)
state.add_global_memory_reads(num_elements * d_in.dtype.itemsize)
state.add_global_memory_writes(num_elements * d_in.dtype.itemsize)
# C++ reports add_global_memory_writes<offset_t>(1) — 1 element of offset type.
state.add_global_memory_writes(d_num_selected_out.dtype.itemsize)
def launcher(launch: bench.Launch):
partitioner(
temp_storage=temp_storage,
d_in=d_in,
d_first_part_out=d_first_part_out,
d_second_part_out=d_second_part_out,
d_unselected_out=d_unselected_out,
d_num_selected_out=d_num_selected_out,
select_first_part_op=select_first_part,
select_second_part_op=select_second_part,
num_items=num_elements,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_three_way_partition)
b.set_name("base")
b.add_string_axis("T{ct}", list(FUNDAMENTAL_TYPES.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
b.add_string_axis("Entropy", ["1.000", "0.544", "0.000"])
# Note: OffsetT axis from C++ is not exposed in Python API
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,89 +0,0 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0
[workspace]
channels = ["conda-forge"]
platforms = ["linux-64"]
channel-priority = "disabled"
# CUDA 13.1 system requirement
[feature.cu13.system-requirements]
cuda = "13"
[feature.cu13.dependencies]
cuda-version = "13.1.*"
# Python benchmark dependencies
[feature.bench.dependencies]
python = "3.13.*"
numpy = "*"
cupy = "*"
pytest-benchmark = "*"
pyyaml = "*"
pre-commit = "*"
[feature.bench.pypi-dependencies]
cuda-bench = ">=0.2.0"
# C++ benchmark build dependencies
[feature.cpp-bench.dependencies]
cmake = "*"
ninja = "*"
cxx-compiler = "*"
fmt = "*"
cuda-cudart-dev = "*"
[feature.cpp-bench.target.linux-64.dependencies]
cuda-crt-dev_linux-64 = "*"
cuda-driver-dev_linux-64 = "*"
[feature.cpp-bench.target.linux-64.activation.env]
CUDA_HOME = "$CONDA_PREFIX/targets/x86_64-linux"
# Important: cuda-cccl installation variants
# Released version from PyPI
[feature.cccl-wheel.pypi-dependencies]
cuda-cccl = { version = ">=0.1.0", extras = ["cu13"] }
# Local source build (editable install from repo)
# Needs nvcc, compilers, and CUDA dev libraries for scikit-build-core to build cuda-cccl
[feature.cccl-source.dependencies]
cuda-nvcc = "*"
cuda-nvrtc-dev = "*"
libnvjitlink-dev = "*"
cuda-cudart-dev = "*"
cuda-driver-dev = "*"
c-compiler = "*"
cxx-compiler = "*"
[feature.cccl-source.pypi-dependencies]
cuda-cccl = { path = "../..", editable = true, extras = ["cu13"] }
# Environments
[environments]
wheel = { features = ["cu13", "bench", "cpp-bench", "cccl-wheel"] }
source = { features = ["cu13", "bench", "cpp-bench", "cccl-source"] }
# Tasks
[tasks.bench]
cmd = ["python", "run_benchmarks.py", "--py"]
description = "Run Python cuda.compute benchmarks"
[tasks.bench-quick]
cmd = ["python", "run_benchmarks.py", "--py", "--quick"]
description = "Run Python benchmarks with reduced parameter set"
[tasks.bench-cpp]
cmd = ["python", "run_benchmarks.py", "--cpp"]
description = "Run C++ CUB benchmarks"
[tasks.bench-all]
cmd = ["python", "run_benchmarks.py"]
description = "Run both Python and C++ benchmarks"
[tasks.pre-commit]
cmd = ["pre-commit", "run", "--all-files"]
cwd = "../../../.."
description = "Run pre-commit checks on the entire repo"

View File

@@ -1,130 +0,0 @@
# Quick mode configurations for fast benchmark testing
# Used by run_benchmarks.py with --quick flag
#
# Each benchmark lists axis name -> value pairs.
# Use C++ axis names (with {ct}/{io} suffixes where applicable).
# The script strips these suffixes for Python benchmarks.
#
# For power-of-two axes (Elements{io}, MaxSegSize, MaxSegmentSize, SegmentSize, Segments{io}),
# values are exponents (e.g., 16 means 2^16 = 65536). SegmentSize is converted to
# actual values for Python benchmarks.
#
# NOTE: Only specify axes that exist in BOTH C++ and Python benchmarks.
# Some C++ benchmarks have extra axes (like OffsetT{ct}) not in Python.
transform/fill:
"T{ct}": "I32"
"Elements{io}": 16
transform/babelstream:
"T{ct}": "F32"
"Elements{io}": 16
transform/heavy:
"Heaviness{ct}": "64"
"Elements{io}": 16
transform/fib:
"Elements{io}": 16
transform/grayscale:
"T{ct}": "F32"
"Elements{io}": 16
transform/complex_cmp:
"Elements{io}": 16
transform_reduce/sum:
"T{ct}": "I32"
"Elements{io}": 16
reduce/sum:
"T{ct}": "I32"
"Elements{io}": 16
reduce/min:
"T{ct}": "I32"
"Elements{io}": 16
reduce/custom:
"T{ct}": "I32"
"Elements{io}": 16
reduce/nondeterministic:
"T{ct}": "I32"
"Elements{io}": 16
scan/exclusive/sum:
"T{ct}": "I32"
"Elements{io}": 16
scan/exclusive/custom:
"T{ct}": "I32"
"Elements{io}": 16
histogram/even:
"SampleT{ct}": "I32"
"Elements{io}": 16
"Bins": 128
"Entropy": "1.000"
select/if:
"T{ct}": "I32"
"Elements{io}": 16
"Entropy": "1.000"
select/unique_by_key:
"KeyT{ct}": "I32"
"ValueT{ct}": "I32"
"Elements{io}": 16
"MaxSegSize": 4
radix_sort/keys:
"T{ct}": "I32"
"Elements{io}": 16
"Entropy": "1.000"
radix_sort/pairs:
"KeyT{ct}": "I32"
"ValueT{ct}": "I32"
"Elements{io}": 16
"Entropy": "1.000"
merge_sort/keys:
"T{ct}": "I32"
"Elements{io}": 16
"Entropy": "1.000"
merge_sort/pairs:
"KeyT{ct}": "I32"
"ValueT{ct}": "I32"
"Elements{io}": 16
"Entropy": "1.000"
segmented_sort/keys:
benchmarks:
power:
"T{ct}": "I32"
"Elements{io}": 22
"Segments{io}": 12
"Entropy": "1.000"
small:
"T{ct}": "I32"
"Elements{io}": 22
"MaxSegmentSize": 1
large:
"T{ct}": "I32"
"Elements{io}": 22
"MaxSegmentSize": 10
segmented_reduce/variable_sum:
benchmarks:
variable_default:
"T{ct}": "I32"
"Elements{io}": 16
"MaxSegmentSize": 4
partition/three_way:
"T{ct}": "I32"
"Elements{io}": 16
"Entropy": "1.000"

View File

@@ -1,97 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for radix_sort keys using cuda.compute.
C++ equivalent: cub/benchmarks/bench/radix_sort/keys.cu
Notes:
- The C++ benchmark uses Entropy axis to control data distribution
- Sort order is always ascending (C++ benchmark hardcodes this)
- Keys only (no values) - see radix_sort/pairs.cu for key-value sorting
- begin_bit=0, end_bit=sizeof(T)*8 (full key comparison)
- Migration: Python fixes offsets, excludes int128, and approximates entropy generation.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import FUNDAMENTAL_TYPES as TYPE_MAP
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import SortOrder, make_radix_sort
def bench_radix_sort_keys(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_elements = int(state.get_int64("Elements{io}"))
entropy_str = state.get_string("Entropy")
alloc_stream = as_cupy_stream(state.get_stream())
d_in_keys = generate_data_with_entropy(
num_elements, dtype, entropy_str, alloc_stream
)
# Output array for sorted keys
with alloc_stream:
d_out_keys = cp.empty(num_elements, dtype=dtype)
alloc_stream.synchronize()
sorter = make_radix_sort(
d_in_keys=d_in_keys,
d_out_keys=d_out_keys,
d_in_values=None,
d_out_values=None,
order=SortOrder.ASCENDING,
)
temp_storage_bytes = sorter(
temp_storage=None,
d_in_keys=d_in_keys,
d_out_keys=d_out_keys,
d_in_values=None,
d_out_values=None,
num_items=num_elements,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_elements)
state.add_global_memory_reads(num_elements * d_in_keys.dtype.itemsize, "Size")
state.add_global_memory_writes(num_elements * d_out_keys.dtype.itemsize)
def launcher(launch: bench.Launch):
sorter(
temp_storage=temp_storage,
d_in_keys=d_in_keys,
d_out_keys=d_out_keys,
d_in_values=None,
d_out_values=None,
num_items=num_elements,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_radix_sort_keys)
b.set_name("base")
b.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
b.add_string_axis("Entropy", ["1.000", "0.544", "0.201"])
# Note: OffsetT axis from C++ is not exposed in Python API
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,109 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for radix_sort pairs (key-value) using cuda.compute.
C++ equivalent: cub/benchmarks/bench/radix_sort/pairs.cu
Notes:
- The C++ benchmark uses Entropy axis to control key data distribution
- Sort order is always ascending (C++ benchmark hardcodes this)
- Keys and values are sorted together (values rearranged by key order)
- begin_bit=0, end_bit=sizeof(KeyT)*8 (full key comparison)
- C++ uses integral_types for keys and int8/16/32/64(+int128) for values
- Migration: Python matches C++ integral_types for both keys and values; omits int128 and OffsetT axis.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import INTEGRAL_TYPES, as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import SortOrder, make_radix_sort
KEY_TYPE_MAP = INTEGRAL_TYPES
VALUE_TYPE_MAP = INTEGRAL_TYPES
def bench_radix_sort_pairs(state: bench.State):
key_type_str = state.get_string("KeyT{ct}")
value_type_str = state.get_string("ValueT{ct}")
key_dtype = KEY_TYPE_MAP[key_type_str]
value_dtype = VALUE_TYPE_MAP[value_type_str]
num_elements = int(state.get_int64("Elements{io}"))
entropy_str = state.get_string("Entropy")
alloc_stream = as_cupy_stream(state.get_stream())
d_in_keys = generate_data_with_entropy(
num_elements, key_dtype, entropy_str, alloc_stream
)
d_in_values = generate_data_with_entropy(
num_elements, value_dtype, "1.000", alloc_stream
)
with alloc_stream:
d_out_keys = cp.empty(num_elements, dtype=key_dtype)
d_out_values = cp.empty(num_elements, dtype=value_dtype)
alloc_stream.synchronize()
sorter = make_radix_sort(
d_in_keys=d_in_keys,
d_out_keys=d_out_keys,
d_in_values=d_in_values,
d_out_values=d_out_values,
order=SortOrder.ASCENDING,
)
temp_storage_bytes = sorter(
temp_storage=None,
d_in_keys=d_in_keys,
d_out_keys=d_out_keys,
d_in_values=d_in_values,
d_out_values=d_out_values,
num_items=num_elements,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_elements)
state.add_global_memory_reads(num_elements * d_in_keys.dtype.itemsize)
state.add_global_memory_reads(num_elements * d_in_values.dtype.itemsize)
state.add_global_memory_writes(num_elements * d_out_keys.dtype.itemsize)
state.add_global_memory_writes(num_elements * d_out_values.dtype.itemsize)
def launcher(launch: bench.Launch):
sorter(
temp_storage=temp_storage,
d_in_keys=d_in_keys,
d_out_keys=d_out_keys,
d_in_values=d_in_values,
d_out_values=d_out_values,
num_items=num_elements,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_radix_sort_pairs)
b.set_name("base")
b.add_string_axis("KeyT{ct}", list(KEY_TYPE_MAP.keys()))
b.add_string_axis("ValueT{ct}", list(VALUE_TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
b.add_string_axis("Entropy", ["1.000", "0.201"])
# Note: OffsetT axis from C++ is not exposed in Python API
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,82 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for reduce custom operation using cuda.compute.reduce_into.
C++ equivalent: cub/benchmarks/bench/reduce/custom.cu
Notes:
- Uses a custom max operator (not OpKind) to exercise generic path
- int128 and complex32 are not supported by cupy
- Migration: Python limits to basic numeric types; C++ includes int128/complex.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import SIGNED_TYPES as TYPE_MAP
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import make_reduce_into
def max_op(a, b):
return a if a > b else b
def bench_reduce_custom(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_items = int(state.get_int64("Elements{io}"))
alloc_stream = as_cupy_stream(state.get_stream())
with alloc_stream:
d_in = generate_data_with_entropy(num_items, dtype, "1.000", alloc_stream)
d_out = cp.empty(1, dtype=dtype)
h_init = np.zeros(1, dtype=dtype)
reducer = make_reduce_into(d_in=d_in, d_out=d_out, op=max_op, h_init=h_init)
temp_storage_bytes = reducer(
temp_storage=None,
d_in=d_in,
d_out=d_out,
num_items=num_items,
op=max_op,
h_init=h_init,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_items)
state.add_global_memory_reads(num_items * d_in.dtype.itemsize, "Size")
state.add_global_memory_writes(d_out.dtype.itemsize)
def launcher(launch: bench.Launch):
reducer(
temp_storage=temp_storage,
d_in=d_in,
d_out=d_out,
num_items=num_items,
op=max_op,
h_init=h_init,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_reduce_custom)
b.set_name("base")
b.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,85 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for reduce min operation using cuda.compute.reduce_into.
C++ equivalent: cub/benchmarks/bench/reduce/min.cu
Notes:
- Uses OpKind.MINIMUM for minimum reduction
- C++ uses cuda::minimum<> which CUB recognizes for optimized code paths (DPX on Hopper+)
- int128 and complex32 are not supported by cupy
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import FUNDAMENTAL_TYPES as TYPE_MAP
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import OpKind, make_reduce_into
def bench_reduce_min(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_items = int(state.get_int64("Elements{io}"))
alloc_stream = as_cupy_stream(state.get_stream())
with alloc_stream:
d_in = generate_data_with_entropy(num_items, dtype, "1.000", alloc_stream)
d_out = cp.empty(1, dtype=dtype)
# Initial value for min reduction (max value of type)
if np.issubdtype(dtype, np.integer):
init_val = np.iinfo(dtype).max
else:
init_val = np.finfo(dtype).max
h_init = np.array([init_val], dtype=dtype)
reducer = make_reduce_into(d_in=d_in, d_out=d_out, op=OpKind.MINIMUM, h_init=h_init)
temp_storage_bytes = reducer(
temp_storage=None,
d_in=d_in,
d_out=d_out,
num_items=num_items,
op=OpKind.MINIMUM,
h_init=h_init,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_items)
state.add_global_memory_reads(num_items * d_in.dtype.itemsize, "Size")
state.add_global_memory_writes(d_out.dtype.itemsize)
def launcher(launch: bench.Launch):
reducer(
temp_storage=temp_storage,
d_in=d_in,
d_out=d_out,
num_items=num_items,
op=OpKind.MINIMUM,
h_init=h_init,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_reduce_min)
b.set_name("base")
b.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,87 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for nondeterministic reduce sum using cuda.compute.reduce_into.
C++ equivalent: cub/benchmarks/bench/reduce/nondeterministic.cu
Notes:
- Uses Determinism.NOT_GUARANTEED
- C++ tests int32, int64, float, double
- Migration: Python fixes offsets; C++ exposes an OffsetT axis.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import ALL_TYPES as _ALL_TYPES
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import Determinism, OpKind, make_reduce_into
TYPE_MAP = {k: _ALL_TYPES[k] for k in ("I32", "I64", "F32", "F64")}
def bench_reduce_nondeterministic(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_items = int(state.get_int64("Elements{io}"))
alloc_stream = as_cupy_stream(state.get_stream())
with alloc_stream:
d_in = generate_data_with_entropy(num_items, dtype, "1.000", alloc_stream)
d_out = cp.empty(1, dtype=dtype)
h_init = np.zeros(1, dtype=dtype)
reducer = make_reduce_into(
d_in=d_in,
d_out=d_out,
op=OpKind.PLUS,
h_init=h_init,
determinism=Determinism.NOT_GUARANTEED,
)
temp_storage_bytes = reducer(
temp_storage=None,
d_in=d_in,
d_out=d_out,
num_items=num_items,
op=OpKind.PLUS,
h_init=h_init,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_items)
state.add_global_memory_reads(num_items * d_in.dtype.itemsize, "Size")
state.add_global_memory_writes(1 * d_out.dtype.itemsize)
def launcher(launch: bench.Launch):
reducer(
temp_storage=temp_storage,
d_in=d_in,
d_out=d_out,
num_items=num_items,
op=OpKind.PLUS,
h_init=h_init,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_reduce_nondeterministic)
b.set_name("base")
b.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,80 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for reduce sum operation using cuda.compute.reduce_into.
C++ equivalent: cub/benchmarks/bench/reduce/sum.cu
Notes:
- int128 and complex32 are not supported by cupy
- Migration: Python excludes int128/complex; C++ supports more types/tuning.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import SIGNED_TYPES as TYPE_MAP
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import OpKind, make_reduce_into
def bench_reduce_sum(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_items = int(state.get_int64("Elements{io}"))
alloc_stream = as_cupy_stream(state.get_stream())
with alloc_stream:
d_in = generate_data_with_entropy(num_items, dtype, "1.000", alloc_stream)
d_out = cp.empty(1, dtype=dtype)
# Initial value for reduction
h_init = np.zeros(1, dtype=dtype)
reducer = make_reduce_into(d_in=d_in, d_out=d_out, op=OpKind.PLUS, h_init=h_init)
temp_storage_bytes = reducer(
temp_storage=None,
d_in=d_in,
d_out=d_out,
num_items=num_items,
op=OpKind.PLUS,
h_init=h_init,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_items)
state.add_global_memory_reads(num_items * d_in.dtype.itemsize, "Size")
state.add_global_memory_writes(d_out.dtype.itemsize)
def launcher(launch: bench.Launch):
reducer(
temp_storage=temp_storage,
d_in=d_in,
d_out=d_out,
num_items=num_items,
op=OpKind.PLUS,
h_init=h_init,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_reduce_sum)
b.set_name("base")
b.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,528 +0,0 @@
#!/usr/bin/env python3
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Run Python cuda.compute and C++ CUB benchmarks.
Prerequisites: C++ benchmarks must be built first via:
cd /path/to/cccl && ./ci/build_cub.sh -arch 89
Usage:
python run_benchmarks.py [options]
Options:
-d, --device ID GPU device ID [default: 0]
-b, --benchmark NAME Run specific benchmark only [default: all]
--py Only run Python benchmarks
--cpp Only run C++ benchmarks
--quick, -q Run with reduced parameter set for fast testing
--profile Run each config once (nvbench profile mode), no sampling
-h, --help Show this help message
Benchmark names follow CUB structure:
e.g. transform/fill, transform/babelstream
Examples:
python run_benchmarks.py # Run all benchmarks
python run_benchmarks.py -b transform/fill # Only fill benchmark
python run_benchmarks.py -b reduce/sum -d 0 # Reduce sum on device 0
python run_benchmarks.py -b scan/exclusive/sum --py # Only Python
python run_benchmarks.py --quick # Quick mode (reduced params)
python run_benchmarks.py -b merge_sort/keys -q # Quick mode, single benchmark
"""
import argparse
import os
import shlex
import subprocess
import sys
from pathlib import Path
# ============================================================================
# Configuration
# ============================================================================
SCRIPT_DIR = Path(__file__).parent
CCCL_ROOT = SCRIPT_DIR.parents[3]
RESULTS_DIR = SCRIPT_DIR / "results"
CUB_BENCH_DIR = CCCL_ROOT / "build" / "cub" / "bin"
QUICK_CONFIG_FILE = SCRIPT_DIR / "quick_configs.yaml"
# Supported benchmarks (Python implementations available)
SUPPORTED_BENCHMARKS = [
"transform/fill",
"transform/babelstream",
"transform/heavy",
"transform/fib",
"transform/grayscale",
"transform/complex_cmp",
"transform_reduce/sum",
"reduce/sum",
"reduce/min",
"reduce/custom",
"reduce/nondeterministic",
"scan/exclusive/sum",
"scan/exclusive/custom",
"histogram/even",
"select/if",
"select/unique_by_key",
"radix_sort/keys",
"radix_sort/pairs",
"merge_sort/keys",
"merge_sort/pairs",
"segmented_sort/keys",
"segmented_reduce/variable_sum",
"partition/three_way",
]
# Axes that use power-of-two values (need [pow2] suffix for nvbench)
# These are the base names (without {ct}/{io} suffixes)
POW2_AXES_CPP = {
"Elements",
"GuaranteedMaxSegSize",
"MaxSegSize",
"MaxSegmentSize",
"SegmentSize",
"Segments",
}
POW2_AXES_PY = {
"Elements",
"GuaranteedMaxSegSize",
"MaxSegSize",
"MaxSegmentSize",
"Segments",
}
# Axis name mappings from C++ to Python.
# Keep type axes in their C++ form (`{ct}`) to match benchmark axis names.
CPP_TO_PY_AXIS_MAP = {
"T{ct}": "T{ct}",
"KeyT{ct}": "KeyT{ct}",
"ValueT{ct}": "ValueT{ct}",
"SampleT{ct}": "SampleT{ct}",
"Heaviness{ct}": "Heaviness{ct}",
}
def strip_axis_suffix(axis_name: str) -> str:
"""Strip {ct} or {io} suffix from axis name for Python benchmarks.
e.g., "T{ct}" -> "T{ct}", "Elements{io}" -> "Elements{io}".
"""
if axis_name in CPP_TO_PY_AXIS_MAP:
return CPP_TO_PY_AXIS_MAP[axis_name]
# Generic suffix stripping for any other axes
if axis_name.endswith("{ct}"):
return axis_name.rsplit("{", 1)[0]
if axis_name.endswith("{io}"):
return axis_name
return axis_name
def get_base_axis_name(axis_name: str) -> str:
"""Get base axis name (without suffix) for POW2 check.
e.g., "Elements{io}" -> "Elements"
"""
if axis_name.endswith("{ct}") or axis_name.endswith("{io}"):
return axis_name.rsplit("{", 1)[0]
return axis_name
# ============================================================================
# Helper Functions
# ============================================================================
def print_banner(msg: str) -> None:
"""Print a banner message."""
print()
print("=" * 72)
print(msg)
print("=" * 72)
print()
def print_section(msg: str) -> None:
"""Print a section header."""
print()
print("-" * 72)
print(msg)
print("-" * 72)
def load_quick_configs() -> dict:
"""Load quick mode configurations from YAML file."""
import yaml # only needed for --quick
with open(QUICK_CONFIG_FILE) as f:
return yaml.safe_load(f)
def build_axis_args_from_config(axis_config: dict, for_python: bool) -> list:
"""Build --axis arguments for nvbench CLI from an axis config dict.
Args:
axis_config: Dict of axis_name -> value
for_python: If True, strip C++ suffixes for Python benchmarks
"""
args = []
for axis_name, value in axis_config.items():
# For Python, strip C++ suffixes from axis names
if for_python:
axis_name = strip_axis_suffix(axis_name)
# Check if this is a power-of-two axis (using base name)
base_name = get_base_axis_name(axis_name)
if for_python and base_name == "SegmentSize":
actual_value = 2 ** int(value)
args.extend(["--axis", f"{axis_name}={actual_value}"])
continue
pow2_axes = POW2_AXES_PY if for_python else POW2_AXES_CPP
if base_name in pow2_axes:
args.extend(["--axis", f"{axis_name}[pow2]={value}"])
else:
args.extend(["--axis", f"{axis_name}={value}"])
return args
def get_quick_config_entry(benchmark: str, quick_configs: dict) -> dict:
"""Get quick config entry for a benchmark, raising if missing."""
if benchmark not in quick_configs:
raise ValueError(
f"Benchmark '{benchmark}' not found in quick_configs.yaml.\n"
f"Cannot run in --quick mode. Add configuration for this benchmark."
)
return quick_configs[benchmark]
def get_cpp_binary(benchmark: str) -> str:
"""Get C++ binary name from benchmark path.
e.g., "transform/fill" -> "cub.bench.transform.fill.base"
"""
return f"cub.bench.{benchmark.replace('/', '.')}.base"
def get_py_script(benchmark: str) -> Path:
"""Get Python script path from benchmark path.
e.g., "transform/fill" -> "transform/fill.py"
"""
return SCRIPT_DIR / f"{benchmark}.py"
def get_result_path(benchmark: str, suffix: str) -> Path:
"""Get results file path.
e.g., "transform/fill", "cpp" -> "results/transform/fill_cpp.json"
"""
bench_path = Path(benchmark)
return RESULTS_DIR / bench_path.parent / f"{bench_path.name}_{suffix}.json"
def get_log_path(benchmark: str, suffix: str) -> Path:
"""Get log file path under results/logs.
e.g., "transform/fill", "cpp" -> "results/logs/transform/fill_cpp.log"
"""
bench_path = Path(benchmark)
return RESULTS_DIR / "logs" / bench_path.parent / f"{bench_path.name}_{suffix}.log"
def run_and_log(cmd: list, log_path: Path, env: dict | None = None) -> dict:
"""Run command and write stdout/stderr to log file."""
log_path.parent.mkdir(parents=True, exist_ok=True)
with open(log_path, "w", encoding="utf-8") as log_file:
log_file.write(f"Command: {shlex.join(cmd)}\n\n")
log_file.flush()
try:
result = subprocess.run(
cmd, check=False, stdout=log_file, stderr=log_file, env=env
)
except Exception as exc: # noqa: BLE001
log_file.write("\nERROR: Runner failed to execute command.\n")
log_file.write(f"{exc}\n")
return {"status": "error", "returncode": None, "error": str(exc)}
status = "ok" if result.returncode == 0 else "failed"
return {"status": status, "returncode": result.returncode}
# ============================================================================
# Benchmark Runner
# ============================================================================
def run_benchmark(
benchmark: str,
device: str,
run_py: bool,
run_cpp: bool,
quick_mode: bool,
quick_configs: dict,
profile: bool,
) -> dict:
"""Run a single benchmark.
Returns dict with paths to generated result files.
"""
cpp_binary = get_cpp_binary(benchmark)
py_script = get_py_script(benchmark)
cpp_result = get_result_path(benchmark, "cpp")
py_result = get_result_path(benchmark, "py")
cpp_log = get_log_path(benchmark, "cpp")
py_log = get_log_path(benchmark, "py")
# Ensure results subdirectory exists
cpp_result.parent.mkdir(parents=True, exist_ok=True)
# Build axis arguments for quick mode
cpp_axis_args = []
py_axis_args = []
if quick_mode:
config_entry = get_quick_config_entry(benchmark, quick_configs)
if "benchmarks" in config_entry:
for bench_name, axis_config in config_entry["benchmarks"].items():
cpp_axis_args.extend(["--benchmark", bench_name])
cpp_axis_args.extend(
build_axis_args_from_config(axis_config, for_python=False)
)
py_axis_args.extend(["--benchmark", bench_name])
py_axis_args.extend(
build_axis_args_from_config(axis_config, for_python=True)
)
else:
cpp_axis_args = build_axis_args_from_config(config_entry, for_python=False)
py_axis_args = build_axis_args_from_config(config_entry, for_python=True)
results = {}
# Run C++ benchmark
if run_cpp:
print(f"Running C++ benchmark: {cpp_binary}")
cpp_bin = CUB_BENCH_DIR / cpp_binary
if not cpp_bin.exists():
print(f"ERROR: C++ binary not found: {cpp_bin}")
print()
print("Please build C++ benchmarks first:")
print(f" cd {CCCL_ROOT}")
print(" ./ci/build_cub.sh -arch <your_gpu_arch> # e.g., 89 for RTX 4090")
print()
print("Available benchmarks in build directory:")
if CUB_BENCH_DIR.exists():
binaries = list(CUB_BENCH_DIR.glob("*.base"))[:10]
for b in binaries:
print(f" {b.name}")
if len(list(CUB_BENCH_DIR.glob("*.base"))) > 10:
print(" ...")
else:
print(f" (directory not found: {CUB_BENCH_DIR})")
sys.exit(1)
cmd = [str(cpp_bin), "--json", str(cpp_result), "--devices", device]
cmd.extend(cpp_axis_args)
if profile:
cmd.append("--profile")
# Ensure the CUB build lib dir (containing libnvbench.so) is on the
# dynamic linker search path for the child process.
cpp_lib_dir = str(CUB_BENCH_DIR.parent / "lib")
cpp_env = os.environ.copy()
existing_ld = cpp_env.get("LD_LIBRARY_PATH", "")
cpp_env["LD_LIBRARY_PATH"] = (
f"{cpp_lib_dir}:{existing_ld}" if existing_ld else cpp_lib_dir
)
cpp_status = run_and_log(cmd, cpp_log, env=cpp_env)
print(f" Results: {cpp_result}")
print(f" Log: {cpp_log}")
if cpp_status["status"] != "ok":
print(f" WARNING: C++ benchmark failed (exit {cpp_status['returncode']}).")
results["cpp"] = cpp_result
results["cpp_status"] = cpp_status
# Run Python benchmark
if run_py:
print(f"Running Python benchmark: {py_script.relative_to(SCRIPT_DIR)}")
if not py_script.exists():
print(f"ERROR: Python script not found: {py_script}")
sys.exit(1)
cmd = [
sys.executable,
str(py_script),
"--json",
str(py_result),
"--devices",
device,
]
cmd.extend(py_axis_args)
if profile:
cmd.append("--profile")
py_status = run_and_log(cmd, py_log)
print(f" Results: {py_result}")
print(f" Log: {py_log}")
if py_status["status"] != "ok":
print(
f" WARNING: Python benchmark failed (exit {py_status['returncode']})."
)
results["py"] = py_result
results["py_status"] = py_status
return results
# ============================================================================
# Main
# ============================================================================
def main():
parser = argparse.ArgumentParser(
description="Run Python cuda.compute and C++ CUB benchmarks",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=f"""
Supported benchmarks:
{chr(10).join(f" {b}" for b in SUPPORTED_BENCHMARKS)}
""",
)
parser.add_argument(
"-d", "--device", default="0", help="GPU device ID [default: 0]"
)
parser.add_argument(
"-b", "--benchmark", help="Run specific benchmark only [default: all]"
)
parser.add_argument("--py", action="store_true", help="Only run Python benchmarks")
parser.add_argument("--cpp", action="store_true", help="Only run C++ benchmarks")
parser.add_argument(
"-q",
"--quick",
action="store_true",
help="Run with reduced parameter set for fast testing",
)
parser.add_argument(
"--profile",
action="store_true",
help="Run each benchmark configuration once (nvbench profile mode) with "
"no sampling -- for smoke-testing that benchmarks execute without error, "
"not for measuring performance.",
)
args = parser.parse_args()
# Determine what to run
run_py = True
run_cpp = True
if args.py and not args.cpp:
run_cpp = False
elif args.cpp and not args.py:
run_py = False
# Load quick configs if needed
quick_configs = {}
if args.quick:
quick_configs = load_quick_configs()
# Validate and determine benchmarks to run
if args.benchmark:
if args.benchmark not in SUPPORTED_BENCHMARKS:
print(f"ERROR: Benchmark '{args.benchmark}' not supported.")
print()
print("Available benchmarks:")
for b in SUPPORTED_BENCHMARKS:
print(f" {b}")
sys.exit(1)
benchmarks_to_run = [args.benchmark]
else:
benchmarks_to_run = SUPPORTED_BENCHMARKS
# Print configuration
print_banner("CCCL Benchmark Runner")
print("Configuration:")
print(f" CCCL Root: {CCCL_ROOT}")
print(f" C++ Binaries: {CUB_BENCH_DIR}")
print(f" Results Dir: {RESULTS_DIR}")
print(f" Device: {args.device}")
print(f" Benchmarks: {' '.join(benchmarks_to_run)}")
print(f" Run C++: {run_cpp}")
print(f" Run Python: {run_py}")
print(f" Quick Mode: {args.quick}")
print(f" Profile Mode: {args.profile}")
print()
# Check C++ binaries directory exists (if running C++)
if run_cpp and not CUB_BENCH_DIR.exists():
print(f"ERROR: C++ benchmark directory not found: {CUB_BENCH_DIR}")
print()
print("Please build C++ benchmarks first:")
print(f" cd {CCCL_ROOT}")
print(" ./ci/build_cub.sh -arch <your_gpu_arch> # e.g., 89 for RTX 4090")
sys.exit(1)
# Run benchmarks
all_results = {}
for bench in benchmarks_to_run:
print_section(f"Benchmark: {bench}")
results = run_benchmark(
bench, args.device, run_py, run_cpp, args.quick, quick_configs, args.profile
)
all_results[bench] = results
print()
# Print summary
print_banner("Summary")
print(f"Results directory: {RESULTS_DIR}")
print()
print("Generated files:")
for bench in benchmarks_to_run:
cpp_result = get_result_path(bench, "cpp")
py_result = get_result_path(bench, "py")
cpp_log = get_log_path(bench, "cpp")
py_log = get_log_path(bench, "py")
print(f" {bench}:")
if cpp_result.exists():
print(f" C++ results: {cpp_result}")
if cpp_log.exists():
print(f" C++ log: {cpp_log}")
if py_result.exists():
print(f" Python results: {py_result}")
if py_log.exists():
print(f" Python log: {py_log}")
status = all_results.get(bench, {})
cpp_status = status.get("cpp_status")
py_status = status.get("py_status")
if cpp_status and cpp_status.get("status") != "ok":
print(
" C++ status: "
f"{cpp_status.get('status')} (exit {cpp_status.get('returncode')})"
)
if py_status and py_status.get("status") != "ok":
print(
" Python status: "
f"{py_status.get('status')} (exit {py_status.get('returncode')})"
)
print()
# Exit non-zero if any benchmark failed so CI (e.g. the --profile smoke gate)
# catches benchmark rot instead of silently passing on printed warnings.
failed = []
for bench in benchmarks_to_run:
results = all_results.get(bench, {})
for key in ("cpp_status", "py_status"):
if results.get(key, {}).get("status") not in (None, "ok"):
failed.append(bench)
break
if failed:
print(f"ERROR: {len(failed)} benchmark(s) failed: {failed}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -1,83 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for exclusive scan custom operation using cuda.compute.exclusive_scan.
C++ equivalent: cub/benchmarks/bench/scan/exclusive/custom.cu
Notes:
- Uses a custom max operator (not OpKind)
- int128 and complex32 are not supported by cupy
- Migration: Python fixes offsets; C++ exposes an OffsetT axis.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
import cupy as cp
import numpy as np
from utils import SIGNED_TYPES as TYPE_MAP
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import make_exclusive_scan
def max_op(a, b):
return a if a > b else b
def bench_scan_exclusive_custom(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_items = int(state.get_int64("Elements{io}"))
alloc_stream = as_cupy_stream(state.get_stream())
with alloc_stream:
d_in = generate_data_with_entropy(num_items, dtype, "1.000", alloc_stream)
d_out = cp.empty(num_items, dtype=dtype)
h_init = np.zeros(1, dtype=dtype)
scanner = make_exclusive_scan(d_in=d_in, d_out=d_out, op=max_op, init_value=h_init)
temp_storage_bytes = scanner(
temp_storage=None,
d_in=d_in,
d_out=d_out,
op=max_op,
init_value=h_init,
num_items=num_items,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_items)
state.add_global_memory_reads(num_items * d_in.dtype.itemsize, "Size")
state.add_global_memory_writes(num_items * d_out.dtype.itemsize)
def launcher(launch: bench.Launch):
scanner(
temp_storage=temp_storage,
d_in=d_in,
d_out=d_out,
op=max_op,
init_value=h_init,
num_items=num_items,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_scan_exclusive_custom)
b.set_name("base")
b.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 33, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,84 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for exclusive scan sum operation using cuda.compute.exclusive_scan.
C++ equivalent: cub/benchmarks/bench/scan/exclusive/sum.cu
Notes:
- int128 and complex32 are not supported by cupy
- Migration: Python fixes offsets; C++ exposes an OffsetT axis.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
import cupy as cp
import numpy as np
from utils import SIGNED_TYPES as TYPE_MAP
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import OpKind, make_exclusive_scan
def bench_scan_exclusive_sum(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_items = int(state.get_int64("Elements{io}"))
alloc_stream = as_cupy_stream(state.get_stream())
with alloc_stream:
d_in = generate_data_with_entropy(num_items, dtype, "1.000", alloc_stream)
# Output is same size as input for scan
d_out = cp.empty(num_items, dtype=dtype)
# Initial value for scan (identity for addition)
h_init = np.zeros(1, dtype=dtype)
scanner = make_exclusive_scan(
d_in=d_in, d_out=d_out, op=OpKind.PLUS, init_value=h_init
)
temp_storage_bytes = scanner(
temp_storage=None,
d_in=d_in,
d_out=d_out,
op=OpKind.PLUS,
init_value=h_init,
num_items=num_items,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_items)
state.add_global_memory_reads(num_items * d_in.dtype.itemsize, "Size")
state.add_global_memory_writes(num_items * d_out.dtype.itemsize)
def launcher(launch: bench.Launch):
scanner(
temp_storage=temp_storage,
d_in=d_in,
d_out=d_out,
op=OpKind.PLUS,
init_value=h_init,
num_items=num_items,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_scan_exclusive_sum)
b.set_name("base")
b.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 33, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,165 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for segmented_reduce (sum) with variable-size segments.
C++ equivalent: cub/benchmarks/bench/segmented_reduce/variable_sum.cu (uses variable_base.cuh)
Notes:
- Implements four sub-benchmarks: variable_default, variable_small_dynamic,
variable_medium_dynamic, variable_large_dynamic.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import (
ALL_TYPES,
as_cupy_stream,
generate_data_with_entropy,
generate_uniform_segment_offsets,
)
import cuda.bench as bench
from cuda.compute import OpKind, make_segmented_reduce
TYPE_MAP = {k: ALL_TYPES[k] for k in ("I32", "I64", "F32", "F64")}
def run_segmented_reduce(
state: bench.State,
d_in,
d_out,
h_init,
start_offsets,
end_offsets,
num_segments,
guaranteed_max_seg_size,
):
reducer = make_segmented_reduce(
d_in=d_in,
d_out=d_out,
start_offsets_in=start_offsets,
end_offsets_in=end_offsets,
op=OpKind.PLUS,
h_init=h_init,
)
temp_storage_bytes = reducer(
temp_storage=None,
d_in=d_in,
d_out=d_out,
num_segments=num_segments,
start_offsets_in=start_offsets,
end_offsets_in=end_offsets,
op=OpKind.PLUS,
h_init=h_init,
max_segment_size=guaranteed_max_seg_size,
)
alloc_stream = as_cupy_stream(state.get_stream())
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
def launcher(launch: bench.Launch):
reducer(
temp_storage=temp_storage,
d_in=d_in,
d_out=d_out,
num_segments=num_segments,
start_offsets_in=start_offsets,
end_offsets_in=end_offsets,
op=OpKind.PLUS,
h_init=h_init,
max_segment_size=guaranteed_max_seg_size,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False, sync=True)
def bench_variable_segmented_reduce(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_elements = int(state.get_int64("Elements{io}"))
max_segment_size = int(state.get_int64("MaxSegmentSize"))
guaranteed_max_seg_size = int(state.get_int64("GuaranteedMaxSegSize"))
# Skip cases where hint would be incorrect (max > guaranteed)
if guaranteed_max_seg_size != 0 and max_segment_size > guaranteed_max_seg_size:
state.skip("max_segment_size > guaranteed_max_seg_size")
return
min_segment_size = 1
offsets = generate_uniform_segment_offsets(
num_elements, min_segment_size, max_segment_size
)
alloc_stream = as_cupy_stream(state.get_stream())
h_init = np.zeros(1, dtype=dtype)
d_in = generate_data_with_entropy(num_elements, dtype, "1.000", alloc_stream)
with alloc_stream:
start_offsets = cp.asarray(offsets[:-1], dtype=np.int64)
end_offsets = cp.asarray(offsets[1:], dtype=np.int64)
d_out = cp.empty(int(start_offsets.size), dtype=dtype)
alloc_stream.synchronize()
num_segments = int(start_offsets.size)
state.add_element_count(num_elements)
state.add_global_memory_reads(num_elements * d_in.dtype.itemsize)
state.add_global_memory_writes(num_segments * d_out.dtype.itemsize)
state.add_global_memory_reads((num_segments + 1) * start_offsets.dtype.itemsize)
run_segmented_reduce(
state,
d_in,
d_out,
h_init,
start_offsets,
end_offsets,
num_segments,
guaranteed_max_seg_size,
)
if __name__ == "__main__":
# Default: no size hint — uses generic large-reduce kernel regardless of segment size
b_default = bench.register(bench_variable_segmented_reduce)
b_default.set_name("variable_default")
b_default.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b_default.add_int64_power_of_two_axis("Elements{io}", range(16, 28, 4))
b_default.add_int64_power_of_two_axis("MaxSegmentSize", range(1, 17, 1))
b_default.add_int64_axis("GuaranteedMaxSegSize", [0])
# Small segments (116 items): hint enables warp-level reduction
b_small = bench.register(bench_variable_segmented_reduce)
b_small.set_name("variable_small_dynamic")
b_small.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b_small.add_int64_power_of_two_axis("Elements{io}", range(16, 28, 4))
b_small.add_int64_power_of_two_axis("MaxSegmentSize", range(1, 5, 1))
b_small.add_int64_power_of_two_axis("GuaranteedMaxSegSize", range(1, 5, 1))
# Medium segments (32256 items): hint enables warp-level reduction
b_medium = bench.register(bench_variable_segmented_reduce)
b_medium.set_name("variable_medium_dynamic")
b_medium.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b_medium.add_int64_power_of_two_axis("Elements{io}", range(16, 28, 4))
b_medium.add_int64_power_of_two_axis("MaxSegmentSize", range(5, 9, 1))
b_medium.add_int64_power_of_two_axis("GuaranteedMaxSegSize", range(5, 9, 1))
# Large segments (512+ items): hint enables block-level reduction
b_large = bench.register(bench_variable_segmented_reduce)
b_large.set_name("variable_large_dynamic")
b_large.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b_large.add_int64_power_of_two_axis("Elements{io}", range(16, 28, 4))
b_large.add_int64_power_of_two_axis("MaxSegmentSize", range(9, 17, 1))
b_large.add_int64_power_of_two_axis("GuaranteedMaxSegSize", range(9, 17, 1))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,167 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for segmented_sort keys using cuda.compute.
C++ equivalent: cub/benchmarks/bench/segmented_sort/keys.cu
Notes:
- Implements three sub-benchmarks: power, small, large
- Power uses power-law segment sizes with Entropy axis
- Small/large use uniform segment sizes with MaxSegmentSize axis
- Migration: uniform offsets use min_segment_size ~ max/2.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import (
FUNDAMENTAL_TYPES as TYPE_MAP,
)
from utils import (
as_cupy_stream,
generate_data_with_entropy,
generate_power_law_offsets,
generate_uniform_segment_offsets,
)
import cuda.bench as bench
from cuda.compute import SortOrder, make_segmented_sort
def run_segmented_sort(
state: bench.State,
d_in_keys,
d_out_keys,
start_offsets,
end_offsets,
num_items,
num_segments,
):
sorter = make_segmented_sort(
d_in_keys=d_in_keys,
d_out_keys=d_out_keys,
d_in_values=None,
d_out_values=None,
start_offsets_in=start_offsets,
end_offsets_in=end_offsets,
order=SortOrder.ASCENDING,
)
temp_storage_bytes = sorter(
temp_storage=None,
d_in_keys=d_in_keys,
d_out_keys=d_out_keys,
d_in_values=None,
d_out_values=None,
num_items=num_items,
num_segments=num_segments,
start_offsets_in=start_offsets,
end_offsets_in=end_offsets,
)
alloc_stream = as_cupy_stream(state.get_stream())
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
def launcher(launch: bench.Launch):
sorter(
temp_storage=temp_storage,
d_in_keys=d_in_keys,
d_out_keys=d_out_keys,
d_in_values=None,
d_out_values=None,
num_items=num_items,
num_segments=num_segments,
start_offsets_in=start_offsets,
end_offsets_in=end_offsets,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False, sync=True)
def bench_segmented_sort(state: bench.State, use_power_law: bool):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_elements = int(state.get_int64("Elements{io}"))
alloc_stream = as_cupy_stream(state.get_stream())
if use_power_law:
num_segments = int(state.get_int64("Segments{io}"))
entropy_str = state.get_string("Entropy")
else:
max_segment_size = int(state.get_int64("MaxSegmentSize"))
min_segment_size = max(1, max_segment_size // 2)
entropy_str = "1.000"
if use_power_law:
offsets = generate_power_law_offsets(num_elements, num_segments)
else:
offsets = generate_uniform_segment_offsets(
num_elements, min_segment_size, max_segment_size
)
d_in_keys = generate_data_with_entropy(
num_elements, dtype, entropy_str, alloc_stream
)
with alloc_stream:
d_out_keys = cp.empty(num_elements, dtype=dtype)
start_offsets = cp.asarray(offsets[:-1], dtype=np.int64)
end_offsets = cp.asarray(offsets[1:], dtype=np.int64)
alloc_stream.synchronize()
num_segments = int(start_offsets.size)
state.add_element_count(num_elements)
state.add_global_memory_reads(num_elements * d_in_keys.dtype.itemsize)
state.add_global_memory_writes(num_elements * d_out_keys.dtype.itemsize)
state.add_global_memory_reads((num_segments + 1) * start_offsets.dtype.itemsize)
run_segmented_sort(
state,
d_in_keys,
d_out_keys,
start_offsets,
end_offsets,
num_elements,
num_segments,
)
def bench_segmented_sort_power(state: bench.State):
bench_segmented_sort(state, use_power_law=True)
def bench_segmented_sort_uniform(state: bench.State):
bench_segmented_sort(state, use_power_law=False)
if __name__ == "__main__":
b_power = bench.register(bench_segmented_sort_power)
b_power.set_name("power")
b_power.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b_power.add_int64_power_of_two_axis("Elements{io}", range(22, 31, 4))
b_power.add_int64_power_of_two_axis("Segments{io}", range(12, 21, 4))
b_power.add_string_axis("Entropy", ["1.000", "0.201"])
b_small = bench.register(bench_segmented_sort_uniform)
b_small.set_name("small")
b_small.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b_small.add_int64_power_of_two_axis("Elements{io}", range(22, 31, 4))
b_small.add_int64_power_of_two_axis("MaxSegmentSize", range(1, 9, 1))
b_large = bench.register(bench_segmented_sort_uniform)
b_large.set_name("large")
b_large.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b_large.add_int64_power_of_two_axis("Elements{io}", range(22, 31, 4))
b_large.add_int64_power_of_two_axis("MaxSegmentSize", range(10, 19, 2))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,119 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for select_if using cuda.compute.
C++ equivalent: cub/benchmarks/bench/select/if.cu
Notes:
- The C++ benchmark uses a `less_then_t<T>` predicate with threshold based on entropy
- Entropy controls what fraction of elements are selected:
- 1.000 → selects ~100% (threshold = max value)
- 0.544 → selects ~54.4% (threshold at 54.4% of range)
- 0.000 → selects ~0% (threshold = min value)
- InPlace axis controls whether output can alias input (not exposed in Python API)
- Migration: Python cannot expose InPlace axis; output is sized to num_elements but metrics use actual selected count.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import (
ENTROPY_TO_PROB,
as_cupy_stream,
generate_data_with_entropy,
lerp_min_max,
)
from utils import (
FUNDAMENTAL_TYPES as TYPE_MAP,
)
import cuda.bench as bench
from cuda.compute import make_select
# Entropy values from C++ benchmark
# These control the selection threshold and thus how many elements are selected
def bench_select_if(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_elements = int(state.get_int64("Elements{io}"))
entropy_str = state.get_string("Entropy")
probability = ENTROPY_TO_PROB[entropy_str]
threshold = lerp_min_max(dtype, probability)
alloc_stream = as_cupy_stream(state.get_stream())
# Match C++ benchmark: input data generation is independent of Entropy.
# Entropy only controls the selection threshold.
d_in = generate_data_with_entropy(num_elements, dtype, "1.000", alloc_stream)
with alloc_stream:
selected_elements = int(cp.count_nonzero(d_in < threshold).get())
d_out = cp.empty(selected_elements, dtype=dtype)
d_num_selected = cp.zeros(1, dtype=np.int64)
alloc_stream.synchronize()
# Create predicate: select elements less than threshold
# For numba device functions, we need to use the value directly in closure
thresh_val = threshold
def less_than_threshold(x):
return x < thresh_val
selector = make_select(
d_in=d_in,
d_out=d_out,
d_num_selected_out=d_num_selected,
cond=less_than_threshold,
)
temp_storage_bytes = selector(
temp_storage=None,
d_in=d_in,
d_out=d_out,
d_num_selected_out=d_num_selected,
cond=less_than_threshold,
num_items=num_elements,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_elements)
state.add_global_memory_reads(num_elements * d_in.dtype.itemsize)
state.add_global_memory_writes(selected_elements * d_out.dtype.itemsize)
state.add_global_memory_writes(1 * d_num_selected.dtype.itemsize)
def launcher(launch: bench.Launch):
selector(
temp_storage=temp_storage,
d_in=d_in,
d_out=d_out,
d_num_selected_out=d_num_selected,
cond=less_than_threshold,
num_items=num_elements,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_select_if)
b.set_name("base")
b.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
b.add_string_axis("Entropy", ["1.000", "0.544", "0.000"])
# Note: InPlace axis is not exposed in Python API, so we skip it
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,137 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for unique_by_key using cuda.compute.
C++ equivalent: cub/benchmarks/bench/select/unique_by_key.cu
Notes:
- The C++ benchmark uses MaxSegSize axis to control segment sizes
- Uses equal_to comparison operator for key equality
- Generates key segments with sizes between 1 and MaxSegSize
- Both keys and values are processed
- Migration: Python fixes offsets and generates key segments on GPU to mirror C++.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import INTEGRAL_TYPES, SIGNED_TYPES, as_cupy_stream, generate_key_segments
import cuda.bench as bench
from cuda.compute import OpKind, make_unique_by_key
KEY_TYPE_MAP = INTEGRAL_TYPES
VALUE_TYPE_MAP = {**SIGNED_TYPES, "C32": np.complex64}
def bench_unique_by_key(state: bench.State):
key_type_str = state.get_string("KeyT{ct}")
value_type_str = state.get_string("ValueT{ct}")
key_dtype = KEY_TYPE_MAP[key_type_str]
value_dtype = VALUE_TYPE_MAP[value_type_str]
num_elements = int(state.get_int64("Elements{io}"))
max_seg_size = int(state.get_int64("MaxSegSize"))
if num_elements > np.iinfo(np.int32).max:
state.skip("Skipping: num_elements exceeds int32 limits")
return
alloc_stream = as_cupy_stream(state.get_stream())
d_in_keys = generate_key_segments(
num_elements,
key_dtype,
min_segment_size=1,
max_segment_size=max_seg_size,
stream=alloc_stream,
)
with alloc_stream:
d_in_values = cp.zeros(num_elements, dtype=value_dtype)
d_out_keys = cp.empty(num_elements, dtype=key_dtype)
d_out_values = cp.empty(num_elements, dtype=value_dtype)
d_num_selected = cp.empty(1, dtype=np.int32)
alloc_stream.synchronize()
uniquer = make_unique_by_key(
d_in_keys=d_in_keys,
d_in_items=d_in_values,
d_out_keys=d_out_keys,
d_out_items=d_out_values,
d_out_num_selected=d_num_selected,
op=OpKind.EQUAL_TO,
)
temp_storage_bytes = uniquer(
temp_storage=None,
d_in_keys=d_in_keys,
d_in_items=d_in_values,
d_out_keys=d_out_keys,
d_out_items=d_out_values,
d_out_num_selected=d_num_selected,
op=OpKind.EQUAL_TO,
num_items=num_elements,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
# Run once before timing to materialize the number of selected runs,
# matching the C++ metric accounting flow.
uniquer(
temp_storage=temp_storage,
d_in_keys=d_in_keys,
d_in_items=d_in_values,
d_out_keys=d_out_keys,
d_out_items=d_out_values,
d_out_num_selected=d_num_selected,
op=OpKind.EQUAL_TO,
num_items=num_elements,
stream=alloc_stream,
)
alloc_stream.synchronize()
num_runs = int(d_num_selected.get()[0])
state.add_element_count(num_elements)
state.add_global_memory_reads(int(num_elements * d_in_keys.dtype.itemsize))
state.add_global_memory_reads(int(num_elements * d_in_values.dtype.itemsize))
state.add_global_memory_writes(int(num_runs * d_out_keys.dtype.itemsize))
state.add_global_memory_writes(int(num_runs * d_out_values.dtype.itemsize))
state.add_global_memory_writes(int(d_num_selected.dtype.itemsize))
def launcher(launch: bench.Launch):
uniquer(
temp_storage=temp_storage,
d_in_keys=d_in_keys,
d_in_items=d_in_values,
d_out_keys=d_out_keys,
d_out_items=d_out_values,
d_out_num_selected=d_num_selected,
op=OpKind.EQUAL_TO,
num_items=num_elements,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_unique_by_key)
b.set_name("base")
b.add_string_axis("KeyT{ct}", list(KEY_TYPE_MAP.keys()))
b.add_string_axis("ValueT{ct}", list(VALUE_TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
b.add_int64_power_of_two_axis("MaxSegSize", [1, 4, 8])
# Note: OffsetT axis from C++ is not exposed in Python API
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,237 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for BabelStream operations using cuda.compute transforms.
C++ equivalent: cub/benchmarks/bench/transform/babelstream.cu
Notes:
- Migration: Python omits OffsetT axis and int128 types.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
from utils import ALL_TYPES as _ALL_TYPES
from utils import as_cupy_stream
import cuda.bench as bench
import cuda.compute
from cuda.compute import ZipIterator
TYPE_MAP = {k: _ALL_TYPES[k] for k in ("I8", "I16", "F32", "F64")}
START_A = 11
START_B = 2
START_C = 1
START_SCALAR = -2
assert START_A == START_A + START_B + START_SCALAR * START_C
def _reset_pools():
cp.get_default_memory_pool().free_all_blocks()
cp.get_default_pinned_memory_pool().free_all_blocks()
def bench_mul(state: bench.State):
"""
Benchmark: b[i] = c[i] * scalar
Unary transform with scalar multiplication.
"""
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_items = int(state.get_int64("Elements{io}"))
_reset_pools()
alloc_stream = as_cupy_stream(state.get_stream())
try:
with alloc_stream:
c = cp.full(num_items, START_C, dtype=dtype)
b = cp.full(num_items, START_B, dtype=dtype)
except (MemoryError, cp.cuda.memory.OutOfMemoryError):
state.skip("Skipping: out of memory.")
return
scalar = dtype(START_SCALAR)
def mul_op(ci):
return ci * scalar
transform = cuda.compute.make_unary_transform(d_in=c, d_out=b, op=mul_op)
state.add_element_count(num_items)
state.add_global_memory_reads(num_items * c.dtype.itemsize)
state.add_global_memory_writes(num_items * b.dtype.itemsize)
def launcher(launch: bench.Launch):
transform(
d_in=c, d_out=b, op=mul_op, num_items=num_items, stream=launch.get_stream()
)
state.exec(launcher, batched=False)
def bench_add(state: bench.State):
"""
Benchmark: c[i] = a[i] + b[i]
Binary transform with addition.
"""
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_items = int(state.get_int64("Elements{io}"))
_reset_pools()
alloc_stream = as_cupy_stream(state.get_stream())
try:
with alloc_stream:
a = cp.full(num_items, START_A, dtype=dtype)
b = cp.full(num_items, START_B, dtype=dtype)
c = cp.full(num_items, START_C, dtype=dtype)
except (MemoryError, cp.cuda.memory.OutOfMemoryError):
state.skip("Skipping: out of memory.")
return
def add_op(ai, bi):
return ai + bi
transform = cuda.compute.make_binary_transform(d_in1=a, d_in2=b, d_out=c, op=add_op)
state.add_element_count(num_items)
state.add_global_memory_reads(2 * num_items * a.dtype.itemsize)
state.add_global_memory_writes(num_items * c.dtype.itemsize)
def launcher(launch: bench.Launch):
transform(
d_in1=a,
d_in2=b,
d_out=c,
op=add_op,
num_items=num_items,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
def bench_triad(state: bench.State):
"""
Benchmark: a[i] = b[i] + scalar * c[i]
Binary transform with fused multiply-add.
"""
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_items = int(state.get_int64("Elements{io}"))
_reset_pools()
alloc_stream = as_cupy_stream(state.get_stream())
try:
with alloc_stream:
a = cp.full(num_items, START_A, dtype=dtype)
b = cp.full(num_items, START_B, dtype=dtype)
c = cp.full(num_items, START_C, dtype=dtype)
except (MemoryError, cp.cuda.memory.OutOfMemoryError):
state.skip("Skipping: out of memory.")
return
scalar = dtype(START_SCALAR)
def triad_op(bi, ci):
return bi + scalar * ci
transform = cuda.compute.make_binary_transform(
d_in1=b, d_in2=c, d_out=a, op=triad_op
)
state.add_element_count(num_items)
state.add_global_memory_reads(2 * num_items * a.dtype.itemsize)
state.add_global_memory_writes(num_items * a.dtype.itemsize)
def launcher(launch: bench.Launch):
transform(
d_in1=b,
d_in2=c,
d_out=a,
op=triad_op,
num_items=num_items,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
def bench_nstream(state: bench.State):
"""
Benchmark: a[i] = a[i] + b[i] + scalar * c[i]
Ternary transform using ZipIterator to combine (a, b, c) as input.
"""
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_items = int(state.get_int64("Elements{io}"))
_reset_pools()
alloc_stream = as_cupy_stream(state.get_stream())
try:
with alloc_stream:
a = cp.full(num_items, START_A, dtype=dtype)
b = cp.full(num_items, START_B, dtype=dtype)
c = cp.full(num_items, START_C, dtype=dtype)
except (MemoryError, cp.cuda.memory.OutOfMemoryError):
state.skip("Skipping: out of memory.")
return
scalar = dtype(START_SCALAR)
# Use ZipIterator to combine 3 inputs into one for unary transform
zip_in = ZipIterator(a, b, c)
def nstream_op(abc):
return abc[0] + abc[1] + scalar * abc[2]
transform = cuda.compute.make_unary_transform(d_in=zip_in, d_out=a, op=nstream_op)
state.add_element_count(num_items)
state.add_global_memory_reads(3 * num_items * a.dtype.itemsize)
state.add_global_memory_writes(num_items * a.dtype.itemsize)
def launcher(launch: bench.Launch):
# Update ZipIterator state for each iteration
zip_in_iter = ZipIterator(a, b, c)
transform(
d_in=zip_in_iter,
d_out=a,
op=nstream_op,
num_items=num_items,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
# Registry of all BabelStream benchmarks
BENCHMARKS = {
"mul": bench_mul,
"add": bench_add,
"triad": bench_triad,
"nstream": bench_nstream,
}
if __name__ == "__main__":
for name, bench_fn in BENCHMARKS.items():
b = bench.register(bench_fn)
b.set_name(name)
b.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 33, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,105 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for complex comparison using cuda.compute.
C++ equivalent: cub/benchmarks/bench/transform/complex_cmp.cu
Notes:
- Uses two overlapping input ranges (in[0:n-1], in[1:n])
- Output is boolean array of size n-1
- Benchmark name is "compare_complex" to match C++
- Migration: Python uses explicit lexicographic compare for complex64.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import math
import cupy as cp
import numpy as np
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import make_binary_transform
_COMPLEX_EPS = np.finfo(np.float32).eps
_COMPLEX_THRESHOLD = _COMPLEX_EPS * 2.0
def less_complex(a, b):
mag0 = math.sqrt(a.real * a.real + a.imag * a.imag)
mag1 = math.sqrt(b.real * b.real + b.imag * b.imag)
if math.isnan(mag0) or math.isnan(mag1):
return False
if math.isinf(mag0) or math.isinf(mag1):
scaler = 0.5
mag0 = math.sqrt(
(a.real * scaler) * (a.real * scaler)
+ (a.imag * scaler) * (a.imag * scaler)
)
mag1 = math.sqrt(
(b.real * scaler) * (b.real * scaler)
+ (b.imag * scaler) * (b.imag * scaler)
)
if abs(mag0 - mag1) < _COMPLEX_THRESHOLD:
phase0 = math.atan2(a.imag, a.real)
phase1 = math.atan2(b.imag, b.real)
return phase0 < phase1
return mag0 < mag1
def bench_compare_complex(state: bench.State):
num_elements = int(state.get_int64("Elements{io}"))
alloc_stream = as_cupy_stream(state.get_stream())
try:
with alloc_stream:
real = generate_data_with_entropy(
num_elements, np.float32, "1.000", alloc_stream
)
imag = generate_data_with_entropy(
num_elements, np.float32, "1.000", alloc_stream
)
d_in = (real + 1j * imag).astype(np.complex64)
d_out = cp.empty(num_elements - 1, dtype=np.bool_)
except (MemoryError, cp.cuda.memory.OutOfMemoryError):
state.skip("Skipping: out of memory.")
return
num_items = num_elements - 1
transformer = make_binary_transform(
d_in1=d_in[:-1], d_in2=d_in[1:], d_out=d_out, op=less_complex
)
state.add_element_count(num_elements)
state.add_global_memory_reads(num_elements * d_in.dtype.itemsize)
state.add_global_memory_writes(num_elements * d_out.dtype.itemsize)
def launcher(launch: bench.Launch):
transformer(
d_in1=d_in[:-1],
d_in2=d_in[1:],
d_out=d_out,
op=less_complex,
num_items=num_items,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_compare_complex)
b.set_name("compare_complex")
b.add_int64_power_of_two_axis("Elements{io}", range(16, 33, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,94 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for transform fibonacci using cuda.compute.
C++ equivalent: cub/benchmarks/bench/transform/fib.cu
Notes:
- Input values are int64 in [0, 42]
- Output values are uint32
- Benchmark name is "fibonacci" to match C++
- Migration: Python fixes offsets to int64.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import make_unary_transform
def fib_op(n):
t1 = 0
t2 = 1
if n < 1:
return t1
if n == 1:
return t1
if n == 2:
return t2
i = 3
while i <= n:
next_val = t1 + t2
t1 = t2
t2 = next_val
i += 1
return t2
def bench_transform_fib(state: bench.State):
# Axes
num_elements = int(state.get_int64("Elements{io}"))
alloc_stream = as_cupy_stream(state.get_stream())
try:
with alloc_stream:
d_in = generate_data_with_entropy(
num_elements,
np.int64,
"1.000",
alloc_stream,
min_val=np.int64(0),
max_val=np.int64(42),
)
d_out = cp.empty(num_elements, dtype=np.uint32)
except (MemoryError, cp.cuda.memory.OutOfMemoryError):
state.skip("Skipping: out of memory.")
return
transformer = make_unary_transform(d_in=d_in, d_out=d_out, op=fib_op)
state.add_element_count(num_elements)
state.add_global_memory_reads(num_elements * d_in.dtype.itemsize)
state.add_global_memory_writes(num_elements * d_out.dtype.itemsize)
def launcher(launch: bench.Launch):
transformer(
d_in=d_in,
d_out=d_out,
op=fib_op,
num_items=num_elements,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_transform_fib)
b.set_name("fibonacci")
b.add_int64_power_of_two_axis("Elements{io}", range(16, 33, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,72 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for fill operation using cuda.compute.ConstantIterator.
C++ equivalent: cub/benchmarks/bench/transform/fill.cu
Notes:
- Migration: Python matches C++ integral_types (I8-I64); no tune parameters.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
from utils import INTEGRAL_TYPES as TYPE_MAP
from utils import as_cupy_stream
import cuda.bench as bench
import cuda.compute
from cuda.compute import ConstantIterator, OpKind
def bench_fill(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_items = int(state.get_int64("Elements{io}"))
# Setup data
alloc_stream = as_cupy_stream(state.get_stream())
try:
with alloc_stream:
d_out = cp.empty(num_items, dtype=dtype)
except (MemoryError, cp.cuda.memory.OutOfMemoryError):
state.skip("Skipping: out of memory.")
return
# Python equivalent of C++ return_constant<T>{42}
constant_it = ConstantIterator(dtype(42))
transform = cuda.compute.make_unary_transform(
d_in=constant_it, d_out=d_out, op=OpKind.IDENTITY
)
state.add_element_count(num_items)
state.add_global_memory_reads(0)
state.add_global_memory_writes(num_items * d_out.dtype.itemsize)
def launcher(launch: bench.Launch):
transform(
d_in=constant_it,
d_out=d_out,
op=OpKind.IDENTITY,
num_items=num_items,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_fill)
b.set_name("fill")
b.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 33, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,89 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for transform grayscale using cuda.compute.
C++ equivalent: cub/benchmarks/bench/transform/grayscale.cu
Notes:
- Input is an RGB struct with three channels
- Output is grayscale value of the same type
- Benchmark name is "grayscale" to match C++
- Migration: Python uses AoS (`gpu_struct`) to mirror C++ `rgb_t<T>`.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
from utils import FLOAT_TYPES as TYPE_MAP
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import gpu_struct, make_unary_transform
def bench_transform_grayscale(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_elements = int(state.get_int64("Elements{io}"))
# Grayscale weights
w_r = dtype(0.2989)
w_g = dtype(0.587)
w_b = dtype(0.114)
RGB = gpu_struct({"r": dtype, "g": dtype, "b": dtype})
def to_grayscale(pixel: RGB):
return w_r * pixel.r + w_g * pixel.g + w_b * pixel.b
alloc_stream = as_cupy_stream(state.get_stream())
try:
with alloc_stream:
r_data = generate_data_with_entropy(
num_elements, dtype, "1.000", alloc_stream
)
g_data = generate_data_with_entropy(
num_elements, dtype, "1.000", alloc_stream
)
b_data = generate_data_with_entropy(
num_elements, dtype, "1.000", alloc_stream
)
d_pixels = cp.empty(num_elements, dtype=RGB.dtype)
d_pixels["r"] = r_data
d_pixels["g"] = g_data
d_pixels["b"] = b_data
d_out = cp.empty(num_elements, dtype=dtype)
except (MemoryError, cp.cuda.memory.OutOfMemoryError):
state.skip("Skipping: out of memory.")
return
transformer = make_unary_transform(d_in=d_pixels, d_out=d_out, op=to_grayscale)
state.add_element_count(num_elements)
state.add_global_memory_reads(num_elements * d_pixels.dtype.itemsize)
state.add_global_memory_writes(num_elements * d_out.dtype.itemsize)
def launcher(launch: bench.Launch):
transformer(
d_in=d_pixels,
d_out=d_out,
op=to_grayscale,
num_items=num_elements,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_transform_grayscale)
b.set_name("grayscale")
b.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 33, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,138 +0,0 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Python benchmark for heavy transform using cuda.compute.
C++ equivalent: cub/benchmarks/bench/transform/heavy.cu
Notes:
- Migration: Python uses Numba local arrays to emulate register pressure.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numba
import numpy as np
from numba import cuda as lang
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
import cuda.compute
def _heavy_op_32(data):
reg = lang.local.array(shape=32, dtype=numba.uint32)
reg[0] = data
for i in range(1, 32):
x = reg[i - 1]
reg[i] = x * x + 1
for i in range(32):
x = reg[i]
reg[i] = (x * x) % 19
for i in range(32):
reg[i] = reg[32 - i - 1] * reg[i]
out = data - data # uint32(0)
for i in range(32):
out += reg[i]
return out
def _heavy_op_64(data):
reg = lang.local.array(shape=64, dtype=numba.uint32)
reg[0] = data
for i in range(1, 64):
x = reg[i - 1]
reg[i] = x * x + 1
for i in range(64):
x = reg[i]
reg[i] = (x * x) % 19
for i in range(64):
reg[i] = reg[64 - i - 1] * reg[i]
out = data - data
for i in range(64):
out += reg[i]
return out
def _heavy_op_128(data):
reg = lang.local.array(shape=128, dtype=numba.uint32)
reg[0] = data
for i in range(1, 128):
x = reg[i - 1]
reg[i] = x * x + 1
for i in range(128):
x = reg[i]
reg[i] = (x * x) % 19
for i in range(128):
reg[i] = reg[128 - i - 1] * reg[i]
out = data - data
for i in range(128):
out += reg[i]
return out
def _heavy_op_256(data):
reg = lang.local.array(shape=256, dtype=numba.uint32)
reg[0] = data
for i in range(1, 256):
x = reg[i - 1]
reg[i] = x * x + 1
for i in range(256):
x = reg[i]
reg[i] = (x * x) % 19
for i in range(256):
reg[i] = reg[256 - i - 1] * reg[i]
out = data - data
for i in range(256):
out += reg[i]
return out
_HEAVY_OPS = {
32: _heavy_op_32,
64: _heavy_op_64,
128: _heavy_op_128,
256: _heavy_op_256,
}
def bench_heavy(state: bench.State):
# Axes
n_regs = int(state.get_string("Heaviness{ct}"))
size = int(state.get_int64("Elements{io}"))
alloc_stream = as_cupy_stream(state.get_stream())
try:
with alloc_stream:
d_in = generate_data_with_entropy(size, np.uint32, "1.000", alloc_stream)
d_out = cp.empty(size, dtype=np.uint32)
except (MemoryError, cp.cuda.memory.OutOfMemoryError):
state.skip("Skipping: out of memory.")
return
op = _HEAVY_OPS[n_regs]
transform = cuda.compute.make_unary_transform(d_in=d_in, d_out=d_out, op=op)
state.add_element_count(size)
state.add_global_memory_reads(size * d_in.dtype.itemsize)
state.add_global_memory_writes(size * d_out.dtype.itemsize)
def launcher(launch: bench.Launch):
transform(
d_in=d_in, d_out=d_out, op=op, num_items=size, stream=launch.get_stream()
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_heavy)
b.set_name("heavy")
b.add_string_axis("Heaviness{ct}", [str(v) for v in (32, 64, 128, 256)])
b.add_int64_power_of_two_axis("Elements{io}", range(16, 33, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,86 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
Python benchmark for transform reduce sum using cuda.compute.
C++ equivalent: cub/benchmarks/bench/transform_reduce/sum.cu
Notes:
- Uses TransformIterator with a square operation
- OffsetT axis from C++ is fixed to Python default (int64)
- Migration: Python fixes offsets and omits int128/complex types.
- OffsetT axis is omitted because the Python API does not expose offset type.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import cupy as cp
import numpy as np
from utils import SIGNED_TYPES as TYPE_MAP
from utils import as_cupy_stream, generate_data_with_entropy
import cuda.bench as bench
from cuda.compute import OpKind, TransformIterator, make_reduce_into
def square_op(x):
return x * x
def bench_transform_reduce_sum(state: bench.State):
type_str = state.get_string("T{ct}")
dtype = TYPE_MAP[type_str]
num_items = int(state.get_int64("Elements{io}"))
alloc_stream = as_cupy_stream(state.get_stream())
with alloc_stream:
d_in = generate_data_with_entropy(num_items, dtype, "1.000", alloc_stream)
d_out = cp.empty(1, dtype=dtype)
transform_it = TransformIterator(d_in, square_op)
h_init = np.zeros(1, dtype=dtype)
reducer = make_reduce_into(
d_in=transform_it, d_out=d_out, op=OpKind.PLUS, h_init=h_init
)
temp_storage_bytes = reducer(
temp_storage=None,
d_in=transform_it,
d_out=d_out,
num_items=num_items,
op=OpKind.PLUS,
h_init=h_init,
)
with alloc_stream:
temp_storage = cp.empty(temp_storage_bytes, dtype=np.uint8)
state.add_element_count(num_items)
state.add_global_memory_reads(num_items * d_in.dtype.itemsize, "Size")
state.add_global_memory_writes(1 * d_out.dtype.itemsize)
def launcher(launch: bench.Launch):
reducer(
temp_storage=temp_storage,
d_in=transform_it,
d_out=d_out,
num_items=num_items,
op=OpKind.PLUS,
h_init=h_init,
stream=launch.get_stream(),
)
state.exec(launcher, batched=False)
if __name__ == "__main__":
b = bench.register(bench_transform_reduce_sum)
b.set_name("base")
b.add_string_axis("T{ct}", list(TYPE_MAP.keys()))
b.add_int64_power_of_two_axis("Elements{io}", range(16, 29, 4))
bench.run_all_benchmarks(sys.argv)

View File

@@ -1,252 +0,0 @@
import cupy as cp
import numpy as np
import cuda.bench as bench
ALL_TYPES = {
"I8": np.int8,
"I16": np.int16,
"I32": np.int32,
"I64": np.int64,
"U8": np.uint8,
"U16": np.uint16,
"U32": np.uint32,
"U64": np.uint64,
"F32": np.float32,
"F64": np.float64,
}
SIGNED_TYPES = {k: ALL_TYPES[k] for k in ("I8", "I16", "I32", "I64", "F32", "F64")}
FLOAT_TYPES = {k: ALL_TYPES[k] for k in ("F32", "F64")}
# Matches C++ integral_types = {int8_t, int16_t, int32_t, int64_t}
INTEGRAL_TYPES = {k: ALL_TYPES[k] for k in ("I8", "I16", "I32", "I64")}
# Matches C++ fundamental_types = {int8..int64, [int128,] float, double}
# int128 is excluded because it is not supported by numpy/cupy.
FUNDAMENTAL_TYPES = {k: ALL_TYPES[k] for k in ("I8", "I16", "I32", "I64", "F32", "F64")}
ENTROPY_TO_STEPS = {
"1.000": 0,
"0.811": 1,
"0.544": 2,
"0.337": 3,
"0.201": 4,
"0.000": 0,
}
ENTROPY_TO_PROB = {
"1.000": 1.0,
"0.811": 0.811,
"0.544": 0.544,
"0.337": 0.337,
"0.201": 0.201,
"0.000": 0.0,
}
def as_cupy_stream(cs: bench.CudaStream) -> cp.cuda.Stream:
"""Convert nvbench CudaStream to CuPy Stream."""
return cp.cuda.ExternalStream(cs.addressof())
def lerp_min_max(dtype, probability):
"""Interpolate between min/max for dtype like nvbench_helper.cuh."""
if probability == 1.0:
if np.issubdtype(dtype, np.integer):
return np.iinfo(dtype).max
return np.finfo(dtype).max
if np.issubdtype(dtype, np.integer):
min_val = float(np.iinfo(dtype).min)
max_val = float(np.iinfo(dtype).max)
else:
min_val = float(np.finfo(dtype).min)
max_val = float(np.finfo(dtype).max)
return dtype(min_val + probability * (max_val - min_val))
def _bitwise_and(a, b, dtype):
if np.issubdtype(dtype, np.floating):
view_dtype = cp.uint32 if dtype == np.float32 else cp.uint64
return (a.view(view_dtype) & b.view(view_dtype)).view(dtype)
return a & b
def _uniform_random(num_elements, dtype, min_val, max_val):
rand = cp.random.random(num_elements)
if np.issubdtype(dtype, np.floating):
return ((float(max_val) - float(min_val)) * rand + float(min_val)).astype(dtype)
min_f = float(min_val)
max_f = float(max_val)
return cp.floor((max_f - min_f + 1) * rand + min_f).astype(dtype)
def generate_data_with_entropy(
num_elements, dtype, entropy_str, stream, min_val=None, max_val=None
):
"""Generate data with nvbench_helper-style bit entropy."""
if min_val is None or max_val is None:
if np.issubdtype(dtype, np.integer):
info = np.iinfo(dtype)
default_min = info.min
default_max = info.max
else:
info = np.finfo(dtype)
default_min = info.tiny
default_max = info.max
min_val = default_min if min_val is None else min_val
max_val = default_max if max_val is None else max_val
steps = ENTROPY_TO_STEPS[entropy_str]
with stream:
if entropy_str == "0.000":
scalar = _uniform_random(1, dtype, min_val, max_val)[0]
data = cp.full(num_elements, scalar, dtype=dtype)
else:
data = _uniform_random(num_elements, dtype, min_val, max_val)
for _ in range(steps):
tmp = _uniform_random(num_elements, dtype, min_val, max_val)
data = _bitwise_and(data, tmp, dtype)
return data
def generate_uniform_segment_offsets(num_elements, min_segment_size, max_segment_size):
num_elements = int(num_elements)
if min_segment_size <= 0:
raise ValueError("min_segment_size must be positive")
if max_segment_size < min_segment_size:
raise ValueError("max_segment_size must be >= min_segment_size")
num_segments_est = int(np.ceil(num_elements / min_segment_size))
if num_segments_est > np.iinfo(np.int32).max:
raise MemoryError("Too many segments for int32 offsets")
sizes = cp.random.randint(
min_segment_size,
max_segment_size + 1,
size=num_segments_est,
dtype=cp.int64,
)
cumsum = cp.cumsum(sizes)
cutoff = int(
cp.searchsorted(
cumsum, cp.asarray(num_elements, dtype=cp.int64), side="left"
).item()
)
sizes = sizes[: cutoff + 1]
prev = 0 if cutoff == 0 else int(cumsum[cutoff - 1].item())
sizes[cutoff] = num_elements - prev
offsets = cp.empty(cutoff + 2, dtype=cp.int64)
offsets[0] = 0
offsets[1:] = cp.cumsum(sizes)
offsets[-1] = num_elements
return offsets
def generate_power_law_offsets(num_elements, num_segments):
if num_segments <= 0:
return cp.asarray([0, num_elements], dtype=cp.int64)
# Mirror nvbench_helper power-law generation:
# draw log-normal samples, normalize to total elements,
# floor to integer segment sizes, then distribute remainder
# across the first `diff` segments.
samples = cp.random.lognormal(3.0, 1.2, size=num_segments)
if int(cp.count_nonzero(samples).item()) == 0:
samples = cp.ones(num_segments, dtype=cp.float64)
sample_sum = float(samples.sum().item())
sizes = cp.floor(samples * num_elements / sample_sum).astype(cp.int64)
diff = int(num_elements - sizes.sum().item())
if diff > 0:
sizes[:diff] += 1
offsets = cp.empty(num_segments + 1, dtype=cp.int64)
offsets[0] = 0
offsets[1:] = cp.cumsum(sizes)
return offsets
def generate_fixed_segment_offsets(num_elements, segment_size, stream):
num_segments = max(1, num_elements // segment_size)
actual_elements = num_segments * segment_size
with stream:
start_offsets = cp.arange(0, actual_elements, segment_size, dtype=np.int64)
end_offsets = cp.arange(
segment_size, actual_elements + 1, segment_size, dtype=np.int64
)
return start_offsets, end_offsets, num_segments, actual_elements
def generate_key_segments(
num_elements, key_dtype, min_segment_size, max_segment_size, stream
):
"""Generate GPU key segments (runs of equal keys) matching C++ generate.uniform.key_segments.
All computation stays on GPU via CuPy. We avoid ``cp.repeat`` (which
doesn't accept a device array for *repeats*) by building a segment-id
array through ``cp.searchsorted`` on cumulative offsets instead.
"""
num_elements = int(num_elements)
if min_segment_size <= 0:
raise ValueError("min_segment_size must be positive")
if max_segment_size < min_segment_size:
raise ValueError("max_segment_size must be >= min_segment_size")
num_segments_est = int(np.ceil(num_elements / min_segment_size))
if num_segments_est > np.iinfo(np.int32).max:
raise MemoryError("Too many segments for int32 offsets")
with stream:
sizes = cp.random.randint(
min_segment_size,
max_segment_size + 1,
size=num_segments_est,
dtype=cp.int64,
)
cumsum = cp.cumsum(sizes)
# Find how many full segments fit within num_elements
cutoff = int(
cp.searchsorted(
cumsum, cp.asarray(num_elements, dtype=cp.int64), side="left"
).item()
)
sizes = sizes[: cutoff + 1]
prev = 0 if cutoff == 0 else int(cumsum[cutoff - 1].item())
sizes[cutoff] = num_elements - prev
# Build cumulative offsets for the final segments
offsets = cp.empty(cutoff + 2, dtype=cp.int64)
offsets[0] = 0
offsets[1:] = cp.cumsum(sizes)
# Instead of cp.repeat (which doesn't support device repeats),
# use searchsorted to map each element index to its segment id.
indices = cp.arange(num_elements, dtype=cp.int64)
# searchsorted(offsets[1:], indices, side="right") gives the segment id
segment_ids = cp.searchsorted(offsets[1:], indices, side="right")
# Map segment ids to key values, wrapping within dtype range
if np.issubdtype(key_dtype, np.integer):
info = np.iinfo(key_dtype)
if np.dtype(key_dtype).itemsize < 8:
range_size = int(info.max) - int(info.min) + 1
keys = ((segment_ids % range_size) + int(info.min)).astype(
key_dtype, copy=False
)
else:
# For int64, avoid Python overflow: just cast directly
keys = segment_ids.astype(key_dtype, copy=False)
else:
keys = segment_ids.astype(key_dtype, copy=False)
return keys