Skip to content

Commit c1fea41

Browse files
authored
Merge pull request #256 from ksimpson-work/add_system
Add the cuda.core.experimental.system singleton
2 parents 1ff0b3e + 769ac66 commit c1fea41

File tree

8 files changed

+151
-1
lines changed

8 files changed

+151
-1
lines changed

cuda_core/cuda/core/experimental/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@
99
from cuda.core.experimental._linker import Linker, LinkerOptions
1010
from cuda.core.experimental._program import Program
1111
from cuda.core.experimental._stream import Stream, StreamOptions
12+
from cuda.core.experimental._system import System
13+
14+
system = System()
15+
__import__("sys").modules[__spec__.name + ".system"] = system
16+
del System

cuda_core/cuda/core/experimental/_linker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ def link(self, target_type) -> ObjectCode:
443443
return ObjectCode(bytes(code), target_type)
444444

445445
def get_error_log(self) -> str:
446-
""" Get the error log generated by the linker.
446+
"""Get the error log generated by the linker.
447447
448448
Returns
449449
-------
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2+
#
3+
# SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE
4+
5+
from typing import Tuple
6+
7+
from cuda import cuda, cudart
8+
from cuda.core.experimental._device import Device
9+
from cuda.core.experimental._utils import handle_return
10+
11+
12+
class System:
13+
"""Provide information about the cuda system.
14+
This class is a singleton and should not be instantiated directly.
15+
"""
16+
17+
_instance = None
18+
19+
def __new__(cls):
20+
if cls._instance is None:
21+
cls._instance = super().__new__(cls)
22+
return cls._instance
23+
24+
def __init__(self):
25+
if hasattr(self, "_initialized") and self._initialized:
26+
return
27+
self._initialized = True
28+
29+
@property
30+
def driver_version(self) -> Tuple[int, int]:
31+
"""
32+
Query the CUDA driver version.
33+
34+
Returns
35+
-------
36+
tuple of int
37+
A 2-tuple of (major, minor) version numbers.
38+
"""
39+
version = handle_return(cuda.cuDriverGetVersion())
40+
major = version // 1000
41+
minor = (version % 1000) // 10
42+
return (major, minor)
43+
44+
@property
45+
def num_devices(self) -> int:
46+
"""
47+
Query the number of available GPUs.
48+
49+
Returns
50+
-------
51+
int
52+
The number of available GPU devices.
53+
"""
54+
return handle_return(cudart.cudaGetDeviceCount())
55+
56+
@property
57+
def devices(self) -> tuple:
58+
"""
59+
Query the available device instances.
60+
61+
Returns
62+
-------
63+
tuple of Device
64+
A tuple containing instances of available devices.
65+
"""
66+
total = self.num_devices
67+
return tuple(Device(device_id) for device_id in range(total))

cuda_core/docs/source/api.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ CUDA compilation toolchain
3838
LinkerOptions
3939

4040

41+
CUDA system information
42+
-----------------------
43+
44+
.. autodata:: cuda.core.experimental.system.driver_version
45+
:no-value:
46+
.. autodata:: cuda.core.experimental.system.num_devices
47+
:no-value:
48+
.. autodata:: cuda.core.experimental.system.devices
49+
:no-value:
50+
51+
4152
.. module:: cuda.core.experimental.utils
4253

4354
Utility functions

cuda_core/docs/source/conf.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,31 @@
9191

9292
napoleon_google_docstring = False
9393
napoleon_numpy_docstring = True
94+
95+
96+
section_titles = ["Returns"]
97+
def autodoc_process_docstring(app, what, name, obj, options, lines):
98+
if name.startswith("cuda.core.experimental.system"):
99+
# patch the docstring (in lines) *in-place*. Should docstrings include section titles other than "Returns",
100+
# this will need to be modified to handle them.
101+
attr = name.split(".")[-1]
102+
from cuda.core.experimental._system import System
103+
104+
lines_new = getattr(System, attr).__doc__.split("\n")
105+
formatted_lines = []
106+
for line in lines_new:
107+
title = line.strip()
108+
if title in section_titles:
109+
formatted_lines.append(line.replace(title, f".. rubric:: {title}"))
110+
elif line.strip() == "-" * len(title):
111+
formatted_lines.append(" " * len(title))
112+
else:
113+
formatted_lines.append(line)
114+
n_pops = len(lines)
115+
lines.extend(formatted_lines)
116+
for _ in range(n_pops):
117+
lines.pop(0)
118+
119+
120+
def setup(app):
121+
app.connect("autodoc-process-docstring", autodoc_process_docstring)

cuda_core/docs/source/release.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ maxdepth: 3
77
88
0.1.1 <release/0.1.1-notes>
99
0.1.0 <release/0.1.0-notes>
10+
1011
```

cuda_core/docs/source/release/0.1.1-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Released on Dec XX, 2024
99
- Add `Linker` that can link one or multiple `ObjectCode` instances generated by `Program`s. Under
1010
the hood, it uses either the nvJitLink or cuLink APIs depending on the CUDA version detected
1111
in the current environment.
12+
- Add a `cuda.core.experimental.system` module for querying system- or process- wide information.
1213
- Support TCC devices with a default synchronous memory resource to avoid the use of memory pools
1314

1415

cuda_core/tests/test_system.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
try:
2+
from cuda.bindings import driver, runtime
3+
except ImportError:
4+
from cuda import cuda as driver
5+
from cuda import cudart as runtime
6+
7+
from cuda.core.experimental import Device, system
8+
from cuda.core.experimental._utils import handle_return
9+
10+
11+
def test_system_singleton():
12+
system1 = system
13+
system2 = system
14+
assert id(system1) == id(system2), "system is not a singleton"
15+
16+
17+
def test_driver_version():
18+
driver_version = system.driver_version
19+
print(driver_version)
20+
version = handle_return(driver.cuDriverGetVersion())
21+
expected_driver_version = (version // 1000, (version % 1000) // 10)
22+
assert driver_version == expected_driver_version, "Driver version does not match expected value"
23+
24+
25+
def test_num_devices():
26+
num_devices = system.num_devices
27+
expected_num_devices = handle_return(runtime.cudaGetDeviceCount())
28+
assert num_devices == expected_num_devices, "Number of devices does not match expected value"
29+
30+
31+
def test_devices():
32+
devices = system.devices
33+
expected_num_devices = handle_return(runtime.cudaGetDeviceCount())
34+
expected_devices = tuple(Device(device_id) for device_id in range(expected_num_devices))
35+
assert len(devices) == len(expected_devices), "Number of devices does not match expected value"
36+
for device, expected_device in zip(devices, expected_devices):
37+
assert device.device_id == expected_device.device_id, "Device ID does not match expected value"

0 commit comments

Comments
 (0)