Skip to content

Commit 0f2329b

Browse files
authored
cuda.core.system: Add APIs related to events (#1448)
* cuda.core.system: Add APIs related to events * Address copilot's comments in the PR * Add support for system events * Add new classes to API docs * Mark test as not working on Windows * Fix memory management bug * Try to get more info from CI * Fix tests * Hide handle as an implementation detail * Docstring corrections * Remove erroneous print
1 parent 213e388 commit 0f2329b

9 files changed

Lines changed: 653 additions & 84 deletions

File tree

cuda_bindings/cuda/bindings/_nvml.pxd

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ ctypedef nvmlViolationTime_t ViolationTime
3434
ctypedef nvmlUUIDValue_t UUIDValue
3535
ctypedef nvmlVgpuPlacementList_v1_t VgpuPlacementList_v1
3636
ctypedef nvmlNvLinkPowerThres_t NvLinkPowerThres
37-
ctypedef nvmlSystemEventData_v1_t SystemEventData_v1
3837
ctypedef nvmlGpuInstanceProfileInfo_t GpuInstanceProfileInfo
3938
ctypedef nvmlComputeInstanceProfileInfo_t ComputeInstanceProfileInfo
4039
ctypedef nvmlMask255_t Mask255
@@ -329,10 +328,6 @@ cpdef device_register_events(intptr_t device, unsigned long long event_types, in
329328
cpdef unsigned long long device_get_supported_event_types(intptr_t device) except? 0
330329
cpdef object event_set_wait_v2(intptr_t set, unsigned int timeoutms)
331330
cpdef event_set_free(intptr_t set)
332-
cpdef system_event_set_create(intptr_t request)
333-
cpdef system_event_set_free(intptr_t request)
334-
cpdef system_register_events(intptr_t request)
335-
cpdef system_event_set_wait(intptr_t request)
336331
cpdef device_modify_drain_state(intptr_t pci_info, int new_state)
337332
cpdef int device_query_drain_state(intptr_t pci_info) except? -1
338333
cpdef device_remove_gpu_v2(intptr_t pci_info, int gpu_state, int link_state)

cuda_bindings/cuda/bindings/_nvml.pyx

Lines changed: 208 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10155,11 +10155,157 @@ cdef class EventData:
1015510155
return obj
1015610156

1015710157

10158+
cdef _get_system_event_data_v1_dtype_offsets():
10159+
cdef nvmlSystemEventData_v1_t pod = nvmlSystemEventData_v1_t()
10160+
return _numpy.dtype({
10161+
'names': ['event_type', 'gpu_id'],
10162+
'formats': [_numpy.uint64, _numpy.uint32],
10163+
'offsets': [
10164+
(<intptr_t>&(pod.eventType)) - (<intptr_t>&pod),
10165+
(<intptr_t>&(pod.gpuId)) - (<intptr_t>&pod),
10166+
],
10167+
'itemsize': sizeof(nvmlSystemEventData_v1_t),
10168+
})
10169+
10170+
system_event_data_v1_dtype = _get_system_event_data_v1_dtype_offsets()
10171+
10172+
cdef class SystemEventData_v1:
10173+
"""Empty-initialize an array of `nvmlSystemEventData_v1_t`.
10174+
10175+
The resulting object is of length `size` and of dtype `system_event_data_v1_dtype`.
10176+
If default-constructed, the instance represents a single struct.
10177+
10178+
Args:
10179+
size (int): number of structs, default=1.
10180+
10181+
10182+
.. seealso:: `nvmlSystemEventData_v1_t`
10183+
"""
10184+
cdef:
10185+
readonly object _data
10186+
10187+
10188+
10189+
def __init__(self, size=1):
10190+
arr = _numpy.empty(size, dtype=system_event_data_v1_dtype)
10191+
self._data = arr.view(_numpy.recarray)
10192+
assert self._data.itemsize == sizeof(nvmlSystemEventData_v1_t), \
10193+
f"itemsize {self._data.itemsize} mismatches struct size { sizeof(nvmlSystemEventData_v1_t) }"
10194+
10195+
def __repr__(self):
10196+
if self._data.size > 1:
10197+
return f"<{__name__}.SystemEventData_v1_Array_{self._data.size} object at {hex(id(self))}>"
10198+
else:
10199+
return f"<{__name__}.SystemEventData_v1 object at {hex(id(self))}>"
10200+
10201+
@property
10202+
def ptr(self):
10203+
"""Get the pointer address to the data as Python :class:`int`."""
10204+
return self._data.ctypes.data
10205+
10206+
cdef intptr_t _get_ptr(self):
10207+
return self._data.ctypes.data
10208+
10209+
def __int__(self):
10210+
if self._data.size > 1:
10211+
raise TypeError("int() argument must be a bytes-like object of size 1. "
10212+
"To get the pointer address of an array, use .ptr")
10213+
return self._data.ctypes.data
10214+
10215+
def __len__(self):
10216+
return self._data.size
10217+
10218+
def __eq__(self, other):
10219+
cdef object self_data = self._data
10220+
if (not isinstance(other, SystemEventData_v1)) or self_data.size != other._data.size or self_data.dtype != other._data.dtype:
10221+
return False
10222+
return bool((self_data == other._data).all())
10223+
10224+
@property
10225+
def event_type(self):
10226+
"""Union[~_numpy.uint64, int]: Information about what specific system event occurred."""
10227+
if self._data.size == 1:
10228+
return int(self._data.event_type[0])
10229+
return self._data.event_type
10230+
10231+
@event_type.setter
10232+
def event_type(self, val):
10233+
self._data.event_type = val
10234+
10235+
@property
10236+
def gpu_id(self):
10237+
"""Union[~_numpy.uint32, int]: gpuId in PCI format"""
10238+
if self._data.size == 1:
10239+
return int(self._data.gpu_id[0])
10240+
return self._data.gpu_id
10241+
10242+
@gpu_id.setter
10243+
def gpu_id(self, val):
10244+
self._data.gpu_id = val
10245+
10246+
def __getitem__(self, key):
10247+
cdef ssize_t key_
10248+
cdef ssize_t size
10249+
if isinstance(key, int):
10250+
key_ = key
10251+
size = self._data.size
10252+
if key_ >= size or key_ <= -(size+1):
10253+
raise IndexError("index is out of bounds")
10254+
if key_ < 0:
10255+
key_ += size
10256+
return SystemEventData_v1.from_data(self._data[key_:key_+1])
10257+
out = self._data[key]
10258+
if isinstance(out, _numpy.recarray) and out.dtype == system_event_data_v1_dtype:
10259+
return SystemEventData_v1.from_data(out)
10260+
return out
10261+
10262+
def __setitem__(self, key, val):
10263+
self._data[key] = val
10264+
10265+
@staticmethod
10266+
def from_data(data):
10267+
"""Create an SystemEventData_v1 instance wrapping the given NumPy array.
10268+
10269+
Args:
10270+
data (_numpy.ndarray): a 1D array of dtype `system_event_data_v1_dtype` holding the data.
10271+
"""
10272+
cdef SystemEventData_v1 obj = SystemEventData_v1.__new__(SystemEventData_v1)
10273+
if not isinstance(data, _numpy.ndarray):
10274+
raise TypeError("data argument must be a NumPy ndarray")
10275+
if data.ndim != 1:
10276+
raise ValueError("data array must be 1D")
10277+
if data.dtype != system_event_data_v1_dtype:
10278+
raise ValueError("data array must be of dtype system_event_data_v1_dtype")
10279+
obj._data = data.view(_numpy.recarray)
10280+
10281+
return obj
10282+
10283+
@staticmethod
10284+
def from_ptr(intptr_t ptr, size_t size=1, bint readonly=False):
10285+
"""Create an SystemEventData_v1 instance wrapping the given pointer.
10286+
10287+
Args:
10288+
ptr (intptr_t): pointer address as Python :class:`int` to the data.
10289+
size (int): number of structs, default=1.
10290+
readonly (bool): whether the data is read-only (to the user). default is `False`.
10291+
"""
10292+
if ptr == 0:
10293+
raise ValueError("ptr must not be null (0)")
10294+
cdef SystemEventData_v1 obj = SystemEventData_v1.__new__(SystemEventData_v1)
10295+
cdef flag = cpython.buffer.PyBUF_READ if readonly else cpython.buffer.PyBUF_WRITE
10296+
cdef object buf = cpython.memoryview.PyMemoryView_FromMemory(
10297+
<char*>ptr, sizeof(nvmlSystemEventData_v1_t) * size, flag)
10298+
data = _numpy.ndarray(size, buffer=buf, dtype=system_event_data_v1_dtype)
10299+
obj._data = data.view(_numpy.recarray)
10300+
10301+
return obj
10302+
10303+
1015810304
cdef _get_accounting_stats_dtype_offsets():
1015910305
cdef nvmlAccountingStats_t pod = nvmlAccountingStats_t()
1016010306
return _numpy.dtype({
1016110307
'names': ['gpu_utilization', 'memory_utilization', 'max_memory_usage', 'time', 'start_time', 'is_running', 'reserved'],
10162-
'formats': [_numpy.uint32, _numpy.uint32, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint32, _numpy.uint32],
10308+
'formats': [_numpy.uint32, _numpy.uint32, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint32, (_numpy.uint32, 5)],
1016310309
'offsets': [
1016410310
(<intptr_t>&(pod.gpuUtilization)) - (<intptr_t>&pod),
1016510311
(<intptr_t>&(pod.memoryUtilization)) - (<intptr_t>&pod),
@@ -24913,58 +25059,6 @@ cpdef event_set_free(intptr_t set):
2491325059
check_status(__status__)
2491425060

2491525061

24916-
cpdef system_event_set_create(intptr_t request):
24917-
"""Create an empty set of system events. Event set should be freed by ``nvmlSystemEventSetFree``.
24918-
24919-
Args:
24920-
request (intptr_t): Reference to nvmlSystemEventSetCreateRequest_t.
24921-
24922-
.. seealso:: `nvmlSystemEventSetCreate`
24923-
"""
24924-
with nogil:
24925-
__status__ = nvmlSystemEventSetCreate(<nvmlSystemEventSetCreateRequest_t*>request)
24926-
check_status(__status__)
24927-
24928-
24929-
cpdef system_event_set_free(intptr_t request):
24930-
"""Releases system event set.
24931-
24932-
Args:
24933-
request (intptr_t): Reference to nvmlSystemEventSetFreeRequest_t.
24934-
24935-
.. seealso:: `nvmlSystemEventSetFree`
24936-
"""
24937-
with nogil:
24938-
__status__ = nvmlSystemEventSetFree(<nvmlSystemEventSetFreeRequest_t*>request)
24939-
check_status(__status__)
24940-
24941-
24942-
cpdef system_register_events(intptr_t request):
24943-
"""Starts recording of events on system and add the events to specified ``nvmlSystemEventSet_t``.
24944-
24945-
Args:
24946-
request (intptr_t): Reference to the struct nvmlSystemRegisterEventRequest_t.
24947-
24948-
.. seealso:: `nvmlSystemRegisterEvents`
24949-
"""
24950-
with nogil:
24951-
__status__ = nvmlSystemRegisterEvents(<nvmlSystemRegisterEventRequest_t*>request)
24952-
check_status(__status__)
24953-
24954-
24955-
cpdef system_event_set_wait(intptr_t request):
24956-
"""Waits on system events and delivers events.
24957-
24958-
Args:
24959-
request (intptr_t): Reference in which to nvmlSystemEventSetWaitRequest_t.
24960-
24961-
.. seealso:: `nvmlSystemEventSetWait`
24962-
"""
24963-
with nogil:
24964-
__status__ = nvmlSystemEventSetWait(<nvmlSystemEventSetWaitRequest_t*>request)
24965-
check_status(__status__)
24966-
24967-
2496825062
cpdef device_modify_drain_state(intptr_t pci_info, int new_state):
2496925063
"""Modify the drain state of a GPU. This method forces a GPU to no longer accept new incoming requests. Any new NVML process will no longer see this GPU. Persistence mode for this GPU must be turned off before this call is made. Must be called as administrator. For Linux only.
2497025064

@@ -27908,3 +28002,64 @@ cpdef object device_get_nvlink_info(intptr_t device):
2790828002
__status__ = nvmlDeviceGetNvLinkInfo(<Device>device, info)
2790928003
check_status(__status__)
2791028004
return info_v1_py
28005+
28006+
28007+
cpdef intptr_t system_event_set_create():
28008+
"""Create an empty set of system events. Event set should be freed by ``nvmlSystemEventSetFree``."""
28009+
cdef nvmlSystemEventSetCreateRequest_v1_t[1] request
28010+
with nogil:
28011+
request[0].version = sizeof(nvmlSystemEventSetCreateRequest_v1_t) | (1 << 24)
28012+
__status__ = nvmlSystemEventSetCreate(<nvmlSystemEventSetCreateRequest_t*>request)
28013+
check_status(__status__)
28014+
return <intptr_t>(request[0].set)
28015+
28016+
28017+
cpdef system_event_set_free(intptr_t event_set):
28018+
"""Frees an event set."""
28019+
cdef nvmlSystemEventSetFreeRequest_v1_t[1] request
28020+
request[0].set = <SystemEventSet>event_set
28021+
with nogil:
28022+
request[0].version = sizeof(nvmlSystemEventSetFreeRequest_v1_t) | (1 << 24)
28023+
__status__ = nvmlSystemEventSetFree(<nvmlSystemEventSetFreeRequest_t*>request)
28024+
check_status(__status__)
28025+
28026+
28027+
cpdef system_register_events(unsigned long long event_types, intptr_t event_set):
28028+
"""Starts recording of events on system and add the events to specified ``nvmlSystemEventSet_t``.
28029+
28030+
Args:
28031+
event_types (unsigned long long): Bitmask of nvmlSystemEventType_t values representing the events to register.
28032+
event_set (intptr_t): The system event set handle.
28033+
"""
28034+
cdef nvmlSystemRegisterEventRequest_v1_t[1] request
28035+
request[0].set = <SystemEventSet>event_set
28036+
request[0].eventTypes = event_types
28037+
with nogil:
28038+
request[0].version = sizeof(nvmlSystemRegisterEventRequest_v1_t) | (1 << 24)
28039+
__status__ = nvmlSystemRegisterEvents(<nvmlSystemRegisterEventRequest_t*>request)
28040+
check_status(__status__)
28041+
28042+
28043+
cpdef object system_event_set_wait(intptr_t event_set, unsigned int timeout_ms, unsigned int buffer_size):
28044+
"""Waits for events to occur on the system event set.
28045+
28046+
Args:
28047+
event_set (intptr_t): The system event set handle.
28048+
timeout_ms (unsigned int): The maximum amount of time in milliseconds to wait for an event.
28049+
buffer_size (unsigned int): The size of the event buffer.
28050+
28051+
Returns:
28052+
SystemEvent: The system event that occurred.
28053+
"""
28054+
cdef nvmlSystemEventSetWaitRequest_v1_t[1] request
28055+
cdef SystemEventData_v1 event_data = SystemEventData_v1(buffer_size)
28056+
request[0].timeoutms = timeout_ms
28057+
request[0].set = <SystemEventSet>event_set
28058+
request[0].data = <nvmlSystemEventData_v1_t *><intptr_t>(event_data._get_ptr())
28059+
request[0].dataSize = buffer_size
28060+
with nogil:
28061+
request[0].version = sizeof(nvmlSystemEventSetWaitRequest_v1_t) | (1 << 24)
28062+
__status__ = nvmlSystemEventSetWait(<nvmlSystemEventSetWaitRequest_t*>request)
28063+
check_status(__status__)
28064+
event_data._data.resize((request[0].numEvent,))
28065+
return event_data

cuda_core/cuda/core/system/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
if CUDA_BINDINGS_NVML_IS_COMPATIBLE:
2626
from ._device import *
2727
from ._device import __all__ as _device_all
28+
from ._system_events import *
29+
from ._system_events import __all__ as _system_events_all
2830
from .exceptions import *
2931
from .exceptions import __all__ as _exceptions_all
3032

3133
__all__.append("get_nvml_version")
3234
__all__.extend(_device_all)
35+
__all__.extend(_system_events_all)
3336
__all__.extend(_exceptions_all)

0 commit comments

Comments
 (0)