Skip to content

Commit 083dc38

Browse files
Simnol22Delaunay
andauthored
Adding Ray Backend (#1049)
* Initial commit. adding working ray backend but still not passing every tests * adding runtime * FIxing delete bug and cleaning tests * add working_dir * adding ray to setup.py * fixing import, config and some code * adding docs, and fixing some changes from review * fixing pretest errors * removing unused line --------- Co-authored-by: Setepenre <pierre.delaunay.tr@gmail.com>
1 parent c7a38ed commit 083dc38

5 files changed

Lines changed: 159 additions & 2 deletions

File tree

docs/src/code/executor/ray.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Ray Executor
2+
=============
3+
4+
.. automodule:: orion.executor.ray_backend
5+
:members:

docs/src/user/parallel.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,10 @@ For more control over Dask, you should prefer using Dask executor backend direct
7474
The executor configuration is used to create the Dask Client. See Dask's documentation
7575
`here <https://distributed.dask.org/en/latest/api.html#distributed.Client>`__ for
7676
more information on possible arguments.
77+
78+
Ray
79+
----
80+
81+
We can also use the ray executor backend. For more control with ray, you can see ray's
82+
documentation `here <https://docs.ray.io/en/latest/ray-core/package-ref.html#python-api>`__ for
83+
more information on ray and it's python api.

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"sphinx_gallery",
5757
],
5858
"dask": ["dask[complete]"],
59+
"ray": ["ray"],
5960
"track": ["track @ git+https://github.com/Delaunay/track@master#egg=track"],
6061
"profet": ["emukit", "GPy", "torch", "pybnn"],
6162
"configspace": ["ConfigSpace"],
@@ -145,6 +146,7 @@
145146
"joblib = orion.executor.joblib_backend:Joblib",
146147
"poolexecutor = orion.executor.multiprocess_backend:PoolExecutor",
147148
"dask = orion.executor.dask_backend:Dask",
149+
"ray = orion.executor.ray_backend:Ray",
148150
],
149151
},
150152
install_requires=[

src/orion/executor/ray_backend.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import logging
2+
import traceback
3+
4+
from orion.core.utils.module_import import ImportOptional
5+
from orion.executor.base import (
6+
AsyncException,
7+
AsyncResult,
8+
BaseExecutor,
9+
ExecutorClosed,
10+
Future,
11+
)
12+
13+
with ImportOptional("ray") as import_optional:
14+
import ray
15+
16+
HAS_RAY = not import_optional.failed
17+
18+
logger = logging.getLogger(__name__)
19+
20+
21+
class _Future(Future):
22+
def __init__(self, future):
23+
self.future = future
24+
self.exception = None
25+
26+
def get(self, timeout=None):
27+
if self.exception:
28+
raise self.exception
29+
try:
30+
return ray.get(self.future, timeout=timeout)
31+
except ray.exceptions.GetTimeoutError as e:
32+
raise TimeoutError() from e
33+
34+
def wait(self, timeout=None):
35+
try:
36+
ray.get(self.future, timeout=timeout)
37+
except ray.exceptions.GetTimeoutError:
38+
pass
39+
except Exception as e:
40+
self.exception = e
41+
42+
def ready(self):
43+
obj_ready = ray.wait([self.future])
44+
return len(obj_ready[0]) == 1
45+
46+
def successful(self):
47+
# Python 3.6 raise assertion error
48+
if not self.ready():
49+
raise ValueError()
50+
51+
return self.future.successful()
52+
53+
54+
class Ray(BaseExecutor):
55+
def __init__(
56+
self,
57+
n_workers=-1,
58+
**config,
59+
):
60+
super().__init__(n_workers=n_workers)
61+
self.initialized = False
62+
if not HAS_RAY:
63+
raise ImportError("Ray must be installed to use Ray executor.")
64+
self.config = config
65+
66+
if not ray.is_initialized():
67+
ray.init(**self.config)
68+
self.initialized = True
69+
logger.debug("Ray was initiated with runtime_env : %s", **config)
70+
71+
def close(self):
72+
if self.initialized:
73+
self.initialized = False
74+
ray.shutdown()
75+
76+
def __del__(self):
77+
self.close()
78+
79+
def __enter__(self):
80+
return self
81+
82+
def submit(self, function, *args, **kwargs):
83+
if not ray.is_initialized():
84+
raise ExecutorClosed()
85+
86+
remote_g = ray.remote(function)
87+
return _Future(remote_g.remote(*args, **kwargs))
88+
89+
def wait(self, futures):
90+
return [future.get() for future in futures]
91+
92+
def async_get(self, futures, timeout=None):
93+
results = []
94+
tobe_deleted = []
95+
for i, future in enumerate(futures):
96+
if timeout and i == 0:
97+
future.wait(timeout)
98+
99+
if future.ready():
100+
try:
101+
results.append(AsyncResult(future, future.get()))
102+
except Exception as err:
103+
results.append(AsyncException(future, err, traceback.format_exc()))
104+
105+
tobe_deleted.append(future)
106+
for future in tobe_deleted:
107+
futures.remove(future)
108+
109+
return results
110+
111+
def __exit__(self, exc_type, exc_value, traceback):
112+
self.close()
113+
super().__exit__(exc_type, exc_value, traceback)

tests/unittests/executor/test_executor.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import os
12
import time
23

34
import pytest
45

56
from orion.executor.base import AsyncException, ExecutorClosed, executor_factory
67
from orion.executor.dask_backend import HAS_DASK, Dask
78
from orion.executor.multiprocess_backend import PoolExecutor
9+
from orion.executor.ray_backend import HAS_RAY, Ray
810
from orion.executor.single_backend import SingleExecutor
911

1012

@@ -16,6 +18,11 @@ def thread(n):
1618
return PoolExecutor(n, "threading")
1719

1820

21+
def ray(n):
22+
test_working_dir = os.path.dirname(os.path.abspath(__file__))
23+
return Ray(n, runtime_env={"working_dir": test_working_dir})
24+
25+
1926
def skip_dask_if_not_installed(
2027
value, reason="Dask dependency is required for these tests."
2128
):
@@ -39,18 +46,43 @@ def xfail_dask_if_not_installed(
3946
)
4047

4148

49+
def skip_ray_if_not_installed(
50+
value, reason="Ray dependency is required for these tests."
51+
):
52+
return pytest.param(
53+
value,
54+
marks=pytest.mark.skipif(
55+
not HAS_RAY,
56+
reason=reason,
57+
),
58+
)
59+
60+
61+
def xfail_ray_if_not_installed(
62+
value, reason="Ray dependency is required for these tests."
63+
):
64+
return pytest.param(
65+
value,
66+
marks=pytest.mark.xfail(
67+
condition=not HAS_RAY, reason=reason, raises=ImportError
68+
),
69+
)
70+
71+
4272
executors = [
4373
"joblib",
4474
"poolexecutor",
4575
"singleexecutor",
4676
skip_dask_if_not_installed("dask"),
77+
skip_ray_if_not_installed("ray"),
4778
]
4879

4980
backends = [
5081
thread,
5182
multiprocess,
5283
SingleExecutor,
5384
skip_dask_if_not_installed(Dask),
85+
skip_ray_if_not_installed(ray),
5486
]
5587

5688

@@ -191,9 +223,7 @@ def test_execute_async_bad(backend):
191223

192224
def nested_jobs(executor):
193225
with executor:
194-
print("nested_jobs sub")
195226
futures = [executor.submit(function, 1, 2, i) for i in range(10)]
196-
print("nested_jobs wait")
197227
all_results = executor.wait(futures)
198228
return sum(all_results)
199229

0 commit comments

Comments
 (0)