From 35983faa92bfc178a05d53955c1bea88f6c8b26b Mon Sep 17 00:00:00 2001 From: Vyron Vasileiadis Date: Mon, 26 Oct 2026 10:54:20 +0200 Subject: [PATCH 1/3] cuda.core: replace library asserts with explicit errors python -O strips assert statements, and Cython drops them too, so the checks they carried vanished in optimized runs. Values that come from a caller, a DLPack or CUDA Array Interface producer, or an NVML payload now raise ValueError or TypeError naming the value. Internal invariants raise RuntimeError("internal cuda.core error, please report: ..."). The unmatched-capsule branch in view_as_dlpack was an assert False, but a producer reaches it by returning anything other than an unconsumed DLPack capsule, so it raises ValueError rather than the internal error. Enforce ruff S101 for cuda_core/cuda/core only. Enabling it for everything outside tests would flag about 180 asserts in pathfinder, the samples and the shared test helpers. The two asserts in _memory/_ipc.pyx are handled by #2704. --- cuda_core/cuda/core/_device.pyx | 6 +++- .../cuda/core/_memory/_managed_buffer.py | 3 +- .../core/_memory/_managed_memory_resource.pyx | 3 +- cuda_core/cuda/core/_memoryview.pyx | 13 +++++--- cuda_core/cuda/core/_module.pyx | 3 +- cuda_core/cuda/core/_program.pyx | 3 +- cuda_core/cuda/core/system/_device.pyx | 5 ++- cuda_core/cuda/core/system/_field_values.pxi | 3 +- cuda_core/cuda/core/system/_system_events.pyx | 3 +- .../core/utils/_program_cache/_file_stream.py | 3 +- .../cuda/core/utils/_program_cache/_keys.py | 3 +- cuda_core/tests/system/test_system_device.py | 6 ++++ cuda_core/tests/system/test_system_events.py | 6 ++++ cuda_core/tests/test_memory.py | 13 ++++++++ cuda_core/tests/test_module.py | 6 ++++ cuda_core/tests/test_utils.py | 10 ++++++ cuda_core/tests/test_utils_dlpack.py | 31 +++++++++++++++++++ ruff.toml | 3 +- 18 files changed, 106 insertions(+), 17 deletions(-) diff --git a/cuda_core/cuda/core/_device.pyx b/cuda_core/cuda/core/_device.pyx index 29b515bbe17..5ce22959e98 100644 --- a/cuda_core/cuda/core/_device.pyx +++ b/cuda_core/cuda/core/_device.pyx @@ -1684,7 +1684,11 @@ cdef inline int Device_resolve_device_id(device_id) except? -1: elif err == cydriver.CUresult.CUDA_ERROR_INVALID_CONTEXT: with nogil: HANDLE_RETURN(cydriver.cuCtxGetCurrent(&ctx)) - assert (ctx) == NULL + if (ctx) != NULL: + raise RuntimeError( + "internal cuda.core error, please report: cuCtxGetDevice returned " + "CUDA_ERROR_INVALID_CONTEXT while a context is current" + ) return 0 # cudart behavior else: HANDLE_RETURN(err) diff --git a/cuda_core/cuda/core/_memory/_managed_buffer.py b/cuda_core/cuda/core/_memory/_managed_buffer.py index 42acc5f2315..662ef8c118f 100644 --- a/cuda_core/cuda/core/_memory/_managed_buffer.py +++ b/cuda_core/cuda/core/_memory/_managed_buffer.py @@ -276,7 +276,8 @@ def accessed_by(self, locations: Iterable[Device | Host]) -> None: target.add(loc) for loc in target: spec = _coerce_location(loc) - assert spec is not None + if spec is None: + raise RuntimeError(f"internal cuda.core error, please report: no location spec for {loc!r}") if spec.kind not in ("device", "host"): raise ValueError(f"advise {_SET_ACCESSED_BY.name} does not support location_type='{spec.kind}'") current = set(_query_accessed_by(self)) diff --git a/cuda_core/cuda/core/_memory/_managed_memory_resource.pyx b/cuda_core/cuda/core/_memory/_managed_memory_resource.pyx index f12be4c246c..f50472758b9 100644 --- a/cuda_core/cuda/core/_memory/_managed_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_managed_memory_resource.pyx @@ -122,7 +122,8 @@ cdef class ManagedMemoryResource(_MemPool): (``prefetch``, ``discard``, ``discard_prefetch``). """ MP_check_open(self) - assert isinstance(stream, Stream), "Only Stream is supported for managed memory allocations" + if not isinstance(stream, Stream): + raise TypeError(f"Only Stream is supported for managed memory allocations, got {type(stream).__name__}") if self.is_mapped: raise TypeError("Cannot allocate from a mapped IPC-enabled memory resource") cdef Stream s = Stream_accept(stream) diff --git a/cuda_core/cuda/core/_memoryview.pyx b/cuda_core/cuda/core/_memoryview.pyx index 48252e86203..24520351678 100644 --- a/cuda_core/cuda/core/_memoryview.pyx +++ b/cuda_core/cuda/core/_memoryview.pyx @@ -1040,14 +1040,17 @@ cdef StridedMemoryView view_as_dlpack(obj, stream_ptr, view=None): stream_ptr = int(stream_ptr) dldevice, device_id = obj.__dlpack_device__() if dldevice == _kDLCPU: - assert device_id == 0 + if device_id != 0: + raise ValueError(f"__dlpack_device__() reported device_id {device_id} for a kDLCPU tensor; expected 0") device_id = -1 if stream_ptr is None: raise BufferError("stream=None is ambiguous with view()") elif stream_ptr == -1: stream_ptr = None elif dldevice == _kDLCUDA: - assert device_id >= 0 + if device_id < 0: + raise ValueError( + f"__dlpack_device__() reported device_id {device_id} for a kDLCUDA tensor; expected 0 or more") is_device_accessible = True # no need to check other stream values, it's a pass-through if stream_ptr is None: @@ -1090,7 +1093,7 @@ cdef StridedMemoryView view_as_dlpack(obj, stream_ptr, view=None): is_readonly = False used_name = DLPACK_TENSOR_USED_NAME else: - assert False + raise ValueError(f"__dlpack__() returned {capsule!r}, not an unconsumed DLPack capsule") cpython.PyCapsule_SetName(capsule, used_name) @@ -1214,7 +1217,9 @@ cpdef StridedMemoryView view_as_cai(obj, stream_ptr, view=None): if stream is not None: producer_s = (stream) consumer_s = (stream_ptr) - assert producer_s > 0 + if producer_s <= 0: + raise ValueError( + f"__cuda_array_interface__ reported stream {stream}; expected None or a positive stream handle") # establish stream order if producer_s != consumer_s: with nogil: diff --git a/cuda_core/cuda/core/_module.pyx b/cuda_core/cuda/core/_module.pyx index d4de653406f..52a2794cfa4 100644 --- a/cuda_core/cuda/core/_module.pyx +++ b/cuda_core/cuda/core/_module.pyx @@ -632,7 +632,8 @@ cdef class ObjectCode: @classmethod def _init(cls, module, code_type, *, name: str = "", symbol_mapping: dict[str, str] | None = None) -> ObjectCode: - assert code_type in _supported_code_type, f"{code_type=} is not supported" + if code_type not in _supported_code_type: + raise ValueError(f"{code_type=} is not supported") cdef ObjectCode self = ObjectCode.__new__(ObjectCode) # _h_library is assigned during _lazy_load_module diff --git a/cuda_core/cuda/core/_program.pyx b/cuda_core/cuda/core/_program.pyx index 94f5bf74f72..c749744c690 100644 --- a/cuda_core/cuda/core/_program.pyx +++ b/cuda_core/cuda/core/_program.pyx @@ -1359,7 +1359,8 @@ cdef inline object _prepare_nvvm_options_impl(object opts, bint as_bytes): options = [] # Options supported by NVVM - assert opts.arch is not None + if opts.arch is None: + raise RuntimeError("internal cuda.core error, please report: ProgramOptions.arch is None") arch = opts.arch if arch.startswith("sm_"): arch = f"compute_{arch[3:]}" diff --git a/cuda_core/cuda/core/system/_device.pyx b/cuda_core/cuda/core/system/_device.pyx index 76bfb128503..f06614420ce 100644 --- a/cuda_core/cuda/core/system/_device.pyx +++ b/cuda_core/cuda/core/system/_device.pyx @@ -43,9 +43,8 @@ if TYPE_CHECKING: cdef object _pstate_to_int(object pstate): if pstate == nvml.Pstates.PSTATE_UNKNOWN: return None - assert ( - int(pstate) >= 0 and int(pstate) <= 15 - ), f"Invalid P-state: {pstate}. Must be between 0 and 15 inclusive, or PSTATE_UNKNOWN." + if int(pstate) < 0 or int(pstate) > 15: + raise ValueError(f"Invalid P-state: {pstate}. Must be between 0 and 15 inclusive, or PSTATE_UNKNOWN.") return int(pstate) - int(nvml.Pstates.PSTATE_0) diff --git a/cuda_core/cuda/core/system/_field_values.pxi b/cuda_core/cuda/core/system/_field_values.pxi index 65259da3768..beca31df6b3 100644 --- a/cuda_core/cuda/core/system/_field_values.pxi +++ b/cuda_core/cuda/core/system/_field_values.pxi @@ -12,7 +12,8 @@ cdef class FieldValue: cdef object _field_value def __init__(self, field_value: nvml.FieldValue): - assert len(field_value) == 1 + if len(field_value) != 1: + raise ValueError(f"FieldValue expects exactly one field value, got {len(field_value)}") self._field_value = field_value @property diff --git a/cuda_core/cuda/core/system/_system_events.pyx b/cuda_core/cuda/core/system/_system_events.pyx index ad1e4bb7847..6e57ad28fb5 100644 --- a/cuda_core/cuda/core/system/_system_events.pyx +++ b/cuda_core/cuda/core/system/_system_events.pyx @@ -40,7 +40,8 @@ cdef class SystemEvent: cdef object _event_data def __init__(self, event_data not None: nvml.SystemEventData_v1): - assert len(event_data) == 1 + if len(event_data) != 1: + raise ValueError(f"SystemEvent expects exactly one event, got {len(event_data)}") self._event_data = event_data @property diff --git a/cuda_core/cuda/core/utils/_program_cache/_file_stream.py b/cuda_core/cuda/core/utils/_program_cache/_file_stream.py index 314c7b612bc..8063d7e6c16 100644 --- a/cuda_core/cuda/core/utils/_program_cache/_file_stream.py +++ b/cuda_core/cuda/core/utils/_program_cache/_file_stream.py @@ -93,7 +93,8 @@ def _with_sharing_retry( last_exc = exc if on_exhausted is not None: return on_exhausted(last_exc) - assert last_exc is not None # at least one iteration ran and caught a PermissionError + if last_exc is None: + raise RuntimeError("internal cuda.core error, please report: sharing retry ran no attempts") raise last_exc diff --git a/cuda_core/cuda/core/utils/_program_cache/_keys.py b/cuda_core/cuda/core/utils/_program_cache/_keys.py index 2919f48ee34..2cf5b8913c8 100644 --- a/cuda_core/cuda/core/utils/_program_cache/_keys.py +++ b/cuda_core/cuda/core/utils/_program_cache/_keys.py @@ -543,7 +543,8 @@ def hash_version_probe(self, update: Callable[[str, bytes], None]) -> None: # so the bytes are still in the digest via ``linker_version``. use_driver = self._decide_driver() if use_driver is None: - assert self._cached_decision_exc is not None + if self._cached_decision_exc is None: + raise RuntimeError("internal cuda.core error, please report: linker probe failed without an exception") _hash_probe_failure(update, "linker", self._cached_decision_exc) return try: diff --git a/cuda_core/tests/system/test_system_device.py b/cuda_core/tests/system/test_system_device.py index bdef726c099..80be43e75c7 100644 --- a/cuda_core/tests/system/test_system_device.py +++ b/cuda_core/tests/system/test_system_device.py @@ -478,6 +478,12 @@ def test_field_value_decodes_signed_long_long(): assert type(value) is int +@pytest.mark.agent_authored(model="claude-opus-5-5") +def test_field_value_rejects_multiple_values(): + with pytest.raises(ValueError, match="exactly one field value, got 2"): + _device.FieldValue(nvml.FieldValue(2)) + + @pytest.mark.skipif(helpers.IS_WSL or helpers.IS_WINDOWS, reason="Device attributes not supported on WSL or Windows") def test_get_all_devices_with_cpu_affinity(subtests): for i in range(multiprocessing.cpu_count()): diff --git a/cuda_core/tests/system/test_system_events.py b/cuda_core/tests/system/test_system_events.py index 19e7e1f9f57..210149d93d8 100644 --- a/cuda_core/tests/system/test_system_events.py +++ b/cuda_core/tests/system/test_system_events.py @@ -36,6 +36,12 @@ def test_system_events_wraps_event_data(): assert event.gpu_id == 0x0000_0200 +@pytest.mark.agent_authored(model="claude-opus-5-5") +def test_system_event_rejects_multiple_events(): + with pytest.raises(ValueError, match="exactly one event, got 2"): + SystemEvent(nvml.SystemEventData_v1(2)) + + @pytest.mark.agent_authored(model="claude-opus-4.7") @pytest.mark.parametrize( ("gpu_id", "expected"), diff --git a/cuda_core/tests/test_memory.py b/cuda_core/tests/test_memory.py index db492288fb5..2e14e8b1f8d 100644 --- a/cuda_core/tests/test_memory.py +++ b/cuda_core/tests/test_memory.py @@ -1151,6 +1151,19 @@ def test_managed_memory_resource_buffer_dlpack_device_type(init_cuda): assert view.__dlpack_device__() == (int(DLDeviceType.kDLCUDAManaged), 0) +@pytest.mark.agent_authored(model="claude-opus-5-5") +def test_managed_memory_resource_allocate_rejects_graph_builder(init_cuda): + device = init_cuda + skip_if_managed_memory_unsupported(device) + mr = create_managed_memory_resource_or_skip(ManagedMemoryResourceOptions(preferred_location=device.device_id)) + gb = device.create_graph_builder() + try: + with pytest.raises(TypeError, match="Only Stream is supported .*got GraphBuilder"): + mr.allocate(64, stream=gb) + finally: + gb.close() + + @pytest.mark.parametrize("mr_kind", ["device", "pinned"]) def test_non_managed_resources_report_not_managed(init_cuda, mr_kind): """Non-managed memory resources must report is_managed=False.""" diff --git a/cuda_core/tests/test_module.py b/cuda_core/tests/test_module.py index a3ffa6d4e75..f6b71ed5b19 100644 --- a/cuda_core/tests/test_module.py +++ b/cuda_core/tests/test_module.py @@ -727,6 +727,12 @@ def test_module_serialization_roundtrip(get_saxpy_kernel_cubin): assert objcode.code_type == result.code_type +@pytest.mark.agent_authored(model="claude-opus-5-5") +def test_module_unpickle_rejects_unknown_code_type(): + with pytest.raises(ValueError, match="code_type='bogus' is not supported"): + ObjectCode._reduce_helper(b"", "bogus", "", None) + + def test_kernel_from_handle(get_saxpy_kernel_cubin): """Test Kernel.from_handle() with a valid handle""" original_kernel, objcode = get_saxpy_kernel_cubin diff --git a/cuda_core/tests/test_utils.py b/cuda_core/tests/test_utils.py index a54e8378cf9..1e961e173ed 100644 --- a/cuda_core/tests/test_utils.py +++ b/cuda_core/tests/test_utils.py @@ -1071,6 +1071,16 @@ def test_view_as_cai_device_pointer_and_stream_ordering(init_cuda): dev.default_stream.sync() +@pytest.mark.agent_authored(model="claude-opus-5-5") +def test_view_as_cai_rejects_stream_zero(init_cuda): + """The CUDA Array Interface does not allow ``stream`` 0.""" + consumer = init_cuda.create_stream() + obj = _make_cuda_array_interface_obj(shape=(4,), strides=None, typestr=" Date: Tue, 27 Oct 2026 13:12:02 +0200 Subject: [PATCH 2/3] cuda.core: replace the unsqueeze_extents assert, add release note unsqueeze_extents in _layout.pyx still checked an internal invariant with assert, which python -O strips. It now raises the "internal cuda.core error, please report" RuntimeError. Add a 1.3.0 release note that lists the entry points whose failed checks now raise ValueError or TypeError instead of AssertionError. The managed memory allocate test now closes the resource as well as the graph builder. --- cuda_core/cuda/core/_layout.pyx | 5 ++++- cuda_core/docs/source/release/1.3.0-notes.rst | 19 +++++++++++++++++++ cuda_core/tests/test_memory.py | 11 ++++++++--- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/cuda_core/cuda/core/_layout.pyx b/cuda_core/cuda/core/_layout.pyx index 1935e9969fa..4ebc9234258 100644 --- a/cuda_core/cuda/core/_layout.pyx +++ b/cuda_core/cuda/core/_layout.pyx @@ -1182,7 +1182,10 @@ cdef inline int unsqueeze_extents(BaseLayout& out_layout, BaseLayout& in_layout, out_layout.shape[i] = in_shape[in_i] out_layout.strides[i] = in_strides[in_i] in_i += 1 - assert in_i == ndim + if in_i != ndim: + raise RuntimeError( + f"internal cuda.core error, please report: unsqueeze_extents consumed {in_i} of {ndim} input axes" + ) return 0 diff --git a/cuda_core/docs/source/release/1.3.0-notes.rst b/cuda_core/docs/source/release/1.3.0-notes.rst index f63ce1f1cff..2ccf6c39d7c 100644 --- a/cuda_core/docs/source/release/1.3.0-notes.rst +++ b/cuda_core/docs/source/release/1.3.0-notes.rst @@ -321,6 +321,25 @@ Fixes and enhancements ``repr`` now shows ``U`` for unified operands. (`#2649 `__) +- Checks that were ``assert`` statements now raise exceptions, so they also + run under ``python -O``, which strips asserts from Python and Cython code. + :meth:`~utils.StridedMemoryView.from_dlpack` raises ``ValueError`` when + ``__dlpack_device__()`` reports a negative device ID for a CUDA tensor or a + nonzero one for a CPU tensor, or when ``__dlpack__()`` returns anything + other than an unconsumed DLPack capsule. + :meth:`~utils.StridedMemoryView.from_cuda_array_interface` raises + ``ValueError`` when the producer's ``stream`` is 0 or negative. + :meth:`ManagedMemoryResource.allocate` raises ``TypeError`` when ``stream`` + is not a :class:`Stream`. Unpickling an :class:`ObjectCode` with an unknown + code type raises ``ValueError``, as do :class:`~system._device.FieldValue` + and :class:`~system._system_events.SystemEvent` when the NVML data does not + hold exactly one entry, and :attr:`system.Device.performance_state` when + NVML reports a P-state outside 0 to 15. Before, these raised + ``AssertionError``, or nothing under ``python -O``. A failed internal check + now raises ``RuntimeError`` with a message that starts with "internal + cuda.core error, please report". + (`#3202 `__) + Deprecations ------------ diff --git a/cuda_core/tests/test_memory.py b/cuda_core/tests/test_memory.py index 2e14e8b1f8d..4408cf97b3a 100644 --- a/cuda_core/tests/test_memory.py +++ b/cuda_core/tests/test_memory.py @@ -1155,13 +1155,18 @@ def test_managed_memory_resource_buffer_dlpack_device_type(init_cuda): def test_managed_memory_resource_allocate_rejects_graph_builder(init_cuda): device = init_cuda skip_if_managed_memory_unsupported(device) - mr = create_managed_memory_resource_or_skip(ManagedMemoryResourceOptions(preferred_location=device.device_id)) - gb = device.create_graph_builder() + mr = None + gb = None try: + mr = create_managed_memory_resource_or_skip(ManagedMemoryResourceOptions(preferred_location=device.device_id)) + gb = device.create_graph_builder() with pytest.raises(TypeError, match="Only Stream is supported .*got GraphBuilder"): mr.allocate(64, stream=gb) finally: - gb.close() + if gb is not None: + gb.close() + if mr is not None: + mr.close() @pytest.mark.parametrize("mr_kind", ["device", "pinned"]) From 565ddcef55a406b68b513a6051055f4583f10747 Mon Sep 17 00:00:00 2001 From: Vyron Vasileiadis Date: Mon, 2 Nov 2026 12:08:14 +0200 Subject: [PATCH 3/3] cuda_bindings_12: keep S101 ignored for the CUDA 12 sources cuda_bindings_12/pyproject.toml has its own [tool.ruff] section, so ruff lints that tree with it instead of the root ruff.toml, and the root per-file-ignores never reach it. The Bandit job passes --select S with only the S codes from the root ignore list, so moving S101 out of that list exposed 911 asserts in cuda_bindings_12. Ignore S101 for the whole tree in its own config. --- cuda_bindings_12/pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cuda_bindings_12/pyproject.toml b/cuda_bindings_12/pyproject.toml index aee928a8aef..bed6398769f 100644 --- a/cuda_bindings_12/pyproject.toml +++ b/cuda_bindings_12/pyproject.toml @@ -104,6 +104,9 @@ ignore = [ exclude = ["cuda/bindings/_version.py"] [tool.ruff.lint.per-file-ignores] +# This config replaces ../ruff.toml for this tree, so it repeats that file's +# S101 policy: asserts are rejected only in cuda_core/cuda/core. +"**" = ["S101"] "setup.py" = ["F401"] "__init__.py" = ["F401"] "site-packages/_cuda_bindings_redirector.py" = ["F401"]