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
8 changes: 3 additions & 5 deletions cuda_core/cuda/core/_device_resources.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ from cuda.core._resource_handles cimport ContextHandle, GreenCtxHandle, as_cu, g
from cuda.core._utils.cuda_utils cimport check_or_create_options, HANDLE_RETURN
from cuda.core._utils.cuda_utils import is_sequence
from cuda.core._utils.version cimport cy_binding_version, cy_driver_version
from cuda.core._utils.validators import check_str_enum


__all__ = [
Expand Down Expand Up @@ -147,11 +148,8 @@ cdef class WorkqueueResourceOptions:
concurrency_limit: int | None = None

def __post_init__(self):
if self.sharing_scope not in (None, "device_ctx", "green_ctx_balanced"):
raise ValueError(
f"Unknown sharing_scope: {self.sharing_scope!r}. "
"Expected 'device_ctx' or 'green_ctx_balanced'."
)
from cuda.core.typing import WorkqueueSharingScopeType
check_str_enum(self.sharing_scope, WorkqueueSharingScopeType, allow_none=True)
if self.concurrency_limit is not None and self.concurrency_limit < 1:
raise ValueError(
f"concurrency_limit must be >= 1, got {self.concurrency_limit}"
Expand Down
12 changes: 4 additions & 8 deletions cuda_core/cuda/core/_memory/_managed_memory_resource.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import warnings
from cuda.core._memory._managed_buffer import ManagedBuffer
from cuda.core.typing import ManagedMemoryLocationType

IF CUDA_CORE_BUILD_MAJOR >= 13:
from cuda.core._utils.validators import check_str_enum

if TYPE_CHECKING:
from cuda.core.graph import GraphBuilder

Expand Down Expand Up @@ -163,9 +166,6 @@ cdef class ManagedMemoryResource(_MemPool):


IF CUDA_CORE_BUILD_MAJOR >= 13:
cdef tuple _VALID_LOCATION_TYPES = ("device", "host", "host_numa")


cdef _resolve_preferred_location(ManagedMemoryResourceOptions opts):
"""Resolve preferred location options into driver and stored values.

Expand All @@ -175,11 +175,7 @@ IF CUDA_CORE_BUILD_MAJOR >= 13:
cdef object pref_loc = opts.preferred_location if opts is not None else None
cdef object pref_type = opts.preferred_location_type if opts is not None else None

if pref_type is not None and pref_type not in _VALID_LOCATION_TYPES:
raise ValueError(
f"preferred_location_type must be one of {_VALID_LOCATION_TYPES!r} "
f"or None, got {pref_type!r}"
)
check_str_enum(pref_type, ManagedMemoryLocationType, allow_none=True)

if pref_type is None:
# Legacy behavior
Expand Down
35 changes: 35 additions & 0 deletions cuda_core/cuda/core/_utils/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0


def format_or_list(values):
"""Format an iterable of values as a human-readable ``A, B or C`` string.

Each value is passed through :func:`repr`. A single value is returned
as-is; two values are joined with ``" or "``; three or more use a
comma-separated list with ``" or "`` before the last item.
"""
reprs = [repr(v) for v in values]
if len(reprs) <= 1:
return reprs[0] if reprs else ""
*head, tail = reprs
return ", ".join(head) + " or " + tail


def check_str_enum(value, enum_class, *, allow_none=False):
"""Raise ValueError if *value* is not a member of *enum_class*.

Derives the list of acceptable values from the enum itself so callers
do not need to maintain a parallel copy of the valid strings.

If *allow_none* is True, ``None`` is also accepted and included in the
error message when an invalid value is provided.
"""
if allow_none and value is None:
return
if value not in {m.value for m in enum_class}:
valid = sorted(m.value for m in enum_class)
if allow_none:
valid = [None, *valid]
raise ValueError(f"{value!r} is not a valid {enum_class.__name__}. Must be {format_or_list(valid)}")
5 changes: 2 additions & 3 deletions cuda_core/cuda/core/graph/_graph_node.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import ctypes as ct
import weakref

from cuda.core.graph._adjacency_set_proxy import AdjacencySetProxy
from cuda.core._utils.validators import check_str_enum
from cuda.core._utils.cuda_utils import driver
from cuda.core.typing import GraphMemoryType

Expand Down Expand Up @@ -787,6 +788,7 @@ cdef inline AllocNode GN_alloc(GraphNode self, size_t size, object device,
alloc_params.poolProps.handleTypes = cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_NONE
alloc_params.bytesize = size

check_str_enum(memory_type_str, GraphMemoryType)
if memory_type_str == "device":
alloc_params.poolProps.allocType = cydriver.CUmemAllocationType.CU_MEM_ALLOCATION_TYPE_PINNED
alloc_params.poolProps.location.type = cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_DEVICE
Expand All @@ -802,9 +804,6 @@ cdef inline AllocNode GN_alloc(GraphNode self, size_t size, object device,
alloc_params.poolProps.location.id = device_id
ELSE:
raise ValueError("memory_type='managed' requires CUDA 13.0 or later")
else:
raise ValueError(f"Invalid memory_type: {memory_type_str!r}. "
"Must be 'device', 'host', or 'managed'.")

if access_descs.size() > 0:
alloc_params.accessDescs = access_descs.data()
Expand Down
11 changes: 7 additions & 4 deletions cuda_core/cuda/core/utils/_program_cache/_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@
from cuda.core._utils.cuda_utils import (
nvrtc as _nvrtc,
)
from cuda.core._utils.validators import check_str_enum, format_or_list
from cuda.core.typing import SourceCodeType

# Bump when the key schema changes in a way that invalidates existing caches.
_KEY_SCHEMA_VERSION = 2

_VALID_CODE_TYPES = frozenset({"c++", "ptx", "nvvm"})
_VALID_TARGET_TYPES = frozenset({"ptx", "cubin", "ltoir"})

# code_type -> allowed target_type set, mirroring Program.compile's
Expand Down Expand Up @@ -752,10 +753,12 @@ def make_program_cache_key(
# the lowercase form.
code_type = code_type.lower() if isinstance(code_type, str) else code_type
target_type = target_type.lower() if isinstance(target_type, str) else target_type
if code_type not in _VALID_CODE_TYPES:
raise ValueError(f"code_type={code_type!r} is not supported (must be one of {sorted(_VALID_CODE_TYPES)})")
check_str_enum(code_type, SourceCodeType)
if target_type not in _VALID_TARGET_TYPES:
raise ValueError(f"target_type={target_type!r} is not supported (must be one of {sorted(_VALID_TARGET_TYPES)})")
raise ValueError(
f"target_type={target_type!r} is not supported by the program cache "
f"(must be {format_or_list(sorted(_VALID_TARGET_TYPES))})"
)
supported_for_code = _SUPPORTED_TARGETS_BY_CODE_TYPE[code_type]
if target_type not in supported_for_code:
raise ValueError(
Expand Down
2 changes: 1 addition & 1 deletion cuda_core/tests/graph/test_graph_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@ def test_alloc_free_chain(sample_graphdef):

def test_alloc_memory_type_invalid(sample_graphdef):
"""Invalid memory type raises ValueError."""
with pytest.raises(ValueError, match="Invalid memory_type"):
with pytest.raises(ValueError, match="'invalid' is not a valid GraphMemoryType. Must be "):
sample_graphdef.allocate(ALLOC_SIZE, memory_type="invalid")


Expand Down
2 changes: 1 addition & 1 deletion cuda_core/tests/test_green_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def test_device_id_matches_source_multi_gpu(self):
assert wq1.device.device_id == 1

def test_invalid_scope_raises_at_construction(self):
with pytest.raises(ValueError, match="Unknown sharing_scope"):
with pytest.raises(ValueError, match="'bogus' is not a valid WorkqueueSharingScopeType. Must be "):
WorkqueueResourceOptions(sharing_scope="bogus")

def test_configure_concurrency_limit(self, wq_resource):
Expand Down
2 changes: 1 addition & 1 deletion cuda_core/tests/test_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1278,7 +1278,7 @@ def test_managed_memory_resource_preferred_location_validation(init_cuda):
device.set_current()

# Invalid preferred_location_type
with pytest.raises(ValueError, match="preferred_location_type must be one of"):
with pytest.raises(ValueError, match="'invalid' is not a valid ManagedMemoryLocationType. Must be "):
ManagedMemoryResource(
ManagedMemoryResourceOptions(
preferred_location_type="invalid",
Expand Down
4 changes: 3 additions & 1 deletion cuda_core/tests/test_program_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,9 @@ def test_make_program_cache_key_rejects_extra_sources_outside_nvvm(code_type, co
@pytest.mark.parametrize(
"kwargs, exc_type, match",
[
pytest.param({"code_type": "fortran"}, ValueError, "code_type", id="unknown_code_type"),
pytest.param(
{"code_type": "fortran"}, ValueError, "'fortran' is not a valid SourceCodeType", id="unknown_code_type"
),
pytest.param({"target_type": "exe"}, ValueError, "target_type", id="unknown_target_type"),
pytest.param({"code": 12345}, TypeError, "code", id="non_str_bytes_code"),
# Backend-specific target matrix -- Program.compile rejects these
Expand Down
Loading