From 5c6f9b6c242293217903af1ce9ace6fc7996ff1b Mon Sep 17 00:00:00 2001 From: Andres Guzman-Ballen Date: Thu, 21 May 2026 11:35:30 -0500 Subject: [PATCH 1/8] ENH: Add support instantiating ObjectCode with path-like instances --- cuda_core/cuda/core/_module.pyx | 23 ++++++++++++++++++----- cuda_core/tests/test_module.py | 33 +++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/cuda_core/cuda/core/_module.pyx b/cuda_core/cuda/core/_module.pyx index 96ac65effc3..ea334aae075 100644 --- a/cuda_core/cuda/core/_module.pyx +++ b/cuda_core/cuda/core/_module.pyx @@ -4,6 +4,8 @@ from __future__ import annotations +import os + from libc.stddef cimport size_t from collections import namedtuple @@ -28,6 +30,7 @@ from cuda.core._resource_handles cimport ( ) from cuda.core._stream import Stream from cuda.core._utils.clear_error_support import ( + assert_type, assert_type_str_or_bytes_like, raise_code_path_meant_to_be_unreachable, ) @@ -620,7 +623,7 @@ cdef class ObjectCode: return ObjectCode._reduce_helper, (self._module, self._code_type, self._name, self._sym_map) @staticmethod - def from_cubin(module: bytes | str, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: + def from_cubin(module: bytes | str | os.PathLike, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: """Create an :class:`ObjectCode` instance from an existing cubin. Parameters @@ -638,7 +641,7 @@ cdef class ObjectCode: return ObjectCode._init(module, ObjectCodeFormatType.CUBIN, name=name, symbol_mapping=symbol_mapping) @staticmethod - def from_ptx(module: bytes | str, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: + def from_ptx(module: bytes | str | os.PathLike, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: """Create an :class:`ObjectCode` instance from an existing PTX. Parameters @@ -656,7 +659,7 @@ cdef class ObjectCode: return ObjectCode._init(module, ObjectCodeFormatType.PTX, name=name, symbol_mapping=symbol_mapping) @staticmethod - def from_ltoir(module: bytes | str, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: + def from_ltoir(module: bytes | str | os.PathLike, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: """Create an :class:`ObjectCode` instance from an existing LTOIR. Parameters @@ -674,7 +677,7 @@ cdef class ObjectCode: return ObjectCode._init(module, ObjectCodeFormatType.LTOIR, name=name, symbol_mapping=symbol_mapping) @staticmethod - def from_fatbin(module: bytes | str, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: + def from_fatbin(module: bytes | str | os.PathLike, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: """Create an :class:`ObjectCode` instance from an existing fatbin. Parameters @@ -733,8 +736,18 @@ cdef class ObjectCode: if self._h_library: return 0 module = self._module - assert_type_str_or_bytes_like(module) + try: + assert_type(module, os.PathLike) + except TypeError: + assert_type_str_or_bytes_like(module) cdef bytes path_bytes + # TODO: should code below move to try block? + if isinstance(module, os.PathLike): + path_bytes = os.fsencode(module) + self._h_library = create_library_handle_from_file(path_bytes) + if not self._h_library: + HANDLE_RETURN(get_last_error()) + return 0 if isinstance(module, str): path_bytes = module.encode() self._h_library = create_library_handle_from_file(path_bytes) diff --git a/cuda_core/tests/test_module.py b/cuda_core/tests/test_module.py index 5c994b6f5e3..2523d40adb0 100644 --- a/cuda_core/tests/test_module.py +++ b/cuda_core/tests/test_module.py @@ -52,6 +52,11 @@ def cuda12_4_prerequisite_check(): return binding_version() >= (12, 0, 0) and driver_version() >= (12, 4, 0) +@pytest.fixture(name="convert_path", params=[str, lambda p: p], ids=["str", "path"]) +def convert_path_to_arg(request): + return request.param + + def test_kernel_attributes_init_disabled(): with pytest.raises(RuntimeError, match=r"^KernelAttributes cannot be instantiated directly\."): cuda.core._module.KernelAttributes() # Ensure back door is locked. @@ -231,14 +236,15 @@ def test_object_code_load_ptx(get_saxpy_kernel_ptx): mod_obj.get_kernel("saxpy") # force loading -def test_object_code_load_ptx_from_file(get_saxpy_kernel_ptx, tmp_path): +def test_object_code_load_ptx_from_file(get_saxpy_kernel_ptx, tmp_path, convert_path): ptx, mod = get_saxpy_kernel_ptx sym_map = mod.symbol_mapping assert isinstance(ptx, bytes) ptx_file = tmp_path / "test.ptx" ptx_file.write_bytes(ptx) - mod_obj = ObjectCode.from_ptx(str(ptx_file), symbol_mapping=sym_map) - assert mod_obj.code == str(ptx_file) + arg = convert_path(ptx_file) + mod_obj = ObjectCode.from_ptx(arg, symbol_mapping=sym_map) + assert mod_obj.code == arg assert mod_obj.code_type == "ptx" if not _can_load_generated_ptx(): pytest.skip("PTX version too new for current driver") @@ -255,15 +261,16 @@ def test_object_code_load_cubin(get_saxpy_kernel_cubin): mod.get_kernel("saxpy") # force loading -def test_object_code_load_cubin_from_file(get_saxpy_kernel_cubin, tmp_path): +def test_object_code_load_cubin_from_file(get_saxpy_kernel_cubin, tmp_path, convert_path): _, mod = get_saxpy_kernel_cubin cubin = mod.code sym_map = mod.symbol_mapping assert isinstance(cubin, bytes) cubin_file = tmp_path / "test.cubin" cubin_file.write_bytes(cubin) - mod = ObjectCode.from_cubin(str(cubin_file), symbol_mapping=sym_map) - assert mod.code == str(cubin_file) + arg = convert_path(cubin_file) + mod = ObjectCode.from_cubin(arg, symbol_mapping=sym_map) + assert mod.code == arg mod.get_kernel("saxpy") # force loading @@ -286,15 +293,16 @@ def test_object_code_load_ltoir(get_saxpy_kernel_ltoir): mod_obj.get_kernel("saxpy") -def test_object_code_load_ltoir_from_file(get_saxpy_kernel_ltoir, tmp_path): +def test_object_code_load_ltoir_from_file(get_saxpy_kernel_ltoir, tmp_path, convert_path): mod = get_saxpy_kernel_ltoir ltoir = mod.code sym_map = mod.symbol_mapping assert isinstance(ltoir, bytes) ltoir_file = tmp_path / "test.ltoir" ltoir_file.write_bytes(ltoir) - mod_obj = ObjectCode.from_ltoir(str(ltoir_file), symbol_mapping=sym_map) - assert mod_obj.code == str(ltoir_file) + arg = convert_path(ltoir_file) + mod_obj = ObjectCode.from_ltoir(arg, symbol_mapping=sym_map) + assert mod_obj.code == arg assert mod_obj.code_type == "ltoir" # ltoir doesn't support kernel retrieval directly as it's used for linking @@ -310,13 +318,14 @@ def test_object_code_load_fatbin(get_saxpy_fatbin): @nvfatbin_available -def test_object_code_load_fatbin_from_file(get_saxpy_fatbin, tmp_path): +def test_object_code_load_fatbin_from_file(get_saxpy_fatbin, tmp_path, convert_path): fatbin, sym_map = get_saxpy_fatbin assert isinstance(fatbin, bytes) fatbin_file = tmp_path / "test.fatbin" fatbin_file.write_bytes(fatbin) - mod_obj = ObjectCode.from_fatbin(str(fatbin_file), symbol_mapping=sym_map) - assert mod_obj.code == str(fatbin_file) + arg = convert_path(fatbin_file) + mod_obj = ObjectCode.from_fatbin(arg, symbol_mapping=sym_map) + assert mod_obj.code == arg assert mod_obj.code_type == "fatbin" mod_obj.get_kernel("saxpy") # force loading From 547d1e274b554f00540b30c46fc7aa33d92fa8ca Mon Sep 17 00:00:00 2001 From: Andres Guzman-Ballen Date: Thu, 21 May 2026 16:35:46 -0500 Subject: [PATCH 2/8] Incorporate suggestions by @kkraus14; optimize cython code --- cuda_core/cuda/core/_module.pyx | 35 ++++++++++++--------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/cuda_core/cuda/core/_module.pyx b/cuda_core/cuda/core/_module.pyx index ea334aae075..3bf03d5314e 100644 --- a/cuda_core/cuda/core/_module.pyx +++ b/cuda_core/cuda/core/_module.pyx @@ -5,7 +5,7 @@ from __future__ import annotations import os - +from cpython.object cimport PyObject_HasAttrString from libc.stddef cimport size_t from collections import namedtuple @@ -30,7 +30,6 @@ from cuda.core._resource_handles cimport ( ) from cuda.core._stream import Stream from cuda.core._utils.clear_error_support import ( - assert_type, assert_type_str_or_bytes_like, raise_code_path_meant_to_be_unreachable, ) @@ -736,31 +735,23 @@ cdef class ObjectCode: if self._h_library: return 0 module = self._module - try: - assert_type(module, os.PathLike) - except TypeError: - assert_type_str_or_bytes_like(module) cdef bytes path_bytes - # TODO: should code below move to try block? - if isinstance(module, os.PathLike): - path_bytes = os.fsencode(module) - self._h_library = create_library_handle_from_file(path_bytes) - if not self._h_library: - HANDLE_RETURN(get_last_error()) - return 0 if isinstance(module, str): path_bytes = module.encode() self._h_library = create_library_handle_from_file(path_bytes) - if not self._h_library: - HANDLE_RETURN(get_last_error()) - return 0 - if isinstance(module, (bytes, bytearray)): + elif isinstance(module, (bytes, bytearray)): self._h_library = create_library_handle_from_data(module) - if not self._h_library: - HANDLE_RETURN(get_last_error()) - return 0 - raise_code_path_meant_to_be_unreachable() - return -1 + elif PyObject_HasAttrString(module, "__fspath__"): + path_str = module.__fspath__() + path_bytes = path_str.encode('utf-8') if isinstance(path_str, str) else path_str + self._h_library = create_library_handle_from_file(path_bytes) + else: + assert_type_str_or_bytes_like(module) + raise_code_path_meant_to_be_unreachable() + return -1 + if not self._h_library: + HANDLE_RETURN(get_last_error()) + return 0 def get_kernel(self, name) -> Kernel: """Return the :obj:`~_module.Kernel` of a specified name from this object code. From e4faa0aef6c2a02f3de166245fafe4b361d58edc Mon Sep 17 00:00:00 2001 From: Andres Guzman-Ballen Date: Thu, 21 May 2026 19:52:09 -0500 Subject: [PATCH 3/8] Remove redundant encoding parameter --- cuda_core/cuda/core/_module.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuda_core/cuda/core/_module.pyx b/cuda_core/cuda/core/_module.pyx index 3bf03d5314e..e9fb17223c8 100644 --- a/cuda_core/cuda/core/_module.pyx +++ b/cuda_core/cuda/core/_module.pyx @@ -743,7 +743,7 @@ cdef class ObjectCode: self._h_library = create_library_handle_from_data(module) elif PyObject_HasAttrString(module, "__fspath__"): path_str = module.__fspath__() - path_bytes = path_str.encode('utf-8') if isinstance(path_str, str) else path_str + path_bytes = path_str.encode() if isinstance(path_str, str) else path_str self._h_library = create_library_handle_from_file(path_bytes) else: assert_type_str_or_bytes_like(module) From d1bf91722d1adad36300108fdb72dd9404e56398 Mon Sep 17 00:00:00 2001 From: Andres Guzman-Ballen Date: Thu, 21 May 2026 19:55:51 -0500 Subject: [PATCH 4/8] Assert module.__fspath__ returns expected string or bytes object --- cuda_core/cuda/core/_module.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/cuda_core/cuda/core/_module.pyx b/cuda_core/cuda/core/_module.pyx index e9fb17223c8..da2eb26502c 100644 --- a/cuda_core/cuda/core/_module.pyx +++ b/cuda_core/cuda/core/_module.pyx @@ -743,6 +743,7 @@ cdef class ObjectCode: self._h_library = create_library_handle_from_data(module) elif PyObject_HasAttrString(module, "__fspath__"): path_str = module.__fspath__() + assert_type_str_or_bytes_like(path_str) path_bytes = path_str.encode() if isinstance(path_str, str) else path_str self._h_library = create_library_handle_from_file(path_bytes) else: From 80c7671576181669f6ba0bfcd17125dcd2e3926a Mon Sep 17 00:00:00 2001 From: Andres Guzman-Ballen Date: Thu, 21 May 2026 21:10:48 -0500 Subject: [PATCH 5/8] Update 'from_' documentation --- cuda_core/cuda/core/_module.pyx | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/cuda_core/cuda/core/_module.pyx b/cuda_core/cuda/core/_module.pyx index da2eb26502c..71980a1f986 100644 --- a/cuda_core/cuda/core/_module.pyx +++ b/cuda_core/cuda/core/_module.pyx @@ -627,9 +627,10 @@ cdef class ObjectCode: Parameters ---------- - module : Union[bytes, str] + module : Union[bytes, str, os.PathLike] Either a bytes object containing the in-memory cubin to load, or - a file path string pointing to the on-disk cubin to load. + a file path object (or its string representation) pointing to the + on-disk cubin to load. name : Optional[str] A human-readable identifier representing this code object. symbol_mapping : Optional[dict] @@ -645,9 +646,10 @@ cdef class ObjectCode: Parameters ---------- - module : Union[bytes, str] + module : Union[bytes, str, os.PathLike] Either a bytes object containing the in-memory ptx code to load, or - a file path string pointing to the on-disk ptx file to load. + a file path object (or its string representation) pointing to the + on-disk ptx file to load. name : Optional[str] A human-readable identifier representing this code object. symbol_mapping : Optional[dict] @@ -663,9 +665,10 @@ cdef class ObjectCode: Parameters ---------- - module : Union[bytes, str] - Either a bytes object containing the in-memory ltoir code to load, or - a file path string pointing to the on-disk ltoir file to load. + module : Union[bytes, str, os.PathLike] + Either a bytes object containing the in-memory ltoir code to load, + or a file path object (or its string representation) pointing to the + on-disk ltoir file to load. name : Optional[str] A human-readable identifier representing this code object. symbol_mapping : Optional[dict] @@ -681,9 +684,10 @@ cdef class ObjectCode: Parameters ---------- - module : Union[bytes, str] + module : Union[bytes, str, os.PathLike] Either a bytes object containing the in-memory fatbin to load, or - a file path string pointing to the on-disk fatbin to load. + or a file path object (or its string representation) pointing to the + on-disk fatbin to load. name : Optional[str] A human-readable identifier representing this code object. symbol_mapping : Optional[dict] From 4895346c032f2b817afe70389392e4cb89bbe1fb Mon Sep 17 00:00:00 2001 From: Andres Guzman-Ballen Date: Fri, 22 May 2026 09:28:58 -0500 Subject: [PATCH 6/8] Use isinstance over PyObject_HasAttrString --- cuda_core/cuda/core/_module.pyx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cuda_core/cuda/core/_module.pyx b/cuda_core/cuda/core/_module.pyx index 71980a1f986..bee055cf60b 100644 --- a/cuda_core/cuda/core/_module.pyx +++ b/cuda_core/cuda/core/_module.pyx @@ -5,7 +5,7 @@ from __future__ import annotations import os -from cpython.object cimport PyObject_HasAttrString + from libc.stddef cimport size_t from collections import namedtuple @@ -745,10 +745,8 @@ cdef class ObjectCode: self._h_library = create_library_handle_from_file(path_bytes) elif isinstance(module, (bytes, bytearray)): self._h_library = create_library_handle_from_data(module) - elif PyObject_HasAttrString(module, "__fspath__"): - path_str = module.__fspath__() - assert_type_str_or_bytes_like(path_str) - path_bytes = path_str.encode() if isinstance(path_str, str) else path_str + elif isinstance(module, os.PathLike): + path_bytes = os.fsencode(module) self._h_library = create_library_handle_from_file(path_bytes) else: assert_type_str_or_bytes_like(module) From e7e7519f3ab06e69421fcf891148a53eabaa4aba Mon Sep 17 00:00:00 2001 From: Andres Guzman-Ballen Date: Fri, 22 May 2026 13:16:44 -0500 Subject: [PATCH 7/8] Scope path-like modules to str before persisting --- cuda_core/cuda/core/_module.pyx | 21 +++++++++++++-------- cuda_core/tests/test_module.py | 8 ++++---- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/cuda_core/cuda/core/_module.pyx b/cuda_core/cuda/core/_module.pyx index bee055cf60b..cdb506358ff 100644 --- a/cuda_core/cuda/core/_module.pyx +++ b/cuda_core/cuda/core/_module.pyx @@ -4,11 +4,10 @@ from __future__ import annotations -import os - from libc.stddef cimport size_t from collections import namedtuple +from os import PathLike from cuda.core._device import Device from cuda.core._launch_config cimport LaunchConfig @@ -608,7 +607,13 @@ cdef class ObjectCode: self._h_library = LibraryHandle() # Empty handle self._code_type = str(code_type) - self._module = module + + if isinstance(module, CodeTypeT): + self._module = module + elif isinstance(module, PathLike): + self._module = os.fspath(module) + else: + self._module = module self._sym_map = {} if symbol_mapping is None else symbol_mapping self._name = name if name else "" @@ -622,7 +627,7 @@ cdef class ObjectCode: return ObjectCode._reduce_helper, (self._module, self._code_type, self._name, self._sym_map) @staticmethod - def from_cubin(module: bytes | str | os.PathLike, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: + def from_cubin(module: bytes | str | PathLike, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: """Create an :class:`ObjectCode` instance from an existing cubin. Parameters @@ -641,7 +646,7 @@ cdef class ObjectCode: return ObjectCode._init(module, ObjectCodeFormatType.CUBIN, name=name, symbol_mapping=symbol_mapping) @staticmethod - def from_ptx(module: bytes | str | os.PathLike, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: + def from_ptx(module: bytes | str | PathLike, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: """Create an :class:`ObjectCode` instance from an existing PTX. Parameters @@ -660,7 +665,7 @@ cdef class ObjectCode: return ObjectCode._init(module, ObjectCodeFormatType.PTX, name=name, symbol_mapping=symbol_mapping) @staticmethod - def from_ltoir(module: bytes | str | os.PathLike, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: + def from_ltoir(module: bytes | str | PathLike, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: """Create an :class:`ObjectCode` instance from an existing LTOIR. Parameters @@ -679,7 +684,7 @@ cdef class ObjectCode: return ObjectCode._init(module, ObjectCodeFormatType.LTOIR, name=name, symbol_mapping=symbol_mapping) @staticmethod - def from_fatbin(module: bytes | str | os.PathLike, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: + def from_fatbin(module: bytes | str | PathLike, *, name: str = "", symbol_mapping: dict | None = None) -> ObjectCode: """Create an :class:`ObjectCode` instance from an existing fatbin. Parameters @@ -745,7 +750,7 @@ cdef class ObjectCode: self._h_library = create_library_handle_from_file(path_bytes) elif isinstance(module, (bytes, bytearray)): self._h_library = create_library_handle_from_data(module) - elif isinstance(module, os.PathLike): + elif isinstance(module, PathLike): path_bytes = os.fsencode(module) self._h_library = create_library_handle_from_file(path_bytes) else: diff --git a/cuda_core/tests/test_module.py b/cuda_core/tests/test_module.py index 2523d40adb0..3a438f825a0 100644 --- a/cuda_core/tests/test_module.py +++ b/cuda_core/tests/test_module.py @@ -244,7 +244,7 @@ def test_object_code_load_ptx_from_file(get_saxpy_kernel_ptx, tmp_path, convert_ ptx_file.write_bytes(ptx) arg = convert_path(ptx_file) mod_obj = ObjectCode.from_ptx(arg, symbol_mapping=sym_map) - assert mod_obj.code == arg + assert mod_obj.code == str(arg) assert mod_obj.code_type == "ptx" if not _can_load_generated_ptx(): pytest.skip("PTX version too new for current driver") @@ -270,7 +270,7 @@ def test_object_code_load_cubin_from_file(get_saxpy_kernel_cubin, tmp_path, conv cubin_file.write_bytes(cubin) arg = convert_path(cubin_file) mod = ObjectCode.from_cubin(arg, symbol_mapping=sym_map) - assert mod.code == arg + assert mod.code == str(arg) mod.get_kernel("saxpy") # force loading @@ -302,7 +302,7 @@ def test_object_code_load_ltoir_from_file(get_saxpy_kernel_ltoir, tmp_path, conv ltoir_file.write_bytes(ltoir) arg = convert_path(ltoir_file) mod_obj = ObjectCode.from_ltoir(arg, symbol_mapping=sym_map) - assert mod_obj.code == arg + assert mod_obj.code == str(arg) assert mod_obj.code_type == "ltoir" # ltoir doesn't support kernel retrieval directly as it's used for linking @@ -325,7 +325,7 @@ def test_object_code_load_fatbin_from_file(get_saxpy_fatbin, tmp_path, convert_p fatbin_file.write_bytes(fatbin) arg = convert_path(fatbin_file) mod_obj = ObjectCode.from_fatbin(arg, symbol_mapping=sym_map) - assert mod_obj.code == arg + assert mod_obj.code == str(arg) assert mod_obj.code_type == "fatbin" mod_obj.get_kernel("saxpy") # force loading From 6ed4a7fda1226472483ed657322462ac64d2fb79 Mon Sep 17 00:00:00 2001 From: Andres Guzman-Ballen Date: Fri, 22 May 2026 15:53:29 -0500 Subject: [PATCH 8/8] Fix typos and explicitly check types --- cuda_core/cuda/core/_module.pyx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cuda_core/cuda/core/_module.pyx b/cuda_core/cuda/core/_module.pyx index cdb506358ff..b2dd5b4d8d8 100644 --- a/cuda_core/cuda/core/_module.pyx +++ b/cuda_core/cuda/core/_module.pyx @@ -7,7 +7,7 @@ from __future__ import annotations from libc.stddef cimport size_t from collections import namedtuple -from os import PathLike +from os import fsencode, fspath, PathLike from cuda.core._device import Device from cuda.core._launch_config cimport LaunchConfig @@ -608,10 +608,10 @@ cdef class ObjectCode: self._code_type = str(code_type) - if isinstance(module, CodeTypeT): + if isinstance(module, (str, bytes, bytearray)): self._module = module elif isinstance(module, PathLike): - self._module = os.fspath(module) + self._module = fspath(module) else: self._module = module self._sym_map = {} if symbol_mapping is None else symbol_mapping @@ -751,7 +751,7 @@ cdef class ObjectCode: elif isinstance(module, (bytes, bytearray)): self._h_library = create_library_handle_from_data(module) elif isinstance(module, PathLike): - path_bytes = os.fsencode(module) + path_bytes = fsencode(module) self._h_library = create_library_handle_from_file(path_bytes) else: assert_type_str_or_bytes_like(module)