PyCDFt 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pycdft/gpu/devices.py ADDED
@@ -0,0 +1,160 @@
1
+ """Device detection and resolution for :mod:`pycdft.gpu`.
2
+
3
+ The torch backend is device-agnostic: it runs wherever torch exposes a
4
+ device. Detection order (first available wins for the automatic default):
5
+
6
+ - ``cuda`` -- NVIDIA CUDA, and also AMD ROCm builds of torch, which expose
7
+ the ROCm devices through the same ``cuda`` API (a ROCm build is
8
+ identified by ``torch.version.hip`` being set and
9
+ ``torch.cuda.is_available()`` being true);
10
+ - ``rocm`` -- reported (as an alias of the same ``cuda`` device) when a
11
+ ROCm build of torch is detected;
12
+ - ``mps`` -- Apple Metal (MacOS);
13
+ - ``cpu`` -- always available.
14
+
15
+ A specific device can be requested (``"cuda"``, ``"cuda:0"``, ``"mps"``,
16
+ ``"cpu"``, or ``"rocm"`` as an alias for the CUDA device of a ROCm build);
17
+ ``resolve_device`` raises ``ValueError`` when the requested device is not
18
+ available, so explicit requests never silently fall back to CPU.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import TYPE_CHECKING
24
+
25
+ from ._torch import TORCH_AVAILABLE, require_torch
26
+
27
+ if TYPE_CHECKING: # annotations only, never runtime
28
+ import torch
29
+
30
+ __all__ = [
31
+ "TORCH_AVAILABLE",
32
+ "available_devices",
33
+ "rocm_available",
34
+ "is_rocm_build",
35
+ "resolve_device",
36
+ "describe",
37
+ ]
38
+
39
+ #: Preferred order for the automatic default device.
40
+ _DEFAULT_ORDER = ("cuda", "mps", "cpu")
41
+
42
+
43
+ def is_rocm_build() -> bool:
44
+ """True when the installed torch was built for AMD ROCm."""
45
+ if not TORCH_AVAILABLE:
46
+ return False
47
+ torch = require_torch()
48
+ return getattr(torch.version, "hip", None) is not None
49
+
50
+
51
+ def rocm_available() -> bool:
52
+ """True when torch exposes an AMD ROCm device.
53
+
54
+ A ROCm build of torch is identified by ``torch.version.hip`` (set) and
55
+ exposes the AMD devices through the ``cuda`` API. ``torch.backends.rocm``
56
+ exists only in some builds; the HIP build string plus
57
+ ``torch.cuda.is_available()`` is the reliable marker.
58
+ """
59
+ if not TORCH_AVAILABLE:
60
+ return False
61
+ torch = require_torch()
62
+ backend = getattr(torch.backends, "rocm", None)
63
+ if backend is not None and getattr(backend, "is_available", lambda: False)():
64
+ return True
65
+ return bool(is_rocm_build() and torch.cuda.is_available())
66
+
67
+
68
+ def available_devices() -> list:
69
+ """Names of the devices torch can use, best first (``["cuda", "cpu"]``,
70
+ ``["cuda", "rocm", "cpu"]`` for a ROCm build, ``["mps", "cpu"]``, ...).
71
+
72
+ An empty list means torch is not installed.
73
+ """
74
+ if not TORCH_AVAILABLE:
75
+ return []
76
+ torch = require_torch()
77
+ devices = []
78
+ if torch.cuda.is_available():
79
+ devices.append("cuda")
80
+ if rocm_available():
81
+ devices.append("rocm") # same physical device, ROCm flavour
82
+ if torch.backends.mps.is_available():
83
+ devices.append("mps")
84
+ devices.append("cpu")
85
+ return devices
86
+
87
+
88
+ def resolve_device(preferred=None) -> "torch.device":
89
+ """Resolve a device name (or ``None`` for the best available device).
90
+
91
+ Parameters
92
+ ----------
93
+ preferred : str, torch.device or None
94
+ ``"cuda"`` / ``"cuda:0"``, ``"mps"``, ``"cpu"``, or ``"rocm"``
95
+ (alias for the CUDA device of a ROCm build). ``None`` picks the
96
+ best available device automatically.
97
+
98
+ Returns
99
+ -------
100
+ torch.device
101
+
102
+ Raises
103
+ ------
104
+ ImportError
105
+ If torch is not installed.
106
+ ValueError
107
+ If the requested device is not available on this machine (an
108
+ explicit request never silently falls back).
109
+ """
110
+ torch = require_torch()
111
+ available = available_devices()
112
+ if preferred is None:
113
+ for name in _DEFAULT_ORDER:
114
+ if name in available:
115
+ return torch.device(name)
116
+ raise RuntimeError("torch is installed but exposes no usable device")
117
+ if isinstance(preferred, torch.device):
118
+ name = preferred.type
119
+ index = preferred.index
120
+ else:
121
+ name, _, index_part = str(preferred).partition(":")
122
+ index = int(index_part) if index_part else None
123
+ if name == "rocm":
124
+ if "rocm" not in available:
125
+ raise ValueError(
126
+ f"torch device 'rocm' is not available on this machine "
127
+ f"(available: {available}); a ROCm build of torch is "
128
+ f"required on AMD GPUs")
129
+ return torch.device("cuda" if index is None else f"cuda:{index}")
130
+ if name not in available:
131
+ raise ValueError(
132
+ f"torch device {preferred!r} is not available on this machine "
133
+ f"(available: {available})")
134
+ return torch.device(name if index is None else f"{name}:{index}")
135
+
136
+
137
+ def describe() -> dict:
138
+ """Human-readable description of the torch installation and devices.
139
+
140
+ Returns a dict with the torch version, the CUDA / ROCm build versions,
141
+ the available devices and, for CUDA devices, ``torch.cuda.get_device_name``
142
+ of each device. Useful for diagnostics (e.g. on a compute node).
143
+ """
144
+ if not TORCH_AVAILABLE:
145
+ return {"torch_available": False, "devices": []}
146
+ torch = require_torch()
147
+ info = {
148
+ "torch_available": True,
149
+ "torch_version": torch.__version__,
150
+ "cuda_build": getattr(torch.version, "cuda", None),
151
+ "hip_build": getattr(torch.version, "hip", None),
152
+ "rocm_backend": rocm_available(),
153
+ "devices": available_devices(),
154
+ }
155
+ if torch.cuda.is_available():
156
+ info["cuda_device_count"] = torch.cuda.device_count()
157
+ info["cuda_device_names"] = [
158
+ torch.cuda.get_device_name(i) for i in range(torch.cuda.device_count())
159
+ ]
160
+ return info