Skip to content

Commit 4992921

Browse files
committed
Address checkpoint review feedback
1 parent d8a2031 commit 4992921

4 files changed

Lines changed: 197 additions & 57 deletions

File tree

cuda_core/cuda/core/checkpoint.py

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,43 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
from collections.abc import Mapping as _Mapping
6-
from dataclasses import dataclass as _dataclass
7-
from enum import IntEnum as _IntEnum
86
from typing import Any as _Any
7+
from typing import Literal as _Literal
98

109
from 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

1213
try:
1314
from cuda.bindings import driver as _driver
1415
except 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)
3043
class 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

106123
def _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+
126170
def _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
]

cuda_core/docs/source/api.rst

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,17 +177,42 @@ CUDA compilation toolchain
177177
CUDA process checkpointing
178178
--------------------------
179179

180-
.. autosummary::
181-
:toctree: generated/
180+
The :mod:`cuda.core.checkpoint` module wraps the CUDA driver process
181+
checkpoint APIs. These APIs are intended for Linux process checkpoint and
182+
restore workflows, and require a CUDA driver with checkpoint API support and
183+
a ``cuda-bindings`` version that exposes those driver entry points.
182184

183-
:template: class.rst
185+
A checkpoint workflow operates on a CUDA process by process ID. The typical
186+
sequence is to lock the process, capture its GPU memory state, restore it
187+
when needed, and then unlock it so CUDA API calls can resume:
184188

185-
checkpoint.Process
189+
.. code-block:: python
190+
191+
from cuda.core import checkpoint
192+
193+
process = checkpoint.Process(pid)
194+
process.lock(timeout_ms=5000)
195+
process.checkpoint()
196+
process.restore()
197+
process.unlock()
198+
199+
``Process.state`` returns one of ``"running"``, ``"locked"``,
200+
``"checkpointed"``, or ``"failed"``. Restore may optionally remap GPUs by
201+
passing ``gpu_mapping`` from each checkpointed GPU UUID to the GPU UUID that
202+
should be used during restore. A successful restore returns the process to
203+
the locked state; call ``Process.unlock`` after restore to allow CUDA API
204+
calls to resume.
205+
206+
The CUDA driver requires restore to run from the process restore thread.
207+
Use ``Process.restore_thread_id`` to discover that thread before calling
208+
``Process.restore`` from a checkpoint coordinator.
186209

187210
.. autosummary::
188211
:toctree: generated/
189212

190-
checkpoint.ProcessState
213+
:template: class.rst
214+
215+
checkpoint.Process
191216

192217

193218
CUDA system information and NVIDIA Management Library (NVML)

cuda_core/docs/source/release/1.0.0-notes.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ New features
1717
------------
1818

1919
- Added the :mod:`cuda.core.checkpoint` module for CUDA process checkpointing,
20-
including process state queries, lock/checkpoint/restore/unlock operations,
21-
and GPU UUID remapping support for restore.
20+
including string process state queries, lock/checkpoint/restore/unlock
21+
operations, and GPU UUID remapping support for restore.
2222
(`#1343 <https://github.com/NVIDIA/cuda-python/issues/1343>`__)
2323

2424

0 commit comments

Comments
 (0)