[030aeb]: / dosma / core / device.py

Download this file

254 lines (196 with data), 7.3 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
"""Functions and classes for getting and setting computing devices.
"""
import numpy as np
from dosma.utils import env
if env.cupy_available():
import cupy as cp
if env.sigpy_available():
import sigpy as sp
if env.torch_available():
import torch
__all__ = ["Device", "get_device", "to_device"]
class Device(object):
"""Device class.
This class extends ``cupy.Device`` and can also be used to interface with
``torch.Device`` and ``sigpy.Device``. This class contains a device type
('cpu' or 'cuda') and optional device ordinal (i.e. the id) for the device type.
This class can also be constructed using only the ordinal id, where id >= 0
representing the id-th GPU, and id = -1 representing CPU. cupy must be installed to use GPUs.
The array module for the corresponding device can be obtained via ``device.xp``.
Similar to cupy.Device, the Device object can be used as a context:
>>> device = Device(1) # gpu 1
>>> xp = device.xp # xp is cupy.
>>> with device:
>>> x = xp.array([1, 2, 3])
>>> x += 1
Args:
id_or_device (int or Device or cupy.cuda.Device): id > 0 represents
the corresponding GPUs, and id = -1 represents CPU.
Attributes:
type (str): Device type. Either ``'cpu'`` or ``'cuda'``.
index (int): index = -1 represents CPU, and others represents the id_th ordinances.
Note:
This class is heavily based on
`sigpy.Device <https://sigpy.readthedocs.io/en/latest/generated/sigpy.Device.html>`_.
"""
def __init__(self, id_or_device):
_type, id = None, None
if isinstance(id_or_device, int):
id = id_or_device
elif id_or_device == "cpu":
_type, id = id_or_device, -1
elif isinstance(id_or_device, Device):
_type, id = id_or_device.type, id_or_device.id
elif env.cupy_available() and isinstance(id_or_device, cp.cuda.Device):
_type, id = "cuda", id_or_device.id
elif env.sigpy_available() and isinstance(id_or_device, sp.Device):
id = id_or_device.id
elif env.torch_available() and isinstance(id_or_device, torch.device):
_type, id = id_or_device.type, id_or_device.index
if id is None:
if _type == "cuda":
id = torch.cuda.current_device()
elif _type == "cpu":
id = -1
else:
raise ValueError(f"Unsupported device type: {_type}")
else:
raise ValueError(
f"Accepts int, Device, cupy.cuda.Device, or torch.device" f"got {id_or_device}"
)
assert id >= -1
if _type is None:
_type = "cpu" if id == -1 else "cuda"
cpdevice = None
if id != -1:
if env.cupy_available():
cpdevice = cp.cuda.Device(id)
else:
raise ValueError("cupy not installed, but set device {}.".format(id))
self._type = _type
self._id = id
self._cpdevice = cpdevice
@property
def id(self):
"""int: The device ordinal."""
return self._id
@property
def type(self):
"""str: Type of device. Either ``"cpu"`` or ``"cuda"``."""
return self._type
@property
def index(self):
"""int: Alias for ``self.id``."""
return self.id
@property
def cpdevice(self):
"""cupy.Device: The equivalent ```cupy.Device```."""
return self._cpdevice
@property
def ptdevice(self):
"""torch.device: The equivalent ```torch.device```."""
if not env.torch_available():
raise RuntimeError("`torch` not installed.")
if self.id == -1:
return torch.device("cpu")
return torch.device(f"{self.type}:{self.id}")
@property
def spdevice(self):
"""sigpy.Device: The equivalent ```sigpy.Device```."""
if not env.sigpy_available():
raise RuntimeError("`sigpy` not installed.")
if self.id >= 0 and self.type != "cuda":
raise RuntimeError(f"sigpy.Device does not support type {self.type}")
return sp.Device(self.id)
@property
def xp(self):
"""module: numpy or cupy module for the device."""
if self.id == -1:
return np
return cp
def use(self):
"""Use computing device.
All operations after use() will use the device when using ``cupy``.
"""
if self.id > 0:
self.cpdevice.use()
def __int__(self):
return self.id
def __eq__(self, other):
if other == -1:
# other integers are not compared as self.type may be subject to change
return self.id == other
elif isinstance(other, Device):
return self.type == other.type and self.id == other.id
elif env.cupy_available() and isinstance(other, cp.cuda.Device):
return self.type == "cuda" and self.id == other.id
elif env.sigpy_available() and isinstance(other, sp.Device):
try:
return self.spdevice == other
except RuntimeError:
return False
elif env.torch_available() and isinstance(other, torch.device):
return self.ptdevice == other
else:
return False
def __ne__(self, other):
return not self == other
def __enter__(self):
if self.id == -1:
return None
return self.cpdevice.__enter__()
def __exit__(self, *args):
if self.id == -1:
pass
else:
self.cpdevice.__exit__()
def __repr__(self):
if self.id == -1:
return "Device(type='cpu')"
return f"Device(type='{self.type}', index={self.id})"
cpu_device = Device(-1)
def get_array_module(array):
"""Gets an appropriate module from :mod:`numpy` or :mod:`cupy`.
This is almost equivalent to :func:`cupy.get_array_module`. The differences
are that this function can be used even if cupy is not available.
Adapted from :mod:`sigpy`.
Args:
array: Input array.
Returns:
module: :mod:`cupy` or :mod:`numpy` is returned based on input.
"""
if env.cupy_available():
return cp.get_array_module(array)
else:
return np
def get_device(array):
"""Get Device from input array.
Adapted from :mod:`sigpy`.
Args:
array (array): Array.
Returns:
Device.
"""
if hasattr(array, "device"):
return Device(array.device)
else:
return cpu_device
def to_device(input, device=cpu_device):
"""Move input to device. Does not copy if same device.
Adapted from :mod:`sigpy`.
Args:
input (array): Input.
device (int or Device or cupy.Device): Output device.
Returns:
array: Output array placed in device.
"""
idevice = get_device(input)
odevice = Device(device)
if idevice == odevice:
return input
if odevice == cpu_device:
with idevice:
return input.get()
else:
with odevice:
return cp.asarray(input)