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"] 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/_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/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 47fd1cbeefc..287a01a70bc 100644 --- a/cuda_core/cuda/core/_memoryview.pyx +++ b/cuda_core/cuda/core/_memoryview.pyx @@ -1044,14 +1044,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: @@ -1094,7 +1097,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) @@ -1218,7 +1221,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/docs/source/release/1.3.0-notes.rst b/cuda_core/docs/source/release/1.3.0-notes.rst index 90a36fdc4b4..086d020a623 100644 --- a/cuda_core/docs/source/release/1.3.0-notes.rst +++ b/cuda_core/docs/source/release/1.3.0-notes.rst @@ -360,6 +360,25 @@ Fixes and enhancements physical allocations. (`#2694 `__) +- 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/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 7217fae0c33..6f42bdc00e0 100644 --- a/cuda_core/tests/test_memory.py +++ b/cuda_core/tests/test_memory.py @@ -1152,6 +1152,24 @@ 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 = 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: + if gb is not None: + gb.close() + if mr is not None: + mr.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 5b8c7a4b791..c2a912fcf57 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="