Skip to content

Commit 7ee02b1

Browse files
committed
feat(hardware): add print_hardware_info for CPU/CUDA/MPS detection
1 parent 24ab6f1 commit 7ee02b1

5 files changed

Lines changed: 336 additions & 8 deletions

File tree

deeptab/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
NotFittedError,
99
PerformanceWarning,
1010
)
11+
from .core.hardware import print_hardware_info
1112
from .core.inference import InferenceModel
1213
from .core.reproducibility import seed_context, set_seed
1314

@@ -25,6 +26,7 @@
2526
"distributions",
2627
"metrics",
2728
"models",
29+
"print_hardware_info",
2830
"seed_context",
2931
"set_seed",
3032
]

deeptab/core/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
NotFittedError,
1919
PerformanceWarning,
2020
)
21+
from .hardware import print_hardware_info
2122
from .inference import InferenceModel
2223
from .inspection import ImportanceGetter, InspectionMixin, get_feature_dimensions
2324
from .registry import MODEL_REGISTRY, ModelInfo
@@ -67,6 +68,7 @@
6768
"get_feature_dimensions",
6869
"load_state_dict",
6970
"make_random_batches",
71+
"print_hardware_info",
7072
"restore_loaded_metadata",
7173
"save_state_dict",
7274
"seed_context",

deeptab/core/hardware.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
from __future__ import annotations
2+
3+
import os
4+
import platform
5+
import sys
6+
from typing import Any
7+
8+
import torch
9+
10+
__all__ = ["get_hardware_info", "print_hardware_info"]
11+
12+
13+
def _mps_available() -> bool:
14+
"""Return ``True`` when the Apple Silicon MPS backend is usable."""
15+
backends = getattr(torch, "backends", None)
16+
mps = getattr(backends, "mps", None)
17+
if mps is None:
18+
return False
19+
try:
20+
return bool(mps.is_available())
21+
except Exception: # pragma: no cover - defensive, backend should not raise
22+
return False
23+
24+
25+
def _mps_built() -> bool:
26+
"""Return ``True`` when this PyTorch build includes the MPS backend."""
27+
backends = getattr(torch, "backends", None)
28+
mps = getattr(backends, "mps", None)
29+
if mps is None or not hasattr(mps, "is_built"):
30+
return False
31+
try:
32+
return bool(mps.is_built())
33+
except Exception: # pragma: no cover - defensive
34+
return False
35+
36+
37+
def _recommended_accelerator(cuda_available: bool, mps_available: bool) -> str:
38+
"""Pick the accelerator DeepTab would use by default."""
39+
if cuda_available:
40+
return "cuda"
41+
if mps_available:
42+
return "mps"
43+
return "cpu"
44+
45+
46+
def _cuda_devices(detailed: bool) -> list[dict[str, Any]]:
47+
"""Return one entry per visible CUDA device."""
48+
devices: list[dict[str, Any]] = []
49+
for index in range(torch.cuda.device_count()):
50+
entry: dict[str, Any] = {
51+
"index": index,
52+
"name": torch.cuda.get_device_name(index),
53+
}
54+
if detailed:
55+
major, minor = torch.cuda.get_device_capability(index)
56+
props = torch.cuda.get_device_properties(index)
57+
free, _total = torch.cuda.mem_get_info(index)
58+
entry.update(
59+
{
60+
"compute_capability": f"{major}.{minor}",
61+
"multi_processor_count": props.multi_processor_count,
62+
"total_memory_gb": round(props.total_memory / 1024**3, 2),
63+
"free_memory_gb": round(free / 1024**3, 2),
64+
}
65+
)
66+
devices.append(entry)
67+
return devices
68+
69+
70+
def _mps_detail() -> dict[str, Any]:
71+
"""Return MPS memory counters when the build exposes them."""
72+
detail: dict[str, Any] = {}
73+
mps = getattr(torch, "mps", None)
74+
if mps is None:
75+
return detail
76+
if hasattr(mps, "current_allocated_memory"):
77+
detail["allocated_memory_gb"] = round(mps.current_allocated_memory() / 1024**3, 3)
78+
if hasattr(mps, "driver_allocated_memory"):
79+
detail["driver_allocated_memory_gb"] = round(mps.driver_allocated_memory() / 1024**3, 3)
80+
return detail
81+
82+
83+
def get_hardware_info(detailed: bool = False) -> dict[str, Any]:
84+
"""Return the compute hardware DeepTab can see on this machine.
85+
86+
The report covers the CPU, NVIDIA CUDA GPUs, and the Apple Silicon MPS
87+
backend, plus the ``accelerator`` value DeepTab would pick by default. It is
88+
safe to call at import time and never raises on machines without a GPU.
89+
90+
Parameters
91+
----------
92+
detailed : bool, default=False
93+
When ``False``, return a compact summary: core count, device counts,
94+
availability flags, and the recommended accelerator. When ``True``,
95+
also include per-GPU memory, compute capability, multiprocessor count,
96+
the CUDA / cuDNN versions PyTorch was built against, and MPS memory
97+
counters where the build exposes them.
98+
99+
Returns
100+
-------
101+
dict
102+
A nested dictionary with the keys ``platform``, ``cpu``, ``cuda``,
103+
``mps``, and ``recommended_accelerator``.
104+
105+
Examples
106+
--------
107+
>>> from deeptab import get_hardware_info
108+
>>> info = get_hardware_info()
109+
>>> sorted(info)
110+
['cpu', 'cuda', 'mps', 'platform', 'recommended_accelerator']
111+
>>> info["cpu"]["logical_cores"] >= 1
112+
True
113+
"""
114+
cuda_available = torch.cuda.is_available()
115+
mps_available = _mps_available()
116+
117+
info: dict[str, Any] = {
118+
"platform": {
119+
"system": platform.system(),
120+
"machine": platform.machine(),
121+
"python": platform.python_version(),
122+
"torch": torch.__version__,
123+
},
124+
"cpu": {
125+
"logical_cores": os.cpu_count(),
126+
},
127+
"cuda": {
128+
"available": cuda_available,
129+
"device_count": torch.cuda.device_count() if cuda_available else 0,
130+
"devices": _cuda_devices(detailed) if cuda_available else [],
131+
},
132+
"mps": {
133+
"available": mps_available,
134+
"built": _mps_built(),
135+
},
136+
"recommended_accelerator": _recommended_accelerator(cuda_available, mps_available),
137+
}
138+
139+
if detailed:
140+
info["platform"]["processor"] = platform.processor()
141+
info["platform"]["python_implementation"] = platform.python_implementation()
142+
info["platform"]["executable"] = sys.executable
143+
info["cuda"]["built_version"] = torch.version.cuda
144+
cudnn = getattr(torch.backends, "cudnn", None)
145+
info["cuda"]["cudnn_version"] = cudnn.version() if cuda_available and cudnn is not None else None
146+
if mps_available:
147+
info["mps"].update(_mps_detail())
148+
149+
return info
150+
151+
152+
def print_hardware_info(detailed: bool = False) -> None:
153+
"""Print :func:`get_hardware_info` as a readable report.
154+
155+
Parameters
156+
----------
157+
detailed : bool, default=False
158+
Forwarded to :func:`get_hardware_info`. When ``True``, the report adds
159+
per-GPU memory and capability, build versions, and MPS memory counters.
160+
161+
Examples
162+
--------
163+
>>> from deeptab import print_hardware_info
164+
>>> print_hardware_info() # doctest: +SKIP
165+
DeepTab hardware report
166+
-----------------------
167+
Platform: Darwin (arm64), Python 3.11.8, PyTorch 2.2.0
168+
...
169+
"""
170+
info = get_hardware_info(detailed=detailed)
171+
plat = info["platform"]
172+
cpu = info["cpu"]
173+
cuda = info["cuda"]
174+
mps = info["mps"]
175+
176+
lines = [
177+
"DeepTab hardware report",
178+
"-----------------------",
179+
f"Platform: {plat['system']} ({plat['machine']}), Python {plat['python']}, PyTorch {plat['torch']}",
180+
f"CPU: {cpu['logical_cores']} logical cores",
181+
]
182+
183+
if cuda["available"]:
184+
lines.append(f"CUDA: available, {cuda['device_count']} device(s)")
185+
for device in cuda["devices"]:
186+
if detailed:
187+
lines.append(
188+
f" [{device['index']}] {device['name']}: "
189+
f"{device['total_memory_gb']} GB total, "
190+
f"{device['free_memory_gb']} GB free, "
191+
f"compute capability {device['compute_capability']}, "
192+
f"{device['multi_processor_count']} SMs"
193+
)
194+
else:
195+
lines.append(f" [{device['index']}] {device['name']}")
196+
if detailed:
197+
lines.append(f" Built against CUDA {cuda['built_version']}, cuDNN {cuda['cudnn_version']}")
198+
else:
199+
lines.append("CUDA: not available")
200+
201+
if mps["available"]:
202+
mps_line = "MPS (Apple Silicon): available"
203+
if detailed and "driver_allocated_memory_gb" in mps:
204+
allocated = mps["driver_allocated_memory_gb"]
205+
if allocated > 0:
206+
mps_line += f", {allocated} GB driver-allocated"
207+
else:
208+
mps_line += ", none allocated yet"
209+
lines.append(mps_line)
210+
else:
211+
lines.append(f"MPS (Apple Silicon): not available (built: {mps['built']})")
212+
213+
lines.append(f"Recommended accelerator: {info['recommended_accelerator']}")
214+
215+
print("\n".join(lines))

docs/getting_started/installation.md

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
```{important}
44
**Requirements:** Python 3.10+ | PyTorch 2.2+ (auto-installed)
5-
**Installation time:** ~2 minutes
65
```
76

87
## Quick Install
@@ -20,15 +19,40 @@ import deeptab
2019
print(deeptab.__version__) # e.g., "2.0.0"
2120
```
2221

23-
## GPU Support
22+
## Hardware Support
2423

25-
DeepTab automatically detects and uses your GPU, with no configuration needed.
24+
DeepTab automatically detects and uses your accelerator, with no configuration
25+
needed. It supports NVIDIA GPUs through CUDA and Apple Silicon through the MPS
26+
backend, and falls back to the CPU when neither is present.
2627

27-
**Verify GPU:**
28+
**Inspect what DeepTab can see:**
29+
30+
```python
31+
from deeptab import print_hardware_info
32+
33+
print_hardware_info()
34+
```
35+
36+
```text
37+
DeepTab hardware report
38+
-----------------------
39+
Platform: Darwin (arm64), Python 3.11.8, PyTorch 2.2.0
40+
CPU: 14 logical cores
41+
CUDA: not available
42+
MPS (Apple Silicon): available
43+
Recommended accelerator: mps
44+
```
45+
46+
The report covers the CPU core count, CUDA GPUs, the Apple Silicon MPS backend,
47+
and the `accelerator` value DeepTab would pick by default.
48+
49+
### NVIDIA GPUs (CUDA)
2850

2951
```python
3052
import torch
31-
print(f"GPU available: {torch.cuda.is_available()}")
53+
54+
print(f"CUDA available: {torch.cuda.is_available()}")
55+
print(f"GPU count: {torch.cuda.device_count()}")
3256
```
3357

3458
```{warning}
@@ -48,6 +72,25 @@ See [PyTorch installation guide](https://pytorch.org/get-started/locally/) for y
4872
export CUDA_VISIBLE_DEVICES=0,1 # Use specific GPUs
4973
```
5074

75+
### Apple Silicon (MPS)
76+
77+
On M-series Macs, DeepTab uses the Metal Performance Shaders (MPS) backend.
78+
MPS ships with the standard PyTorch build, so no extra install step is needed.
79+
80+
```python
81+
import torch
82+
83+
print(f"MPS available: {torch.backends.mps.is_available()}")
84+
print(f"MPS built: {torch.backends.mps.is_built()}")
85+
```
86+
87+
```{note}
88+
`is_available()` reports `True` only on macOS 12.3+ with Apple Silicon and a
89+
PyTorch build that includes MPS. When it returns `False` but `is_built()` is
90+
`True`, the backend is present but the OS or hardware does not support it, and
91+
DeepTab runs on the CPU.
92+
```
93+
5194
## Development Installation
5295

5396
For contributing or using unreleased features:
@@ -87,11 +130,12 @@ model = FTTransformerClassifier(
87130
)
88131
```
89132

90-
**Training slow?** Check GPU is being used:
133+
**Training slow?** Check which accelerator DeepTab detected:
91134

92135
```python
93-
import torch
94-
assert torch.cuda.is_available(), "GPU not detected"
136+
from deeptab import print_hardware_info
137+
138+
print_hardware_info() # "Recommended accelerator" should not be cpu
95139
```
96140

97141
**Module not found?** Verify correct environment:

0 commit comments

Comments
 (0)