33# SPDX-License-Identifier: Apache-2.0
44
55from collections .abc import Mapping as _Mapping
6- from dataclasses import dataclass as _dataclass
7- from enum import IntEnum as _IntEnum
86from typing import Any as _Any
7+ from typing import Literal as _Literal
98
109from cuda .core ._utils .cuda_utils import handle_return as _handle_cuda_return
10+ from cuda .core ._utils .version import binding_version as _binding_version
11+ from cuda .core ._utils .version import driver_version as _driver_version
1112
1213try :
1314 from cuda .bindings import driver as _driver
1415except ImportError :
1516 from cuda import cuda as _driver
1617
1718
18- class ProcessState (_IntEnum ):
19- """
20- CUDA checkpoint state for a process.
21- """
19+ ProcessStateT = _Literal ["running" , "locked" , "checkpointed" , "failed" ]
20+
21+ _PROCESS_STATE_NAMES : dict [int , ProcessStateT ] = {
22+ 0 : "running" ,
23+ 1 : "locked" ,
24+ 2 : "checkpointed" ,
25+ 3 : "failed" ,
26+ }
2227
23- RUNNING = 0
24- LOCKED = 1
25- CHECKPOINTED = 2
26- FAILED = 3
28+ _REQUIRED_BINDING_ATTRS = (
29+ "cuCheckpointProcessCheckpoint" ,
30+ "cuCheckpointProcessGetRestoreThreadId" ,
31+ "cuCheckpointProcessGetState" ,
32+ "cuCheckpointProcessLock" ,
33+ "cuCheckpointProcessRestore" ,
34+ "cuCheckpointProcessUnlock" ,
35+ "CUcheckpointGpuPair" ,
36+ "CUcheckpointLockArgs" ,
37+ "CUcheckpointRestoreArgs" ,
38+ )
39+ _REQUIRED_DRIVER_VERSION = (12 , 8 , 0 )
40+ _driver_capability_checked = False
2741
2842
29- @_dataclass (frozen = True )
3043class Process :
3144 """
3245 CUDA process that can be locked, checkpointed, restored, and unlocked.
@@ -37,27 +50,31 @@ class Process:
3750 Process ID of the CUDA process.
3851 """
3952
40- pid : int
53+ __slots__ = ( "pid" ,)
4154
42- def __post_init__ (self ):
43- _check_pid ( self .pid )
55+ def __init__ (self , pid : int ):
56+ self .pid = _check_pid ( pid )
4457
4558 @property
46- def state (self ) -> ProcessState :
59+ def state (self ) -> ProcessStateT :
4760 """
4861 CUDA checkpoint state for this process.
4962 """
5063 driver = _get_driver ()
51- state = _handle_return (driver , driver .cuCheckpointProcessGetState (self .pid ))
52- return ProcessState (int (state ))
64+ state = _call_driver (driver , driver .cuCheckpointProcessGetState , self .pid )
65+ state_value = int (state )
66+ try :
67+ return _PROCESS_STATE_NAMES [state_value ]
68+ except KeyError as e :
69+ raise RuntimeError (f"Unknown CUDA checkpoint process state: { state_value } " ) from e
5370
5471 @property
5572 def restore_thread_id (self ) -> int :
5673 """
5774 CUDA restore thread ID for this process.
5875 """
5976 driver = _get_driver ()
60- return _handle_return (driver , driver .cuCheckpointProcessGetRestoreThreadId ( self .pid ) )
77+ return _call_driver (driver , driver .cuCheckpointProcessGetRestoreThreadId , self .pid )
6178
6279 def lock (self , timeout_ms : int = 0 ) -> None :
6380 """
@@ -71,14 +88,14 @@ def lock(self, timeout_ms: int = 0) -> None:
7188 driver = _get_driver ()
7289 args = driver .CUcheckpointLockArgs ()
7390 args .timeoutMs = _check_timeout_ms (timeout_ms )
74- _handle_return (driver , driver .cuCheckpointProcessLock ( self .pid , args ) )
91+ _call_driver (driver , driver .cuCheckpointProcessLock , self .pid , args )
7592
7693 def checkpoint (self ) -> None :
7794 """
7895 Checkpoint the GPU memory contents of this locked process.
7996 """
8097 driver = _get_driver ()
81- _handle_return (driver , driver .cuCheckpointProcessCheckpoint ( self .pid , None ) )
98+ _call_driver (driver , driver .cuCheckpointProcessCheckpoint , self .pid , None )
8299
83100 def restore (self , gpu_mapping : _Mapping [_Any , _Any ] | None = None ) -> None :
84101 """
@@ -93,36 +110,63 @@ def restore(self, gpu_mapping: _Mapping[_Any, _Any] | None = None) -> None:
93110 """
94111 driver = _get_driver ()
95112 args = _make_restore_args (driver , gpu_mapping )
96- _handle_return (driver , driver .cuCheckpointProcessRestore ( self .pid , args ) )
113+ _call_driver (driver , driver .cuCheckpointProcessRestore , self .pid , args )
97114
98115 def unlock (self ) -> None :
99116 """
100117 Unlock this locked process so it can resume CUDA API calls.
101118 """
102119 driver = _get_driver ()
103- _handle_return (driver , driver .cuCheckpointProcessUnlock ( self .pid , None ) )
120+ _call_driver (driver , driver .cuCheckpointProcessUnlock , self .pid , None )
104121
105122
106123def _get_driver ():
107- required = (
108- "cuCheckpointProcessCheckpoint" ,
109- "cuCheckpointProcessGetRestoreThreadId" ,
110- "cuCheckpointProcessGetState" ,
111- "cuCheckpointProcessLock" ,
112- "cuCheckpointProcessRestore" ,
113- "cuCheckpointProcessUnlock" ,
114- "CUcheckpointGpuPair" ,
115- "CUcheckpointLockArgs" ,
116- "CUcheckpointRestoreArgs" ,
117- )
118- missing = [name for name in required if not hasattr (_driver , name )]
124+ global _driver_capability_checked
125+ if _driver_capability_checked :
126+ return _driver
127+
128+ binding_ver = _binding_version ()
129+ if not _binding_version_supports_checkpoint ( binding_ver ):
130+ raise RuntimeError (
131+ "CUDA checkpointing requires cuda.bindings with CUDA checkpoint API support. "
132+ f"Found cuda.bindings { '.' . join ( str ( part ) for part in binding_ver [: 3 ]) } ."
133+ )
134+
135+ missing = [name for name in _REQUIRED_BINDING_ATTRS if not hasattr (_driver , name )]
119136 if missing :
120137 raise RuntimeError (
121138 f"CUDA checkpointing requires cuda.bindings with CUDA checkpoint API support. Missing: { ', ' .join (missing )} "
122139 )
140+
141+ driver_ver = _driver_version ()
142+ if driver_ver < _REQUIRED_DRIVER_VERSION :
143+ raise RuntimeError (
144+ "CUDA checkpointing is not supported by the installed NVIDIA driver. "
145+ "Upgrade to a driver version with CUDA checkpoint API support."
146+ )
147+
148+ _driver_capability_checked = True
123149 return _driver
124150
125151
152+ def _binding_version_supports_checkpoint (version ) -> bool :
153+ major , minor , patch = version [:3 ]
154+ return (major == 12 and (minor , patch ) >= (8 , 0 )) or (major == 13 and (minor , patch ) >= (0 , 2 )) or major > 13
155+
156+
157+ def _call_driver (driver , func , * args ):
158+ try :
159+ result = func (* args )
160+ except RuntimeError as e :
161+ if "cuCheckpointProcess" in str (e ) and "not found" in str (e ):
162+ raise RuntimeError (
163+ "CUDA checkpointing is not supported by the installed NVIDIA driver. "
164+ "Upgrade to a driver version with CUDA checkpoint API support."
165+ ) from e
166+ raise
167+ return _handle_return (driver , result )
168+
169+
126170def _handle_return (driver , result ):
127171 err = result [0 ]
128172 not_supported_errors = (
@@ -178,5 +222,4 @@ def _make_restore_args(driver, gpu_mapping: _Mapping[_Any, _Any] | None):
178222
179223__all__ = [
180224 "Process" ,
181- "ProcessState" ,
182225]
0 commit comments