diff --git a/cuda_core/cuda/core/experimental/_context.py b/cuda_core/cuda/core/experimental/_context.py index 9d9434dc8..e59e94db3 100644 --- a/cuda_core/cuda/core/experimental/_context.py +++ b/cuda_core/cuda/core/experimental/_context.py @@ -15,13 +15,13 @@ class ContextOptions: class Context: __slots__ = ("_handle", "_id") - def __init__(self): - raise NotImplementedError("TODO") + def __new__(self, *args, **kwargs): + raise RuntimeError("Context objects cannot be instantiated directly. Please use Device or Stream APIs.") - @staticmethod - def _from_ctx(obj, dev_id): + @classmethod + def _from_ctx(cls, obj, dev_id): assert isinstance(obj, driver.CUcontext) - ctx = Context.__new__(Context) + ctx = super().__new__(cls) ctx._handle = obj ctx._id = dev_id return ctx diff --git a/cuda_core/cuda/core/experimental/_device.py b/cuda_core/cuda/core/experimental/_device.py index 74888dca8..2eb827d38 100644 --- a/cuda_core/cuda/core/experimental/_device.py +++ b/cuda_core/cuda/core/experimental/_device.py @@ -22,13 +22,14 @@ class DeviceProperties: Attributes are read-only and provide information about the device. """ - def __init__(self): - raise RuntimeError("DeviceProperties should not be instantiated directly") + def __new__(self, *args, **kwargs): + raise RuntimeError("DeviceProperties cannot be instantiated directly. Please use Device APIs.") __slots__ = ("_handle", "_cache") - def _init(handle): - self = DeviceProperties.__new__(DeviceProperties) + @classmethod + def _init(cls, handle): + self = super().__new__(cls) self._handle = handle self._cache = {} return self diff --git a/cuda_core/cuda/core/experimental/_event.py b/cuda_core/cuda/core/experimental/_event.py index 3b269179b..bd97305a6 100644 --- a/cuda_core/cuda/core/experimental/_event.py +++ b/cuda_core/cuda/core/experimental/_event.py @@ -67,14 +67,14 @@ def close(self): handle_return(driver.cuEventDestroy(self.handle)) self.handle = None - __slots__ = ("__weakref__", "_mnff", "_timing_disabled", "_busy_waited") + def __new__(self, *args, **kwargs): + raise RuntimeError("Event objects cannot be instantiated directly. Please use Stream APIs (record).") - def __init__(self): - raise NotImplementedError("directly creating an Event object can be ambiguous. Please call Stream.record().") + __slots__ = ("__weakref__", "_mnff", "_timing_disabled", "_busy_waited") - @staticmethod - def _init(options: Optional[EventOptions] = None): - self = Event.__new__(Event) + @classmethod + def _init(cls, options: Optional[EventOptions] = None): + self = super().__new__(cls) self._mnff = Event._MembersNeededForFinalize(self, None) options = check_or_create_options(EventOptions, options, "Event options") diff --git a/cuda_core/cuda/core/experimental/_module.py b/cuda_core/cuda/core/experimental/_module.py index 33827fcef..39a7c0f5b 100644 --- a/cuda_core/cuda/core/experimental/_module.py +++ b/cuda_core/cuda/core/experimental/_module.py @@ -47,13 +47,14 @@ def _lazy_init(): class KernelAttributes: - def __init__(self): - raise RuntimeError("KernelAttributes should not be instantiated directly") + def __new__(self, *args, **kwargs): + raise RuntimeError("KernelAttributes cannot be instantiated directly. Please use Kernel APIs.") slots = ("_handle", "_cache", "_backend_version", "_loader") - def _init(handle): - self = KernelAttributes.__new__(KernelAttributes) + @classmethod + def _init(cls, handle): + self = super().__new__(cls) self._handle = handle self._cache = {} @@ -189,14 +190,14 @@ class Kernel: __slots__ = ("_handle", "_module", "_attributes") - def __init__(self): - raise RuntimeError("directly constructing a Kernel instance is not supported") + def __new__(self, *args, **kwargs): + raise RuntimeError("Kernel objects cannot be instantiated directly. Please use ObjectCode APIs.") - @staticmethod - def _from_obj(obj, mod): + @classmethod + def _from_obj(cls, obj, mod): assert isinstance(obj, _kernel_ctypes) assert isinstance(mod, ObjectCode) - ker = Kernel.__new__(Kernel) + ker = super().__new__(cls) ker._handle = obj ker._module = mod ker._attributes = None @@ -237,15 +238,15 @@ class ObjectCode: __slots__ = ("_handle", "_backend_version", "_code_type", "_module", "_loader", "_sym_map") _supported_code_type = ("cubin", "ptx", "ltoir", "fatbin") - def __init__(self): - raise NotImplementedError( - "directly creating an ObjectCode object can be ambiguous. Please either call Program.compile() " - "or one of the ObjectCode.from_*() constructors" + def __new__(self, *args, **kwargs): + raise RuntimeError( + "ObjectCode objects cannot be instantiated directly. " + "Please use ObjectCode APIs (from_cubin, from_ptx) or Program APIs (compile)." ) - @staticmethod - def _init(module, code_type, *, symbol_mapping: Optional[dict] = None): - self = ObjectCode.__new__(ObjectCode) + @classmethod + def _init(cls, module, code_type, *, symbol_mapping: Optional[dict] = None): + self = super().__new__(cls) assert code_type in self._supported_code_type, f"{code_type=} is not supported" _lazy_init() diff --git a/cuda_core/cuda/core/experimental/_stream.py b/cuda_core/cuda/core/experimental/_stream.py index 93479340a..95b59595d 100644 --- a/cuda_core/cuda/core/experimental/_stream.py +++ b/cuda_core/cuda/core/experimental/_stream.py @@ -71,18 +71,37 @@ def close(self): self.owner = None self.handle = None + def __new__(self, *args, **kwargs): + raise RuntimeError( + "Stream objects cannot be instantiated directly. " + "Please use Device APIs (create_stream) or other Stream APIs (from_handle)." + ) + __slots__ = ("__weakref__", "_mnff", "_nonblocking", "_priority", "_device_id", "_ctx_handle") - def __init__(self): - raise NotImplementedError( - "directly creating a Stream object can be ambiguous. Please either " - "call Device.create_stream() or, if a stream pointer is already " - "available from somewhere else, Stream.from_handle()" - ) + @classmethod + def _legacy_default(cls): + self = super().__new__(cls) + self._mnff = Stream._MembersNeededForFinalize(self, driver.CUstream(driver.CU_STREAM_LEGACY), None, True) + self._nonblocking = None # delayed + self._priority = None # delayed + self._device_id = None # delayed + self._ctx_handle = None # delayed + return self - @staticmethod - def _init(obj=None, *, options: Optional[StreamOptions] = None): - self = Stream.__new__(Stream) + @classmethod + def _per_thread_default(cls): + self = super().__new__(cls) + self._mnff = Stream._MembersNeededForFinalize(self, driver.CUstream(driver.CU_STREAM_PER_THREAD), None, True) + self._nonblocking = None # delayed + self._priority = None # delayed + self._device_id = None # delayed + self._ctx_handle = None # delayed + return self + + @classmethod + def _init(cls, obj=None, *, options: Optional[StreamOptions] = None): + self = super().__new__(cls) self._mnff = Stream._MembersNeededForFinalize(self, None, None, False) if obj is not None and options is not None: @@ -295,22 +314,8 @@ def __cuda_stream__(self): return Stream._init(obj=_stream_holder()) -class _LegacyDefaultStream(Stream): - def __init__(self): - self._mnff = Stream._MembersNeededForFinalize(self, driver.CUstream(driver.CU_STREAM_LEGACY), None, True) - self._nonblocking = None # delayed - self._priority = None # delayed - - -class _PerThreadDefaultStream(Stream): - def __init__(self): - self._mnff = Stream._MembersNeededForFinalize(self, driver.CUstream(driver.CU_STREAM_PER_THREAD), None, True) - self._nonblocking = None # delayed - self._priority = None # delayed - - -LEGACY_DEFAULT_STREAM = _LegacyDefaultStream() -PER_THREAD_DEFAULT_STREAM = _PerThreadDefaultStream() +LEGACY_DEFAULT_STREAM = Stream._legacy_default() +PER_THREAD_DEFAULT_STREAM = Stream._per_thread_default() def default_stream(): diff --git a/cuda_core/tests/test_context.py b/cuda_core/tests/test_context.py new file mode 100644 index 000000000..c907b19cf --- /dev/null +++ b/cuda_core/tests/test_context.py @@ -0,0 +1,16 @@ +# Copyright 2025 NVIDIA Corporation. All rights reserved. +# +# Please refer to the NVIDIA end user license agreement (EULA) associated +# with this source code for terms and conditions that govern your use of +# this software. Any use, reproduction, disclosure, or distribution of +# this software and related documentation outside the terms of the EULA +# is strictly prohibited. + +import pytest + +import cuda.core.experimental + + +def test_context_init_disabled(): + with pytest.raises(RuntimeError, match=r"^Context objects cannot be instantiated directly\."): + cuda.core.experimental._context.Context() # Ensure back door is locked. diff --git a/cuda_core/tests/test_device.py b/cuda_core/tests/test_device.py index 16dac78cc..1d20adfed 100644 --- a/cuda_core/tests/test_device.py +++ b/cuda_core/tests/test_device.py @@ -13,10 +13,16 @@ from cuda import cudart as runtime import pytest +import cuda.core.experimental from cuda.core.experimental import Device from cuda.core.experimental._utils import ComputeCapability, get_binding_version, handle_return +def test_device_init_disabled(): + with pytest.raises(RuntimeError, match=r"^DeviceProperties cannot be instantiated directly\."): + cuda.core.experimental._device.DeviceProperties() # Ensure back door is locked. + + @pytest.fixture(scope="module") def cuda_version(): # binding availability depends on cuda-python version diff --git a/cuda_core/tests/test_event.py b/cuda_core/tests/test_event.py index df1a7fc47..3cae72bc3 100644 --- a/cuda_core/tests/test_event.py +++ b/cuda_core/tests/test_event.py @@ -8,9 +8,15 @@ import pytest +import cuda.core.experimental from cuda.core.experimental import Device, EventOptions +def test_event_init_disabled(): + with pytest.raises(RuntimeError, match=r"^Event objects cannot be instantiated directly\."): + cuda.core.experimental._event.Event() # Ensure back door is locked. + + @pytest.mark.parametrize("enable_timing", [True, False, None]) def test_timing(init_cuda, enable_timing): options = EventOptions(enable_timing=enable_timing) diff --git a/cuda_core/tests/test_module.py b/cuda_core/tests/test_module.py index 4bc7ceab3..c780c1a03 100644 --- a/cuda_core/tests/test_module.py +++ b/cuda_core/tests/test_module.py @@ -11,6 +11,7 @@ import pytest +import cuda.core.experimental from cuda.core.experimental import ObjectCode, Program, ProgramOptions, system SAXPY_KERNEL = """ @@ -28,6 +29,21 @@ """ +def test_kernel_attributes_init_disabled(): + with pytest.raises(RuntimeError, match=r"^KernelAttributes cannot be instantiated directly\."): + cuda.core.experimental._module.KernelAttributes() # Ensure back door is locked. + + +def test_kernel_init_disabled(): + with pytest.raises(RuntimeError, match=r"^Kernel objects cannot be instantiated directly\."): + cuda.core.experimental._module.Kernel() # Ensure back door is locked. + + +def test_object_code_init_disabled(): + with pytest.raises(RuntimeError, match=r"^ObjectCode objects cannot be instantiated directly\."): + ObjectCode() # Reject at front door. + + @pytest.fixture(scope="function") def get_saxpy_kernel(init_cuda): # prepare program diff --git a/cuda_core/tests/test_stream.py b/cuda_core/tests/test_stream.py index e8b59b34f..c1ebc3d2e 100644 --- a/cuda_core/tests/test_stream.py +++ b/cuda_core/tests/test_stream.py @@ -14,9 +14,9 @@ from cuda.core.experimental._utils import driver -def test_stream_init(): - with pytest.raises(NotImplementedError): - Stream() +def test_stream_init_disabled(): + with pytest.raises(RuntimeError, match=r"^Stream objects cannot be instantiated directly\."): + Stream() # Reject at front door. def test_stream_init_with_options(init_cuda):