Files
nvbench/python/examples/cuda_compute_segmented_reduce.py
Oleksandr Pavlyk 44ec7de6bd Implement decorators to register benchmarks add axis and options (#347)
* Add decorators for registering benchmarks and adding axis

cuda.bench.register(fn) continues returning Benchmark, and supports
legacy use.

New signature added:
   cuda.bench.register():
      Returns a decorator

```
@bench.register()
@bench.axis.float64("Duration (s)", [7e-5, 1e-4, 5e-4])
@bench.option.min_samples(120)
def single_float64_axis(state: bench.State):
   ...
```

* Remove example/auto_throughput.py

The C++ counterpart's purpose is to demonstrate use of CUPTI
metrics, but these are not supported in Python bindings, so
this example is a duplicate of example/throughput.py

* Add wrong decorator order test for bench.axis.*

* Strengthen type annotation for register function

Acting on code rabbit nit-pick require that function being
registered take cuda.bench.State object as an argument.

Verified the fix as

```
(py313) :~/repos/nvbench/python$ python -m mypy --ignore-missing-import /tmp/t.py
/tmp/t.py:8: error: Argument 1 has incompatible type "Callable[[], None]"; expected "Callable[[State], None]"  [arg-type]
Found 1 error in 1 file (checked 1 source file)
(py313) :~/repos/nvbench/python$ nl -ba /tmp/t.py
     1  # /tmp/check_nvbench_register.py
     2  import cuda.bench as bench
     3
     4  @bench.register()
     5  def good(state: bench.State) -> None:
     6      pass
     7
     8  @bench.register()
     9  def bad() -> None:
    10      pass
```

* Replace use of global variable with thread-safe lru_cache

This improves thread-safety of module initialization.

* Abide by RUF005 linting rule

* Expand docstrings regarding cuda.bench.register() decorator

It explains to the user what the decorator does and provides
a concise usage example.

* Sharpen wording on exception maybe-thrown by decorator
2026-05-14 15:41:30 -05:00

118 lines
3.2 KiB
Python

# Copyright 2025-2026 NVIDIA Corporation
#
# Licensed under the Apache License, Version 2.0 with the LLVM exception
# (the "License"); you may not use this file except in compliance with
# the License.
#
# You may obtain a copy of the License at
#
# http://llvm.org/foundation/relicensing/LICENSE.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import cuda.bench as bench
import cuda.compute.algorithms as algorithms
import cuda.compute.iterators as iterators
import cuda.core as core
import cupy as cp
import numpy as np
from cuda.compute import OpKind
def as_core_Stream(cs: bench.CudaStream) -> core.Stream:
return core.Stream.from_handle(cs.addressof())
def as_cp_ExternalStream(cs: bench.CudaStream) -> cp.cuda.ExternalStream:
return cp.cuda.Stream.from_external(cs)
@bench.register()
@bench.axis.int64("numElems", [2**20, 2**22, 2**24])
@bench.axis.int64("numCols", [1024, 2048, 4096, 8192])
def segmented_reduce(state: bench.State):
"Benchmark segmented_reduce example"
n_elems = state.get_int64("numElems")
n_cols = state.get_int64("numCols")
n_rows = n_elems // n_cols
state.add_summary("numRows", n_rows)
cp_stream = as_cp_ExternalStream(state.get_stream())
def make_scaler(step):
def scale(row_id):
return row_id * step
return scale
zero = np.int32(0)
row_offset = make_scaler(np.int32(n_cols))
start_offsets = iterators.TransformIterator(
iterators.CountingIterator(zero), row_offset
)
end_offsets = start_offsets + 1
h_init = np.zeros(tuple(), dtype=np.int32)
with cp_stream:
rng = cp.random.default_rng()
mat = rng.integers(low=-31, high=32, dtype=np.int32, size=(n_rows, n_cols))
d_input = mat
d_output = cp.empty(n_rows, dtype=d_input.dtype)
add_op = OpKind.PLUS
alg = algorithms.make_segmented_reduce(
d_in=d_input,
d_out=d_output,
start_offsets_in=start_offsets,
end_offsets_in=end_offsets,
op=add_op,
h_init=h_init,
)
cccl_stream = state.get_stream()
# query size of temporary storage and allocate
temp_nbytes = alg(
temp_storage=None,
d_in=d_input,
d_out=d_output,
op=add_op,
num_segments=n_rows,
start_offsets_in=start_offsets,
end_offsets_in=end_offsets,
h_init=h_init,
stream=cccl_stream,
)
h_init = np.zeros(tuple(), dtype=np.int32)
with cp_stream:
temp_storage = cp.empty(temp_nbytes, dtype=cp.uint8)
def launcher(launch: bench.Launch):
s = launch.get_stream()
alg(
temp_storage=temp_storage,
d_in=d_input,
d_out=d_output,
op=add_op,
num_segments=n_rows,
start_offsets_in=start_offsets,
end_offsets_in=end_offsets,
h_init=h_init,
stream=s,
)
state.exec(launcher)
if __name__ == "__main__":
bench.run_all_benchmarks(sys.argv)