ezRay 1.0.2__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.
- ezRay/__init__.py +4 -0
- ezRay/ezRay.py +650 -0
- ezray-1.0.2.dist-info/LICENSE +674 -0
- ezray-1.0.2.dist-info/METADATA +68 -0
- ezray-1.0.2.dist-info/RECORD +6 -0
- ezray-1.0.2.dist-info/WHEEL +4 -0
ezRay/__init__.py
ADDED
ezRay/ezRay.py
ADDED
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
## Dependencies:
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import webbrowser
|
|
5
|
+
from typing import Any, Callable, Dict, List, NoReturn, Optional, Tuple, Union
|
|
6
|
+
|
|
7
|
+
import psutil
|
|
8
|
+
import ray
|
|
9
|
+
|
|
10
|
+
# typing
|
|
11
|
+
import ray.remote_function
|
|
12
|
+
import ray.runtime_context
|
|
13
|
+
|
|
14
|
+
# context aware progress bar
|
|
15
|
+
# detect jupyter notebook
|
|
16
|
+
from IPython import get_ipython
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
ipy_str = str(type(get_ipython()))
|
|
20
|
+
if 'zmqshell' in ipy_str:
|
|
21
|
+
from tqdm.notebook import tqdm
|
|
22
|
+
else:
|
|
23
|
+
from tqdm import tqdm
|
|
24
|
+
except Exception as _:
|
|
25
|
+
from tqdm import tqdm
|
|
26
|
+
|
|
27
|
+
#%% Multi Core Execution Main
|
|
28
|
+
class MultiCoreExecutionTool:
|
|
29
|
+
RuntimeData:Dict[Any,Dict[Any,Any]]
|
|
30
|
+
RuntimeResults:Dict[Any,Dict[str,Any]]
|
|
31
|
+
|
|
32
|
+
runimte_context:ray.runtime_context.RuntimeContext
|
|
33
|
+
RuntimeMetadata:Dict[str,Union[str, bool, int, float]]
|
|
34
|
+
DashboardURL:str
|
|
35
|
+
|
|
36
|
+
silent:bool
|
|
37
|
+
DEBUG:bool
|
|
38
|
+
|
|
39
|
+
def __init__(self, RuntimeData:Dict[Any,Dict[Any,Any]] = None, /, **kwargs)->'MultiCoreExecutionTool':
|
|
40
|
+
"""Constructor for the MultiCoreExecutionTool class.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
RuntimeData (Dict[Any,Dict[Any,Any]], optional): Dictionary containing keyword arguments for the methods to run. Defaults to None.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
MultiCoreExecutionTool: MultiCoreExecutionTool object.
|
|
47
|
+
"""
|
|
48
|
+
## Default Verbosity
|
|
49
|
+
self.ListenerSleeptime = 0.1
|
|
50
|
+
self.LaunchDashboard = False
|
|
51
|
+
self.silent = False
|
|
52
|
+
self.DEBUG = False
|
|
53
|
+
|
|
54
|
+
## Initialize attributes
|
|
55
|
+
self.DashboardURL = None
|
|
56
|
+
self.RuntimeContext = None
|
|
57
|
+
self.RuntimeMetadata = None
|
|
58
|
+
self.RuntimeResults = None
|
|
59
|
+
|
|
60
|
+
## Setattributes
|
|
61
|
+
for key, value in kwargs.items():
|
|
62
|
+
if hasattr(self, key):
|
|
63
|
+
setattr(self, key, value)
|
|
64
|
+
|
|
65
|
+
## set the debug flag
|
|
66
|
+
if 'DEBUG' in kwargs.keys():
|
|
67
|
+
self.DEBUG = kwargs['DEBUG']
|
|
68
|
+
self.silent = False
|
|
69
|
+
|
|
70
|
+
self.__post_init__(RuntimeData, **kwargs)
|
|
71
|
+
|
|
72
|
+
def __post_init__(self, RuntimeData:Dict[Any,Dict[Any,Any]], /, **kwargs)->NoReturn:
|
|
73
|
+
"""Post initialization method for the MultiCoreExecutionTool class. Handles routine initialization tasks.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
RuntimeData (Dict[Any,Dict[Any,Any]]): Structured data to be processed by the methods.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
NoReturn: No Return
|
|
80
|
+
"""
|
|
81
|
+
self.__initialize_metadata__(**kwargs)
|
|
82
|
+
self.__initialize_ray_cluster__()
|
|
83
|
+
self.__offload_on_init__(RuntimeData)
|
|
84
|
+
|
|
85
|
+
#%% Class methods
|
|
86
|
+
@classmethod
|
|
87
|
+
def from_dict(cls, data:Dict[str,Any])->'MultiCoreExecutionTool':
|
|
88
|
+
"""Convenience method to create a MultiCoreExecutionTool object from a dictionary.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
MultiCoreExecutionTool: MultiCoreExecutionTool object.
|
|
92
|
+
"""
|
|
93
|
+
return cls(**data)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def from_json(cls, path:str)->'MultiCoreExecutionTool':
|
|
97
|
+
"""Convenience method to create a MultiCoreExecutionTool object from a JSON file.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
path (str): Path to the JSON file.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
MultiCoreExecutionTool: MultiCoreExecutionTool object.
|
|
104
|
+
"""
|
|
105
|
+
with open(path, 'r') as file:
|
|
106
|
+
data = json.load(file)
|
|
107
|
+
return cls(**data)
|
|
108
|
+
|
|
109
|
+
#%% DEBUG & DEMO
|
|
110
|
+
@ray.remote(num_cpus=1, num_returns=1)
|
|
111
|
+
def test_function(kwargs)->Dict[Any,Any]:
|
|
112
|
+
"""Test function for the framework that merely forwards the input."""
|
|
113
|
+
return {k:v for k,v in kwargs.items()}
|
|
114
|
+
|
|
115
|
+
#%% Ray Wrapper
|
|
116
|
+
def __setup_wrapper__(self)->Callable:
|
|
117
|
+
@ray.remote(**self.RuntimeMetadata['task_metadata'])
|
|
118
|
+
def __method_wrapper__(method:Callable, input:Dict[Any,Any])->ray.remote_function.RemoteFunction:
|
|
119
|
+
"""Ray wrapper for arbitrary function logic.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
method (Callable): Arbitrary method that takes at least one input.
|
|
123
|
+
input (Dict[Any,Any]): Method input that will be forwarded to the main logic.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Callable: Returns a ray.remote callable object.
|
|
127
|
+
"""
|
|
128
|
+
return method(**input)
|
|
129
|
+
return __method_wrapper__
|
|
130
|
+
|
|
131
|
+
#%% Main Backend
|
|
132
|
+
def __run__(self, worker:Union[Callable, ray.remote_function.RemoteFunction])->bool:
|
|
133
|
+
"""Main execution method for the MultiCoreExecutionTool class. Runs the provided worker on the provided data.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
worker (Union[Callable, ray.remote_function.RemoteFunction]): Main worker or logic. Can be either ray.remote or a callable.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
Exception: Exception is raised if the core logic is not ray compatible.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
bool: Boolean flag that is True if the execution is successful.
|
|
143
|
+
"""
|
|
144
|
+
## check if ray is initialized
|
|
145
|
+
if not ray.is_initialized():
|
|
146
|
+
raise Exception('Ray is not initialized. Use object.initialize() to initialize Ray.')
|
|
147
|
+
|
|
148
|
+
if not self.is_ray_compatible(worker):
|
|
149
|
+
try:
|
|
150
|
+
coreLogic = worker
|
|
151
|
+
worker = self.__setup_wrapper__()
|
|
152
|
+
except Exception as e:
|
|
153
|
+
print(f'Error: {e}')
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
## prepare schedule
|
|
157
|
+
schedule = self.__setup_schedule__()
|
|
158
|
+
if len(schedule) == 0:
|
|
159
|
+
print('No pending tasks to run.')
|
|
160
|
+
return True
|
|
161
|
+
|
|
162
|
+
## workflow factory
|
|
163
|
+
if self.silent:
|
|
164
|
+
permision, states = self.__multicore_workflow__(worker = worker,
|
|
165
|
+
schedule = schedule,
|
|
166
|
+
listener = self.__silent_listener__,
|
|
167
|
+
scheduler = self.__silent_scheduler__,
|
|
168
|
+
coreLogic = coreLogic if 'coreLogic' in locals() else None)
|
|
169
|
+
else:
|
|
170
|
+
permision, states = self.__multicore_workflow__(worker = worker,
|
|
171
|
+
schedule = schedule,
|
|
172
|
+
listener = self.__verbose_listener__,
|
|
173
|
+
scheduler = self.__verbose_scheduler__,
|
|
174
|
+
coreLogic = coreLogic if 'coreLogic' in locals() else None)
|
|
175
|
+
|
|
176
|
+
## update the results
|
|
177
|
+
if permision:
|
|
178
|
+
for k in schedule:
|
|
179
|
+
self.RuntimeResults[k].update({'result':states[k], 'status':'completed'})
|
|
180
|
+
|
|
181
|
+
return permision
|
|
182
|
+
|
|
183
|
+
def __verbose_scheduler__(self,
|
|
184
|
+
worker:ray.remote_function.RemoteFunction,
|
|
185
|
+
schedule:List[Any],
|
|
186
|
+
coreLogic:Optional[Callable])->Dict[ray.ObjectRef,int]:
|
|
187
|
+
"""Verbose scheduler that handles remote task execution.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
worker (ray.remote_function.RemoteFunction): Remote callable object. See ray.remote for more information.
|
|
191
|
+
schedule (List[Any]): List of keys referring to RuntimeData values to be processed using the provided method.
|
|
192
|
+
coreLogic (Optional[Callable]): Core logic of local function that will be forwarded to ray.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Dict[ray.ObjectRef,int]: Dictionary containing the object references and their corresponding keys for keeping track of the progress and upholding the order of input data provided.
|
|
196
|
+
"""
|
|
197
|
+
## VERBOSE MODE
|
|
198
|
+
|
|
199
|
+
# if coreLogic is provided, pass it to the wrapper
|
|
200
|
+
if coreLogic is not None:
|
|
201
|
+
return {worker.remote(coreLogic, self.RuntimeData_ref[schedule_index]):schedule_index
|
|
202
|
+
for schedule_index in tqdm(schedule, total=len(schedule), desc="Scheduling Workers", position = 0)}
|
|
203
|
+
|
|
204
|
+
# if a ray compatible worker is provided, forward the worker directly
|
|
205
|
+
return {worker.remote(self.RuntimeData_ref[schedule_index]):schedule_index
|
|
206
|
+
for schedule_index in tqdm(schedule, total=len(schedule), desc="Scheduling Workers", position = 0)}
|
|
207
|
+
|
|
208
|
+
def __silent_scheduler__(self,
|
|
209
|
+
worker:ray.remote_function.RemoteFunction,
|
|
210
|
+
schedule:List,
|
|
211
|
+
coreLogic:Optional[Callable])->Dict[ray.ObjectRef,int]:
|
|
212
|
+
"""Silent scheduler that handles remote task execution.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
worker (ray.remote_function.RemoteFunction): Remote callable object. See ray.remote for more information.
|
|
216
|
+
schedule (List[Any]): List of keys referring to RuntimeData values to be processed using the provided method.
|
|
217
|
+
coreLogic (Optional[Callable]): Core logic of local function that will be forwarded to ray.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Dict[ray.ObjectRef,int]: Dictionary containing the object references and their corresponding keys for keeping track of the progress and upholding the order of input data provided.
|
|
221
|
+
"""
|
|
222
|
+
## SILENT MODE
|
|
223
|
+
|
|
224
|
+
# if coreLogic is provided, pass it to the wrapper
|
|
225
|
+
if coreLogic is not None:
|
|
226
|
+
return {worker.remote(coreLogic, self.RuntimeData_ref[schedule_index]):schedule_index
|
|
227
|
+
for schedule_index in schedule}
|
|
228
|
+
|
|
229
|
+
# if a ray compatible worker is provided, forward the worker directly
|
|
230
|
+
return {worker.remote(self.RuntimeData_ref[schedule_index]):schedule_index
|
|
231
|
+
for schedule_index in schedule}
|
|
232
|
+
|
|
233
|
+
def __multicore_workflow__(self,
|
|
234
|
+
worker:Union[Callable, ray.remote_function.RemoteFunction],
|
|
235
|
+
schedule:List[Any],
|
|
236
|
+
listener:Callable,
|
|
237
|
+
scheduler:Callable,
|
|
238
|
+
coreLogic:Optional[Callable]
|
|
239
|
+
)->Tuple[bool, Dict[int,Any]]:
|
|
240
|
+
"""Workflow for the MultiCoreExecutionTool class. Handles the main execution logic.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
worker (Union[Callable, ray.remote_function.RemoteFunction]): Remote callable object. See ray.remote for more information.
|
|
244
|
+
schedule (List[Any]): List of keys referring to RuntimeData values to be processed using the provided method.
|
|
245
|
+
listener (Callable): Chosen listener.
|
|
246
|
+
scheduler (Callable): Chosen scheduler.
|
|
247
|
+
coreLogic (Optional[Callable]): Core logic of local function that will be forwarded to ray.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Tuple[bool, Dict[int,Any]]: Boolean flag signaling the success or the execution, Dictionary containing the results of the execution.
|
|
251
|
+
"""
|
|
252
|
+
## workflow and listening
|
|
253
|
+
permission, finished_states = listener(scheduler(worker, schedule, coreLogic))
|
|
254
|
+
|
|
255
|
+
## check completion
|
|
256
|
+
if permission:
|
|
257
|
+
self.RuntimeResults | {k:{'result':v, 'status':'completed'} for k,v in finished_states.items()}
|
|
258
|
+
|
|
259
|
+
## Shutdown Ray
|
|
260
|
+
if self.DEBUG:
|
|
261
|
+
print("Multi Core Execution Complete...")
|
|
262
|
+
print("Use 'OverlayGenerator.shutdown_multi_core()' to shutdown the cluster.")
|
|
263
|
+
|
|
264
|
+
return True, finished_states
|
|
265
|
+
|
|
266
|
+
return False, None
|
|
267
|
+
#%% Process Listener
|
|
268
|
+
def __silent_listener__(self, object_references:Dict[ray.ObjectRef,int])->Tuple[bool, Dict[int,Any]]:
|
|
269
|
+
"""Silently listenes to the ray progress and retrieves the results.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
object_references (Dict[ray.ObjectRef,int]): Dictionary containing the object references and their corresponding keys for keeping track of the progress and upholding the order of input data provided.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Tuple[bool, Dict[int,Any]]: Boolean flag signaling the success or the execution, Dictionary containing the results of the execution.
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
# setup collection list
|
|
280
|
+
pending_states:list = list(object_references.keys())
|
|
281
|
+
finished_states:list = []
|
|
282
|
+
|
|
283
|
+
if self.DEBUG:
|
|
284
|
+
print('Listening to Ray Progress...')
|
|
285
|
+
|
|
286
|
+
while len(pending_states) > 0:
|
|
287
|
+
try:
|
|
288
|
+
# get the ready refs
|
|
289
|
+
finished, pending_states = ray.wait(
|
|
290
|
+
pending_states, timeout=8.0
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
finished_states.extend(finished)
|
|
294
|
+
|
|
295
|
+
except KeyboardInterrupt:
|
|
296
|
+
print('Interrupted')
|
|
297
|
+
break
|
|
298
|
+
|
|
299
|
+
if self.ListenerSleeptime > 0:
|
|
300
|
+
time.sleep(self.ListenerSleeptime)
|
|
301
|
+
|
|
302
|
+
# sort and return the results
|
|
303
|
+
finished_states = {object_references[ref]:ray.get(ref) for ref in finished_states}
|
|
304
|
+
|
|
305
|
+
return True, finished_states
|
|
306
|
+
|
|
307
|
+
except Exception as e:
|
|
308
|
+
print(f'Error: {e}')
|
|
309
|
+
return False, None
|
|
310
|
+
|
|
311
|
+
def __verbose_listener__(self, object_references:Dict[ray.ObjectRef,int])->Tuple[bool, Dict[int,Any]]:
|
|
312
|
+
"""Listenes to and reports on the ray progress and system CPU and Memory. Retrieves results of successful tasks.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
object_references (Dict[ray.ObjectRef,int]): Dictionary containing the object references and their corresponding keys for keeping track of the progress and upholding the order of input data provided.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Tuple[bool, Dict[int,Any]]: Boolean flag signaling the success or the execution, Dictionary containing the results of the execution.
|
|
319
|
+
"""
|
|
320
|
+
try:
|
|
321
|
+
if self.DEBUG:
|
|
322
|
+
print('Setting up progress monitors...')
|
|
323
|
+
|
|
324
|
+
## create progress monitors
|
|
325
|
+
core_progress = tqdm(total = len(object_references), desc = 'Workers', position = 1)
|
|
326
|
+
cpu_progress = tqdm(total = 100, desc="CPU usage", bar_format='{desc}: {percentage:3.0f}%|{bar}|', position = 2)
|
|
327
|
+
mem_progress = tqdm(total=psutil.virtual_memory().total, desc="RAM usage", bar_format='{desc}: {percentage:3.0f}%|{bar}|', position = 3)
|
|
328
|
+
|
|
329
|
+
# setup collection list
|
|
330
|
+
pending_states:list = list(object_references.keys())
|
|
331
|
+
finished_states:list = []
|
|
332
|
+
|
|
333
|
+
if self.DEBUG:
|
|
334
|
+
print('Listening to Ray Progress...')
|
|
335
|
+
## listen for progress
|
|
336
|
+
while len(pending_states) > 0:
|
|
337
|
+
try:
|
|
338
|
+
# get the ready refs
|
|
339
|
+
finished, pending_states = ray.wait(
|
|
340
|
+
pending_states, timeout=8.0
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
finished_states.extend(finished)
|
|
344
|
+
|
|
345
|
+
# update the progress bars
|
|
346
|
+
mem_progress.n = psutil.virtual_memory().used
|
|
347
|
+
mem_progress.refresh()
|
|
348
|
+
|
|
349
|
+
cpu_progress.n = psutil.cpu_percent()
|
|
350
|
+
cpu_progress.refresh()
|
|
351
|
+
|
|
352
|
+
# update the progress bar
|
|
353
|
+
core_progress.n = len(finished_states)
|
|
354
|
+
core_progress.refresh()
|
|
355
|
+
|
|
356
|
+
# sleep for a bit
|
|
357
|
+
if self.ListenerSleeptime > 0:
|
|
358
|
+
time.sleep(self.ListenerSleeptime)
|
|
359
|
+
|
|
360
|
+
except KeyboardInterrupt:
|
|
361
|
+
print('Interrupted')
|
|
362
|
+
break
|
|
363
|
+
|
|
364
|
+
# set the progress bars to success
|
|
365
|
+
core_progress.colour = 'green'
|
|
366
|
+
cpu_progress.colour = 'green'
|
|
367
|
+
mem_progress.colour = 'green'
|
|
368
|
+
|
|
369
|
+
# set the progress bars to their final values
|
|
370
|
+
core_progress.n = len(object_references)
|
|
371
|
+
cpu_progress.n = 0
|
|
372
|
+
mem_progress.n = 0
|
|
373
|
+
|
|
374
|
+
# close the progress bars
|
|
375
|
+
core_progress.close()
|
|
376
|
+
cpu_progress.close()
|
|
377
|
+
mem_progress.close()
|
|
378
|
+
|
|
379
|
+
# sort and return the results
|
|
380
|
+
finished_states = {object_references[ref]:ray.get(ref) for ref in finished_states}
|
|
381
|
+
|
|
382
|
+
if self.DEBUG:
|
|
383
|
+
print('Ray Progress Complete...')
|
|
384
|
+
|
|
385
|
+
return True, finished_states
|
|
386
|
+
|
|
387
|
+
except Exception as e:
|
|
388
|
+
print(f'Error: {e}')
|
|
389
|
+
return False, None
|
|
390
|
+
|
|
391
|
+
##### API #####
|
|
392
|
+
#%% Main Execution
|
|
393
|
+
def run(self, worker:Union[Callable, ray.remote_function.RemoteFunction])->bool:
|
|
394
|
+
"""Run API for the MultiCoreExecutionTool class. Main API for running the provided worker on the provided data.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
worker (Union[Callable, ray.remote_function.RemoteFunction]): Main worker or logic. Can be either ray.remote or a callable.
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
bool: Boolean flag that is True if the execution was successful.
|
|
401
|
+
"""
|
|
402
|
+
try:
|
|
403
|
+
permission:bool = self.__run__(worker)
|
|
404
|
+
assert permission
|
|
405
|
+
|
|
406
|
+
if self.DEBUG:
|
|
407
|
+
print('Multi Core Execution Complete...')
|
|
408
|
+
print('Use "OverlayGenerator.get_results()" to get the results.')
|
|
409
|
+
|
|
410
|
+
return True
|
|
411
|
+
except Exception as e:
|
|
412
|
+
print(f'Error: {e}')
|
|
413
|
+
return False
|
|
414
|
+
|
|
415
|
+
#%% Runtime Control
|
|
416
|
+
def initialize(self)->NoReturn:
|
|
417
|
+
"""Initialize the Ray cluster using the parameters found in sel.RuntimeMetadata['instance_metadata']".
|
|
418
|
+
See https://docs.ray.io/en/latest/ray-core/api/doc/ray.init.html for more information.
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
NoReturn: No Return.
|
|
422
|
+
"""
|
|
423
|
+
try:
|
|
424
|
+
InitInstructions = self.RuntimeMetadata['instance_metadata']
|
|
425
|
+
except Exception as e:
|
|
426
|
+
print(f'Error: {e}')
|
|
427
|
+
return None
|
|
428
|
+
|
|
429
|
+
self.__initialize_ray_cluster__(**InitInstructions)
|
|
430
|
+
|
|
431
|
+
def shutdown(self)->NoReturn:
|
|
432
|
+
"""Shutdown the Ray cluster.
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
NoReturn: No Return.
|
|
436
|
+
"""
|
|
437
|
+
self.__shutdown__()
|
|
438
|
+
def reset(self, **kwargs)->NoReturn:
|
|
439
|
+
"""Resets RuntimeData and RuntimeData reference. Restores RuntimeMetadata defaults.
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
NoReturn: No Return.
|
|
443
|
+
"""
|
|
444
|
+
self.__reset__(**kwargs)
|
|
445
|
+
def reboot(self, **kwargs)->NoReturn:
|
|
446
|
+
"""Reboot the MultiCoreExecutionTool object. Can be provided with new instance parameters. See instance attributes for more information.
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
NoReturn: No Return.
|
|
450
|
+
"""
|
|
451
|
+
self.__reboot__(**kwargs)
|
|
452
|
+
|
|
453
|
+
#%% Runtime Data Control
|
|
454
|
+
def update_data(self, RuntimeData:Dict[Any,Dict[Any,Any]])->NoReturn:
|
|
455
|
+
"""Update the RuntimeData with the provided data.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
RuntimeData (Dict[Any,Dict[Any,Any]]): Structured data to be processed by the methods.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
NoReturn: No Return.
|
|
462
|
+
"""
|
|
463
|
+
self.__update_data__(RuntimeData)
|
|
464
|
+
|
|
465
|
+
def update_metadata(self, **kwargs)->NoReturn:
|
|
466
|
+
self.RuntimeMetadata.update(kwargs)
|
|
467
|
+
|
|
468
|
+
#%% Runtime Handling Backend
|
|
469
|
+
def __initialize_metadata__(self,**kwargs)->NoReturn:
|
|
470
|
+
"""Initializes the metadata for the MultiCoreExecutionTool class. Contains default values and will overwrite with given values.
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
NoReturn: No Return
|
|
474
|
+
"""
|
|
475
|
+
## Default Metadata
|
|
476
|
+
self.RuntimeMetadata = {'instance_metadata':{'num_cpus': 1,
|
|
477
|
+
'num_gpus': 0,
|
|
478
|
+
'address': None,
|
|
479
|
+
'ignore_reinit_error': True},
|
|
480
|
+
'task_metadata': {'num_cpus': 1,
|
|
481
|
+
'num_gpus': 0,
|
|
482
|
+
'num_returns': None},
|
|
483
|
+
}
|
|
484
|
+
# update metadata with given values
|
|
485
|
+
self.RuntimeMetadata.update(kwargs)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def __offload_on_init__(self, RuntimeData:Dict[Any,Dict[Any,Any]])->NoReturn:
|
|
489
|
+
"""Offload RuntimeData items to ray cluster on initialization if RuntimeData is provided.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
RuntimeData (Dict[Any,Dict[Any,Any]]): Structured data to be processed by the methods.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
NoReturn: No Return
|
|
496
|
+
"""
|
|
497
|
+
## This has to be called AFTER the ray is initialized
|
|
498
|
+
# otherwise, a new ray object will be created and the object references will be unreachable from within the main ray object.
|
|
499
|
+
|
|
500
|
+
if RuntimeData is None:
|
|
501
|
+
print('No Runtime Data provided. Use the "update()" method to update the Runtime Data prior to running methods.')
|
|
502
|
+
return None
|
|
503
|
+
|
|
504
|
+
## Set RuntimeData
|
|
505
|
+
self.RuntimeData = RuntimeData if RuntimeData is not None else None
|
|
506
|
+
self.RuntimeData_ref = self.__offload_data__() if RuntimeData is not None else None
|
|
507
|
+
|
|
508
|
+
def __initialize_ray_cluster__(self)->NoReturn:
|
|
509
|
+
"""Initialize the Ray cluster using the parameters found in sel.RuntimeMetadata['instance_metadata']".
|
|
510
|
+
See https://docs.ray.io/en/latest/ray-core/api/doc/ray.init.html for more information.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
NoReturn: No Return
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
if self.DEBUG:
|
|
517
|
+
print('Setting up Ray...')
|
|
518
|
+
|
|
519
|
+
# shutdown any stray ray instances
|
|
520
|
+
ray.shutdown()
|
|
521
|
+
|
|
522
|
+
# ray init
|
|
523
|
+
cluster_context = ray.init(**self.RuntimeMetadata['instance_metadata'])
|
|
524
|
+
self.DashboardURL = f"http://{cluster_context.dashboard_url}/"
|
|
525
|
+
|
|
526
|
+
# dashboard
|
|
527
|
+
if self.LaunchDashboard:
|
|
528
|
+
try:
|
|
529
|
+
webbrowser.get('windows-default').open(self.DashboardURL,
|
|
530
|
+
autoraise = True,
|
|
531
|
+
new = 2)
|
|
532
|
+
except Exception as e:
|
|
533
|
+
print(f'Error: {e}')
|
|
534
|
+
|
|
535
|
+
if self.DEBUG:
|
|
536
|
+
print('Ray setup complete...')
|
|
537
|
+
print(f'Ray Dashboard: {self.DashboardURL}')
|
|
538
|
+
|
|
539
|
+
# set the runtime context
|
|
540
|
+
self.runimte_context = cluster_context
|
|
541
|
+
|
|
542
|
+
def __shutdown__(self)->bool:
|
|
543
|
+
"""Shutdown the Ray cluster.
|
|
544
|
+
|
|
545
|
+
Returns:
|
|
546
|
+
bool: True if the shutdown was successful.
|
|
547
|
+
"""
|
|
548
|
+
if self.DEBUG:
|
|
549
|
+
print('Shutting down Ray...')
|
|
550
|
+
try:
|
|
551
|
+
ray.shutdown()
|
|
552
|
+
return True
|
|
553
|
+
except Exception as e:
|
|
554
|
+
print(f'Error: {e}')
|
|
555
|
+
return False
|
|
556
|
+
|
|
557
|
+
def __reset__(self)->NoReturn:
|
|
558
|
+
"""Resets RuntimeData and RuntimeData reference. Restores RuntimeMetadata defaults.
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
NoReturn: No Return
|
|
562
|
+
"""
|
|
563
|
+
self.RuntimeData_ref = None
|
|
564
|
+
self.RuntimeData = None
|
|
565
|
+
self.__initialize_metadata__()
|
|
566
|
+
|
|
567
|
+
def __reboot__(self, **kwargs)->NoReturn:
|
|
568
|
+
"""Reboots the MultiCoreExecutionTool object. Can be provided with new instance parameters. See https://docs.ray.io/en/latest/ray-core/api/doc/ray.init.html for more information.
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
NoReturn: _description_
|
|
572
|
+
"""
|
|
573
|
+
InitInstructions = self.RuntimeMetadata['instance_metadata'] | kwargs
|
|
574
|
+
try:
|
|
575
|
+
self.__shutdown__()
|
|
576
|
+
self.__initialize_ray_cluster__(**InitInstructions)
|
|
577
|
+
except Exception as e:
|
|
578
|
+
print(f'Error: {e}')
|
|
579
|
+
|
|
580
|
+
#%% Runtime Data Handling
|
|
581
|
+
def __setup_schedule__(self)->List[Any]:
|
|
582
|
+
"""Bundle the RuntimeData keys into a list for scheduling.
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
List[Any]: List of keys referring to RuntimeData values to be processed using the provided method.
|
|
586
|
+
"""
|
|
587
|
+
|
|
588
|
+
self.RuntimeResults = {k:{'result':None, 'status':'pending'} for k in self.RuntimeData.keys()}
|
|
589
|
+
return [k for k,v in self.RuntimeResults.items() if v['status'] == 'pending']
|
|
590
|
+
|
|
591
|
+
def __update_data__(self, RuntimeData:Dict[Any,Dict[Any,Any]])->NoReturn:
|
|
592
|
+
"""Update the RuntimeData with the provided data and offload the data to the ray cluster.
|
|
593
|
+
|
|
594
|
+
Args:
|
|
595
|
+
RuntimeData (Dict[int,Dict[str,Any]]): Structured data to be processed by the methods.
|
|
596
|
+
|
|
597
|
+
Returns:
|
|
598
|
+
NoReturn: No Return
|
|
599
|
+
"""
|
|
600
|
+
self.RuntimeData = RuntimeData
|
|
601
|
+
self.RuntimeData_ref = self.__offload_data__()
|
|
602
|
+
self.__setup_schedule__()
|
|
603
|
+
|
|
604
|
+
def __offload_data__(self)->Dict[int,ray.ObjectRef]:
|
|
605
|
+
"""Offload the RuntimeData to the ray cluster.
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
Dict[int,ray.ObjectRef]: Dictionary of keys and ray object references.
|
|
609
|
+
"""
|
|
610
|
+
if self.DEBUG:
|
|
611
|
+
print('Offloading data to Ray...')
|
|
612
|
+
return {k:ray.put(v) for k,v in self.RuntimeData.items()}
|
|
613
|
+
|
|
614
|
+
#%% Helper
|
|
615
|
+
def is_ray_compatible(self, func:Callable)->bool:
|
|
616
|
+
"""Check if the provided function is ray compatible.
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
func (Callable): Provided function.
|
|
620
|
+
|
|
621
|
+
Returns:
|
|
622
|
+
bool: True if the function is ray compatible. False otherwise.
|
|
623
|
+
"""
|
|
624
|
+
if isinstance(func, ray.remote_function.RemoteFunction):
|
|
625
|
+
return True
|
|
626
|
+
return False
|
|
627
|
+
|
|
628
|
+
def is_initalized(self)->bool:
|
|
629
|
+
"""Check of the Ray cluster is initialized.
|
|
630
|
+
|
|
631
|
+
Returns:
|
|
632
|
+
bool: True if the Ray cluster is initialized. False otherwise.
|
|
633
|
+
"""
|
|
634
|
+
if self.DEBUG:
|
|
635
|
+
print('Checking Ray Status...')
|
|
636
|
+
return ray.is_initialized()
|
|
637
|
+
|
|
638
|
+
def get_results(self)->Dict[Any,Dict[str,Any]]:
|
|
639
|
+
"""Returns RuntimeResults.
|
|
640
|
+
|
|
641
|
+
Returns:
|
|
642
|
+
Dict[Any,Dict[Any,Any]]: Structured data containing the results of the execution.
|
|
643
|
+
"""
|
|
644
|
+
if self.DEBUG:
|
|
645
|
+
print('Fetching Results...')
|
|
646
|
+
|
|
647
|
+
if self.RuntimeResults is None:
|
|
648
|
+
print('No results found. Use the "run()" method to get results.')
|
|
649
|
+
return None
|
|
650
|
+
return self.RuntimeResults
|