Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1c6a5e1
cuda.core: expose driver-populated fields on WorkqueueResource
leofang Jul 9, 2026
2afae3f
cuda.core: fill in PR number in release-notes entry
leofang Jul 9, 2026
a22005d
cuda.core: drop TYPE_CHECKING Device import to satisfy cython-lint
leofang Jul 9, 2026
08a6419
cuda.core: fold #2329 + #2330 release notes; parametrize enum test; a…
leofang Jul 9, 2026
e4ebbba
cuda.core tests: drop init_cuda from multi-GPU workqueue test
leofang Jul 9, 2026
1c3ceb7
cuda.core tests: drop unnecessary set_current calls in multi-GPU work…
leofang Jul 9, 2026
9092dc4
cuda.core tests: extract shared _RESOURCE_UNAVAILABLE_ERRORS tuple wi…
leofang Jul 9, 2026
aebf271
cuda.core: restore Device TYPE_CHECKING import with noqa
leofang Jul 9, 2026
cb15baf
cuda.core: simplify noqa to bare form (cython-lint didn't parse the p…
leofang Jul 9, 2026
202959b
cuda.core: use bare Device annotation on WorkqueueResource.device
leofang Jul 9, 2026
0a1c7df
cuda.core tests: register WorkqueueSharingScopeType in _CASES
leofang Jul 9, 2026
8fd22bc
cuda.core tests: gate WorkqueueSharingScopeType binding entry on CUDA…
leofang Jul 9, 2026
106d5c1
cuda.core tests: sharpen CUDA-version boundary in WorkqueueSharingSco…
leofang Jul 9, 2026
be917b6
cuda.core: apply Mike's suggestion for WorkqueueResourceOptions.shari…
leofang Jul 9, 2026
a9939b5
fix typing
leofang Jul 9, 2026
37483ae
Update _device_resources.pyi
leofang Jul 9, 2026
ccb50cc
cuda.core: regenerate .pyi to match stubgen-pyx output
leofang Jul 9, 2026
119238b
Apply suggestions from code review
leofang Jul 9, 2026
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
37 changes: 33 additions & 4 deletions cuda_core/cuda/core/_device_resources.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ from __future__ import annotations
from collections.abc import Sequence as SequenceABC
from dataclasses import dataclass

from cuda.core._device import Device
from cuda.core.typing import WorkqueueSharingScopeType


@dataclass
class SMResourceOptions:
Expand Down Expand Up @@ -44,9 +47,9 @@ class WorkqueueResourceOptions:

Attributes
----------
sharing_scope : str, optional
Workqueue sharing scope. Accepted values: ``"device_ctx"``
or ``"green_ctx_balanced"``. (Default to ``None``)
sharing_scope : :class:`~cuda.core.typing.WorkqueueSharingScopeType` | str, optional
Workqueue sharing scope. Accepted values: ``"device_ctx"`` or
``"green_ctx_balanced"``.
concurrency_limit : int, optional
Expected maximum number of concurrent stream-ordered
workloads. Must be ``>= 1`` when set. The effective
Expand All @@ -55,7 +58,7 @@ class WorkqueueResourceOptions:
this cap, but the driver will not guarantee that work
submission remains non-overlapping. (Default to ``None``)
"""
sharing_scope: str | None = None
sharing_scope: WorkqueueSharingScopeType | str | None = None
concurrency_limit: int | None = None

def __post_init__(self):
Expand Down Expand Up @@ -125,6 +128,32 @@ class WorkqueueResource:
def handle(self) -> int:
"""Return the address of the underlying config ``CUdevResource`` struct."""

@property
def sharing_scope(self) -> WorkqueueSharingScopeType:
"""Current sharing scope of this workqueue resource.

Returns the :class:`~cuda.core.typing.WorkqueueSharingScopeType`
member corresponding to the driver-populated
``wqConfig.sharingScope`` field. It can be updated via
:meth:`configure` with
:attr:`WorkqueueResourceOptions.sharing_scope`.
"""

@property
def concurrency_limit(self) -> int:
"""Current expected maximum concurrent stream-ordered workloads.

Reflects the driver-populated ``wqConfig.wqConcurrencyLimit`` field.
When first queried from a device, this matches the driver-reported
cap (typically ``CUDA_DEVICE_MAX_CONNECTIONS``). It can be updated
via :meth:`configure` with
:attr:`WorkqueueResourceOptions.concurrency_limit`.
"""

@property
def device(self) -> Device:
"""The :class:`~cuda.core.Device` this workqueue resource is available on."""

def configure(self, options: WorkqueueResourceOptions) -> None:
"""Configure the workqueue resource in place.

Expand Down
64 changes: 60 additions & 4 deletions cuda_core/cuda/core/_device_resources.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ from __future__ import annotations

from collections.abc import Sequence as SequenceABC
from dataclasses import dataclass
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from cuda.core._device import Device
from cuda.core.typing import WorkqueueSharingScopeType

from libc.stdint cimport intptr_t
from libc.stdlib cimport free, malloc
Expand Down Expand Up @@ -126,9 +131,9 @@ cdef class WorkqueueResourceOptions:

Attributes
----------
sharing_scope : str, optional
Workqueue sharing scope. Accepted values: ``"device_ctx"``
or ``"green_ctx_balanced"``. (Default to ``None``)
sharing_scope : :class:`~cuda.core.typing.WorkqueueSharingScopeType` | str, optional
Workqueue sharing scope. Accepted values: ``"device_ctx"`` or
``"green_ctx_balanced"``.
concurrency_limit : int, optional
Expected maximum number of concurrent stream-ordered
workloads. Must be ``>= 1`` when set. The effective
Expand All @@ -138,7 +143,7 @@ cdef class WorkqueueResourceOptions:
submission remains non-overlapping. (Default to ``None``)
"""

sharing_scope: str | None = None
sharing_scope: WorkqueueSharingScopeType | str | None = None
concurrency_limit: int | None = None

def __post_init__(self):
Expand Down Expand Up @@ -554,6 +559,57 @@ cdef class WorkqueueResource:
"""Return the address of the underlying config ``CUdevResource`` struct."""
return <intptr_t>(&self._wq_config_resource)

@property
def sharing_scope(self) -> WorkqueueSharingScopeType:
"""Current sharing scope of this workqueue resource.

Returns the :class:`~cuda.core.typing.WorkqueueSharingScopeType`
member corresponding to the driver-populated
``wqConfig.sharingScope`` field. It can be updated via
:meth:`configure` with
:attr:`WorkqueueResourceOptions.sharing_scope`.
"""
IF CUDA_CORE_BUILD_MAJOR >= 13:
from cuda.core.typing import WorkqueueSharingScopeType
cdef object scope = self._wq_config_resource.wqConfig.sharingScope
if scope == cydriver.CUdevWorkqueueConfigScope.CU_WORKQUEUE_SCOPE_DEVICE_CTX:
return WorkqueueSharingScopeType.DEVICE_CTX
elif scope == cydriver.CUdevWorkqueueConfigScope.CU_WORKQUEUE_SCOPE_GREEN_CTX_BALANCED:
return WorkqueueSharingScopeType.GREEN_CTX_BALANCED
Comment on lines +575 to +578

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Could use a match statement here.

raise RuntimeError(f"Unknown sharing scope enum value: {scope}")
Comment thread
leofang marked this conversation as resolved.
ELSE:
raise RuntimeError(
"WorkqueueResource requires cuda.core to be built with CUDA 13.x bindings"
)

@property
def concurrency_limit(self) -> int:
"""Current expected maximum concurrent stream-ordered workloads.

Reflects the driver-populated ``wqConfig.wqConcurrencyLimit`` field.
When first queried from a device, this matches the driver-reported
cap (typically ``CUDA_DEVICE_MAX_CONNECTIONS``). It can be updated
via :meth:`configure` with
:attr:`WorkqueueResourceOptions.concurrency_limit`.
"""
IF CUDA_CORE_BUILD_MAJOR >= 13:
return self._wq_config_resource.wqConfig.wqConcurrencyLimit
ELSE:
raise RuntimeError(
"WorkqueueResource requires cuda.core to be built with CUDA 13.x bindings"
)

@property
def device(self) -> Device:
"""The :class:`~cuda.core.Device` this workqueue resource is available on."""
IF CUDA_CORE_BUILD_MAJOR >= 13:
from cuda.core._device import Device # avoid circular import
return Device(int(self._wq_config_resource.wqConfig.device))
ELSE:
raise RuntimeError(
"WorkqueueResource requires cuda.core to be built with CUDA 13.x bindings"
)

def configure(self, options: WorkqueueResourceOptions) -> None:
"""Configure the workqueue resource in place.

Expand Down
16 changes: 16 additions & 0 deletions cuda_core/cuda/core/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class StrEnum(str, Enum):
"VirtualMemoryGranularityType",
"VirtualMemoryHandleType",
"VirtualMemoryLocationType",
"WorkqueueSharingScopeType",
]


Expand Down Expand Up @@ -296,4 +297,19 @@ class ReadModeType(StrEnum):
NORMALIZED_FLOAT = "normalized_float"


class WorkqueueSharingScopeType(StrEnum):
"""Sharing scope for :class:`~cuda.core.WorkqueueResource`.

* ``DEVICE_CTX`` — use all shared workqueue resources across all
contexts (default driver behavior).
* ``GREEN_CTX_BALANCED`` — when possible, use non-overlapping
workqueue resources with other balanced green contexts.

.. versionadded:: 1.1.0
"""

DEVICE_CTX = "device_ctx"
GREEN_CTX_BALANCED = "green_ctx_balanced"


del StrEnum
16 changes: 13 additions & 3 deletions cuda_core/docs/source/release/1.1.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,19 @@ New features
(`#2068 <https://github.com/NVIDIA/cuda-python/pull/2068>`__,
closes `#2049 <https://github.com/NVIDIA/cuda-python/issues/2049>`__)

- Added ``concurrency_limit`` to :class:`WorkqueueResourceOptions` to
configure the expected maximum concurrent stream-ordered workloads.
(`#2329 <https://github.com/NVIDIA/cuda-python/pull/2329>`__)
- Extended :class:`WorkqueueResource` and :class:`WorkqueueResourceOptions`
to cover the full driver-side workqueue-config surface. Added
``concurrency_limit`` to :class:`WorkqueueResourceOptions` for
configuring the expected maximum concurrent stream-ordered workloads,
and read-only :attr:`WorkqueueResource.sharing_scope`,
:attr:`~WorkqueueResource.concurrency_limit`, and
:attr:`~WorkqueueResource.device` properties for round-tripping the
driver-populated values. Added
:class:`~cuda.core.typing.WorkqueueSharingScopeType` StrEnum accepted
by :attr:`WorkqueueResourceOptions.sharing_scope` in addition to raw
strings.
(`#2329 <https://github.com/NVIDIA/cuda-python/pull/2329>`__,
`#2330 <https://github.com/NVIDIA/cuda-python/pull/2330>`__)
Comment thread
leofang marked this conversation as resolved.

Fixes and enhancements
----------------------
Expand Down
18 changes: 18 additions & 0 deletions cuda_core/tests/test_enum_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,24 @@
}


# CUdevWorkqueueConfigScope was added to the CUDA driver in 13.1 (missing
# from the 13.0.0 cuda.h and earlier); on cuda-bindings for CUDA 12.x or
# 13.0.x, WorkqueueSharingScopeType has no driver-side counterpart to
# check against.
if hasattr(driver, "CUdevWorkqueueConfigScope"):
_CASES.append(
(
driver.CUdevWorkqueueConfigScope,
cuda.core.typing.WorkqueueSharingScopeType,
None,
set(),
set(),
)
)
else:
_UNBOUND_STR_ENUMS.add(cuda.core.typing.WorkqueueSharingScopeType)


@pytest.mark.parametrize(
"binding, str_enum, mapping, binding_unmapped, str_enum_unmapped",
_CASES,
Expand Down
42 changes: 39 additions & 3 deletions cuda_core/tests/test_green_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
launch,
)
from cuda.core._utils.cuda_utils import CUDAError
from cuda.core.typing import WorkqueueSharingScopeType

# ---------------------------------------------------------------------------
# Kernel source
Expand All @@ -42,12 +43,23 @@
# ---------------------------------------------------------------------------


# Resource queries (dev.resources.sm, dev.resources.workqueue) can fail in
# three orthogonal ways when the resource type isn't supported here:
# * RuntimeError — cuda.core was built against CUDA bindings that don't
# expose the resource (e.g. WorkqueueResource on 12.x).
# * ValueError — the runtime driver version is too old to support the
# resource (green-context / workqueue support gates).
# * CUDAError — the driver rejected the specific device-level query.
# Skip on any of them.
_RESOURCE_UNAVAILABLE_ERRORS = (RuntimeError, ValueError, CUDAError)


@pytest.fixture
def sm_resource(init_cuda):
"""Query SM resources from the device, skip if unsupported."""
try:
return init_cuda.resources.sm
except (RuntimeError, ValueError, CUDAError) as exc:
except _RESOURCE_UNAVAILABLE_ERRORS as exc:
pytest.skip(str(exc))


Expand All @@ -56,7 +68,7 @@ def wq_resource(init_cuda):
"""Query workqueue resources from the device, skip if unsupported."""
try:
return init_cuda.resources.workqueue
except (RuntimeError, ValueError, CUDAError) as exc:
except _RESOURCE_UNAVAILABLE_ERRORS as exc:
pytest.skip(str(exc))


Expand Down Expand Up @@ -207,21 +219,45 @@ def test_arch_constraints_hopper_plus(self, init_cuda, sm_resource):


class TestWorkqueueResource:
def test_query(self, wq_resource):
def test_query(self, init_cuda, wq_resource):
assert wq_resource.handle != 0
assert isinstance(wq_resource.sharing_scope, WorkqueueSharingScopeType)
assert wq_resource.concurrency_limit >= 1
assert wq_resource.device.device_id == init_cuda.device_id

def test_configure_none_is_noop(self, wq_resource):
assert wq_resource.configure(WorkqueueResourceOptions(sharing_scope=None)) is None

def test_configure_valid_scope(self, wq_resource):
wq_resource.configure(WorkqueueResourceOptions(sharing_scope="green_ctx_balanced"))

@pytest.mark.parametrize("scope", list(WorkqueueSharingScopeType))
def test_configure_scope_with_enum(self, wq_resource, scope):
wq_resource.configure(WorkqueueResourceOptions(sharing_scope=scope))
assert wq_resource.sharing_scope is scope

def test_device_id_matches_source_multi_gpu(self):
from cuda.core import Device, system

if system.get_num_devices() < 2:
pytest.skip("requires 2+ GPUs")
dev0 = Device(0)
dev1 = Device(1)
try:
wq0 = dev0.resources.workqueue
wq1 = dev1.resources.workqueue
except _RESOURCE_UNAVAILABLE_ERRORS as exc:
pytest.skip(str(exc))
assert wq0.device.device_id == 0
assert wq1.device.device_id == 1

def test_invalid_scope_raises_at_construction(self):
with pytest.raises(ValueError, match="Unknown sharing_scope"):
WorkqueueResourceOptions(sharing_scope="bogus")

def test_configure_concurrency_limit(self, wq_resource):
wq_resource.configure(WorkqueueResourceOptions(concurrency_limit=4))
assert wq_resource.concurrency_limit == 4

def test_configure_concurrency_and_scope(self, wq_resource):
wq_resource.configure(
Expand Down