Skip to content

Commit ffa4cfd

Browse files
committed
Merge remote-tracking branch 'upstream/main' into cuda.core.system-events
2 parents b009184 + 43f2037 commit ffa4cfd

52 files changed

Lines changed: 3154 additions & 643 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ cuda/_version.py export-subst
66
# we do not own any headers checked in, don't touch them
77
*.h binary
88
*.hpp binary
9+
# Exception: headers we own (cuda_core C++ implementation)
10+
cuda_core/cuda/core/_cpp/*.h -binary text diff
11+
cuda_core/cuda/core/_cpp/*.hpp -binary text diff
912
# git should not convert line endings in PNG files
1013
*.png binary
1114
*.svg binary

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ __pycache__/
1111
.pytest_cache/
1212
.benchmarks/
1313
*.cpp
14+
!*_impl.cpp
1415
!cuda_bindings/cuda/bindings/_lib/param_packer.cpp
1516
!cuda_bindings/cuda/bindings/_bindings/loader.cpp
1617
cache_driver

ci/tools/merge_cuda_core_wheels.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,21 @@ def merge_wheels(wheels: List[Path], output_dir: Path, show_wheel_contents: bool
150150
"__init__.py",
151151
"_version.py",
152152
"_include",
153+
"_cpp", # Headers for Cython development
153154
"cu12",
154155
"cu13",
155156
)
157+
# _resource_handles is shared (not CUDA-version-specific) and must stay
158+
# at top level. It's imported early in __init__.py before versioned code.
159+
items_to_keep_prefix = ("_resource_handles",)
156160
all_items = os.scandir(base_wheel / base_dir)
157161
removed_count = 0
158162
for f in all_items:
159163
f_abspath = f.path
160164
if f.name in items_to_keep:
161165
continue
166+
if any(f.name.startswith(prefix) for prefix in items_to_keep_prefix):
167+
continue
162168
if f.is_dir():
163169
print(f" Removing directory: {f.name}", file=sys.stderr)
164170
shutil.rmtree(f_abspath)

cuda_bindings/docs/source/conf.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@
5353
# This pattern also affects html_static_path and html_extra_path.
5454
exclude_patterns = []
5555

56+
# Include object entries (methods, attributes, etc.) in the table of contents
57+
# This enables the "On This Page" sidebar to show class methods and properties
58+
# Requires Sphinx 5.1+
59+
toc_object_entries = True
60+
toc_object_entries_show_parents = "domain"
61+
5662
# -- Options for HTML output -------------------------------------------------
5763

5864
# The theme to use for HTML and HTML Help pages. See the documentation for
@@ -69,6 +75,10 @@
6975
"version-switcher",
7076
"navbar-nav",
7177
],
78+
# Use custom secondary sidebar that includes autodoc entries
79+
"secondary_sidebar_items": ["page-toc"],
80+
# Show more TOC levels by default
81+
"show_toc_level": 3,
7282
}
7383
if os.environ.get("CI"):
7484
if int(os.environ.get("BUILD_PREVIEW", 0)):

cuda_bindings/tests/nvml/test_nvlink.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ def test_nvlink_get_link_count(all_devices):
1111
"""
1212
for device in all_devices:
1313
fields = nvml.FieldValue(1)
14-
fields[0].field_id = nvml.FI.DEV_NVLINK_LINK_COUNT
14+
fields[0].field_id = nvml.FieldId.DEV_NVLINK_LINK_COUNT
1515
value = nvml.device_get_field_values(device, fields)[0]
1616
assert value.nvml_return == nvml.Return.SUCCESS or value.nvml_return == nvml.Return.ERROR_NOT_SUPPORTED, (
1717
f"Unexpected return {value.nvml_return} for link count field query"
1818
)
1919

2020
# Use the alternative argument to device_get_field_values
21-
value = nvml.device_get_field_values(device, [nvml.FI.DEV_NVLINK_LINK_COUNT])[0]
21+
value = nvml.device_get_field_values(device, [nvml.FieldId.DEV_NVLINK_LINK_COUNT])[0]
2222
assert value.nvml_return == nvml.Return.SUCCESS or value.nvml_return == nvml.Return.ERROR_NOT_SUPPORTED, (
2323
f"Unexpected return {value.nvml_return} for link count field query"
2424
)

cuda_core/MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
recursive-include cuda/core *.pyx *.pxd
6+
recursive-include cuda/core/_cpp *.cpp *.hpp

cuda_core/build_hooks.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ def module_names():
100100
for filename in glob.glob(f"{root_path}/**/*.pyx", recursive=True):
101101
yield filename[len(root_path) : -4]
102102

103+
def get_sources(mod_name):
104+
"""Get source files for a module, including any .cpp files."""
105+
sources = [f"cuda/core/{mod_name}.pyx"]
106+
107+
# Add module-specific .cpp file from _cpp/ directory if it exists
108+
cpp_file = f"cuda/core/_cpp/{mod_name.lstrip('_')}.cpp"
109+
if os.path.exists(cpp_file):
110+
sources.append(cpp_file)
111+
112+
return sources
113+
103114
all_include_dirs = list(os.path.join(root, "include") for root in _get_cuda_paths())
104115
extra_compile_args = []
105116
if COMPILE_FOR_COVERAGE:
@@ -110,8 +121,12 @@ def module_names():
110121
ext_modules = tuple(
111122
Extension(
112123
f"cuda.core.{mod.replace(os.path.sep, '.')}",
113-
sources=[f"cuda/core/{mod}.pyx"],
114-
include_dirs=all_include_dirs,
124+
sources=get_sources(mod),
125+
include_dirs=[
126+
"cuda/core/_include",
127+
"cuda/core/_cpp",
128+
]
129+
+ all_include_dirs,
115130
language="c++",
116131
extra_compile_args=extra_compile_args,
117132
)

cuda_core/cuda/core/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@
1515

1616
import importlib
1717

18+
# The _resource_handles module exports a PyCapsule dispatch table that other
19+
# extension modules access via PyCapsule_Import. We import it here to ensure
20+
# it's loaded before other modules try to use it.
21+
#
22+
# We use importlib.import_module with the full path to avoid triggering
23+
# circular import issues that can occur with relative imports during
24+
# package initialization.
25+
_resource_handles = importlib.import_module("cuda.core._resource_handles")
26+
1827
subdir = f"cu{cuda_major}"
1928
try:
2029
versioned_mod = importlib.import_module(f".{subdir}", __package__)

cuda_core/cuda/core/_context.pxd

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
from cuda.core._resource_handles cimport ContextHandle
6+
7+
cdef class Context:
8+
"""Cython declaration for Context class.
9+
10+
This class provides access to CUDA contexts. Context objects cannot be
11+
instantiated directly - use factory methods or Device/Stream APIs.
12+
"""
13+
14+
cdef:
15+
ContextHandle _h_context
16+
int _device_id
17+
18+
@staticmethod
19+
cdef Context _from_handle(type cls, ContextHandle h_context, int device_id)

cuda_core/cuda/core/_context.pyx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,55 @@
44

55
from dataclasses import dataclass
66

7-
from cuda.core._utils.cuda_utils import driver
7+
from cuda.core._resource_handles cimport (
8+
ContextHandle,
9+
as_intptr,
10+
as_py,
11+
)
812

913

10-
@dataclass
11-
class ContextOptions:
12-
pass # TODO
14+
__all__ = ['Context', 'ContextOptions']
1315

1416

1517
cdef class Context:
18+
"""CUDA context wrapper.
1619
17-
cdef:
18-
readonly object _handle
19-
int _device_id
20+
Context objects represent CUDA contexts and cannot be instantiated directly.
21+
Use Device or Stream APIs to obtain context objects.
22+
"""
2023

2124
def __init__(self, *args, **kwargs):
2225
raise RuntimeError("Context objects cannot be instantiated directly. Please use Device or Stream APIs.")
2326

24-
@classmethod
25-
def _from_ctx(cls, handle: driver.CUcontext, int device_id):
26-
cdef Context ctx = Context.__new__(Context)
27-
ctx._handle = handle
27+
@staticmethod
28+
cdef Context _from_handle(type cls, ContextHandle h_context, int device_id):
29+
"""Create Context from existing ContextHandle (cdef-only factory)."""
30+
cdef Context ctx = cls.__new__(cls)
31+
ctx._h_context = h_context
2832
ctx._device_id = device_id
2933
return ctx
3034

35+
@property
36+
def handle(self):
37+
"""Return the underlying CUcontext handle."""
38+
if self._h_context.get() == NULL:
39+
return None
40+
return as_py(self._h_context)
41+
3142
def __eq__(self, other):
3243
if not isinstance(other, Context):
3344
return NotImplemented
3445
cdef Context _other = <Context>other
35-
return int(self._handle) == int(_other._handle)
46+
return as_intptr(self._h_context) == as_intptr(_other._h_context)
3647

3748
def __hash__(self) -> int:
38-
return hash(int(self._handle))
49+
return hash((type(self), as_intptr(self._h_context)))
50+
51+
52+
@dataclass
53+
class ContextOptions:
54+
"""Options for context creation.
55+
56+
Currently unused, reserved for future use.
57+
"""
58+
pass # TODO

0 commit comments

Comments
 (0)