Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions cuda_core/cuda/core/_launcher.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,33 @@ def launch(
HANDLE_RETURN(cydriver.cuLaunchKernelEx(&drv_cfg, func_handle, args_ptr, NULL))


def _cooperative_block_count(config: LaunchConfig):
"""Number of thread blocks a launch of ``config`` asks the driver for.

``config.grid`` counts *clusters*, not blocks, whenever ``cluster`` is set
-- see :class:`LaunchConfig` and ``_to_native_launch_config``, which
multiplies the two together before filling in ``gridDim``. Residency limits
are expressed in blocks, so any comparison against one has to go through
here.
"""
if config.cluster is None:
return prod(config.grid)
return prod(config.grid) * prod(config.cluster)


cdef _check_cooperative_launch(kernel: Kernel, config: LaunchConfig, stream: Stream):
dev = stream.device
num_sm = dev.properties.multiprocessor_count
max_grid_size = (
kernel.occupancy.max_active_blocks_per_multiprocessor(prod(config.block), config.shmem_size) * num_sm
)
if prod(config.grid) > max_grid_size:
num_blocks = _cooperative_block_count(config)
if num_blocks > max_grid_size:
# For now let's try not to be smart and adjust the grid size behind users' back.
# We explicitly ask users to adjust.
x, y, z = config.grid
raise ValueError(f"The specified grid size ({x} * {y} * {z}) exceeds the limit ({max_grid_size})")
detail = f"{x} * {y} * {z}"
if config.cluster is not None:
cx, cy, cz = config.cluster
detail = f"{detail} clusters of {cx} * {cy} * {cz} blocks = {num_blocks} blocks"
raise ValueError(f"The specified grid size ({detail}) exceeds the limit ({max_grid_size} blocks)")
7 changes: 7 additions & 0 deletions cuda_core/docs/source/release/1.2.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ Fixes and enhancements
Windows, both ``ctypes.CFUNCTYPE`` and ``ctypes.WINFUNCTYPE`` are accepted.
(`#2439 <https://github.com/NVIDIA/cuda-python/issues/2439>`__)

- The cooperative-launch residency check now counts blocks when
:class:`LaunchConfig` specifies a ``cluster``. ``grid`` counts clusters in
that case, so the check compared cluster counts against a per-device block
limit and under-counted by ``prod(cluster)`` -- a cooperative launch that
genuinely over-subscribes the device passed the guard. The error message now
spells out the block count as well.

Deprecation Notices
-------------------

Expand Down
35 changes: 35 additions & 0 deletions cuda_core/tests/test_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,41 @@ class _FakeDev:
LaunchConfig(grid=1, block=1, is_cooperative=True)


@pytest.mark.agent_authored(model="claude-opus-5")
def test_cooperative_block_count_counts_blocks_not_clusters(monkeypatch):
"""The cooperative residency check compares against a *block* limit.

``config.grid`` counts clusters whenever ``cluster`` is set (that is what
``_to_native_launch_config`` multiplies out before filling in ``gridDim``),
so comparing ``prod(config.grid)`` against
``max_active_blocks_per_multiprocessor * num_sm`` under-counted by
``prod(config.cluster)`` and let an over-subscribed cooperative launch
through the guard it exists to trip.

Device is mocked so this runs on any GPU.
"""
from cuda.core import _launch_config as _lc_mod
from cuda.core._launcher import _cooperative_block_count

class _FakeProps:
cooperative_launch = True

class _FakeDev:
compute_capability = (9, 0)
properties = _FakeProps()

monkeypatch.setattr(_lc_mod, "Device", lambda: _FakeDev())

# No cluster: grid is already a block count.
config = LaunchConfig(grid=(2, 3, 1), block=32, is_cooperative=True)
assert _cooperative_block_count(config) == 6

# With a cluster the driver is asked for prod(grid) * prod(cluster) blocks.
config = LaunchConfig(grid=(2, 3, 1), cluster=(2, 2, 1), block=32, is_cooperative=True)
assert config.grid == (2, 3, 1) # grid is still stored in cluster units
assert _cooperative_block_count(config) == 24


def test_to_native_launch_config_cooperative(monkeypatch):
"""Covers the is_cooperative branch of _to_native_launch_config; Device is mocked so it runs on any GPU."""
from cuda.bindings import driver
Expand Down
Loading