From 2069a7b1df54738e331047741160cafcd6e2dfc8 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 09:56:25 -0400 Subject: [PATCH 01/70] CHAIN PIPELINE --- python/ray/experimental/serve/api.py | 24 ++++++--- .../ray/experimental/serve/echo_pipeline.py | 30 +++++++++++ python/ray/experimental/serve/global_state.py | 18 +++++-- .../experimental/serve/kv_store_service.py | 51 +++++++++++++++++-- python/ray/experimental/serve/server.py | 17 +++++-- 5 files changed, 121 insertions(+), 19 deletions(-) create mode 100644 python/ray/experimental/serve/echo_pipeline.py diff --git a/python/ray/experimental/serve/api.py b/python/ray/experimental/serve/api.py index dd89961b6b84..fa2858a4d84c 100644 --- a/python/ray/experimental/serve/api.py +++ b/python/ray/experimental/serve/api.py @@ -37,7 +37,7 @@ def init(blocking=False, object_store_memory=int(1e8)): global_state.wait_until_http_ready() -def create_endpoint(endpoint_name, route_expression, blocking=True): +def create_endpoint_pipeline(pipeline_name, route_expression, blocking=True): """Create a service endpoint given route_expression. Args: @@ -49,11 +49,13 @@ def create_endpoint(endpoint_name, route_expression, blocking=True): registered before returning """ future = global_state.kv_store_actor_handle.register_service.remote( - route_expression, endpoint_name) + route_expression, pipeline_name) if blocking: ray.get(future) - global_state.registered_endpoints.add(endpoint_name) + global_state.registered_endpoints.add(pipeline_name) +def create_no_http_service(service_name): + global_state.registered_services.add(service_name) def create_backend(func_or_class, backend_tag, *actor_init_args): """Create a backend using func_or_class and assign backend_tag. @@ -89,8 +91,16 @@ class CustomActor(RayServeMixin, func_or_class): global_state.registered_backends.add(backend_tag) +def add_service_to_pipeline(pipeline_name,service_name,blocking=True): + assert service_name in global_state.registered_services + assert pipeline_name in global_state.registered_endpoints -def link(endpoint_name, backend_tag): + future = global_state.kv_store_actor_handle_pipeline.add_node.remote(pipeline_name,service_name) + if blocking: + ray.get(future) + + +def link_service(service_name, backend_tag): """Associate a service endpoint with backend tag. Example: @@ -102,10 +112,10 @@ def link(endpoint_name, backend_tag): >>> serve.split("service-name", {"backend:v1": 1.0}) """ - assert endpoint_name in global_state.registered_endpoints + assert service_name in global_state.registered_services - global_state.router_actor_handle.link.remote(endpoint_name, backend_tag) - global_state.policy_action_history[endpoint_name].append({backend_tag: 1}) + global_state.router_actor_handle.link.remote(service_name, backend_tag) + global_state.policy_action_history[service_name].append({backend_tag: 1}) def split(endpoint_name, traffic_policy_dictionary): diff --git a/python/ray/experimental/serve/echo_pipeline.py b/python/ray/experimental/serve/echo_pipeline.py new file mode 100644 index 000000000000..9c5ffafc9a16 --- /dev/null +++ b/python/ray/experimental/serve/echo_pipeline.py @@ -0,0 +1,30 @@ +import time + +import requests + +from ray.experimental import serve +from ray.experimental.serve.utils import pformat_color_json + + +def echo1(context): + context["query_string"] += 'FROM MODEL1 -> ' + return context +def echo2(context): + context["query_string"] += 'FROM MODEL2 -> ' + return context + +serve.init(blocking=True) + +serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) + +serve.create_backend(echo, "echo:v1") +serve.create_no_http_service("serve1") +serve.link_service("serve1", "echo:v1") +serve.add_service_to_pipeline("pipeline1","serve1") + +while True: + resp = requests.get("http://127.0.0.1:8000/echo").json() + print(pformat_color_json(resp)) + + print("...Sleeping for 2 seconds...") + time.sleep(2) \ No newline at end of file diff --git a/python/ray/experimental/serve/global_state.py b/python/ray/experimental/serve/global_state.py index aea51d526ee4..0b8126e09f51 100644 --- a/python/ray/experimental/serve/global_state.py +++ b/python/ray/experimental/serve/global_state.py @@ -2,7 +2,7 @@ from collections import defaultdict, deque import ray -from ray.experimental.serve.kv_store_service import KVStoreProxyActor +from ray.experimental.serve.kv_store_service import KVPipelineProxyActor,KVStoreProxyActor from ray.experimental.serve.queues import CentralizedQueuesActor from ray.experimental.serve.utils import logger from ray.experimental.serve.server import HTTPActor @@ -29,7 +29,9 @@ def __init__(self): self.backend_actor_handles = [] #: actor handle to KV store actor + self.kv_store_actor_handle = None + self.kv_store_actor_handle_pipeline = None #: actor handle to HTTP server self.http_actor_handle = None #: actor handle the router actor @@ -39,6 +41,7 @@ def __init__(self): self.registered_backends = set() #: Set[str] list of service endpoint names, used for deduplication self.registered_endpoints = set() + self.registered_services = set() #: Mapping of endpoints -> a stack of traffic policy self.policy_action_history = defaultdict(deque) @@ -51,12 +54,14 @@ def __init__(self): def init_api_server(self): logger.info(LOG_PREFIX + "Initalizing routing table") self.kv_store_actor_handle = KVStoreProxyActor.remote() + self.kv_store_actor_handle_pipeline = KVPipelineProxyActor.remote() logger.info((LOG_PREFIX + "Health checking routing table {}").format( ray.get(self.kv_store_actor_handle.get_request_count.remote())), ) def init_http_server(self): logger.info(LOG_PREFIX + "Initializing HTTP server") self.http_actor_handle = HTTPActor.remote(self.kv_store_actor_handle, + self.kv_store_actor_handle_pipeline, self.router_actor_handle) self.http_actor_handle.run.remote(host="0.0.0.0", port=8000) self.http_address = "http://localhost:8000" @@ -69,11 +74,16 @@ def init_router(self): def wait_until_http_ready(self, num_retries=5, backoff_time_s=1): routing_table_request_count = 0 + routing_pipeline_request_count = 0 retries = num_retries - while not routing_table_request_count: - routing_table_request_count = (ray.get( - self.kv_store_actor_handle.get_request_count.remote())) + while not routing_table_request_count and not routing_pipeline_request_count: + if routing_table_request_count == 0: + routing_table_request_count = (ray.get( + self.kv_store_actor_handle.get_request_count.remote())) + if routing_pipeline_request_count == 0: + routing_pipeline_request_count = (ray.get( + self.kv_store_actor_handle_pipeline.get_request_count.remote())) logger.debug((LOG_PREFIX + "Checking if HTTP server is ready." "{} retries left.").format(retries)) time.sleep(backoff_time_s) diff --git a/python/ray/experimental/serve/kv_store_service.py b/python/ray/experimental/serve/kv_store_service.py index e472fee694a9..ce2b5dd3da57 100644 --- a/python/ray/experimental/serve/kv_store_service.py +++ b/python/ray/experimental/serve/kv_store_service.py @@ -79,9 +79,10 @@ def as_dict(self): class RayInternalKVStore(NamespacedKVStore): """A NamespacedKVStore implementation using ray's `internal_kv`.""" - def __init__(self, namespace): + def __init__(self, index_key,namespace): assert ray_kv._internal_kv_initialized() - self.index_key = "RAY_SERVE_INDEX" + # self.index_key = "RAY_SERVE_INDEX" + self.index_key = index_key self.namespace = namespace self._put(self.index_key, []) @@ -103,11 +104,16 @@ def _put(self, key, value): self._serialize(value), overwrite=True, ) + def _hexists(self,key): + return ray_kv._internal_kv_exists(self._format_key(self._serialize(key))) def _get(self, key): return self._deserialize( ray_kv._internal_kv_get(self._format_key(self._serialize(key)))) + def exists(self,key): + return self._hexists(key) + def get(self, key): return self._get(key) @@ -127,10 +133,44 @@ def as_dict(self): data[self._remove_format_key(key)] = self._get(key) return data +# A class which stores pipeline name and it's dependent service +class KVPipelineProxy: + def __init__(self, kv_class=RayInternalKVStore): + self.pipeline_storage = kv_class(index_key = "RAY_PIPELINE_INDEX",namespace="pipelines") + self.request_count = 0 + + def add_node(self,pipeline: str, service_no_http: str): + if self.pipeline_storage.exists(pipeline): + l = set(self.pipeline_storage.get(pipeline)) + l.add(service_no_http) + self.pipeline_storage.put(pipeline,list(l)) + else: + self.pipeline_storage.put(pipeline,[l]) + + def list_pipeline_service(self): + self.request_count += 1 + table = self.pipeline_storage.as_dict() + return table + + def get_request_count(self): + """Return the number of requests that fetched the routing table. + This method is used for two purpose: + + 1. Make sure HTTP server has started and healthy. Incremented request + count means HTTP server is actively fetching routing table. + + 2. Make sure HTTP server does not have stale routing table. This number + should be incremented every HTTP_ROUTER_CHECKER_INTERVAL_S seconds. + Supervisor should check this number as indirect indicator of http + server's health. + """ + return self.request_count + + class KVStoreProxy: def __init__(self, kv_class=InMemoryKVStore): - self.routing_table = kv_class(namespace="routes") + self.routing_table = kv_class(index_key = "RAY_SERVE_INDEX",namespace="routes") self.request_count = 0 def register_service(self, route: str, service: str): @@ -171,3 +211,8 @@ def get_request_count(self): class KVStoreProxyActor(KVStoreProxy): def __init__(self, kv_class=RayInternalKVStore): super().__init__(kv_class=kv_class) + +@ray.remote +class KVPipelineProxyActor(KVPipelineProxy): + def __init__(self, kv_class=RayInternalKVStore): + super().__init__(kv_class=kv_class) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index af70a782b156..5d343dc0a09d 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -54,7 +54,7 @@ class HTTPProxy: # blocks forever """ - def __init__(self, kv_store_actor_handle, router_handle): + def __init__(self, kv_store_actor_handle,kv_store_actor_pipeline_handle ,router_handle): """ Args: kv_store_actor_handle (ray.actor.ActorHandle): handle to routing @@ -67,14 +67,18 @@ def __init__(self, kv_store_actor_handle, router_handle): assert ray.is_initialized() self.admin_actor = kv_store_actor_handle + self.pipeline_actor = kv_store_actor_pipeline_handle self.router = router_handle self.route_table = dict() + self.pipeline_table = dict() async def route_checker(self, interval): while True: try: self.route_table = await as_future( self.admin_actor.list_service.remote()) + self.pipeline_table = await as_future( + self.pipeline_actor.list_pipeline_service.remote()) except ray.exceptions.RayletError: # Gracefully handle termination return @@ -94,10 +98,13 @@ async def __call__(self, scope, receive, send): if current_path == "/": await JSONResponse(self.route_table)(scope, receive, send) elif current_path in self.route_table: - endpoint_name = self.route_table[current_path] - result_object_id_bytes = await as_future( - self.router.enqueue_request.remote(endpoint_name, scope)) - result = await as_future(ray.ObjectID(result_object_id_bytes)) + pipeline_name = self.route_table[current_path] + services_list = self.pipeline_table[pipeline_name] + result = scope + for service in services_list: + result_object_id_bytes = await as_future( + self.router.enqueue_request.remote(endpoint_name, result)) + result = await as_future(ray.ObjectID(result_object_id_bytes)) if isinstance(result, ray.exceptions.RayTaskError): await JSONResponse({ From c72517ab0dadbe5ee0f000b86fe63937d81d6374 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:03:46 -0400 Subject: [PATCH 02/70] change location --- .../serve/examples/echo_pipeline.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 python/ray/experimental/serve/examples/echo_pipeline.py diff --git a/python/ray/experimental/serve/examples/echo_pipeline.py b/python/ray/experimental/serve/examples/echo_pipeline.py new file mode 100644 index 000000000000..9c5ffafc9a16 --- /dev/null +++ b/python/ray/experimental/serve/examples/echo_pipeline.py @@ -0,0 +1,30 @@ +import time + +import requests + +from ray.experimental import serve +from ray.experimental.serve.utils import pformat_color_json + + +def echo1(context): + context["query_string"] += 'FROM MODEL1 -> ' + return context +def echo2(context): + context["query_string"] += 'FROM MODEL2 -> ' + return context + +serve.init(blocking=True) + +serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) + +serve.create_backend(echo, "echo:v1") +serve.create_no_http_service("serve1") +serve.link_service("serve1", "echo:v1") +serve.add_service_to_pipeline("pipeline1","serve1") + +while True: + resp = requests.get("http://127.0.0.1:8000/echo").json() + print(pformat_color_json(resp)) + + print("...Sleeping for 2 seconds...") + time.sleep(2) \ No newline at end of file From 02d63b4d04c00c992b269e523949d51799d63900 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:06:42 -0400 Subject: [PATCH 03/70] removed errors --- python/ray/experimental/serve/examples/echo_pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ray/experimental/serve/examples/echo_pipeline.py b/python/ray/experimental/serve/examples/echo_pipeline.py index 9c5ffafc9a16..df629595aad7 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_pipeline.py @@ -8,10 +8,10 @@ def echo1(context): context["query_string"] += 'FROM MODEL1 -> ' - return context + return context def echo2(context): context["query_string"] += 'FROM MODEL2 -> ' - return context + return context serve.init(blocking=True) From 1da01b7f3111cd9ec65b45bb513ca93e79da56f8 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:11:38 -0400 Subject: [PATCH 04/70] included api calls in init --- python/ray/experimental/serve/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ray/experimental/serve/__init__.py b/python/ray/experimental/serve/__init__.py index cd3a84c42a6b..6e7a46d192ca 100644 --- a/python/ray/experimental/serve/__init__.py +++ b/python/ray/experimental/serve/__init__.py @@ -2,11 +2,11 @@ if sys.version_info < (3, 0): raise ImportError("serve is Python 3 only.") -from ray.experimental.serve.api import (init, create_backend, create_endpoint, - link, split, rollback, get_handle, +from ray.experimental.serve.api import (init, create_backend, create_endpoint_pipeline,create_no_http_service, + add_service_to_pipeline,link_service, split, rollback, get_handle, global_state) # noqa: E402 __all__ = [ - "init", "create_backend", "create_endpoint", "link", "split", "rollback", + "init", "create_backend", "create_endpoint_pipeline","create_no_http_service","add_service_to_pipeline","link", "link_service", "rollback", "get_handle", "global_state" ] From ef36962c91ba88429c8276cc1dd41db74b3fad4f Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:15:12 -0400 Subject: [PATCH 05/70] removed errors --- python/ray/experimental/serve/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 5d343dc0a09d..7b97a6007d5a 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -125,8 +125,8 @@ async def __call__(self, scope, receive, send): @ray.remote class HTTPActor: - def __init__(self, kv_store_actor_handle, router_handle): - self.app = HTTPProxy(kv_store_actor_handle, router_handle) + def __init__(self, kv_store_actor_handle,kv_store_actor_pipeline_handle ,router_handle): + self.app = HTTPProxy(kv_store_actor_handle,kv_store_actor_pipeline_handle,router_handle) def run(self, host="0.0.0.0", port=8000): uvicorn.run(self.app, host=host, port=port, lifespan="on") From 38dd3773675ff0a8e42311ce8eaa4cb00dc111b9 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:18:02 -0400 Subject: [PATCH 06/70] removed errors --- python/ray/experimental/serve/examples/echo_pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/examples/echo_pipeline.py b/python/ray/experimental/serve/examples/echo_pipeline.py index df629595aad7..2bc155bc4293 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_pipeline.py @@ -17,7 +17,7 @@ def echo2(context): serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) -serve.create_backend(echo, "echo:v1") +serve.create_backend(echo1, "echo:v1") serve.create_no_http_service("serve1") serve.link_service("serve1", "echo:v1") serve.add_service_to_pipeline("pipeline1","serve1") From 0fb33bf3412b83830a15f3f5811649b9c5426872 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:21:50 -0400 Subject: [PATCH 07/70] internal kv fix --- python/ray/experimental/internal_kv.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/ray/experimental/internal_kv.py b/python/ray/experimental/internal_kv.py index f2a19450c202..5eabfb937a74 100644 --- a/python/ray/experimental/internal_kv.py +++ b/python/ray/experimental/internal_kv.py @@ -22,6 +22,12 @@ def _internal_kv_get(key): return worker.redis_client.hget(key, "value") +def _internal_kv_exists(key): + worker = ray.worker.get_global_worker() + if worker.mode == ray.worker.LOCAL_MODE: + return key in _local + return worker.redis_client.hexists(key,"value") + def _internal_kv_put(key, value, overwrite=False): """Globally associates a value with a given binary key. From 6a0ba87759fe897587efd9e060f108a87e42d013 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:24:04 -0400 Subject: [PATCH 08/70] bug fix --- python/ray/experimental/serve/kv_store_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/kv_store_service.py b/python/ray/experimental/serve/kv_store_service.py index ce2b5dd3da57..84af04f84791 100644 --- a/python/ray/experimental/serve/kv_store_service.py +++ b/python/ray/experimental/serve/kv_store_service.py @@ -145,7 +145,7 @@ def add_node(self,pipeline: str, service_no_http: str): l.add(service_no_http) self.pipeline_storage.put(pipeline,list(l)) else: - self.pipeline_storage.put(pipeline,[l]) + self.pipeline_storage.put(pipeline,[service_no_http]) def list_pipeline_service(self): self.request_count += 1 From 874afc6b84741be836d98e18771b1fc6db6aafa2 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:26:23 -0400 Subject: [PATCH 09/70] bug fix --- python/ray/experimental/serve/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 7b97a6007d5a..63b63557bd26 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -103,7 +103,7 @@ async def __call__(self, scope, receive, send): result = scope for service in services_list: result_object_id_bytes = await as_future( - self.router.enqueue_request.remote(endpoint_name, result)) + self.router.enqueue_request.remote(service, result)) result = await as_future(ray.ObjectID(result_object_id_bytes)) if isinstance(result, ray.exceptions.RayTaskError): From f4a5752a57cede928e825801d3bc9a3ea34cbb36 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:32:10 -0400 Subject: [PATCH 10/70] temp --- python/ray/experimental/serve/server.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 63b63557bd26..18c1cbc53b98 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -100,18 +100,19 @@ async def __call__(self, scope, receive, send): elif current_path in self.route_table: pipeline_name = self.route_table[current_path] services_list = self.pipeline_table[pipeline_name] - result = scope - for service in services_list: - result_object_id_bytes = await as_future( - self.router.enqueue_request.remote(service, result)) - result = await as_future(ray.ObjectID(result_object_id_bytes)) - - if isinstance(result, ray.exceptions.RayTaskError): - await JSONResponse({ - "error": "internal error, please use python API to debug" - })(scope, receive, send) - else: - await JSONResponse({"result": result})(scope, receive, send) + await JSONResponse({"result": str(services_list)})(scope, receive, send) + # result = scope + # for service in services_list: + # result_object_id_bytes = await as_future( + # self.router.enqueue_request.remote(service, result)) + # result = await as_future(ray.ObjectID(result_object_id_bytes)) + + # if isinstance(result, ray.exceptions.RayTaskError): + # await JSONResponse({ + # "error": "internal error, please use python API to debug" + # })(scope, receive, send) + # else: + # await JSONResponse({"result": result})(scope, receive, send) else: error_message = ("Path {} not found. " "Please ping http://.../ for routing table" From 462d0020c86cd3fa3b57ebf0f27e60b9121f46c5 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:35:27 -0400 Subject: [PATCH 11/70] temp --- .../serve/examples/echo_pipeline.py | 2 +- python/ray/experimental/serve/server.py | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/python/ray/experimental/serve/examples/echo_pipeline.py b/python/ray/experimental/serve/examples/echo_pipeline.py index 2bc155bc4293..f549097d5840 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_pipeline.py @@ -7,7 +7,7 @@ def echo1(context): - context["query_string"] += 'FROM MODEL1 -> ' + # context["query_string"] += 'FROM MODEL1 -> ' return context def echo2(context): context["query_string"] += 'FROM MODEL2 -> ' diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 18c1cbc53b98..6765506736c3 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -100,19 +100,19 @@ async def __call__(self, scope, receive, send): elif current_path in self.route_table: pipeline_name = self.route_table[current_path] services_list = self.pipeline_table[pipeline_name] - await JSONResponse({"result": str(services_list)})(scope, receive, send) - # result = scope - # for service in services_list: - # result_object_id_bytes = await as_future( - # self.router.enqueue_request.remote(service, result)) - # result = await as_future(ray.ObjectID(result_object_id_bytes)) - - # if isinstance(result, ray.exceptions.RayTaskError): - # await JSONResponse({ - # "error": "internal error, please use python API to debug" - # })(scope, receive, send) - # else: - # await JSONResponse({"result": result})(scope, receive, send) + # await JSONResponse({"result": str(services_list)})(scope, receive, send) + result = scope + for service in services_list: + result_object_id_bytes = await as_future( + self.router.enqueue_request.remote(service, result)) + result = await as_future(ray.ObjectID(result_object_id_bytes)) + + if isinstance(result, ray.exceptions.RayTaskError): + await JSONResponse({ + "error": "internal error, please use python API to debug" + })(scope, receive, send) + else: + await JSONResponse({"result": result})(scope, receive, send) else: error_message = ("Path {} not found. " "Please ping http://.../ for routing table" From 22f6be5c93416254570e7a01b39ab528e1692c38 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:48:59 -0400 Subject: [PATCH 12/70] temp --- .../experimental/serve/examples/echo_pipeline.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/python/ray/experimental/serve/examples/echo_pipeline.py b/python/ray/experimental/serve/examples/echo_pipeline.py index f549097d5840..44c8a3e5f63f 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_pipeline.py @@ -1,16 +1,23 @@ import time import requests +from werkzeug import urls from ray.experimental import serve from ray.experimental.serve.utils import pformat_color_json def echo1(context): + # query_string_dict = urls.url_decode(context["query_string"]) + message = "" + # message += query_string_dict.get("message", "") + # message += " " + message += 'FROM MODEL1 -> ' + return message # context["query_string"] += 'FROM MODEL1 -> ' - return context + # return context def echo2(context): - context["query_string"] += 'FROM MODEL2 -> ' + context += 'FROM MODEL2 -> ' return context serve.init(blocking=True) @@ -20,7 +27,7 @@ def echo2(context): serve.create_backend(echo1, "echo:v1") serve.create_no_http_service("serve1") serve.link_service("serve1", "echo:v1") -serve.add_service_to_pipeline("pipeline1","serve1") +serve.add_service_to_pipeline("pipeline1","serve1",blocking=True) while True: resp = requests.get("http://127.0.0.1:8000/echo").json() From 2cffaef14b9e14e88a215718a2d2c2df4c57074f Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:54:05 -0400 Subject: [PATCH 13/70] check --- .../ray/experimental/serve/examples/echo_pipeline.py | 12 ++++++++++-- python/ray/experimental/serve/global_state.py | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/python/ray/experimental/serve/examples/echo_pipeline.py b/python/ray/experimental/serve/examples/echo_pipeline.py index 44c8a3e5f63f..386fed51e029 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_pipeline.py @@ -17,17 +17,25 @@ def echo1(context): # context["query_string"] += 'FROM MODEL1 -> ' # return context def echo2(context): - context += 'FROM MODEL2 -> ' - return context + result = context['result'] + result += 'FROM MODEL2 -> ' + return result serve.init(blocking=True) serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) serve.create_backend(echo1, "echo:v1") +serve.create_backend(echo2, "echo:v2") + serve.create_no_http_service("serve1") +serve.create_no_http_service("serve2") + serve.link_service("serve1", "echo:v1") +serve.link_service("serve2", "echo:v2") + serve.add_service_to_pipeline("pipeline1","serve1",blocking=True) +serve.add_service_to_pipeline("pipeline1","serve2",blocking=True) while True: resp = requests.get("http://127.0.0.1:8000/echo").json() diff --git a/python/ray/experimental/serve/global_state.py b/python/ray/experimental/serve/global_state.py index 0b8126e09f51..7a444591704c 100644 --- a/python/ray/experimental/serve/global_state.py +++ b/python/ray/experimental/serve/global_state.py @@ -77,7 +77,7 @@ def wait_until_http_ready(self, num_retries=5, backoff_time_s=1): routing_pipeline_request_count = 0 retries = num_retries - while not routing_table_request_count and not routing_pipeline_request_count: + while not routing_table_request_count or not routing_pipeline_request_count: if routing_table_request_count == 0: routing_table_request_count = (ray.get( self.kv_store_actor_handle.get_request_count.remote())) From 1aa38a5a9976ce0ed2baccd53acb17e4352ee4c1 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 10:57:59 -0400 Subject: [PATCH 14/70] check --- python/ray/experimental/serve/examples/echo_pipeline.py | 4 +--- python/ray/experimental/serve/global_state.py | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/python/ray/experimental/serve/examples/echo_pipeline.py b/python/ray/experimental/serve/examples/echo_pipeline.py index 386fed51e029..e0cc9d570f5a 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_pipeline.py @@ -17,9 +17,7 @@ def echo1(context): # context["query_string"] += 'FROM MODEL1 -> ' # return context def echo2(context): - result = context['result'] - result += 'FROM MODEL2 -> ' - return result + return context serve.init(blocking=True) diff --git a/python/ray/experimental/serve/global_state.py b/python/ray/experimental/serve/global_state.py index 7a444591704c..789d8891d040 100644 --- a/python/ray/experimental/serve/global_state.py +++ b/python/ray/experimental/serve/global_state.py @@ -92,3 +92,4 @@ def wait_until_http_ready(self, num_retries=5, backoff_time_s=1): raise Exception( "HTTP server not ready after {} retries.".format( num_retries)) + print("BOTH VALUES kv:{} kv_pipeline: {}".format(routing_table_request_count,routing_pipeline_request_count)) From 5ee82ed27d8c68ed95f35d7326f8e8fb407572ff Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 11:05:08 -0400 Subject: [PATCH 15/70] check --- python/ray/experimental/serve/examples/echo_pipeline.py | 7 +++++-- python/ray/experimental/serve/global_state.py | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/python/ray/experimental/serve/examples/echo_pipeline.py b/python/ray/experimental/serve/examples/echo_pipeline.py index e0cc9d570f5a..6c7874eafd49 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_pipeline.py @@ -5,7 +5,7 @@ from ray.experimental import serve from ray.experimental.serve.utils import pformat_color_json - +import json def echo1(context): # query_string_dict = urls.url_decode(context["query_string"]) @@ -17,7 +17,10 @@ def echo1(context): # context["query_string"] += 'FROM MODEL1 -> ' # return context def echo2(context): - return context + result = json.loads(context) + result = result['result'] + result += 'FROM MODEL2 -> ' + return result serve.init(blocking=True) diff --git a/python/ray/experimental/serve/global_state.py b/python/ray/experimental/serve/global_state.py index 789d8891d040..2df9fd92c31b 100644 --- a/python/ray/experimental/serve/global_state.py +++ b/python/ray/experimental/serve/global_state.py @@ -92,4 +92,5 @@ def wait_until_http_ready(self, num_retries=5, backoff_time_s=1): raise Exception( "HTTP server not ready after {} retries.".format( num_retries)) - print("BOTH VALUES kv:{} kv_pipeline: {}".format(routing_table_request_count,routing_pipeline_request_count)) + + logger.info("BOTH VALUES kv:{} kv_pipeline: {}".format(routing_table_request_count,routing_pipeline_request_count)) From f83492ba8c1203deb37fe54ca5628d767b6a5b05 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 11:08:39 -0400 Subject: [PATCH 16/70] check --- python/ray/experimental/serve/examples/echo_pipeline.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/ray/experimental/serve/examples/echo_pipeline.py b/python/ray/experimental/serve/examples/echo_pipeline.py index 6c7874eafd49..ad6a8c7335f3 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_pipeline.py @@ -17,10 +17,10 @@ def echo1(context): # context["query_string"] += 'FROM MODEL1 -> ' # return context def echo2(context): - result = json.loads(context) - result = result['result'] - result += 'FROM MODEL2 -> ' - return result + # result = json.loads(context) + # result = result['result'] + context += 'FROM MODEL2 -> ' + return context serve.init(blocking=True) From 514e7b493ca3bcd94b5b0a42c901249b9fb499d3 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 11:11:16 -0400 Subject: [PATCH 17/70] check --- python/ray/experimental/serve/echo_pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/echo_pipeline.py b/python/ray/experimental/serve/echo_pipeline.py index 9c5ffafc9a16..64418e359a66 100644 --- a/python/ray/experimental/serve/echo_pipeline.py +++ b/python/ray/experimental/serve/echo_pipeline.py @@ -10,7 +10,7 @@ def echo1(context): context["query_string"] += 'FROM MODEL1 -> ' return context def echo2(context): - context["query_string"] += 'FROM MODEL2 -> ' + context += 'FROM MODEL2 -> ' return context serve.init(blocking=True) From 8056e39c1f6cc391789fd7b23f5388561cf23cc0 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 11:22:21 -0400 Subject: [PATCH 18/70] check --- python/ray/experimental/serve/constants.py | 2 +- .../ray/experimental/serve/examples/echo_pipeline.py | 10 ++++++++++ python/ray/experimental/serve/global_state.py | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/python/ray/experimental/serve/constants.py b/python/ray/experimental/serve/constants.py index e3aa5fc77465..8d0f5faac0e2 100644 --- a/python/ray/experimental/serve/constants.py +++ b/python/ray/experimental/serve/constants.py @@ -1,2 +1,2 @@ #: The interval which http server refreshes its routing table -HTTP_ROUTER_CHECKER_INTERVAL_S = 2 +HTTP_ROUTER_CHECKER_INTERVAL_S = 1 diff --git a/python/ray/experimental/serve/examples/echo_pipeline.py b/python/ray/experimental/serve/examples/echo_pipeline.py index ad6a8c7335f3..d52ba5194d83 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_pipeline.py @@ -22,21 +22,31 @@ def echo2(context): context += 'FROM MODEL2 -> ' return context +def echo3(context): + # result = json.loads(context) + # result = result['result'] + context += 'FROM MODEL3 -> ' + return context + serve.init(blocking=True) serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) serve.create_backend(echo1, "echo:v1") serve.create_backend(echo2, "echo:v2") +serve.create_backend(echo3,"echo:v3") serve.create_no_http_service("serve1") serve.create_no_http_service("serve2") +serve.create_no_http_service("serve3") serve.link_service("serve1", "echo:v1") serve.link_service("serve2", "echo:v2") +serve.link_service("serve3","echo:v3") serve.add_service_to_pipeline("pipeline1","serve1",blocking=True) serve.add_service_to_pipeline("pipeline1","serve2",blocking=True) +serve.add_service_to_pipeline("pipeline1","serve3",blocking=True) while True: resp = requests.get("http://127.0.0.1:8000/echo").json() diff --git a/python/ray/experimental/serve/global_state.py b/python/ray/experimental/serve/global_state.py index 2df9fd92c31b..b67c81ed15de 100644 --- a/python/ray/experimental/serve/global_state.py +++ b/python/ray/experimental/serve/global_state.py @@ -84,7 +84,7 @@ def wait_until_http_ready(self, num_retries=5, backoff_time_s=1): if routing_pipeline_request_count == 0: routing_pipeline_request_count = (ray.get( self.kv_store_actor_handle_pipeline.get_request_count.remote())) - logger.debug((LOG_PREFIX + "Checking if HTTP server is ready." + logger.info((LOG_PREFIX + "Checking if HTTP server is ready." "{} retries left.").format(retries)) time.sleep(backoff_time_s) retries -= 1 From 458f52a17711d86fe0e591ba99975a2b926d75a4 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Tue, 8 Oct 2019 11:42:16 -0400 Subject: [PATCH 19/70] final --- .../ray/experimental/serve/examples/echo.py | 28 ----------- .../experimental/serve/examples/echo_actor.py | 41 --------------- .../experimental/serve/examples/echo_error.py | 44 ---------------- .../serve/examples/echo_rollback.py | 50 ------------------- .../experimental/serve/examples/echo_split.py | 41 --------------- 5 files changed, 204 deletions(-) delete mode 100644 python/ray/experimental/serve/examples/echo.py delete mode 100644 python/ray/experimental/serve/examples/echo_actor.py delete mode 100644 python/ray/experimental/serve/examples/echo_error.py delete mode 100644 python/ray/experimental/serve/examples/echo_rollback.py delete mode 100644 python/ray/experimental/serve/examples/echo_split.py diff --git a/python/ray/experimental/serve/examples/echo.py b/python/ray/experimental/serve/examples/echo.py deleted file mode 100644 index f6cf5afbebea..000000000000 --- a/python/ray/experimental/serve/examples/echo.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Example service that prints out http context. -""" - -import time - -import requests - -from ray.experimental import serve -from ray.experimental.serve.utils import pformat_color_json - - -def echo(context): - return context - - -serve.init(blocking=True) - -serve.create_endpoint("my_endpoint", "/echo", blocking=True) -serve.create_backend(echo, "echo:v1") -serve.link("my_endpoint", "echo:v1") - -while True: - resp = requests.get("http://127.0.0.1:8000/echo").json() - print(pformat_color_json(resp)) - - print("...Sleeping for 2 seconds...") - time.sleep(2) diff --git a/python/ray/experimental/serve/examples/echo_actor.py b/python/ray/experimental/serve/examples/echo_actor.py deleted file mode 100644 index 98422679d71e..000000000000 --- a/python/ray/experimental/serve/examples/echo_actor.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Example actor that adds message to the end of query_string. -""" - -import time - -import requests -from werkzeug import urls - -from ray.experimental import serve -from ray.experimental.serve.utils import pformat_color_json - - -class EchoActor: - def __init__(self, message): - self.message = message - - def __call__(self, context): - query_string_dict = urls.url_decode(context["query_string"]) - message = "" - message += query_string_dict.get("message", "") - message += " " - message += self.message - return message - - -serve.init(blocking=True) - -serve.create_endpoint("my_endpoint", "/echo", blocking=True) -serve.create_backend(EchoActor, "echo:v1", "world") -serve.link("my_endpoint", "echo:v1") - -while True: - resp = requests.get("http://127.0.0.1:8000/echo?message=hello").json() - print(pformat_color_json(resp)) - - resp = requests.get("http://127.0.0.1:8000/echo").json() - print(pformat_color_json(resp)) - - print("...Sleeping for 2 seconds...") - time.sleep(2) diff --git a/python/ray/experimental/serve/examples/echo_error.py b/python/ray/experimental/serve/examples/echo_error.py deleted file mode 100644 index 25d900068ae8..000000000000 --- a/python/ray/experimental/serve/examples/echo_error.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Example of error handling mechanism in ray serve. - -We are going to define a buggy function that raise some exception: ->>> def echo(_): - raise Exception("oh no") - -The expected behavior is: -- HTTP server should respond with "internal error" in the response JSON -- ray.get(handle.remote(33)) should raise RayTaskError with traceback. - -This shows that error is hidden from HTTP side but always visible when calling -from Python. -""" - -import time - -import requests - -import ray -from ray.experimental import serve -from ray.experimental.serve.utils import pformat_color_json - - -def echo(_): - raise Exception("Something went wrong...") - - -serve.init(blocking=True) - -serve.create_endpoint("my_endpoint", "/echo", blocking=True) -serve.create_backend(echo, "echo:v1") -serve.link("my_endpoint", "echo:v1") - -for _ in range(2): - resp = requests.get("http://127.0.0.1:8000/echo").json() - print(pformat_color_json(resp)) - - print("...Sleeping for 2 seconds...") - time.sleep(2) - -handle = serve.get_handle("my_endpoint") - -ray.get(handle.remote(33)) diff --git a/python/ray/experimental/serve/examples/echo_rollback.py b/python/ray/experimental/serve/examples/echo_rollback.py deleted file mode 100644 index bcdf7e14e15c..000000000000 --- a/python/ray/experimental/serve/examples/echo_rollback.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Example rollback action in ray serve. We first deploy only v1, then set a - 50/50 deployment between v1 and v2, and finally roll back to only v1. -""" -import time - -import requests - -from ray.experimental import serve -from ray.experimental.serve.utils import pformat_color_json - - -def echo_v1(_): - return "v1" - - -def echo_v2(_): - return "v2" - - -serve.init(blocking=True) - -serve.create_endpoint("my_endpoint", "/echo", blocking=True) -serve.create_backend(echo_v1, "echo:v1") -serve.link("my_endpoint", "echo:v1") - -for _ in range(3): - resp = requests.get("http://127.0.0.1:8000/echo").json() - print(pformat_color_json(resp)) - - print("...Sleeping for 2 seconds...") - time.sleep(2) - -serve.create_backend(echo_v2, "echo:v2") -serve.split("my_endpoint", {"echo:v1": 0.5, "echo:v2": 0.5}) - -for _ in range(6): - resp = requests.get("http://127.0.0.1:8000/echo").json() - print(pformat_color_json(resp)) - - print("...Sleeping for 2 seconds...") - time.sleep(2) - -serve.rollback("my_endpoint") -for _ in range(6): - resp = requests.get("http://127.0.0.1:8000/echo").json() - print(pformat_color_json(resp)) - - print("...Sleeping for 2 seconds...") - time.sleep(2) diff --git a/python/ray/experimental/serve/examples/echo_split.py b/python/ray/experimental/serve/examples/echo_split.py deleted file mode 100644 index 6942db5a57c6..000000000000 --- a/python/ray/experimental/serve/examples/echo_split.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Example of traffic splitting. We will first use echo:v1. Then v1 and v2 -will split the incoming traffic evenly. -""" -import time - -import requests - -from ray.experimental import serve -from ray.experimental.serve.utils import pformat_color_json - - -def echo_v1(_): - return "v1" - - -def echo_v2(_): - return "v2" - - -serve.init(blocking=True) - -serve.create_endpoint("my_endpoint", "/echo", blocking=True) -serve.create_backend(echo_v1, "echo:v1") -serve.link("my_endpoint", "echo:v1") - -for _ in range(3): - resp = requests.get("http://127.0.0.1:8000/echo").json() - print(pformat_color_json(resp)) - - print("...Sleeping for 2 seconds...") - time.sleep(2) - -serve.create_backend(echo_v2, "echo:v2") -serve.split("my_endpoint", {"echo:v1": 0.5, "echo:v2": 0.5}) -while True: - resp = requests.get("http://127.0.0.1:8000/echo").json() - print(pformat_color_json(resp)) - - print("...Sleeping for 2 seconds...") - time.sleep(2) From c63c364ad72624316b3794486319972ab4791784 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 12:04:34 -0400 Subject: [PATCH 20/70] enabling DAG pipeline scheduling --- python/ray/experimental/serve/__init__.py | 8 ++-- python/ray/experimental/serve/api.py | 30 ++++++++++-- .../serve/examples/echo_pipeline.py | 46 +++++++++++------- python/ray/experimental/serve/global_state.py | 2 +- .../experimental/serve/kv_store_service.py | 48 +++++++++++++++++-- python/ray/experimental/serve/server.py | 26 +++++++--- python/ray/experimental/serve/task_runner.py | 1 + 7 files changed, 123 insertions(+), 38 deletions(-) diff --git a/python/ray/experimental/serve/__init__.py b/python/ray/experimental/serve/__init__.py index 6e7a46d192ca..129a76868cc0 100644 --- a/python/ray/experimental/serve/__init__.py +++ b/python/ray/experimental/serve/__init__.py @@ -2,11 +2,11 @@ if sys.version_info < (3, 0): raise ImportError("serve is Python 3 only.") -from ray.experimental.serve.api import (init, create_backend, create_endpoint_pipeline,create_no_http_service, - add_service_to_pipeline,link_service, split, rollback, get_handle, +from ray.experimental.serve.api import (init,provision_pipeline, create_backend, create_endpoint_pipeline,create_no_http_service, + add_service_dependencies,link_service, split, rollback, get_handle, global_state) # noqa: E402 __all__ = [ - "init", "create_backend", "create_endpoint_pipeline","create_no_http_service","add_service_to_pipeline","link", "link_service", "rollback", - "get_handle", "global_state" + "init","provision_pipeline", "create_backend", "create_endpoint_pipeline","create_no_http_service","add_service_dependencies", "link_service", "rollback", + "get_handle","split" ,"global_state" ] diff --git a/python/ray/experimental/serve/api.py b/python/ray/experimental/serve/api.py index fa2858a4d84c..8b9813ad70ce 100644 --- a/python/ray/experimental/serve/api.py +++ b/python/ray/experimental/serve/api.py @@ -48,6 +48,8 @@ def create_endpoint_pipeline(pipeline_name, route_expression, blocking=True): blocking (bool): If true, the function will wait for service to be registered before returning """ + assert pipeline_name in global_state.provisioned_services + future = global_state.kv_store_actor_handle.register_service.remote( route_expression, pipeline_name) if blocking: @@ -91,14 +93,33 @@ class CustomActor(RayServeMixin, func_or_class): global_state.registered_backends.add(backend_tag) -def add_service_to_pipeline(pipeline_name,service_name,blocking=True): - assert service_name in global_state.registered_services - assert pipeline_name in global_state.registered_endpoints +# def add_service_to_pipeline(pipeline_name,service_name,blocking=True): +# assert service_name in global_state.registered_services +# # assert pipeline_name in global_state.registered_endpoints + +# future = global_state.kv_store_actor_handle_pipeline.add_node.remote(pipeline_name,service_name) +# if blocking: +# ray.get(future) - future = global_state.kv_store_actor_handle_pipeline.add_node.remote(pipeline_name,service_name) +def add_service_dependencies(pipeline_name,service_name_1,service_name_2,blocking=True): + assert service_name_1 in global_state.registered_services + assert service_name_2 in global_state.registered_services + assert pipeline_name not in provisioned_services + + future = global_state.kv_store_actor_handle_pipeline.add_edge.remote(pipeline_name,service_name_1,service_name_2) if blocking: ray.get(future) +def provision_pipeline(pipeline_name,blocking=True) : + assert pipeline_name not in global_state.provisioned_services + future = global_state.kv_store_actor_handle_pipeline.provision.remote(pipeline_name) + if blocking : + ray.get(future) + + global_state.provisioned_services.add(pipeline_name) + + + def link_service(service_name, backend_tag): """Associate a service endpoint with backend tag. @@ -113,6 +134,7 @@ def link_service(service_name, backend_tag): >>> serve.split("service-name", {"backend:v1": 1.0}) """ assert service_name in global_state.registered_services + assert backend in global_state.registered_backends global_state.router_actor_handle.link.remote(service_name, backend_tag) global_state.policy_action_history[service_name].append({backend_tag: 1}) diff --git a/python/ray/experimental/serve/examples/echo_pipeline.py b/python/ray/experimental/serve/examples/echo_pipeline.py index d52ba5194d83..6c0f954e71cd 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_pipeline.py @@ -8,45 +8,57 @@ import json def echo1(context): - # query_string_dict = urls.url_decode(context["query_string"]) message = "" - # message += query_string_dict.get("message", "") - # message += " " message += 'FROM MODEL1 -> ' return message - # context["query_string"] += 'FROM MODEL1 -> ' - # return context def echo2(context): - # result = json.loads(context) - # result = result['result'] - context += 'FROM MODEL2 -> ' - return context + data_from_service1 = context['serve1'] + data_from_service1 += 'FROM MODEL2 -> ' + return data_from_service1 def echo3(context): - # result = json.loads(context) - # result = result['result'] - context += 'FROM MODEL3 -> ' - return context + data_from_service2 = context['serve2'] + data_from_service2 += 'FROM MODEL3 -> ' + return data_from_service2 serve.init(blocking=True) -serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) +# serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) +# Create Backends serve.create_backend(echo1, "echo:v1") serve.create_backend(echo2, "echo:v2") serve.create_backend(echo3,"echo:v3") +# Create services serve.create_no_http_service("serve1") serve.create_no_http_service("serve2") serve.create_no_http_service("serve3") +# Link services and backends serve.link_service("serve1", "echo:v1") serve.link_service("serve2", "echo:v2") serve.link_service("serve3","echo:v3") -serve.add_service_to_pipeline("pipeline1","serve1",blocking=True) -serve.add_service_to_pipeline("pipeline1","serve2",blocking=True) -serve.add_service_to_pipeline("pipeline1","serve3",blocking=True) +''' +1. Add service dependencies in a PIPELINE +2. You can add dependency to a PIPELINE only if the PIPELINE has not been provisioned yet. +''' +''' +Creating a pipeline serve1 -> serve2 -> serve3 +''' +# serve2 depends on serve1 +serve.add_service_dependencies("pipeline1","serve1","serve2") +# serve3 depends on serve2 +serve.add_service_dependencies("pipeline1","serve2","serve3") + +# Provision the PIPELINE (You can provision the service only once) +serve.provision_pipeline("pipeline1") + +# You can only create an endpoint for pipeline after provisioning the pipeline +serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) + +time.sleep(2) while True: resp = requests.get("http://127.0.0.1:8000/echo").json() diff --git a/python/ray/experimental/serve/global_state.py b/python/ray/experimental/serve/global_state.py index b67c81ed15de..d279ad0efe6d 100644 --- a/python/ray/experimental/serve/global_state.py +++ b/python/ray/experimental/serve/global_state.py @@ -27,7 +27,7 @@ class GlobalState: def __init__(self): #: holds all actor handles. self.backend_actor_handles = [] - + self.provisioned_services = set() #: actor handle to KV store actor self.kv_store_actor_handle = None diff --git a/python/ray/experimental/serve/kv_store_service.py b/python/ray/experimental/serve/kv_store_service.py index 84af04f84791..b72d32a5581c 100644 --- a/python/ray/experimental/serve/kv_store_service.py +++ b/python/ray/experimental/serve/kv_store_service.py @@ -5,6 +5,10 @@ import ray.experimental.internal_kv as ray_kv from ray.experimental.serve.utils import logger +# Pipeline will store a DAG of services +import networkx as nx +from networkx.readwrite import json_graph +import traceback class NamespacedKVStore(ABC): """Abstract base class for a namespaced key-value store. @@ -139,13 +143,47 @@ def __init__(self, kv_class=RayInternalKVStore): self.pipeline_storage = kv_class(index_key = "RAY_PIPELINE_INDEX",namespace="pipelines") self.request_count = 0 - def add_node(self,pipeline: str, service_no_http: str): + # Adds directed edge from service_no_http_1 --> service_no_http_2 in service DAG for pipeline + def add_edge(self,pipeline: str, service_no_http_1: str , service_no_http_2: str): + if self.pipeline_storage.exists(pipeline): - l = set(self.pipeline_storage.get(pipeline)) - l.add(service_no_http) - self.pipeline_storage.put(pipeline,list(l)) + g_json = self.pipeline_storage.get(pipeline) + G = json_graph.node_link_graph(g_json) else: - self.pipeline_storage.put(pipeline,[service_no_http]) + G = nx.DiGraph() + + # try: + # G.add_edge(service_no_http_1,service_no_http_2) + # if not nx.is_directed_acyclic_graph(G) : + # G.remove_edge(service_no_http_1,service_no_http_2) + # raise Exception('This service dependency creates a cycle!') + # except Exception: + # traceback_str = ray.utils.format_error_message(traceback.format_exc()) + # return ray.exceptions.RayTaskError(str(add_edge), traceback_str) + + + + G.add_edge(service_no_http_1,service_no_http_2) + g_json = json_graph.node_link_data(G) + self.pipeline_storage.put(pipeline,g_json) + + def provision(self,pipeline: str): + try : + if self.pipeline_storage.exists(pipeline): + g_json = self.pipeline_storage.get(pipeline) + G = json_graph.node_link_graph(g_json) + node_order = list(nx.topological_sort(G)) + successor_d = {} + for node in G: + successor_d[node] = list(G.successors(node)) + final_d = {'node_order': node_order , 'successors' : successor_d} + self.pipeline_storage.put(pipeline,final_d) + else: + raise Exception('Add service dependencies to pipeline') + except Exception: + traceback_str = ray.utils.format_error_message(traceback.format_exc()) + return ray.exceptions.RayTaskError(str(provision), traceback_str) + def list_pipeline_service(self): self.request_count += 1 diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 6765506736c3..93b9e9f8dc82 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -2,12 +2,12 @@ import json import uvicorn - +from collections import defaultdict, deque import ray from ray.experimental.async_api import _async_init, as_future from ray.experimental.serve.utils import BytesEncoder from ray.experimental.serve.constants import HTTP_ROUTER_CHECKER_INTERVAL_S - +from networkx.readwrite import json_graph class JSONResponse: """ASGI compliant response class. @@ -99,13 +99,25 @@ async def __call__(self, scope, receive, send): await JSONResponse(self.route_table)(scope, receive, send) elif current_path in self.route_table: pipeline_name = self.route_table[current_path] - services_list = self.pipeline_table[pipeline_name] + service_dependencies = self.pipeline_table[pipeline_name] # await JSONResponse({"result": str(services_list)})(scope, receive, send) result = scope - for service in services_list: - result_object_id_bytes = await as_future( - self.router.enqueue_request.remote(service, result)) - result = await as_future(ray.ObjectID(result_object_id_bytes)) + # for service in services_list: + # result_object_id_bytes = await as_future( + # self.router.enqueue_request.remote(service, result)) + # result = await as_future(ray.ObjectID(result_object_id_bytes)) + data_d = defaultdict(dict) + for node in service_dependencies['node_order']: + data_sent = None + if data_d[node] == {}: + data_sent = scope + else: + data_sent = data_d[node] + result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) + node_result = await as_future(ray.ObjectID(result_object_id_bytes)) + for node_successor in service_dependencies['successors'][node]: + data_d[node_successor][node] = node_result + if isinstance(result, ray.exceptions.RayTaskError): await JSONResponse({ diff --git a/python/ray/experimental/serve/task_runner.py b/python/ray/experimental/serve/task_runner.py index 0533a6c02227..1eabcdf8bf53 100644 --- a/python/ray/experimental/serve/task_runner.py +++ b/python/ray/experimental/serve/task_runner.py @@ -62,6 +62,7 @@ def _ray_serve_main_loop(self, my_handle): work_item = ray.get(ray.ObjectID(work_token)) # TODO(simon): + # D1, D2, D3 # __call__ should be able to take multiple *args and **kwargs. result = wrap_to_ray_error(self.__call__, work_item.request_body) result_object_id = work_item.result_object_id From 38cf462576eb680d36e909e270911cdcadab4dca Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 12:16:42 -0400 Subject: [PATCH 21/70] bug fix --- python/ray/experimental/serve/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/api.py b/python/ray/experimental/serve/api.py index 8b9813ad70ce..720a2d81939d 100644 --- a/python/ray/experimental/serve/api.py +++ b/python/ray/experimental/serve/api.py @@ -134,7 +134,7 @@ def link_service(service_name, backend_tag): >>> serve.split("service-name", {"backend:v1": 1.0}) """ assert service_name in global_state.registered_services - assert backend in global_state.registered_backends + assert backend_tag in global_state.registered_backends global_state.router_actor_handle.link.remote(service_name, backend_tag) global_state.policy_action_history[service_name].append({backend_tag: 1}) From 878972e467ecd940e868583ae61ae0cfee54a265 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 12:24:00 -0400 Subject: [PATCH 22/70] bug fix --- python/ray/experimental/serve/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/api.py b/python/ray/experimental/serve/api.py index 720a2d81939d..02fb4ecf5c63 100644 --- a/python/ray/experimental/serve/api.py +++ b/python/ray/experimental/serve/api.py @@ -104,7 +104,7 @@ class CustomActor(RayServeMixin, func_or_class): def add_service_dependencies(pipeline_name,service_name_1,service_name_2,blocking=True): assert service_name_1 in global_state.registered_services assert service_name_2 in global_state.registered_services - assert pipeline_name not in provisioned_services + assert pipeline_name not in global_state.provisioned_services future = global_state.kv_store_actor_handle_pipeline.add_edge.remote(pipeline_name,service_name_1,service_name_2) if blocking: From 0d2e6e0f71c7946c3641b8a2efcd010bded76934 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 12:30:52 -0400 Subject: [PATCH 23/70] bug fix --- python/ray/experimental/serve/server.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 93b9e9f8dc82..546bde5b9a83 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -107,6 +107,7 @@ async def __call__(self, scope, receive, send): # self.router.enqueue_request.remote(service, result)) # result = await as_future(ray.ObjectID(result_object_id_bytes)) data_d = defaultdict(dict) + for node in service_dependencies['node_order']: data_sent = None if data_d[node] == {}: @@ -115,8 +116,12 @@ async def __call__(self, scope, receive, send): data_sent = data_d[node] result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) node_result = await as_future(ray.ObjectID(result_object_id_bytes)) - for node_successor in service_dependencies['successors'][node]: - data_d[node_successor][node] = node_result + if service_dependencies['successors'][node] == []: + result = node_result + break + else: + for node_successor in service_dependencies['successors'][node]: + data_d[node_successor][node] = node_result if isinstance(result, ray.exceptions.RayTaskError): From 9e0387241758949e753132218d4a97306db69adc Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 12:43:23 -0400 Subject: [PATCH 24/70] Adding examples --- .../{echo_pipeline.py => echo_pipeline_1.py} | 0 .../serve/examples/echo_pipeline_2.py | 71 +++++++++++++++++++ 2 files changed, 71 insertions(+) rename python/ray/experimental/serve/examples/{echo_pipeline.py => echo_pipeline_1.py} (100%) create mode 100644 python/ray/experimental/serve/examples/echo_pipeline_2.py diff --git a/python/ray/experimental/serve/examples/echo_pipeline.py b/python/ray/experimental/serve/examples/echo_pipeline_1.py similarity index 100% rename from python/ray/experimental/serve/examples/echo_pipeline.py rename to python/ray/experimental/serve/examples/echo_pipeline_1.py diff --git a/python/ray/experimental/serve/examples/echo_pipeline_2.py b/python/ray/experimental/serve/examples/echo_pipeline_2.py new file mode 100644 index 000000000000..37c181003251 --- /dev/null +++ b/python/ray/experimental/serve/examples/echo_pipeline_2.py @@ -0,0 +1,71 @@ +import time + +import requests +from werkzeug import urls + +from ray.experimental import serve +from ray.experimental.serve.utils import pformat_color_json +import json + +def echo1(context): + message = "" + message += 'FROM MODEL1 -> ' + return message +def echo2(context): + # data_from_service1 = context['serve1'] + message = "" + message += 'FROM MODEL2 -> ' + return message + +def echo3(context): + data_from_service1 = context['serve1'] + data_from_service2 = context['serve2'] + data = '[ ' + data_from_service1 + ',' + data_from_service2 + '] ->' + data += 'FROM MODEL3 -> ' + return data + +serve.init(blocking=True) + +# serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) + +# Create Backends +serve.create_backend(echo1, "echo:v1") +serve.create_backend(echo2, "echo:v2") +serve.create_backend(echo3,"echo:v3") + +# Create services +serve.create_no_http_service("serve1") +serve.create_no_http_service("serve2") +serve.create_no_http_service("serve3") + +# Link services and backends +serve.link_service("serve1", "echo:v1") +serve.link_service("serve2", "echo:v2") +serve.link_service("serve3","echo:v3") + +''' +1. Add service dependencies in a PIPELINE +2. You can add dependency to a PIPELINE only if the PIPELINE has not been provisioned yet. +''' +''' +Creating a pipeline [serve1 , serve2] -> serve3 +''' +# serve3 depends on serve1 +serve.add_service_dependencies("pipeline1","serve1","serve3") +# serve3 depends on serve2 +serve.add_service_dependencies("pipeline1","serve2","serve3") + +# Provision the PIPELINE (You can provision the service only once) +serve.provision_pipeline("pipeline1") + +# You can only create an endpoint for pipeline after provisioning the pipeline +serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) + +time.sleep(2) + +while True: + resp = requests.get("http://127.0.0.1:8000/echo").json() + print(pformat_color_json(resp)) + + print("...Sleeping for 2 seconds...") + time.sleep(2) \ No newline at end of file From 00fb3b994d1539d56ad614d825e143f38d52e964 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 13:13:46 -0400 Subject: [PATCH 25/70] Complex pipeline --- .../serve/examples/echo_complex_pipeline.py | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 python/ray/experimental/serve/examples/echo_complex_pipeline.py diff --git a/python/ray/experimental/serve/examples/echo_complex_pipeline.py b/python/ray/experimental/serve/examples/echo_complex_pipeline.py new file mode 100644 index 000000000000..04045cfdc443 --- /dev/null +++ b/python/ray/experimental/serve/examples/echo_complex_pipeline.py @@ -0,0 +1,195 @@ +import time + +import requests +from werkzeug import urls + +from ray.experimental import serve +from ray.experimental.serve.utils import pformat_color_json +import json + +def echo1(context): + message = "" + message += 'FROM MODEL1 -> ' + return message + +def echo2(context): + start = "[ " + for key in context.keys(): + start = start + context[key] + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL2 -> ' + return data + +def echo3(context): + start = "[ " + for key in context.keys(): + start = start + context[key] + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL3 -> ' + return data + +def echo4(context): + start = "[ " + for key in context.keys(): + start = start + context[key] + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL4 -> ' + return data + +def echo5(context): + start = "[ " + for key in context.keys(): + start = start + context[key] + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL5 -> ' + return data + +def echo6(context): + start = "[ " + for key in context.keys(): + start = start + context[key] + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL6 -> ' + return data + +def echo7(context): + start = "[ " + for key in context.keys(): + start = start + context[key] + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL7 -> ' + return data + +def echo8(context): + start = "[ " + for key in context.keys(): + start = start + context[key] + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL8 -> ' + return data + +def echo9(context): + start = "[ " + for key in context.keys(): + start = start + context[key] + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL9 -> ' + return data + +def echo10(context): + start = "[ " + for key in context.keys(): + start = start + context[key] + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL10 -> ' + return data + +serve.init(blocking=True) + +# serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) + +# Create Backends +serve.create_backend(echo1, "echo:v1") +serve.create_backend(echo2, "echo:v2") +serve.create_backend(echo3,"echo:v3") +serve.create_backend(echo4,"echo:v4") +serve.create_backend(echo5,"echo:v5") +serve.create_backend(echo6,"echo:v6") +serve.create_backend(echo7,"echo:v7") +serve.create_backend(echo8,"echo:v8") +serve.create_backend(echo9,"echo:v9") +serve.create_backend(echo10,"echo:v10") + +# Create services +serve.create_no_http_service("serve1") +serve.create_no_http_service("serve2") +serve.create_no_http_service("serve3") +serve.create_no_http_service("serve4") +serve.create_no_http_service("serve5") +serve.create_no_http_service("serve6") +serve.create_no_http_service("serve7") +serve.create_no_http_service("serve8") +serve.create_no_http_service("serve9") +serve.create_no_http_service("serve10") +# serve.create_no_http_service("serve3") + +# Link services and backends +serve.link_service("serve1", "echo:v1") +serve.link_service("serve2", "echo:v2") +serve.link_service("serve3","echo:v3") +serve.link_service("serve4","echo:v4") +serve.link_service("serve5","echo:v5") +serve.link_service("serve6","echo:v6") +serve.link_service("serve7","echo:v7") +serve.link_service("serve8","echo:v8") +serve.link_service("serve9","echo:v9") +serve.link_service("serve10","echo:v10") + +''' +1. Add service dependencies in a PIPELINE +2. You can add dependency to a PIPELINE only if the PIPELINE has not been provisioned yet. +''' + +# Adding dependencies +serve.add_service_dependencies("pipeline1","serve1","serve2") +serve.add_service_dependencies("pipeline1","serve1","serve3") +serve.add_service_dependencies("pipeline1","serve1","serve5") + +serve.add_service_dependencies("pipeline1","serve2","serve4") +serve.add_service_dependencies("pipeline1","serve2","serve5") + +serve.add_service_dependencies("pipeline1","serve3","serve6") +serve.add_service_dependencies("pipeline1","serve3","serve7") + +serve.add_service_dependencies("pipeline1","serve4","serve8") + +serve.add_service_dependencies("pipeline1","serve5","serve9") + +serve.add_service_dependencies("pipeline1","serve6","serve5") +serve.add_service_dependencies("pipeline1","serve6","serve8") + +serve.add_service_dependencies("pipeline1","serve7","serve4") +serve.add_service_dependencies("pipeline1","serve7","serve9") + +serve.add_service_dependencies("pipeline1","serve8","serve10") + +serve.add_service_dependencies("pipeline1","serve9","serve10") + + + + + + + +# Provision the PIPELINE (You can provision the service only once) +serve.provision_pipeline("pipeline1") + +# You can only create an endpoint for pipeline after provisioning the pipeline +serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) + +time.sleep(2) + +while True: + resp = requests.get("http://127.0.0.1:8000/echo").json() + print(pformat_color_json(resp)) + + print("...Sleeping for 2 seconds...") + time.sleep(2) \ No newline at end of file From a8726184e9d4ce5557a14580e5a0d4de075d1938 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 14:36:21 -0400 Subject: [PATCH 26/70] pipeline examples --- python/ray/experimental/serve/examples/echo_complex_pipeline.py | 2 +- python/ray/experimental/serve/examples/echo_pipeline_1.py | 2 +- python/ray/experimental/serve/examples/echo_pipeline_2.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ray/experimental/serve/examples/echo_complex_pipeline.py b/python/ray/experimental/serve/examples/echo_complex_pipeline.py index 04045cfdc443..3a76d690e6f8 100644 --- a/python/ray/experimental/serve/examples/echo_complex_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_complex_pipeline.py @@ -179,7 +179,7 @@ def echo10(context): -# Provision the PIPELINE (You can provision the service only once) +# Provision the PIPELINE (You can provision the pipeline only once) serve.provision_pipeline("pipeline1") # You can only create an endpoint for pipeline after provisioning the pipeline diff --git a/python/ray/experimental/serve/examples/echo_pipeline_1.py b/python/ray/experimental/serve/examples/echo_pipeline_1.py index 6c0f954e71cd..890574bb6701 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline_1.py +++ b/python/ray/experimental/serve/examples/echo_pipeline_1.py @@ -52,7 +52,7 @@ def echo3(context): # serve3 depends on serve2 serve.add_service_dependencies("pipeline1","serve2","serve3") -# Provision the PIPELINE (You can provision the service only once) +# Provision the PIPELINE (You can provision the pipeline only once) serve.provision_pipeline("pipeline1") # You can only create an endpoint for pipeline after provisioning the pipeline diff --git a/python/ray/experimental/serve/examples/echo_pipeline_2.py b/python/ray/experimental/serve/examples/echo_pipeline_2.py index 37c181003251..4e43096d2c86 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline_2.py +++ b/python/ray/experimental/serve/examples/echo_pipeline_2.py @@ -55,7 +55,7 @@ def echo3(context): # serve3 depends on serve2 serve.add_service_dependencies("pipeline1","serve2","serve3") -# Provision the PIPELINE (You can provision the service only once) +# Provision the PIPELINE (You can provision the pipeline only once) serve.provision_pipeline("pipeline1") # You can only create an endpoint for pipeline after provisioning the pipeline From f272304983b152cbafe1a56f5ad680c3eec1a2ab Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 14:44:09 -0400 Subject: [PATCH 27/70] error handling --- python/ray/experimental/serve/kv_store_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/kv_store_service.py b/python/ray/experimental/serve/kv_store_service.py index b72d32a5581c..ed004032547a 100644 --- a/python/ray/experimental/serve/kv_store_service.py +++ b/python/ray/experimental/serve/kv_store_service.py @@ -182,7 +182,7 @@ def provision(self,pipeline: str): raise Exception('Add service dependencies to pipeline') except Exception: traceback_str = ray.utils.format_error_message(traceback.format_exc()) - return ray.exceptions.RayTaskError(str(provision), traceback_str) + return ray.exceptions.RayTaskError('Issue with provisioning pipeline', traceback_str) def list_pipeline_service(self): From 73724d94bca3a18eedc5f79539decb6efdc0a27a Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 16:17:00 -0400 Subject: [PATCH 28/70] POST request functionality --- .../serve/examples/echo_post_check.py | 74 +++++++++++++++++ python/ray/experimental/serve/server.py | 80 ++++++++++++------- 2 files changed, 123 insertions(+), 31 deletions(-) create mode 100644 python/ray/experimental/serve/examples/echo_post_check.py diff --git a/python/ray/experimental/serve/examples/echo_post_check.py b/python/ray/experimental/serve/examples/echo_post_check.py new file mode 100644 index 000000000000..77e95e837dd1 --- /dev/null +++ b/python/ray/experimental/serve/examples/echo_post_check.py @@ -0,0 +1,74 @@ +import time + +import requests +from werkzeug import urls + +from ray.experimental import serve +from ray.experimental.serve.utils import pformat_color_json +import json + +def echo1(context): + message = "" + message += 'FROM MODEL1 -> ' + return message +def identity(context): + return context['serve1'] +# def echo2(context): +# data_from_service1 = context['serve1'] +# data_from_service1 += 'FROM MODEL2 -> ' +# return data_from_service1 + +# def echo3(context): +# data_from_service2 = context['serve2'] +# data_from_service2 += 'FROM MODEL3 -> ' +# return data_from_service2 + +serve.init(blocking=True) + +# serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) + +# Create Backends +serve.create_backend(echo1, "echo:v1") +serve.create_backend(identity, "identity:b") +# serve.create_backend(echo2, "echo:v2") +# serve.create_backend(echo3,"echo:v3") + +# Create services +serve.create_no_http_service("serve1") +serve.create_no_http_service("identity") +# serve.create_no_http_service("serve2") +# serve.create_no_http_service("serve3") + +# Link services and backends +serve.link_service("serve1", "echo:v1") +serve.link_service("identity", "identity:b") +# serve.link_service("serve2", "echo:v2") +# serve.link_service("serve3","echo:v3") + +''' +1. Add service dependencies in a PIPELINE +2. You can add dependency to a PIPELINE only if the PIPELINE has not been provisioned yet. +''' +''' +Creating a pipeline serve1 -> serve2 -> serve3 +''' +# serve2 depends on serve1 +serve.add_service_dependencies("pipeline1","serve1","identity") +# serve3 depends on serve2 +# serve.add_service_dependencies("pipeline1","serve2","serve3") + +# Provision the PIPELINE (You can provision the pipeline only once) +serve.provision_pipeline("pipeline1") + +# You can only create an endpoint for pipeline after provisioning the pipeline +serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) + +time.sleep(2) + +data = {'data':[1,2,3,6], 'model': 'resnet'} +while True: + resp = requests.post("http://127.0.0.1:8000/echo",data = data).json() + print(pformat_color_json(resp)) + + print("...Sleeping for 2 seconds...") + time.sleep(2) \ No newline at end of file diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 546bde5b9a83..92e0efaa0bfb 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -83,6 +83,19 @@ async def route_checker(self, interval): return await asyncio.sleep(interval) + async def read_body(self,receive): + """ + Read and return the entire body from an incoming ASGI message. + """ + body = b'' + more_body = True + + while more_body: + message = await receive() + body += message.get('body', b'') + more_body = message.get('more_body', False) + + return body async def __call__(self, scope, receive, send): # NOTE: This implements ASGI protocol specified in @@ -98,38 +111,43 @@ async def __call__(self, scope, receive, send): if current_path == "/": await JSONResponse(self.route_table)(scope, receive, send) elif current_path in self.route_table: - pipeline_name = self.route_table[current_path] - service_dependencies = self.pipeline_table[pipeline_name] - # await JSONResponse({"result": str(services_list)})(scope, receive, send) - result = scope - # for service in services_list: - # result_object_id_bytes = await as_future( - # self.router.enqueue_request.remote(service, result)) - # result = await as_future(ray.ObjectID(result_object_id_bytes)) - data_d = defaultdict(dict) - - for node in service_dependencies['node_order']: - data_sent = None - if data_d[node] == {}: - data_sent = scope + if scope['method'] == 'GET' : + pipeline_name = self.route_table[current_path] + service_dependencies = self.pipeline_table[pipeline_name] + # await JSONResponse({"result": str(services_list)})(scope, receive, send) + result = scope + # for service in services_list: + # result_object_id_bytes = await as_future( + # self.router.enqueue_request.remote(service, result)) + # result = await as_future(ray.ObjectID(result_object_id_bytes)) + data_d = defaultdict(dict) + + for node in service_dependencies['node_order']: + data_sent = None + if data_d[node] == {}: + data_sent = scope + else: + data_sent = data_d[node] + result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) + node_result = await as_future(ray.ObjectID(result_object_id_bytes)) + if service_dependencies['successors'][node] == []: + result = node_result + break + else: + for node_successor in service_dependencies['successors'][node]: + data_d[node_successor][node] = node_result + + + if isinstance(result, ray.exceptions.RayTaskError): + await JSONResponse({ + "error": "internal error, please use python API to debug" + })(scope, receive, send) else: - data_sent = data_d[node] - result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) - node_result = await as_future(ray.ObjectID(result_object_id_bytes)) - if service_dependencies['successors'][node] == []: - result = node_result - break - else: - for node_successor in service_dependencies['successors'][node]: - data_d[node_successor][node] = node_result - - - if isinstance(result, ray.exceptions.RayTaskError): - await JSONResponse({ - "error": "internal error, please use python API to debug" - })(scope, receive, send) - else: - await JSONResponse({"result": result})(scope, receive, send) + await JSONResponse({"result": result})(scope, receive, send) + + elif scope['method'] == 'POST': + body = await self.read_body(receive) + await JSONResponse({"result": body})(scope, receive, send) else: error_message = ("Path {} not found. " "Please ping http://.../ for routing table" From c90fd5e2c58f27f5ff5fc688f0778a387dd0f531 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 16:23:09 -0400 Subject: [PATCH 29/70] POST request functionality attempt 2 --- python/ray/experimental/serve/server.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 92e0efaa0bfb..6e7cabd90138 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -87,15 +87,16 @@ async def read_body(self,receive): """ Read and return the entire body from an incoming ASGI message. """ - body = b'' - more_body = True + # body = b'' + # more_body = True - while more_body: - message = await receive() - body += message.get('body', b'') - more_body = message.get('more_body', False) + # while more_body: + + message = await receive() + # body += message.get('body', b'') + # more_body = message.get('more_body', False) - return body + return message async def __call__(self, scope, receive, send): # NOTE: This implements ASGI protocol specified in @@ -144,7 +145,7 @@ async def __call__(self, scope, receive, send): })(scope, receive, send) else: await JSONResponse({"result": result})(scope, receive, send) - + elif scope['method'] == 'POST': body = await self.read_body(receive) await JSONResponse({"result": body})(scope, receive, send) From 2e255449d324c6d2adb16fe4bea600e6f49830b5 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 17:02:12 -0400 Subject: [PATCH 30/70] POST request functionality attempt 3 --- .../serve/examples/echo_post_check.py | 4 +++- python/ray/experimental/serve/server.py | 15 +++++++-------- python/ray/experimental/serve/utils.py | 10 +++++++++- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/python/ray/experimental/serve/examples/echo_post_check.py b/python/ray/experimental/serve/examples/echo_post_check.py index 77e95e837dd1..3c08f750a8e1 100644 --- a/python/ray/experimental/serve/examples/echo_post_check.py +++ b/python/ray/experimental/serve/examples/echo_post_check.py @@ -6,6 +6,7 @@ from ray.experimental import serve from ray.experimental.serve.utils import pformat_color_json import json +from ray.experimental.serve.utils import BytesEncoder def echo1(context): message = "" @@ -66,8 +67,9 @@ def identity(context): time.sleep(2) data = {'data':[1,2,3,6], 'model': 'resnet'} +sent_data = json.dumps(content, cls=BytesEncoder, indent=2).encode() while True: - resp = requests.post("http://127.0.0.1:8000/echo",data = data).json() + resp = requests.post("http://127.0.0.1:8000/echo",data = sent_data).json() print(pformat_color_json(resp)) print("...Sleeping for 2 seconds...") diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 6e7cabd90138..2a579f2dd4fa 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -87,16 +87,15 @@ async def read_body(self,receive): """ Read and return the entire body from an incoming ASGI message. """ - # body = b'' - # more_body = True + body = b'' + more_body = True - # while more_body: - - message = await receive() - # body += message.get('body', b'') - # more_body = message.get('more_body', False) + while more_body: + message = await receive() + body += message.get('body', b'') + more_body = message.get('more_body', False) - return message + return body.json() async def __call__(self, scope, receive, send): # NOTE: This implements ASGI protocol specified in diff --git a/python/ray/experimental/serve/utils.py b/python/ray/experimental/serve/utils.py index 62fd663e6dd2..90513a6541c8 100644 --- a/python/ray/experimental/serve/utils.py +++ b/python/ray/experimental/serve/utils.py @@ -15,7 +15,15 @@ def _get_logger(): logger = _get_logger() - +def dict_to_binary(the_dict): + str = json.dumps(the_dict) + binary = ' '.join(format(ord(letter), 'b') for letter in str) + return binary + +def binary_to_dict(the_binary): + jsn = ''.join(chr(int(x, 2)) for x in the_binary.split()) + d = json.loads(jsn) + return d class BytesEncoder(json.JSONEncoder): """Allow bytes to be part of the JSON document. From c02fde5580430adda76b04c9cde1f0488b2e2d9f Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 17:04:05 -0400 Subject: [PATCH 31/70] POST request functionality attempt 4 --- python/ray/experimental/serve/examples/echo_post_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/examples/echo_post_check.py b/python/ray/experimental/serve/examples/echo_post_check.py index 3c08f750a8e1..5c3d2952dcbd 100644 --- a/python/ray/experimental/serve/examples/echo_post_check.py +++ b/python/ray/experimental/serve/examples/echo_post_check.py @@ -67,7 +67,7 @@ def identity(context): time.sleep(2) data = {'data':[1,2,3,6], 'model': 'resnet'} -sent_data = json.dumps(content, cls=BytesEncoder, indent=2).encode() +sent_data = json.dumps(data, cls=BytesEncoder, indent=2).encode() while True: resp = requests.post("http://127.0.0.1:8000/echo",data = sent_data).json() print(pformat_color_json(resp)) From 09f33037d6c97cdddd26b3ff52313dd05fa43bc6 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 17:09:14 -0400 Subject: [PATCH 32/70] POST request functionality attempt 5 --- python/ray/experimental/serve/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 2a579f2dd4fa..adad8dbef0d7 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -95,7 +95,7 @@ async def read_body(self,receive): body += message.get('body', b'') more_body = message.get('more_body', False) - return body.json() + return body async def __call__(self, scope, receive, send): # NOTE: This implements ASGI protocol specified in From ecb28d84830b777bc927449df0110062f3339f18 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 17:13:33 -0400 Subject: [PATCH 33/70] POST request functionality attempt 6 --- python/ray/experimental/serve/server.py | 3 ++- python/ray/experimental/serve/utils.py | 11 +---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index adad8dbef0d7..0a92e9b65e44 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -95,7 +95,7 @@ async def read_body(self,receive): body += message.get('body', b'') more_body = message.get('more_body', False) - return body + return json.load(body) async def __call__(self, scope, receive, send): # NOTE: This implements ASGI protocol specified in @@ -147,6 +147,7 @@ async def __call__(self, scope, receive, send): elif scope['method'] == 'POST': body = await self.read_body(receive) + body['data'].append(111) await JSONResponse({"result": body})(scope, receive, send) else: error_message = ("Path {} not found. " diff --git a/python/ray/experimental/serve/utils.py b/python/ray/experimental/serve/utils.py index 90513a6541c8..dd8cfe4845e5 100644 --- a/python/ray/experimental/serve/utils.py +++ b/python/ray/experimental/serve/utils.py @@ -14,16 +14,7 @@ def _get_logger(): logger = _get_logger() - -def dict_to_binary(the_dict): - str = json.dumps(the_dict) - binary = ' '.join(format(ord(letter), 'b') for letter in str) - return binary - -def binary_to_dict(the_binary): - jsn = ''.join(chr(int(x, 2)) for x in the_binary.split()) - d = json.loads(jsn) - return d + class BytesEncoder(json.JSONEncoder): """Allow bytes to be part of the JSON document. From 75d36902cf87952bde757557b3b13521dc17e11e Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 17:21:49 -0400 Subject: [PATCH 34/70] POST request functionality attempt 7 --- python/ray/experimental/serve/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 0a92e9b65e44..2187ebd16ad8 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -95,7 +95,7 @@ async def read_body(self,receive): body += message.get('body', b'') more_body = message.get('more_body', False) - return json.load(body) + return json.load(body.encode('utf-8')) async def __call__(self, scope, receive, send): # NOTE: This implements ASGI protocol specified in From fc2c670d13ffda002292fde6939cc8aa69610789 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 17:24:48 -0400 Subject: [PATCH 35/70] POST request functionality attempt 8 --- python/ray/experimental/serve/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 2187ebd16ad8..2b5c6bc40d1d 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -95,7 +95,7 @@ async def read_body(self,receive): body += message.get('body', b'') more_body = message.get('more_body', False) - return json.load(body.encode('utf-8')) + return json.load(body.decode('utf-8')) async def __call__(self, scope, receive, send): # NOTE: This implements ASGI protocol specified in From ca09f7e4b888d9520dfc98429695dc06c7dd7d30 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 17:27:09 -0400 Subject: [PATCH 36/70] POST request functionality attempt 9 --- python/ray/experimental/serve/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 2b5c6bc40d1d..7331f99a244b 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -95,7 +95,7 @@ async def read_body(self,receive): body += message.get('body', b'') more_body = message.get('more_body', False) - return json.load(body.decode('utf-8')) + return json.loads(body) async def __call__(self, scope, receive, send): # NOTE: This implements ASGI protocol specified in From 47e7311d52eb45a025e1c3a3757542a8d109d6b9 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 22:02:54 -0400 Subject: [PATCH 37/70] POST Image Query and Resnet50 Example --- .../serve/examples/echo_post_check.py | 2 +- .../experimental/serve/examples/elephant.jpg | Bin 0 -> 111477 bytes .../serve/examples/resnet50_rayServe.py | 81 ++++++++++++++++++ python/ray/experimental/serve/server.py | 32 ++++++- 4 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 python/ray/experimental/serve/examples/elephant.jpg create mode 100644 python/ray/experimental/serve/examples/resnet50_rayServe.py diff --git a/python/ray/experimental/serve/examples/echo_post_check.py b/python/ray/experimental/serve/examples/echo_post_check.py index 5c3d2952dcbd..42b36ac153df 100644 --- a/python/ray/experimental/serve/examples/echo_post_check.py +++ b/python/ray/experimental/serve/examples/echo_post_check.py @@ -7,7 +7,7 @@ from ray.experimental.serve.utils import pformat_color_json import json from ray.experimental.serve.utils import BytesEncoder - +import numpy as np def echo1(context): message = "" message += 'FROM MODEL1 -> ' diff --git a/python/ray/experimental/serve/examples/elephant.jpg b/python/ray/experimental/serve/examples/elephant.jpg new file mode 100644 index 0000000000000000000000000000000000000000..32258eed1628648f55450f611daf87bf1c987ac5 GIT binary patch literal 111477 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 z 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 d`Qb_A{{aSPMt7UloErcD002ovPDHLkV1mY Date: Wed, 9 Oct 2019 22:05:40 -0400 Subject: [PATCH 38/70] RESNET50 EXAMPLE --- .../serve/examples/resnet50_rayServe.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index abdff44dbdfe..3868e2dce967 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -25,16 +25,16 @@ def __call__(self,data): @ray.remote(num_gpus=1) class Resnet50: - def __init__(self, model): - self.model = model - - def __call__(self, context): - if 'transform' in context: - data = context['transform'] - data = Variable(data) - data = data.cuda() - return self.model.predict(data) - return '' + def __init__(self, model): + self.model = model + + def __call__(self, context): + if 'transform' in context: + data = context['transform'] + data = Variable(data) + data = data.cuda() + return self.model.predict(data) + return '' From 07f20133cfb6b1c54ce341c6bc20d50fb0ce2849 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 22:06:50 -0400 Subject: [PATCH 39/70] RESNET50 EXAMPLE --- python/ray/experimental/serve/examples/resnet50_rayServe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 3868e2dce967..e5154dda0fef 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -2,7 +2,7 @@ import requests from werkzeug import urls - +import ray from ray.experimental import serve from ray.experimental.serve.utils import pformat_color_json import json From f451823bbe310e61b5d9c095ca3328eea674e931 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 22:14:01 -0400 Subject: [PATCH 40/70] setting device issue --- python/ray/experimental/serve/api.py | 2 +- python/ray/experimental/serve/examples/resnet50_rayServe.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/python/ray/experimental/serve/api.py b/python/ray/experimental/serve/api.py index 02fb4ecf5c63..c10c1887d2be 100644 --- a/python/ray/experimental/serve/api.py +++ b/python/ray/experimental/serve/api.py @@ -75,7 +75,7 @@ def create_backend(func_or_class, backend_tag, *actor_init_args): elif inspect.isclass(func_or_class): # Python inheritance order is right-to-left. We put RayServeMixin # on the left to make sure its methods are not overriden. - @ray.remote + @ray.remote(num_gpus=1) class CustomActor(RayServeMixin, func_or_class): pass diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index e5154dda0fef..9f877145e4a8 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -23,7 +23,6 @@ def __call__(self,data): data = data.unsqueeze(0) return data -@ray.remote(num_gpus=1) class Resnet50: def __init__(self, model): self.model = model From 992e0122c58898fbc528a341573ebe29f64f8f06 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 22:31:50 -0400 Subject: [PATCH 41/70] Increase ray init memory because of resnet --- python/ray/experimental/serve/examples/resnet50_rayServe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 9f877145e4a8..1ef92df2511c 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -45,7 +45,7 @@ def __call__(self, context): model = resnet50(pretrained=True) model = model.cuda() -serve.init(blocking=True) +serve.init(object_store_memory=int(1e9),blocking=True) #create Backends serve.create_backend(Transform, "transform:v1",transform) serve.create_backend(Resnet50,"r50",model) From c762712d7fa748e824ed190955798062f2d36928 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 22:34:14 -0400 Subject: [PATCH 42/70] bug fix --- python/ray/experimental/serve/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 89cbae68ad40..08781ee2365b 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -111,8 +111,8 @@ async def __call__(self, scope, receive, send): if current_path == "/": await JSONResponse(self.route_table)(scope, receive, send) elif current_path in self.route_table: + pipeline_name = self.route_table[current_path] if scope['method'] == 'GET' : - pipeline_name = self.route_table[current_path] service_dependencies = self.pipeline_table[pipeline_name] # await JSONResponse({"result": str(services_list)})(scope, receive, send) result = scope From 61cbddfa3c7b206fb2d89a4a8d27b5e4222d3509 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 22:39:13 -0400 Subject: [PATCH 43/70] check --- python/ray/experimental/serve/server.py | 43 +++++++++++++------------ 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 08781ee2365b..9e3d51556a3c 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -147,27 +147,28 @@ async def __call__(self, scope, receive, send): elif scope['method'] == 'POST': body = await self.read_body(receive) - service_dependencies = self.pipeline_table[pipeline_name] - result = [] - data_d = defaultdict(dict) - for node in service_dependencies['node_order']: - data_sent = None - if data_d[node] == {}: - if node in body: - data_sent = body[node] - else: - result = ray.exceptions.RayTaskError('Specify service name in input', '') - break - else: - data_sent = data_d[node] - result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) - node_result = await as_future(ray.ObjectID(result_object_id_bytes)) - if service_dependencies['successors'][node] == []: - result = node_result - break - else: - for node_successor in service_dependencies['successors'][node]: - data_d[node_successor][node] = node_result + result = body + # service_dependencies = self.pipeline_table[pipeline_name] + # result = [] + # data_d = defaultdict(dict) + # for node in service_dependencies['node_order']: + # data_sent = None + # if data_d[node] == {}: + # if node in body: + # data_sent = body[node] + # else: + # result = ray.exceptions.RayTaskError('Specify service name in input', '') + # break + # else: + # data_sent = data_d[node] + # result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) + # node_result = await as_future(ray.ObjectID(result_object_id_bytes)) + # if service_dependencies['successors'][node] == []: + # result = node_result + # break + # else: + # for node_successor in service_dependencies['successors'][node]: + # data_d[node_successor][node] = node_result if isinstance(result, ray.exceptions.RayTaskError): From f162cde159f63ee85fe3318cbac7e6560780e8bc Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 22:43:43 -0400 Subject: [PATCH 44/70] check --- .../serve/examples/resnet50_rayServe.py | 8 ++-- python/ray/experimental/serve/server.py | 44 +++++++++---------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 1ef92df2511c..99f4ec76d04c 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -21,19 +21,19 @@ def __call__(self,data): data = Image.open(io.BytesIO(data)) data = self.transform(data) data = data.unsqueeze(0) - return data + return data.size() class Resnet50: def __init__(self, model): self.model = model def __call__(self, context): - if 'transform' in context: + if 'transform1' in context: data = context['transform'] data = Variable(data) data = data.cuda() return self.model.predict(data) - return '' + return context @@ -49,9 +49,11 @@ def __call__(self, context): #create Backends serve.create_backend(Transform, "transform:v1",transform) serve.create_backend(Resnet50,"r50",model) + # create service serve.create_no_http_service("transform") serve.create_no_http_service("imagenet-classification") + #link service and backend serve.link_service("transform", "transform:v1") serve.link_service("imagenet-classification", "r50") diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 9e3d51556a3c..e93392770e85 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -147,28 +147,28 @@ async def __call__(self, scope, receive, send): elif scope['method'] == 'POST': body = await self.read_body(receive) - result = body - # service_dependencies = self.pipeline_table[pipeline_name] - # result = [] - # data_d = defaultdict(dict) - # for node in service_dependencies['node_order']: - # data_sent = None - # if data_d[node] == {}: - # if node in body: - # data_sent = body[node] - # else: - # result = ray.exceptions.RayTaskError('Specify service name in input', '') - # break - # else: - # data_sent = data_d[node] - # result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) - # node_result = await as_future(ray.ObjectID(result_object_id_bytes)) - # if service_dependencies['successors'][node] == []: - # result = node_result - # break - # else: - # for node_successor in service_dependencies['successors'][node]: - # data_d[node_successor][node] = node_result + # result = body + service_dependencies = self.pipeline_table[pipeline_name] + result = [] + data_d = defaultdict(dict) + for node in service_dependencies['node_order']: + data_sent = None + if data_d[node] == {}: + if node in body: + data_sent = body[node] + else: + result = ray.exceptions.RayTaskError('Specify service name in input', '') + break + else: + data_sent = data_d[node] + result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) + node_result = await as_future(ray.ObjectID(result_object_id_bytes)) + if service_dependencies['successors'][node] == []: + result = node_result + break + else: + for node_successor in service_dependencies['successors'][node]: + data_d[node_successor][node] = node_result if isinstance(result, ray.exceptions.RayTaskError): From 5f3526c54afc6027fb0ff4a091b84e51dff7189b Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 22:50:47 -0400 Subject: [PATCH 45/70] r50 check --- python/ray/experimental/serve/examples/resnet50_rayServe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 99f4ec76d04c..6c7b85ca010f 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -21,7 +21,7 @@ def __call__(self,data): data = Image.open(io.BytesIO(data)) data = self.transform(data) data = data.unsqueeze(0) - return data.size() + return data class Resnet50: def __init__(self, model): From 99a26413938d54e44351d5197d8585d883fd9dec Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 22:53:07 -0400 Subject: [PATCH 46/70] r50 check --- python/ray/experimental/serve/examples/resnet50_rayServe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 6c7b85ca010f..ff8b34bb6b02 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -21,7 +21,7 @@ def __call__(self,data): data = Image.open(io.BytesIO(data)) data = self.transform(data) data = data.unsqueeze(0) - return data + return tuple(data.size()) class Resnet50: def __init__(self, model): From d463275e47f62207a1ea959299f61d3cad5b3953 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 22:54:40 -0400 Subject: [PATCH 47/70] r50 check --- python/ray/experimental/serve/examples/resnet50_rayServe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index ff8b34bb6b02..44d3ee9640e7 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -33,7 +33,7 @@ def __call__(self, context): data = Variable(data) data = data.cuda() return self.model.predict(data) - return context + return context['transform'] From 2022596e5d65737ccd5f7fdfbc72bed7b46adf81 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 23:06:56 -0400 Subject: [PATCH 48/70] r50 attempt 1 --- .../experimental/serve/examples/resnet50_rayServe.py | 12 +++++++----- python/ray/experimental/serve/server.py | 7 ++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 44d3ee9640e7..363f4ce600c1 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -19,6 +19,8 @@ def __init__(self,transform): self.transform = transform def __call__(self,data): data = Image.open(io.BytesIO(data)) + if data.mode != "RGB": + data = data.convert("RGB") data = self.transform(data) data = data.unsqueeze(0) return tuple(data.size()) @@ -28,11 +30,11 @@ def __init__(self, model): self.model = model def __call__(self, context): - if 'transform1' in context: - data = context['transform'] - data = Variable(data) - data = data.cuda() - return self.model.predict(data) + # if 'transform1' in context: + # data = context['transform'] + # data = Variable(data) + # data = data.cuda() + # return self.model.predict(data) return context['transform'] diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index e93392770e85..82d5a3bf6d9a 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -150,6 +150,7 @@ async def __call__(self, scope, receive, send): # result = body service_dependencies = self.pipeline_table[pipeline_name] result = [] + error_service = "" data_d = defaultdict(dict) for node in service_dependencies['node_order']: data_sent = None @@ -163,6 +164,10 @@ async def __call__(self, scope, receive, send): data_sent = data_d[node] result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) node_result = await as_future(ray.ObjectID(result_object_id_bytes)) + if isinstance(node_result, ray.exceptions.RayTaskError): + error_service = node + result = node_result + break if service_dependencies['successors'][node] == []: result = node_result break @@ -173,7 +178,7 @@ async def __call__(self, scope, receive, send): if isinstance(result, ray.exceptions.RayTaskError): await JSONResponse({ - "error": "internal error, please use python API to debug" + "error": error_service + " internal error, please use python API to debug" })(scope, receive, send) else: await JSONResponse({"result": result})(scope, receive, send) From 974ec631c7a0d9d70ec1f8af9daf1c7026cd9e80 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 23:38:05 -0400 Subject: [PATCH 49/70] r50 attempt 2 --- python/ray/experimental/serve/examples/resnet50_rayServe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 363f4ce600c1..df0c486f299d 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -74,7 +74,7 @@ def __call__(self, context): time.sleep(2) # data = {'data':[1,2,3,6], 'model': 'resnet'} -req_json = { "transform": base64.b64encode(open('elephant.jpg', "rb").read()).decode() } +req_json = { "transform": base64.b64encode(open('elephant.jpg', "rb").read()) } sent_data = json.dumps(req_json, cls=BytesEncoder, indent=2).encode() while True: resp = requests.post("http://127.0.0.1:8000/imgNetClassification",data = sent_data).json() From b99993dff7331f7c3f1820cd61397cab219da443 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 23:48:38 -0400 Subject: [PATCH 50/70] r50 attempt 2 --- python/ray/experimental/serve/examples/resnet50_rayServe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index df0c486f299d..8cb53408697b 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -18,7 +18,7 @@ class Transform: def __init__(self,transform): self.transform = transform def __call__(self,data): - data = Image.open(io.BytesIO(data)) + data = Image.open(io.BytesIO(base64.b64decode(data))) if data.mode != "RGB": data = data.convert("RGB") data = self.transform(data) From 7a34d45c538520d353b99dc3d78256caa0e65cf2 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 23:51:50 -0400 Subject: [PATCH 51/70] r50 attempt 3 --- .../serve/examples/resnet50_rayServe.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 8cb53408697b..c48b0adfd6ba 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -23,19 +23,19 @@ def __call__(self,data): data = data.convert("RGB") data = self.transform(data) data = data.unsqueeze(0) - return tuple(data.size()) + return data class Resnet50: def __init__(self, model): self.model = model def __call__(self, context): - # if 'transform1' in context: - # data = context['transform'] - # data = Variable(data) - # data = data.cuda() - # return self.model.predict(data) - return context['transform'] + if 'transform' in context: + data = context['transform'] + data = Variable(data) + data = data.cuda() + return self.model.predict(data) + return '' From 94d22259a9dcc218d2b2b87a3a85e3e533561d97 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Wed, 9 Oct 2019 23:55:50 -0400 Subject: [PATCH 52/70] r50 attempt 4 --- python/ray/experimental/serve/examples/resnet50_rayServe.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index c48b0adfd6ba..b658d7f7f84e 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -33,9 +33,9 @@ def __call__(self, context): if 'transform' in context: data = context['transform'] data = Variable(data) - data = data.cuda() + # data = data.cuda() return self.model.predict(data) - return '' + # return context['transform'] @@ -45,7 +45,7 @@ def __call__(self, context): transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])]) model = resnet50(pretrained=True) -model = model.cuda() +# model = model.cuda() serve.init(object_store_memory=int(1e9),blocking=True) #create Backends From 808871d230e85c32d16c14828a177bbe21bd564f Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Thu, 10 Oct 2019 00:05:13 -0400 Subject: [PATCH 53/70] r50 attempt 5 --- .../ray/experimental/serve/examples/resnet50_rayServe.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index b658d7f7f84e..8ff2b170de28 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -33,9 +33,10 @@ def __call__(self, context): if 'transform' in context: data = context['transform'] data = Variable(data) - # data = data.cuda() - return self.model.predict(data) + data = data.cuda() + return self.model(data).data.cpu().numpy().argmax() # return context['transform'] + return '' @@ -45,7 +46,7 @@ def __call__(self, context): transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])]) model = resnet50(pretrained=True) -# model = model.cuda() +model = model.cuda() serve.init(object_store_memory=int(1e9),blocking=True) #create Backends From 15d2400b1853ac63916e53d0bee161c4222cbf5e Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Thu, 10 Oct 2019 08:18:39 -0400 Subject: [PATCH 54/70] Resnet50 post query with pipeline RAY serve --- .../serve/examples/echo_post_check.py | 76 ------------------- .../serve/examples/resnet50_rayServe.py | 2 + 2 files changed, 2 insertions(+), 76 deletions(-) delete mode 100644 python/ray/experimental/serve/examples/echo_post_check.py diff --git a/python/ray/experimental/serve/examples/echo_post_check.py b/python/ray/experimental/serve/examples/echo_post_check.py deleted file mode 100644 index 42b36ac153df..000000000000 --- a/python/ray/experimental/serve/examples/echo_post_check.py +++ /dev/null @@ -1,76 +0,0 @@ -import time - -import requests -from werkzeug import urls - -from ray.experimental import serve -from ray.experimental.serve.utils import pformat_color_json -import json -from ray.experimental.serve.utils import BytesEncoder -import numpy as np -def echo1(context): - message = "" - message += 'FROM MODEL1 -> ' - return message -def identity(context): - return context['serve1'] -# def echo2(context): -# data_from_service1 = context['serve1'] -# data_from_service1 += 'FROM MODEL2 -> ' -# return data_from_service1 - -# def echo3(context): -# data_from_service2 = context['serve2'] -# data_from_service2 += 'FROM MODEL3 -> ' -# return data_from_service2 - -serve.init(blocking=True) - -# serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) - -# Create Backends -serve.create_backend(echo1, "echo:v1") -serve.create_backend(identity, "identity:b") -# serve.create_backend(echo2, "echo:v2") -# serve.create_backend(echo3,"echo:v3") - -# Create services -serve.create_no_http_service("serve1") -serve.create_no_http_service("identity") -# serve.create_no_http_service("serve2") -# serve.create_no_http_service("serve3") - -# Link services and backends -serve.link_service("serve1", "echo:v1") -serve.link_service("identity", "identity:b") -# serve.link_service("serve2", "echo:v2") -# serve.link_service("serve3","echo:v3") - -''' -1. Add service dependencies in a PIPELINE -2. You can add dependency to a PIPELINE only if the PIPELINE has not been provisioned yet. -''' -''' -Creating a pipeline serve1 -> serve2 -> serve3 -''' -# serve2 depends on serve1 -serve.add_service_dependencies("pipeline1","serve1","identity") -# serve3 depends on serve2 -# serve.add_service_dependencies("pipeline1","serve2","serve3") - -# Provision the PIPELINE (You can provision the pipeline only once) -serve.provision_pipeline("pipeline1") - -# You can only create an endpoint for pipeline after provisioning the pipeline -serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) - -time.sleep(2) - -data = {'data':[1,2,3,6], 'model': 'resnet'} -sent_data = json.dumps(data, cls=BytesEncoder, indent=2).encode() -while True: - resp = requests.post("http://127.0.0.1:8000/echo",data = sent_data).json() - print(pformat_color_json(resp)) - - print("...Sleeping for 2 seconds...") - time.sleep(2) \ No newline at end of file diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 8ff2b170de28..2eed7d96818f 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -72,6 +72,8 @@ def __call__(self, context): + + time.sleep(2) # data = {'data':[1,2,3,6], 'model': 'resnet'} From d375ed7705a93e8e43b2fbb52e006cdba065852e Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Thu, 10 Oct 2019 23:33:25 -0400 Subject: [PATCH 55/70] Ordered Inputs to models + Grouped Dag execution --- python/ray/experimental/serve/api.py | 4 +- .../serve/examples/echo_complex_pipeline.py | 56 +++++++++--------- .../serve/examples/echo_pipeline_1.py | 4 +- .../serve/examples/echo_pipeline_2.py | 6 +- .../serve/examples/resnet50_rayServe.py | 14 ++--- .../experimental/serve/kv_store_service.py | 16 +++-- python/ray/experimental/serve/server.py | 59 ++++++++++++++----- python/ray/experimental/serve/task_runner.py | 2 +- python/ray/experimental/serve/utils.py | 15 ++++- 9 files changed, 112 insertions(+), 64 deletions(-) diff --git a/python/ray/experimental/serve/api.py b/python/ray/experimental/serve/api.py index c10c1887d2be..8c7ce9fef160 100644 --- a/python/ray/experimental/serve/api.py +++ b/python/ray/experimental/serve/api.py @@ -59,7 +59,7 @@ def create_endpoint_pipeline(pipeline_name, route_expression, blocking=True): def create_no_http_service(service_name): global_state.registered_services.add(service_name) -def create_backend(func_or_class, backend_tag, *actor_init_args): +def create_backend(func_or_class, backend_tag, num_gpu,*actor_init_args): """Create a backend using func_or_class and assign backend_tag. Args: @@ -75,7 +75,7 @@ def create_backend(func_or_class, backend_tag, *actor_init_args): elif inspect.isclass(func_or_class): # Python inheritance order is right-to-left. We put RayServeMixin # on the left to make sure its methods are not overriden. - @ray.remote(num_gpus=1) + @ray.remote(num_gpus=num_gpu) class CustomActor(RayServeMixin, func_or_class): pass diff --git a/python/ray/experimental/serve/examples/echo_complex_pipeline.py b/python/ray/experimental/serve/examples/echo_complex_pipeline.py index 3a76d690e6f8..2d67caa95bf9 100644 --- a/python/ray/experimental/serve/examples/echo_complex_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_complex_pipeline.py @@ -7,95 +7,95 @@ from ray.experimental.serve.utils import pformat_color_json import json -def echo1(context): +def echo1(*context): message = "" message += 'FROM MODEL1 -> ' return message -def echo2(context): +def echo2(*context): start = "[ " - for key in context.keys(): - start = start + context[key] + " , " + for val in context: + start = start + val + " , " start += " ] --> " data = start # message = "" data += 'FROM MODEL2 -> ' return data -def echo3(context): +def echo3(*context): start = "[ " - for key in context.keys(): - start = start + context[key] + " , " + for val in context: + start = start + val + " , " start += " ] --> " data = start # message = "" data += 'FROM MODEL3 -> ' return data -def echo4(context): +def echo4(*context): start = "[ " - for key in context.keys(): - start = start + context[key] + " , " + for val in context: + start = start + val + " , " start += " ] --> " data = start # message = "" data += 'FROM MODEL4 -> ' return data -def echo5(context): +def echo5(*context): start = "[ " - for key in context.keys(): - start = start + context[key] + " , " + for val in context: + start = start + val + " , " start += " ] --> " data = start # message = "" data += 'FROM MODEL5 -> ' return data -def echo6(context): +def echo6(*context): start = "[ " - for key in context.keys(): - start = start + context[key] + " , " + for val in context: + start = start + val + " , " start += " ] --> " data = start # message = "" data += 'FROM MODEL6 -> ' return data -def echo7(context): +def echo7(*context): start = "[ " - for key in context.keys(): - start = start + context[key] + " , " + for val in context: + start = start + val + " , " start += " ] --> " data = start # message = "" data += 'FROM MODEL7 -> ' return data -def echo8(context): +def echo8(*context): start = "[ " - for key in context.keys(): - start = start + context[key] + " , " + for val in context: + start = start + val + " , " start += " ] --> " data = start # message = "" data += 'FROM MODEL8 -> ' return data -def echo9(context): +def echo9(*context): start = "[ " - for key in context.keys(): - start = start + context[key] + " , " + for val in context: + start = start + val + " , " start += " ] --> " data = start # message = "" data += 'FROM MODEL9 -> ' return data -def echo10(context): +def echo10(*context): start = "[ " - for key in context.keys(): - start = start + context[key] + " , " + for val in context: + start = start + val + " , " start += " ] --> " data = start # message = "" diff --git a/python/ray/experimental/serve/examples/echo_pipeline_1.py b/python/ray/experimental/serve/examples/echo_pipeline_1.py index 890574bb6701..1489a4cbe66b 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline_1.py +++ b/python/ray/experimental/serve/examples/echo_pipeline_1.py @@ -12,12 +12,12 @@ def echo1(context): message += 'FROM MODEL1 -> ' return message def echo2(context): - data_from_service1 = context['serve1'] + data_from_service1 = context data_from_service1 += 'FROM MODEL2 -> ' return data_from_service1 def echo3(context): - data_from_service2 = context['serve2'] + data_from_service2 = context data_from_service2 += 'FROM MODEL3 -> ' return data_from_service2 diff --git a/python/ray/experimental/serve/examples/echo_pipeline_2.py b/python/ray/experimental/serve/examples/echo_pipeline_2.py index 4e43096d2c86..3de3e51a3325 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline_2.py +++ b/python/ray/experimental/serve/examples/echo_pipeline_2.py @@ -17,9 +17,9 @@ def echo2(context): message += 'FROM MODEL2 -> ' return message -def echo3(context): - data_from_service1 = context['serve1'] - data_from_service2 = context['serve2'] +def echo3(a,b): + data_from_service1 = a + data_from_service2 = b data = '[ ' + data_from_service1 + ',' + data_from_service2 + '] ->' data += 'FROM MODEL3 -> ' return data diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 2eed7d96818f..4eb1284653de 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -29,14 +29,14 @@ class Resnet50: def __init__(self, model): self.model = model - def __call__(self, context): - if 'transform' in context: - data = context['transform'] - data = Variable(data) - data = data.cuda() - return self.model(data).data.cpu().numpy().argmax() + def __call__(self, data): + # if 'transform' in context: + # data = context['transform'] + data = Variable(data) + data = data.cuda() + return self.model(data).data.cpu().numpy().argmax() # return context['transform'] - return '' + # return '' diff --git a/python/ray/experimental/serve/kv_store_service.py b/python/ray/experimental/serve/kv_store_service.py index ed004032547a..e02dc1ddc616 100644 --- a/python/ray/experimental/serve/kv_store_service.py +++ b/python/ray/experimental/serve/kv_store_service.py @@ -9,6 +9,7 @@ import networkx as nx from networkx.readwrite import json_graph import traceback +from ray.experimental.serve.utils import topological_sort_grouped class NamespacedKVStore(ABC): """Abstract base class for a namespaced key-value store. @@ -151,6 +152,7 @@ def add_edge(self,pipeline: str, service_no_http_1: str , service_no_http_2: str G = json_graph.node_link_graph(g_json) else: G = nx.DiGraph() + G = nx.OrderedDiGraph(G) # try: # G.add_edge(service_no_http_1,service_no_http_2) @@ -172,11 +174,17 @@ def provision(self,pipeline: str): if self.pipeline_storage.exists(pipeline): g_json = self.pipeline_storage.get(pipeline) G = json_graph.node_link_graph(g_json) - node_order = list(nx.topological_sort(G)) - successor_d = {} + G = nx.OrderedDiGraph(G) + if nx.is_directed_acyclic_graph(G): + node_order = list(topological_sort_grouped(G)) + else: + raise Exception('Service dependencies contain cycle') + + # node_order = list(nx.topological_sort(G)) + predecessors_d = {} for node in G: - successor_d[node] = list(G.successors(node)) - final_d = {'node_order': node_order , 'successors' : successor_d} + predecessors_d[node] = list(G.predecessors(node)) + final_d = {'node_order': node_order , 'predecessors' : predecessors_d} self.pipeline_storage.put(pipeline,final_d) else: raise Exception('Add service dependencies to pipeline') diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 82d5a3bf6d9a..42c12a3d9861 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -120,22 +120,49 @@ async def __call__(self, scope, receive, send): # result_object_id_bytes = await as_future( # self.router.enqueue_request.remote(service, result)) # result = await as_future(ray.ObjectID(result_object_id_bytes)) - data_d = defaultdict(dict) - - for node in service_dependencies['node_order']: - data_sent = None - if data_d[node] == {}: - data_sent = scope - else: - data_sent = data_d[node] - result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) - node_result = await as_future(ray.ObjectID(result_object_id_bytes)) - if service_dependencies['successors'][node] == []: - result = node_result - break - else: - for node_successor in service_dependencies['successors'][node]: - data_d[node_successor][node] = node_result + data_d = {} + + size = len(service_dependencies['node_order']) + last_node = service_dependencies['node_order'][size-1] + assert len(last_node) == 1 + last_node = last_node[0] + for node_list in service_dependencies['node_order']: + data_sent = {} + for node in node_list: + if len(service_dependencies['predecessors'][node]) == 0: + data_sent[node] = [scope] + else: + predecessors_list = service_dependencies['predecessors'][node] + list_data = [data_d[p] for p in predecessors_list] + data_sent[node] = list_data + + future_list = [self.router.enqueue_request.remote(node, data_sent[node]) for node in node_list] + completed_futures, _ = ray.wait(future_list) + future_enqueues_binary = ray.get(completed_futures) + + future_enqueues = [ray.ObjectID(x) for x in future_enqueues] + completed_future_enqueues, _ = ray.wait(future_enqueues) + node_data_list = ray.get(completed_future_enqueues) + for k,v in zip(node_list,node_data_list): + data_d[k] = v + + result = data_d[last_node] + + + # data_sent = None + + # if data_d[node] == {}: + # data_sent = scope + # else: + # data_sent = data_d[node] + # result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) + # node_result = await as_future(ray.ObjectID(result_object_id_bytes)) + # if service_dependencies['successors'][node] == []: + # result = node_result + # break + # else: + # for node_successor in service_dependencies['successors'][node]: + # data_d[node_successor][node] = node_result if isinstance(result, ray.exceptions.RayTaskError): diff --git a/python/ray/experimental/serve/task_runner.py b/python/ray/experimental/serve/task_runner.py index 1eabcdf8bf53..d5f949638b3e 100644 --- a/python/ray/experimental/serve/task_runner.py +++ b/python/ray/experimental/serve/task_runner.py @@ -64,7 +64,7 @@ def _ray_serve_main_loop(self, my_handle): # TODO(simon): # D1, D2, D3 # __call__ should be able to take multiple *args and **kwargs. - result = wrap_to_ray_error(self.__call__, work_item.request_body) + result = wrap_to_ray_error(self.__call__, *work_item.request_body) result_object_id = work_item.result_object_id ray.worker.global_worker.put_object(result_object_id, result) diff --git a/python/ray/experimental/serve/utils.py b/python/ray/experimental/serve/utils.py index dd8cfe4845e5..966a2a853229 100644 --- a/python/ray/experimental/serve/utils.py +++ b/python/ray/experimental/serve/utils.py @@ -4,7 +4,20 @@ from pygments import formatters, highlight, lexers import ray - +import networkx as nx + +def topological_sort_grouped(G): + indegree_map = {v: d for v, d in G.in_degree() if d > 0} + zero_indegree = [v for v, d in G.in_degree() if d == 0] + while zero_indegree: + yield zero_indegree + new_zero_indegree = [] + for v in zero_indegree: + for _, child in G.edges(v): + indegree_map[child] -= 1 + if not indegree_map[child]: + new_zero_indegree.append(child) + zero_indegree = new_zero_indegree def _get_logger(): logger = logging.getLogger("ray.serve") From 6f5be435decc16e9b51cc96c7cb4ec9390c3dc71 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Thu, 10 Oct 2019 23:39:50 -0400 Subject: [PATCH 56/70] Changing examples for showing pipeline anonymity to models --- .../serve/examples/echo_complex_pipeline.py | 20 +++++++++---------- .../serve/examples/echo_pipeline_1.py | 6 +++--- .../serve/examples/resnet50_rayServe.py | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/python/ray/experimental/serve/examples/echo_complex_pipeline.py b/python/ray/experimental/serve/examples/echo_complex_pipeline.py index 2d67caa95bf9..125fa93a048a 100644 --- a/python/ray/experimental/serve/examples/echo_complex_pipeline.py +++ b/python/ray/experimental/serve/examples/echo_complex_pipeline.py @@ -107,16 +107,16 @@ def echo10(*context): # serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) # Create Backends -serve.create_backend(echo1, "echo:v1") -serve.create_backend(echo2, "echo:v2") -serve.create_backend(echo3,"echo:v3") -serve.create_backend(echo4,"echo:v4") -serve.create_backend(echo5,"echo:v5") -serve.create_backend(echo6,"echo:v6") -serve.create_backend(echo7,"echo:v7") -serve.create_backend(echo8,"echo:v8") -serve.create_backend(echo9,"echo:v9") -serve.create_backend(echo10,"echo:v10") +serve.create_backend(echo1, "echo:v1",num_gpu=0) +serve.create_backend(echo2, "echo:v2",num_gpu=0) +serve.create_backend(echo3,"echo:v3",num_gpu=0) +serve.create_backend(echo4,"echo:v4",num_gpu=0) +serve.create_backend(echo5,"echo:v5",num_gpu=0) +serve.create_backend(echo6,"echo:v6",num_gpu=0) +serve.create_backend(echo7,"echo:v7",num_gpu=0) +serve.create_backend(echo8,"echo:v8",num_gpu=0) +serve.create_backend(echo9,"echo:v9",num_gpu=0) +serve.create_backend(echo10,"echo:v10",num_gpu=0) # Create services serve.create_no_http_service("serve1") diff --git a/python/ray/experimental/serve/examples/echo_pipeline_1.py b/python/ray/experimental/serve/examples/echo_pipeline_1.py index 1489a4cbe66b..adf02c56d73f 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline_1.py +++ b/python/ray/experimental/serve/examples/echo_pipeline_1.py @@ -26,9 +26,9 @@ def echo3(context): # serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) # Create Backends -serve.create_backend(echo1, "echo:v1") -serve.create_backend(echo2, "echo:v2") -serve.create_backend(echo3,"echo:v3") +serve.create_backend(echo1, "echo:v1",num_gpu=0) +serve.create_backend(echo2, "echo:v2",num_gpu=0) +serve.create_backend(echo3,"echo:v3",num_gpu=0) # Create services serve.create_no_http_service("serve1") diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 4eb1284653de..46bf0eb12875 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -50,8 +50,8 @@ def __call__(self, data): serve.init(object_store_memory=int(1e9),blocking=True) #create Backends -serve.create_backend(Transform, "transform:v1",transform) -serve.create_backend(Resnet50,"r50",model) +serve.create_backend(Transform, "transform:v1",,num_gpu=0,transform) +serve.create_backend(Resnet50,"r50",num_gpu=1,model) # create service serve.create_no_http_service("transform") From e47cc1ba1a2a491680ff651fc872463d38a93bdd Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Thu, 10 Oct 2019 23:43:52 -0400 Subject: [PATCH 57/70] bug fix --- python/ray/experimental/serve/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 42c12a3d9861..7077dac813b4 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -140,7 +140,7 @@ async def __call__(self, scope, receive, send): completed_futures, _ = ray.wait(future_list) future_enqueues_binary = ray.get(completed_futures) - future_enqueues = [ray.ObjectID(x) for x in future_enqueues] + future_enqueues = [ray.ObjectID(x) for x in future_enqueues_binary] completed_future_enqueues, _ = ray.wait(future_enqueues) node_data_list = ray.get(completed_future_enqueues) for k,v in zip(node_list,node_data_list): From c126241c06fe7fa59e28395c977a8d2d75064206 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Thu, 10 Oct 2019 23:48:06 -0400 Subject: [PATCH 58/70] num gpu param addition --- python/ray/experimental/serve/examples/echo_pipeline_2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ray/experimental/serve/examples/echo_pipeline_2.py b/python/ray/experimental/serve/examples/echo_pipeline_2.py index 3de3e51a3325..30549aba36d8 100644 --- a/python/ray/experimental/serve/examples/echo_pipeline_2.py +++ b/python/ray/experimental/serve/examples/echo_pipeline_2.py @@ -29,9 +29,9 @@ def echo3(a,b): # serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) # Create Backends -serve.create_backend(echo1, "echo:v1") -serve.create_backend(echo2, "echo:v2") -serve.create_backend(echo3,"echo:v3") +serve.create_backend(echo1, "echo:v1",num_gpu=0) +serve.create_backend(echo2, "echo:v2",num_gpu=0) +serve.create_backend(echo3,"echo:v3",num_gpu=0) # Create services serve.create_no_http_service("serve1") From b2f05bb8c88b168d3145bb270d236a6806f24ea4 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Fri, 11 Oct 2019 00:03:17 -0400 Subject: [PATCH 59/70] attempt 1 fix --- python/ray/experimental/serve/server.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 7077dac813b4..7bf73e658923 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -137,15 +137,17 @@ async def __call__(self, scope, receive, send): data_sent[node] = list_data future_list = [self.router.enqueue_request.remote(node, data_sent[node]) for node in node_list] - completed_futures, _ = ray.wait(future_list) + completed_futures, non_c = ray.wait(future_list) + assert(len(non_c) == 0) future_enqueues_binary = ray.get(completed_futures) future_enqueues = [ray.ObjectID(x) for x in future_enqueues_binary] - completed_future_enqueues, _ = ray.wait(future_enqueues) + completed_future_enqueues, non_c = ray.wait(future_enqueues) + assert(len(non_c) == 0) node_data_list = ray.get(completed_future_enqueues) for k,v in zip(node_list,node_data_list): data_d[k] = v - + result = data_d[last_node] From 2615b13afb364a0ea11b70339bb15c3e1b094572 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Fri, 11 Oct 2019 00:05:18 -0400 Subject: [PATCH 60/70] attempt 1 fix --- python/ray/experimental/serve/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 7bf73e658923..8a17af2321e2 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -138,7 +138,7 @@ async def __call__(self, scope, receive, send): future_list = [self.router.enqueue_request.remote(node, data_sent[node]) for node in node_list] completed_futures, non_c = ray.wait(future_list) - assert(len(non_c) == 0) + assert(len(non_c) == 0 and len(completed_futures) == 2) future_enqueues_binary = ray.get(completed_futures) future_enqueues = [ray.ObjectID(x) for x in future_enqueues_binary] From 34c76afbd3e5e25422a5063ed27b5504c5a0e5f9 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Fri, 11 Oct 2019 00:06:49 -0400 Subject: [PATCH 61/70] attempt 1 fix --- python/ray/experimental/serve/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 8a17af2321e2..7bf73e658923 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -138,7 +138,7 @@ async def __call__(self, scope, receive, send): future_list = [self.router.enqueue_request.remote(node, data_sent[node]) for node in node_list] completed_futures, non_c = ray.wait(future_list) - assert(len(non_c) == 0 and len(completed_futures) == 2) + assert(len(non_c) == 0) future_enqueues_binary = ray.get(completed_futures) future_enqueues = [ray.ObjectID(x) for x in future_enqueues_binary] From 9f96d019223479a0bdc45d277ebb73379419c84e Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Fri, 11 Oct 2019 00:11:11 -0400 Subject: [PATCH 62/70] num returns fix --- python/ray/experimental/serve/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 7bf73e658923..59ed1084a12b 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -137,12 +137,12 @@ async def __call__(self, scope, receive, send): data_sent[node] = list_data future_list = [self.router.enqueue_request.remote(node, data_sent[node]) for node in node_list] - completed_futures, non_c = ray.wait(future_list) + completed_futures, non_c = ray.wait(future_list,num_returns=len(future_list)) assert(len(non_c) == 0) future_enqueues_binary = ray.get(completed_futures) future_enqueues = [ray.ObjectID(x) for x in future_enqueues_binary] - completed_future_enqueues, non_c = ray.wait(future_enqueues) + completed_future_enqueues, non_c = ray.wait(future_enqueues,num_returns=len(future_enqueues)) assert(len(non_c) == 0) node_data_list = ray.get(completed_future_enqueues) for k,v in zip(node_list,node_data_list): From 41320e93e1035d482b6b7a7ff9c13fc6f3c665c5 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Fri, 11 Oct 2019 08:48:59 -0400 Subject: [PATCH 63/70] Disabling pipeline awareness to models + Ordered Inputs to models --- python/ray/experimental/serve/server.py | 82 +++++++++++-------------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/python/ray/experimental/serve/server.py b/python/ray/experimental/serve/server.py index 59ed1084a12b..621d97ed579c 100644 --- a/python/ray/experimental/serve/server.py +++ b/python/ray/experimental/serve/server.py @@ -114,12 +114,8 @@ async def __call__(self, scope, receive, send): pipeline_name = self.route_table[current_path] if scope['method'] == 'GET' : service_dependencies = self.pipeline_table[pipeline_name] - # await JSONResponse({"result": str(services_list)})(scope, receive, send) result = scope - # for service in services_list: - # result_object_id_bytes = await as_future( - # self.router.enqueue_request.remote(service, result)) - # result = await as_future(ray.ObjectID(result_object_id_bytes)) + data_d = {} size = len(service_dependencies['node_order']) @@ -150,23 +146,6 @@ async def __call__(self, scope, receive, send): result = data_d[last_node] - - # data_sent = None - - # if data_d[node] == {}: - # data_sent = scope - # else: - # data_sent = data_d[node] - # result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) - # node_result = await as_future(ray.ObjectID(result_object_id_bytes)) - # if service_dependencies['successors'][node] == []: - # result = node_result - # break - # else: - # for node_successor in service_dependencies['successors'][node]: - # data_d[node_successor][node] = node_result - - if isinstance(result, ray.exceptions.RayTaskError): await JSONResponse({ "error": "internal error, please use python API to debug" @@ -176,33 +155,44 @@ async def __call__(self, scope, receive, send): elif scope['method'] == 'POST': body = await self.read_body(receive) + assert type(body) is dict # result = body service_dependencies = self.pipeline_table[pipeline_name] - result = [] - error_service = "" - data_d = defaultdict(dict) - for node in service_dependencies['node_order']: - data_sent = None - if data_d[node] == {}: - if node in body: - data_sent = body[node] + result = body + + data_d = {} + + size = len(service_dependencies['node_order']) + last_node = service_dependencies['node_order'][size-1] + assert len(last_node) == 1 + last_node = last_node[0] + for node_list in service_dependencies['node_order']: + data_sent = {} + for node in node_list: + if len(service_dependencies['predecessors'][node]) == 0: + if node in body: + data_sent[node] = [body[node]] + else: + result = ray.exceptions.RayTaskError('Specify service name in input', '') + break else: - result = ray.exceptions.RayTaskError('Specify service name in input', '') - break - else: - data_sent = data_d[node] - result_object_id_bytes = await as_future(self.router.enqueue_request.remote(node, data_sent)) - node_result = await as_future(ray.ObjectID(result_object_id_bytes)) - if isinstance(node_result, ray.exceptions.RayTaskError): - error_service = node - result = node_result - break - if service_dependencies['successors'][node] == []: - result = node_result - break - else: - for node_successor in service_dependencies['successors'][node]: - data_d[node_successor][node] = node_result + predecessors_list = service_dependencies['predecessors'][node] + list_data = [data_d[p] for p in predecessors_list] + data_sent[node] = list_data + + future_list = [self.router.enqueue_request.remote(node, data_sent[node]) for node in node_list] + completed_futures, non_c = ray.wait(future_list,num_returns=len(future_list)) + assert(len(non_c) == 0) + future_enqueues_binary = ray.get(completed_futures) + + future_enqueues = [ray.ObjectID(x) for x in future_enqueues_binary] + completed_future_enqueues, non_c = ray.wait(future_enqueues,num_returns=len(future_enqueues)) + assert(len(non_c) == 0) + node_data_list = ray.get(completed_future_enqueues) + for k,v in zip(node_list,node_data_list): + data_d[k] = v + + result = data_d[last_node] if isinstance(result, ray.exceptions.RayTaskError): From 2ff6a1c981a6f16a58c64f5f2bfd796f7e119b05 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Fri, 11 Oct 2019 08:55:42 -0400 Subject: [PATCH 64/70] Resnet50 POST Query --- python/ray/experimental/serve/examples/resnet50_rayServe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 46bf0eb12875..42ca0635d41f 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -50,7 +50,7 @@ def __call__(self, data): serve.init(object_store_memory=int(1e9),blocking=True) #create Backends -serve.create_backend(Transform, "transform:v1",,num_gpu=0,transform) +serve.create_backend(Transform, "transform:v1",num_gpu=0,transform) serve.create_backend(Resnet50,"r50",num_gpu=1,model) # create service From 38e44623f052e1ac119ad4539a2a8915d03ff37e Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Fri, 11 Oct 2019 08:56:57 -0400 Subject: [PATCH 65/70] Resnet50 POST Query --- python/ray/experimental/serve/examples/resnet50_rayServe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ray/experimental/serve/examples/resnet50_rayServe.py b/python/ray/experimental/serve/examples/resnet50_rayServe.py index 42ca0635d41f..aff49291a2a4 100644 --- a/python/ray/experimental/serve/examples/resnet50_rayServe.py +++ b/python/ray/experimental/serve/examples/resnet50_rayServe.py @@ -50,8 +50,8 @@ def __call__(self, data): serve.init(object_store_memory=int(1e9),blocking=True) #create Backends -serve.create_backend(Transform, "transform:v1",num_gpu=0,transform) -serve.create_backend(Resnet50,"r50",num_gpu=1,model) +serve.create_backend(Transform, "transform:v1",0,transform) +serve.create_backend(Resnet50,"r50",1,model) # create service serve.create_no_http_service("transform") From 0a8bac71549d1a2c5db8fff61994618d0f44db9f Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Fri, 11 Oct 2019 13:28:25 -0400 Subject: [PATCH 66/70] Addition of edges order preserved example --- .../echo_service_ordering_dependency.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 python/ray/experimental/serve/examples/echo_service_ordering_dependency.py diff --git a/python/ray/experimental/serve/examples/echo_service_ordering_dependency.py b/python/ray/experimental/serve/examples/echo_service_ordering_dependency.py new file mode 100644 index 000000000000..500926e90959 --- /dev/null +++ b/python/ray/experimental/serve/examples/echo_service_ordering_dependency.py @@ -0,0 +1,86 @@ +import time + +import requests +from werkzeug import urls + +from ray.experimental import serve +from ray.experimental.serve.utils import pformat_color_json +import json + +def echo1(context): + message = "" + message += 'FROM MODEL1 -> ' + return message +def echo2(context): + # data_from_service1 = context['serve1'] + message = "" + message += 'FROM MODEL2 -> ' + return message + +def echo3(a,b): + data_from_service1 = a + data_from_service2 = b + data = '[ ' + data_from_service1 + ',' + data_from_service2 + '] ->' + data += 'FROM MODEL3 -> ' + return data + +serve.init(blocking=True) + +# serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) + +# Create Backends +serve.create_backend(echo1, "echo:v1",num_gpu=0) +serve.create_backend(echo2, "echo:v2",num_gpu=0) +serve.create_backend(echo3,"echo:v3",num_gpu=0) + +# Create services +serve.create_no_http_service("serve1") +serve.create_no_http_service("serve2") +serve.create_no_http_service("serve3") + +# Link services and backends +serve.link_service("serve1", "echo:v1") +serve.link_service("serve2", "echo:v2") +serve.link_service("serve3","echo:v3") + +''' +1. Add service dependencies in a PIPELINE +2. You can add dependency to a PIPELINE only if the PIPELINE has not been provisioned yet. +''' +''' +Creating a pipeline [serve1 , serve2] -> serve3 +''' +# serve3 depends on serve1 +serve.add_service_dependencies("pipeline1","serve1","serve3") +# serve3 depends on serve2 +serve.add_service_dependencies("pipeline1","serve2","serve3") + +serve.add_service_dependencies("pipeline2","serve2","serve3") +serve.add_service_dependencies("pipeline2","serve1","serve3") + +# Provision the PIPELINE (You can provision the pipeline only once) +serve.provision_pipeline("pipeline1") +serve.provision_pipeline("pipeline2") + +# You can only create an endpoint for pipeline after provisioning the pipeline +serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) +serve.create_endpoint_pipeline("pipeline2", "/reverse_echo", blocking=True) + +time.sleep(2) + +while True: + resp = requests.get("http://127.0.0.1:8000/echo").json() + print("from pipeline1") + print(pformat_color_json(resp)) + + print("...Sleeping for 2 seconds...") + time.sleep(2) + + resp = requests.get("http://127.0.0.1:8000/reverse_echo").json() + print("from pipeline2") + print(pformat_color_json(resp)) + + print("...Sleeping for 2 seconds...") + time.sleep(2) + + From 38494537f94ddedfe81f5330f5ba95eb74e0f197 Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Sat, 12 Oct 2019 14:27:32 -0400 Subject: [PATCH 67/70] Handle + Add node + expose dependency --- python/ray/experimental/serve/__init__.py | 8 +- python/ray/experimental/serve/api.py | 112 ++++++++++-------- .../serve/examples/echo_handle.py | 28 +++++ python/ray/experimental/serve/handle.py | 87 +++++++++----- .../experimental/serve/kv_store_service.py | 25 ++++ 5 files changed, 177 insertions(+), 83 deletions(-) create mode 100644 python/ray/experimental/serve/examples/echo_handle.py diff --git a/python/ray/experimental/serve/__init__.py b/python/ray/experimental/serve/__init__.py index 129a76868cc0..084ef1d4d05b 100644 --- a/python/ray/experimental/serve/__init__.py +++ b/python/ray/experimental/serve/__init__.py @@ -3,10 +3,10 @@ raise ImportError("serve is Python 3 only.") from ray.experimental.serve.api import (init,provision_pipeline, create_backend, create_endpoint_pipeline,create_no_http_service, - add_service_dependencies,link_service, split, rollback, get_handle, - global_state) # noqa: E402 + add_service_dependencies,link_service, get_handle, + global_state,get_service_dependencies,add_service) # noqa: E402 __all__ = [ - "init","provision_pipeline", "create_backend", "create_endpoint_pipeline","create_no_http_service","add_service_dependencies", "link_service", "rollback", - "get_handle","split" ,"global_state" + "init","provision_pipeline", "create_backend", "create_endpoint_pipeline","create_no_http_service","add_service_dependencies", "link_service", + "get_handle" ,"global_state","get_service_dependencies","add_service" ] diff --git a/python/ray/experimental/serve/api.py b/python/ray/experimental/serve/api.py index 8c7ce9fef160..df7ae6b4b0a0 100644 --- a/python/ray/experimental/serve/api.py +++ b/python/ray/experimental/serve/api.py @@ -119,6 +119,16 @@ def provision_pipeline(pipeline_name,blocking=True) : global_state.provisioned_services.add(pipeline_name) +def get_service_dependencies(pipeline_name): + assert pipeline_name in global_state.provisioned_services + future = global_state.kv_store_actor_handle_pipeline.get_dependency.remote(pipeline_name) + return ray.get(future) +def add_service(pipeline_name,service_name,blocking = True): + assert pipeline_name not in global_state.provisioned_services + assert service_name in global_state.registered_services + future = global_state.kv_store_actor_handle_pipeline.add_node.remote(pipeline_name,service_name) + if blocking : + ray.get(future) def link_service(service_name, backend_tag): @@ -140,69 +150,69 @@ def link_service(service_name, backend_tag): global_state.policy_action_history[service_name].append({backend_tag: 1}) -def split(endpoint_name, traffic_policy_dictionary): - """Associate a service endpoint with traffic policy. +# def split(endpoint_name, traffic_policy_dictionary): +# """Associate a service endpoint with traffic policy. - Example: +# Example: - >>> serve.split("service-name", { - "backend:v1": 0.5, - "backend:v2": 0.5 - }) +# >>> serve.split("service-name", { +# "backend:v1": 0.5, +# "backend:v2": 0.5 +# }) - Args: - endpoint_name (str): A registered service endpoint. - traffic_policy_dictionary (dict): a dictionary maps backend names - to their traffic weights. The weights must sum to 1. - """ +# Args: +# endpoint_name (str): A registered service endpoint. +# traffic_policy_dictionary (dict): a dictionary maps backend names +# to their traffic weights. The weights must sum to 1. +# """ - # Perform dictionary checks - assert endpoint_name in global_state.registered_endpoints +# # Perform dictionary checks +# assert endpoint_name in global_state.registered_endpoints - assert isinstance(traffic_policy_dictionary, - dict), "Traffic policy must be dictionary" - prob = 0 - for backend, weight in traffic_policy_dictionary.items(): - prob += weight - assert (backend in global_state.registered_backends - ), "backend {} is not registered".format(backend) - assert np.isclose( - prob, 1, - atol=0.02), "weights must sum to 1, currently it sums to {}".format( - prob) +# assert isinstance(traffic_policy_dictionary, +# dict), "Traffic policy must be dictionary" +# prob = 0 +# for backend, weight in traffic_policy_dictionary.items(): +# prob += weight +# assert (backend in global_state.registered_backends +# ), "backend {} is not registered".format(backend) +# assert np.isclose( +# prob, 1, +# atol=0.02), "weights must sum to 1, currently it sums to {}".format( +# prob) - global_state.router_actor_handle.set_traffic.remote( - endpoint_name, traffic_policy_dictionary) - global_state.policy_action_history[endpoint_name].append( - traffic_policy_dictionary) +# global_state.router_actor_handle.set_traffic.remote( +# endpoint_name, traffic_policy_dictionary) +# global_state.policy_action_history[endpoint_name].append( +# traffic_policy_dictionary) -def rollback(endpoint_name): - """Rollback a traffic policy decision. +# def rollback(endpoint_name): +# """Rollback a traffic policy decision. - Args: - endpoint_name (str): A registered service endpoint. - """ - assert endpoint_name in global_state.registered_endpoints - action_queues = global_state.policy_action_history[endpoint_name] - cur_policy, prev_policy = action_queues[-1], action_queues[-2] +# Args: +# endpoint_name (str): A registered service endpoint. +# """ +# assert endpoint_name in global_state.registered_endpoints +# action_queues = global_state.policy_action_history[endpoint_name] +# cur_policy, prev_policy = action_queues[-1], action_queues[-2] - logger.warning(""" -Current traffic policy is: -{cur_policy} +# logger.warning(""" +# Current traffic policy is: +# {cur_policy} -Will rollback to: -{prev_policy} -""".format( - cur_policy=pformat_color_json(cur_policy), - prev_policy=pformat_color_json(prev_policy))) +# Will rollback to: +# {prev_policy} +# """.format( +# cur_policy=pformat_color_json(cur_policy), +# prev_policy=pformat_color_json(prev_policy))) - action_queues.pop() - global_state.router_actor_handle.set_traffic.remote( - endpoint_name, prev_policy) +# action_queues.pop() +# global_state.router_actor_handle.set_traffic.remote( +# endpoint_name, prev_policy) -def get_handle(endpoint_name): +def get_handle(pipeline_name): """Retrieve RayServeHandle for service endpoint to invoke it from Python. Args: @@ -211,9 +221,9 @@ def get_handle(endpoint_name): Returns: RayServeHandle """ - assert endpoint_name in global_state.registered_endpoints + assert pipeline_name in global_state.provisioned_services # Delay import due to it's dependency on global_state from ray.experimental.serve.handle import RayServeHandle - return RayServeHandle(global_state.router_actor_handle, endpoint_name) + return RayServeHandle(global_state.kv_store_actor_handle_pipeline,global_state.router_actor_handle, pipeline_name) diff --git a/python/ray/experimental/serve/examples/echo_handle.py b/python/ray/experimental/serve/examples/echo_handle.py new file mode 100644 index 000000000000..811b20df1628 --- /dev/null +++ b/python/ray/experimental/serve/examples/echo_handle.py @@ -0,0 +1,28 @@ +import time + +import requests +from werkzeug import urls + +from ray.experimental import serve +from ray.experimental.serve.utils import pformat_color_json +import json +from pprint import pprint +def echo1(context): + message = context + message += 'FROM MODEL1 -> ' + return message + + +serve.init(blocking=True) +serve.create_backend(echo1, "echo:v1",num_gpu=0) +serve.create_no_http_service("serve1") +serve.link_service("serve1", "echo:v1") + +serve.add_service("pipeline1","serve1") +serve.provision_pipeline("pipeline1") +pprint(serve.get_service_dependencies("pipeline1")) +pipeline_handle = serve.get_handle("pipeline1") +args = {"serve1": "Intial Data --> "} +result = pipeline_handle.remote(**args) +print("Result is") +print(result) \ No newline at end of file diff --git a/python/ray/experimental/serve/handle.py b/python/ray/experimental/serve/handle.py index 56886f25c4e6..61d71c2e9964 100644 --- a/python/ray/experimental/serve/handle.py +++ b/python/ray/experimental/serve/handle.py @@ -24,41 +24,72 @@ class RayServeHandle: # raises RayTaskError Exception """ - def __init__(self, router_handle, endpoint_name): + def __init__(self, kv_store_actor_handle_pipeline,router_handle, pipeline_name): self.router_handle = router_handle - self.endpoint_name = endpoint_name - - def remote(self, *args): + self.pipeline_name = pipeline_name + self.kv_store_actor_handle_pipeline = kv_store_actor_handle_pipeline + future = kv_store_actor_handle_pipeline.get_dependency.remote(pipeline_name) + self.service_dependencies = ray.get(future) + def remote(self, **args): # TODO(simon): Support kwargs once #5606 is merged. - result_object_id_bytes = ray.get( - self.router_handle.enqueue_request.remote(self.endpoint_name, - *args)) - return ray.ObjectID(result_object_id_bytes) + data_d = {} + size = len(self.service_dependencies['node_order']) + last_node = self.service_dependencies['node_order'][size-1] + assert len(last_node) == 1 + last_node = last_node[0] + for node_list in self.service_dependencies['node_order']: + data_sent = {} + for node in node_list: + if len(self.service_dependencies['predecessors'][node]) == 0: + if node in args: + data_sent[node] = [args[node]] + else: + raise Exception('Specify service name in input') + else: + predecessors_list = self.service_dependencies['predecessors'][node] + list_data = [data_d[p] for p in predecessors_list] + data_sent[node] = list_data + + future_list = [self.router_handle.enqueue_request.remote(node, data_sent[node]) for node in node_list] + completed_futures, non_c = ray.wait(future_list,num_returns=len(future_list)) + assert(len(non_c) == 0) + future_enqueues_binary = ray.get(completed_futures) + + future_enqueues = [ray.ObjectID(x) for x in future_enqueues_binary] + completed_future_enqueues, non_c = ray.wait(future_enqueues,num_returns=len(future_enqueues)) + assert(len(non_c) == 0) + node_data_list = ray.get(completed_future_enqueues) + for k,v in zip(node_list,node_data_list): + data_d[k] = v + # result_object_id_bytes = ray.get( + # self.router_handle.enqueue_request.remote(self.endpoint_name, + # *args)) + return data_d[last_node] - def get_traffic_policy(self): - # TODO(simon): This method is implemented via checking global state - # because we are sure handle and global_state are in the same process. - # However, once global_state is deprecated, this method need to be - # updated accordingly. - history = serve.global_state.policy_action_history[self.endpoint_name] - if len(history): - return history[-1] - else: - return None + # def get_traffic_policy(self): + # # TODO(simon): This method is implemented via checking global state + # # because we are sure handle and global_state are in the same process. + # # However, once global_state is deprecated, this method need to be + # # updated accordingly. + # history = serve.global_state.policy_action_history[self.endpoint_name] + # if len(history): + # return history[-1] + # else: + # return None def get_http_endpoint(self): return serve.global_state.http_address - def __repr__(self): - return """ -RayServeHandle( - Endpoint="{endpoint_name}", - URL="{http_endpoint}/{endpoint_name}, - Traffic={traffic_policy} -) -""".format(endpoint_name=self.endpoint_name, - http_endpoint=self.get_http_endpoint(), - traffic_policy=self.get_traffic_policy()) +# def __repr__(self): +# return """ +# RayServeHandle( +# Endpoint="{endpoint_name}", +# URL="{http_endpoint}/{endpoint_name}, +# Traffic={traffic_policy} +# ) +# """.format(endpoint_name=self.endpoint_name, +# http_endpoint=self.get_http_endpoint(), +# traffic_policy=self.get_traffic_policy()) # TODO(simon): a convenience function that dumps equivalent requests # code for a given call. diff --git a/python/ray/experimental/serve/kv_store_service.py b/python/ray/experimental/serve/kv_store_service.py index e02dc1ddc616..b7524298d456 100644 --- a/python/ray/experimental/serve/kv_store_service.py +++ b/python/ray/experimental/serve/kv_store_service.py @@ -143,6 +143,18 @@ class KVPipelineProxy: def __init__(self, kv_class=RayInternalKVStore): self.pipeline_storage = kv_class(index_key = "RAY_PIPELINE_INDEX",namespace="pipelines") self.request_count = 0 + self.provision_pipeline_cnt = 0 + + def add_node(self,pipeline: str, service_no_http_1: str): + if self.pipeline_storage.exists(pipeline): + g_json = self.pipeline_storage.get(pipeline) + G = json_graph.node_link_graph(g_json) + else: + G = nx.DiGraph() + G = nx.OrderedDiGraph(G) + G.add_node(service_no_http_1) + g_json = json_graph.node_link_data(G) + self.pipeline_storage.put(pipeline,g_json) # Adds directed edge from service_no_http_1 --> service_no_http_2 in service DAG for pipeline def add_edge(self,pipeline: str, service_no_http_1: str , service_no_http_2: str): @@ -186,6 +198,7 @@ def provision(self,pipeline: str): predecessors_d[node] = list(G.predecessors(node)) final_d = {'node_order': node_order , 'predecessors' : predecessors_d} self.pipeline_storage.put(pipeline,final_d) + self.provision_pipeline_cnt += 1 else: raise Exception('Add service dependencies to pipeline') except Exception: @@ -194,9 +207,21 @@ def provision(self,pipeline: str): def list_pipeline_service(self): + assert self.provision_pipeline_cnt == 1 self.request_count += 1 table = self.pipeline_storage.as_dict() return table + def get_dependency(self,pipeline: str): + try: + assert self.provision_pipeline_cnt == 1 + if self.pipeline_storage.exists(pipeline): + final_d = self.pipeline_storage.get(pipeline) + return final_d + else: + raise Exception('Pipeline does not exists!' ) + except Exception: + traceback_str = ray.utils.format_error_message(traceback.format_exc()) + return ray.exceptions.RayTaskError('Issue with getting dependencies', traceback_str) def get_request_count(self): """Return the number of requests that fetched the routing table. From ca127571c81ceffb28322f198dfae688724f3bcb Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Sat, 12 Oct 2019 14:33:09 -0400 Subject: [PATCH 68/70] Bug fix --- python/ray/experimental/serve/kv_store_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ray/experimental/serve/kv_store_service.py b/python/ray/experimental/serve/kv_store_service.py index b7524298d456..d025b823899f 100644 --- a/python/ray/experimental/serve/kv_store_service.py +++ b/python/ray/experimental/serve/kv_store_service.py @@ -207,7 +207,7 @@ def provision(self,pipeline: str): def list_pipeline_service(self): - assert self.provision_pipeline_cnt == 1 + # assert self.provision_pipeline_cnt == 1 self.request_count += 1 table = self.pipeline_storage.as_dict() return table From 0d2805ef8f97ab4efe27f3b2521ed62f8e24790f Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Sat, 12 Oct 2019 14:58:40 -0400 Subject: [PATCH 69/70] More example added --- .../serve/examples/echo_handle_complex.py | 204 ++++++++++++++++++ .../serve/examples/resnet50_handle.py | 76 +++++++ 2 files changed, 280 insertions(+) create mode 100644 python/ray/experimental/serve/examples/echo_handle_complex.py create mode 100644 python/ray/experimental/serve/examples/resnet50_handle.py diff --git a/python/ray/experimental/serve/examples/echo_handle_complex.py b/python/ray/experimental/serve/examples/echo_handle_complex.py new file mode 100644 index 000000000000..bc32ccb3e2ed --- /dev/null +++ b/python/ray/experimental/serve/examples/echo_handle_complex.py @@ -0,0 +1,204 @@ +import time + +import requests +from werkzeug import urls + +from ray.experimental import serve +from ray.experimental.serve.utils import pformat_color_json +import json +from pprint import pprint +def echo1(*context): + message = context[0] + message += 'FROM MODEL1 -> ' + return message + +def echo2(*context): + start = "[ " + for val in context: + start = start + val + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL2 -> ' + return data + +def echo3(*context): + start = "[ " + for val in context: + start = start + val + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL3 -> ' + return data + +def echo4(*context): + start = "[ " + for val in context: + start = start + val + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL4 -> ' + return data + +def echo5(*context): + start = "[ " + for val in context: + start = start + val + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL5 -> ' + return data + +def echo6(*context): + start = "[ " + for val in context: + start = start + val + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL6 -> ' + return data + +def echo7(*context): + start = "[ " + for val in context: + start = start + val + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL7 -> ' + return data + +def echo8(*context): + start = "[ " + for val in context: + start = start + val + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL8 -> ' + return data + +def echo9(*context): + start = "[ " + for val in context: + start = start + val + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL9 -> ' + return data + +def echo10(*context): + start = "[ " + for val in context: + start = start + val + " , " + start += " ] --> " + data = start + # message = "" + data += 'FROM MODEL10 -> ' + return data + +serve.init(blocking=True) + +# serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) + +# Create Backends +serve.create_backend(echo1, "echo:v1",num_gpu=0) +serve.create_backend(echo2, "echo:v2",num_gpu=0) +serve.create_backend(echo3,"echo:v3",num_gpu=0) +serve.create_backend(echo4,"echo:v4",num_gpu=0) +serve.create_backend(echo5,"echo:v5",num_gpu=0) +serve.create_backend(echo6,"echo:v6",num_gpu=0) +serve.create_backend(echo7,"echo:v7",num_gpu=0) +serve.create_backend(echo8,"echo:v8",num_gpu=0) +serve.create_backend(echo9,"echo:v9",num_gpu=0) +serve.create_backend(echo10,"echo:v10",num_gpu=0) + +# Create services +serve.create_no_http_service("serve1") +serve.create_no_http_service("serve2") +serve.create_no_http_service("serve3") +serve.create_no_http_service("serve4") +serve.create_no_http_service("serve5") +serve.create_no_http_service("serve6") +serve.create_no_http_service("serve7") +serve.create_no_http_service("serve8") +serve.create_no_http_service("serve9") +serve.create_no_http_service("serve10") +# serve.create_no_http_service("serve3") + +# Link services and backends +serve.link_service("serve1", "echo:v1") +serve.link_service("serve2", "echo:v2") +serve.link_service("serve3","echo:v3") +serve.link_service("serve4","echo:v4") +serve.link_service("serve5","echo:v5") +serve.link_service("serve6","echo:v6") +serve.link_service("serve7","echo:v7") +serve.link_service("serve8","echo:v8") +serve.link_service("serve9","echo:v9") +serve.link_service("serve10","echo:v10") + +''' +1. Add service dependencies in a PIPELINE +2. You can add dependency to a PIPELINE only if the PIPELINE has not been provisioned yet. +''' + +# Adding dependencies +serve.add_service_dependencies("pipeline1","serve1","serve2") +serve.add_service_dependencies("pipeline1","serve1","serve3") +serve.add_service_dependencies("pipeline1","serve1","serve5") + +serve.add_service_dependencies("pipeline1","serve2","serve4") +serve.add_service_dependencies("pipeline1","serve2","serve5") + +serve.add_service_dependencies("pipeline1","serve3","serve6") +serve.add_service_dependencies("pipeline1","serve3","serve7") + +serve.add_service_dependencies("pipeline1","serve4","serve8") + +serve.add_service_dependencies("pipeline1","serve5","serve9") + +serve.add_service_dependencies("pipeline1","serve6","serve5") +serve.add_service_dependencies("pipeline1","serve6","serve8") + +serve.add_service_dependencies("pipeline1","serve7","serve4") +serve.add_service_dependencies("pipeline1","serve7","serve9") + +serve.add_service_dependencies("pipeline1","serve8","serve10") + +serve.add_service_dependencies("pipeline1","serve9","serve10") + + + + + + + +# Provision the PIPELINE (You can provision the pipeline only once) +serve.provision_pipeline("pipeline1") +dependency = serve.get_service_dependencies("pipeline1") +pprint(dependency) +node_list = dependency['node_order'][0] +sent = {} +for n in node_list: + sent[n] = "INP" +pipeline_handle = serve.get_handle("pipeline1") +result = pipeline_handle.remote(**sent) +pprint(result) + +# # You can only create an endpoint for pipeline after provisioning the pipeline +# serve.create_endpoint_pipeline("pipeline1", "/echo", blocking=True) + +# time.sleep(2) + +# while True: +# resp = requests.get("http://127.0.0.1:8000/echo").json() +# print(pformat_color_json(resp)) + +# print("...Sleeping for 2 seconds...") +# time.sleep(2) \ No newline at end of file diff --git a/python/ray/experimental/serve/examples/resnet50_handle.py b/python/ray/experimental/serve/examples/resnet50_handle.py new file mode 100644 index 000000000000..ea3c19b016b9 --- /dev/null +++ b/python/ray/experimental/serve/examples/resnet50_handle.py @@ -0,0 +1,76 @@ +import time + +import requests +from werkzeug import urls +import ray +from ray.experimental import serve +from ray.experimental.serve.utils import pformat_color_json +import json +from ray.experimental.serve.utils import BytesEncoder +from torchvision.models.resnet import resnet50 +import io +from PIL import Image +from torch.autograd import Variable +import torchvision.transforms as transforms +import base64 +from pprint import pprint +class Transform: + def __init__(self,transform): + self.transform = transform + def __call__(self,data): + data = Image.open(io.BytesIO(base64.b64decode(data))) + if data.mode != "RGB": + data = data.convert("RGB") + data = self.transform(data) + data = data.unsqueeze(0) + return data + +class Resnet50: + def __init__(self, model): + self.model = model + + def __call__(self, data): + # if 'transform' in context: + # data = context['transform'] + data = Variable(data) + data = data.cuda() + return self.model(data).data.cpu().numpy().argmax() + # return context['transform'] + # return '' + + + +min_img_size = 224 +transform = transforms.Compose([transforms.Resize(min_img_size), + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225])]) +model = resnet50(pretrained=True) +model = model.cuda() + +serve.init(object_store_memory=int(1e9),blocking=True) +#create Backends +serve.create_backend(Transform, "transform:v1",0,transform) +serve.create_backend(Resnet50,"r50",1,model) + +# create service +serve.create_no_http_service("transform") +serve.create_no_http_service("imagenet-classification") + +#link service and backend +serve.link_service("transform", "transform:v1") +serve.link_service("imagenet-classification", "r50") + +#Add service dependencies to pipeline +serve.add_service_dependencies("pipeline1","transform","imagenet-classification") + +# Provision the PIPELINE (You can provision the pipeline only once) +serve.provision_pipeline("pipeline1") + + +dependency = serve.get_service_dependencies("pipeline1") +pipeline_handle = serve.get_handle("pipeline1") +req_json = { "transform": base64.b64encode(open('elephant.jpg', "rb").read()) } +# sent_data = json.dumps(req_json, cls=BytesEncoder, indent=2).encode() +result = pipeline_handle.remote(**req_json) +print(result) From 9ad5125e4d0fee2578b4f66587430b38ada456da Mon Sep 17 00:00:00 2001 From: Alind Khare Date: Sun, 13 Oct 2019 18:25:31 -0400 Subject: [PATCH 70/70] Soft Real Time ML --- .../experimental/serve/kv_store_service.py | 11 +- .../ray/experimental/srtml/AbstractModel.py | 117 ++++++++++++++++++ python/ray/experimental/srtml/__init__.py | 6 + .../experimental/srtml/examples/elephant.jpg | Bin 0 -> 111477 bytes .../srtml/examples/pipeline_edge_order.py | 39 ++++++ .../srtml/examples/pipeline_error.py | 20 +++ .../srtml/examples/pipeline_example_1.py | 16 +++ .../srtml/examples/pipeline_example_2.py | 55 ++++++++ .../srtml/examples/pipeline_http_complex.py | 67 ++++++++++ .../srtml/examples/pipeline_resnet50.py | 38 ++++++ python/ray/experimental/srtml/pipeline.py | 116 +++++++++++++++++ 11 files changed, 481 insertions(+), 4 deletions(-) create mode 100644 python/ray/experimental/srtml/AbstractModel.py create mode 100644 python/ray/experimental/srtml/__init__.py create mode 100644 python/ray/experimental/srtml/examples/elephant.jpg create mode 100644 python/ray/experimental/srtml/examples/pipeline_edge_order.py create mode 100644 python/ray/experimental/srtml/examples/pipeline_error.py create mode 100644 python/ray/experimental/srtml/examples/pipeline_example_1.py create mode 100644 python/ray/experimental/srtml/examples/pipeline_example_2.py create mode 100644 python/ray/experimental/srtml/examples/pipeline_http_complex.py create mode 100644 python/ray/experimental/srtml/examples/pipeline_resnet50.py create mode 100644 python/ray/experimental/srtml/pipeline.py diff --git a/python/ray/experimental/serve/kv_store_service.py b/python/ray/experimental/serve/kv_store_service.py index d025b823899f..4a06173aec44 100644 --- a/python/ray/experimental/serve/kv_store_service.py +++ b/python/ray/experimental/serve/kv_store_service.py @@ -143,7 +143,7 @@ class KVPipelineProxy: def __init__(self, kv_class=RayInternalKVStore): self.pipeline_storage = kv_class(index_key = "RAY_PIPELINE_INDEX",namespace="pipelines") self.request_count = 0 - self.provision_pipeline_cnt = 0 + self.provision_pipeline_cnt = {} def add_node(self,pipeline: str, service_no_http_1: str): if self.pipeline_storage.exists(pipeline): @@ -198,7 +198,7 @@ def provision(self,pipeline: str): predecessors_d[node] = list(G.predecessors(node)) final_d = {'node_order': node_order , 'predecessors' : predecessors_d} self.pipeline_storage.put(pipeline,final_d) - self.provision_pipeline_cnt += 1 + self.provision_pipeline_cnt[pipeline] = 1 else: raise Exception('Add service dependencies to pipeline') except Exception: @@ -213,10 +213,13 @@ def list_pipeline_service(self): return table def get_dependency(self,pipeline: str): try: - assert self.provision_pipeline_cnt == 1 + # assert self.provision_pipeline_cnt[pipeline] == 1 if self.pipeline_storage.exists(pipeline): final_d = self.pipeline_storage.get(pipeline) - return final_d + if type(final_d) is dict: + return final_d + else: + raise Exception('Getting dependency before provisioning pipeline' ) else: raise Exception('Pipeline does not exists!' ) except Exception: diff --git a/python/ray/experimental/srtml/AbstractModel.py b/python/ray/experimental/srtml/AbstractModel.py new file mode 100644 index 000000000000..d482a51f3afb --- /dev/null +++ b/python/ray/experimental/srtml/AbstractModel.py @@ -0,0 +1,117 @@ +from ray.experimental import serve +import uuid +class AbstractModel: + def __init__(self,feature,input_shape='?',output_shape='?',input_type='?',output_type='?',num_inputs='?'): + self.feature = feature + self.num_inputs = num_inputs + self.input_shape = input_shape + self.output_shape = output_shape + self.input_type = input_type + self.output_type = output_type + self.service_name = uuid.uuid1().hex + # serve.create_no_http_service(self.service_name) + self.is_linked = False + self.backends = [] + self.link_model() + + def get_config(self): + if self.is_linked: + d = { + 'input_shape' : self.input_shape, + 'output_shape' : self.output_shape, + 'input_type' : self.input_type, + 'output_type' : self.output_type, + 'num_inputs' : self.num_inputs + } + return d + return None + + ''' + Currently this function is hard-coded + ''' + def find_backends(self): + if self.feature == "echo": + backend_name = uuid.uuid4().hex + def echo1(context): + message = context + message += ' FROM MODEL ({}/{}) -> '.format(self.feature,backend_name) + return message + return backend_name,echo1,None,0 + elif self.feature == "complex-echo": + backend_name = uuid.uuid4().hex + def echoC(*context): + start = "[ " + for val in context: + start = start + val + " , " + start += " ] --> " + message = start + # message = "" + message += 'FROM MODEL ({}/{}) -> '.format(self.feature,backend_name) + return message + return backend_name,echoC,None,0 + elif self.feature == "imagenet-transform": + import torchvision.transforms as transforms + from PIL import Image + import io + import base64 + backend_name = uuid.uuid4().hex + class Transform: + def __init__(self,transform): + self.transform = transform + def __call__(self,data): + data = Image.open(io.BytesIO(base64.b64decode(data))) + if data.mode != "RGB": + data = data.convert("RGB") + data = self.transform(data) + data = data.unsqueeze(0) + return data + + min_img_size = 224 + transform = transforms.Compose([transforms.Resize(min_img_size), + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225])]) + return backend_name,Transform,[transform],0 + + elif self.feature == "imagenet-resnet": + from torch.autograd import Variable + from torchvision.models.resnet import resnet50 + backend_name = uuid.uuid4().hex + class Resnet50: + def __init__(self, model): + self.model = model + + def __call__(self, data): + # if 'transform' in context: + # data = context['transform'] + data = Variable(data) + data = data.cuda() + return self.model(data).data.cpu().numpy().argmax() + + model = resnet50(pretrained=True) + model = model.cuda() + + return backend_name,Resnet50,[model],1 + return None + + def get_backend(self): + if self.is_linked: + return self.backends + def link_model(self): + backend_info = self.find_backends() + if backend_info is not None: + serve.create_no_http_service(self.service_name) + backend_name,cls_or_func,args,num_gpu = backend_info + if args is None: + serve.create_backend(cls_or_func, backend_name,num_gpu=num_gpu) + else: + serve.create_backend(cls_or_func, backend_name,num_gpu,*args) + serve.link_service(self.service_name, backend_name) + self.is_linked = True + self.backends.append(backend_name) + else: + raise Exception('Backend not found for the AbstractModel!') + + + + diff --git a/python/ray/experimental/srtml/__init__.py b/python/ray/experimental/srtml/__init__.py new file mode 100644 index 000000000000..ccf9f5345fae --- /dev/null +++ b/python/ray/experimental/srtml/__init__.py @@ -0,0 +1,6 @@ +import sys +if sys.version_info < (3, 0): + raise ImportError("serve is Python 3 only.") +from ray.experimental.srtml.AbstractModel import AbstractModel +from ray.experimental.srtml.pipeline import Pipeline +__all__ = ["AbstractModel","Pipeline"] \ No newline at end of file diff --git a/python/ray/experimental/srtml/examples/elephant.jpg b/python/ray/experimental/srtml/examples/elephant.jpg new file mode 100644 index 0000000000000000000000000000000000000000..32258eed1628648f55450f611daf87bf1c987ac5 GIT binary patch literal 111477 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 z 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 d`Qb_A{{aSPMt7UloErcD002ovPDHLkV1mY 0: + final_check = True + else: + return False,"Cannot add more incoming edges!" + if (m1_config['output_type'] == m2_config['input_type']) or self.__isWildCard(m1_config['output_type']) or self.__isWildCard(m2_config['input_type']): + final_check = True + else: + return False,"Types do not match!" + if (m1_config['output_shape'] == m2_config['input_shape'] or self.__isWildCard(m1_config['output_shape']) or self.__isWildCard(m2_config['input_shape'])) : + final_check = True + else: + return False,"Shape do not match!" + + return True,"" + + + + def add_dependency(self,model1,model2): + if not self.provisioned: + self.add_model(model1) + self.add_model(model2) + flag,message = self.__addSanityCheck(model1.service_name,model2.service_name) + if flag: + self.inverted_service_dependency[model2.service_name].append(model1.service_name) + if not self.__isWildCard(self.abstract_models_config[model2.service_name]['num_inputs']): + self.abstract_models_config[model2.service_name]['num_inputs'] -= 1 + else: + raise Exception(message) + + def provision_pipeline(self): + if not self.provisioned: + for service1 in self.inverted_service_dependency.keys(): + directed_edges = self.inverted_service_dependency[service1] + for service2 in directed_edges: + serve.add_service_dependencies(self.pipeline_name,service2,service1) + serve.provision_pipeline(self.pipeline_name) + time.sleep(1) + self.pipeline_info = serve.get_service_dependencies(self.pipeline_name) + self.pipeline_handle = serve.get_handle(self.pipeline_name) + self.provisioned = True + + def remote(self,data): + if self.provisioned: + node_list = self.pipeline_info['node_order'][0] + sent = {} + for n in node_list: + sent[n] = data + return self.pipeline_handle.remote(**sent) + def http(self,route=None): + if self.provisioned and not self.http_served: + if route is None: + route = '/{}'.format(self.pipeline_name) + serve.create_endpoint_pipeline(self.pipeline_name, route, blocking=True) + self.http_served = True + address = serve.global_state.http_address + route + return address + + def get_http_formatted_data(self,data): + if self.http_served: + node_list = self.pipeline_info['node_order'][0] + sent = {} + for n in node_list: + sent[n] = data + + sent_data = json.dumps(sent, cls=BytesEncoder, indent=2).encode() + return sent_data + + + + + + + + + + +