-
Notifications
You must be signed in to change notification settings - Fork 146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(frontend-python): module run are scheduled and parallelized in a… #1144
Open
BourgerieQuentin
wants to merge
1
commit into
main
Choose a base branch
from
module-auto-schedule-rebased
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,10 @@ | |
|
||
# pylint: disable=import-error,no-member,no-name-in-module | ||
|
||
import asyncio | ||
from concurrent.futures import Future, ThreadPoolExecutor | ||
from pathlib import Path | ||
from threading import Thread | ||
from typing import Any, Dict, Iterable, List, NamedTuple, Optional, Tuple, Union | ||
|
||
import numpy as np | ||
|
@@ -24,13 +27,40 @@ | |
# pylint: enable=import-error,no-member,no-name-in-module | ||
|
||
|
||
class ExecutionRt(NamedTuple): | ||
class ExecutionRt: | ||
""" | ||
Runtime object class for execution. | ||
""" | ||
|
||
client: Client | ||
server: Server | ||
auto_schedule_run: bool | ||
fhe_executor_pool: ThreadPoolExecutor | ||
fhe_waiter_loop: asyncio.BaseEventLoop | ||
fhe_waiter_thread: Thread # daemon thread | ||
|
||
def __init__(self, client, server, auto_schedule_run): | ||
self.client = client | ||
self.server = server | ||
self.auto_schedule_run = auto_schedule_run | ||
if auto_schedule_run: | ||
self.fhe_executor_pool = ThreadPoolExecutor() | ||
self.fhe_waiter_loop = asyncio.new_event_loop() | ||
|
||
def loop_thread(): | ||
asyncio.set_event_loop(self.fhe_waiter_loop) | ||
self.fhe_waiter_loop.run_forever() | ||
|
||
self.fhe_waiter_thread = Thread(target=loop_thread, args=(), daemon=True) | ||
self.fhe_waiter_thread.start() | ||
else: | ||
self.fhe_executor_pool = None | ||
self.fhe_waiter_loop = None | ||
self.fhe_waiter_thread = None | ||
|
||
def __del__(self): | ||
if self.fhe_waiter_loop: | ||
self.fhe_waiter_loop.stop() # daemon cleanup | ||
|
||
|
||
class SimulationRt(NamedTuple): | ||
|
@@ -177,12 +207,12 @@ def encrypt( | |
return tuple(args) if len(args) > 1 else args[0] # type: ignore | ||
return self.execution_runtime.val.client.encrypt(*args, function_name=self.name) | ||
|
||
def run( | ||
def run_sync( | ||
self, | ||
*args: Optional[Union[Value, Tuple[Optional[Value], ...]]], | ||
) -> Union[Value, Tuple[Value, ...]]: | ||
) -> Any: | ||
""" | ||
Evaluate the function. | ||
Evaluate the function synchronuously. | ||
|
||
Args: | ||
*args (Value): | ||
|
@@ -193,17 +223,115 @@ def run( | |
result(s) of evaluation | ||
""" | ||
|
||
return self._run(True, *args) | ||
|
||
def run_async( | ||
self, *args: Optional[Union[Value, Tuple[Optional[Value], ...]]] | ||
) -> Union[Value, Tuple[Value, ...], Future[Value], Future[Tuple[Value, ...]]]: | ||
""" | ||
Evaluate the function asynchronuously. | ||
|
||
Args: | ||
*args (Value): | ||
argument(s) for evaluation | ||
|
||
Returns: | ||
Union[Future[Value], Future[Tuple[Value, ...]]]: | ||
result(s) a future of the evaluation | ||
""" | ||
if ( | ||
isinstance(self.execution_runtime.val, ExecutionRt) | ||
and not self.execution_runtime.val.fhe_executor_pool | ||
): | ||
client = self.execution_runtime.val.client | ||
server = self.execution_runtime.val.server | ||
self.execution_runtime = Lazy(lambda: ExecutionRt(client, server, True)) | ||
self.execution_runtime.val.auto_schedule_run = False | ||
|
||
return self._run(False, *args) | ||
|
||
def run( | ||
self, | ||
*args: Optional[Union[Value, Tuple[Optional[Value], ...]]], | ||
) -> Union[Value, Tuple[Value, ...], Future[Value], Future[Tuple[Value, ...]]]: | ||
""" | ||
Evaluate the function. | ||
|
||
Args: | ||
*args (Value): | ||
argument(s) for evaluation | ||
|
||
Returns: | ||
Union[Value, Tuple[Value, ...], Future[Value], Future[Tuple[Value, ...]]]: | ||
result(s) of evaluation or future of result(s) of evaluation if configured with async_run=True | ||
""" | ||
if isinstance(self.execution_runtime.val, ExecutionRt): | ||
auto_schedule_run = self.execution_runtime.val.auto_schedule_run | ||
else: | ||
auto_schedule_run = False | ||
return self._run(not auto_schedule_run, *args) | ||
|
||
def _run( | ||
self, | ||
sync: bool, | ||
*args: Optional[Union[Value, Tuple[Optional[Value], ...]]], | ||
) -> Union[Value, Tuple[Value, ...], Future]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can be more specific in the Future type |
||
""" | ||
Evaluate the function. | ||
|
||
Args: | ||
*args (Value): | ||
argument(s) for evaluation | ||
|
||
Returns: | ||
Union[Value, Tuple[Value, ...], Future[Value], Future[Tuple[Value, ...]]]: | ||
result(s) of evaluation if sync=True else future of result(s) of evaluation | ||
""" | ||
if self.configuration.simulate_encrypt_run_decrypt: | ||
return self._simulate_decrypt(self._simulate_run(*args)) # type: ignore | ||
return self.execution_runtime.val.server.run( | ||
|
||
assert isinstance(self.execution_runtime.val, ExecutionRt) | ||
|
||
fhe_work = lambda *args: self.execution_runtime.val.server.run( | ||
*args, | ||
evaluation_keys=self.execution_runtime.val.client.evaluation_keys, | ||
function_name=self.name, | ||
) | ||
|
||
def args_ready(args): | ||
return [arg.result() if isinstance(arg, Future) else arg for arg in args] | ||
|
||
if sync: | ||
return fhe_work(*args_ready(args)) | ||
|
||
all_args_done = all(not isinstance(arg, Future) or arg.done() for arg in args) | ||
|
||
fhe_work_future = lambda *args: self.execution_runtime.val.fhe_executor_pool.submit( | ||
fhe_work, *args | ||
) | ||
if all_args_done: | ||
return fhe_work_future(*args_ready(args)) | ||
|
||
# waiting args to be ready with async coroutines | ||
# it only required one thread to run unlimited waits vs unlimited sync threads | ||
async def wait_async(arg): | ||
if not isinstance(arg, Future): | ||
return arg | ||
if arg.done(): | ||
return arg.result() | ||
return await asyncio.wrap_future(arg, loop=self.execution_runtime.val.fhe_waiter_loop) | ||
|
||
async def args_ready_and_submit(*args): | ||
args = [await wait_async(arg) for arg in args] | ||
return await wait_async(fhe_work_future(*args)) | ||
|
||
run_async = args_ready_and_submit(*args) | ||
return asyncio.run_coroutine_threadsafe( | ||
run_async, self.execution_runtime.val.fhe_waiter_loop | ||
) | ||
|
||
def decrypt( | ||
self, | ||
*results: Union[Value, Tuple[Value, ...]], | ||
self, *results: Union[Value, Tuple[Value, ...], Future[Value], Future[Tuple[Value, ...]]] | ||
) -> Optional[Union[int, np.ndarray, Tuple[Optional[Union[int, np.ndarray]], ...]]]: | ||
""" | ||
Decrypt result(s) of evaluation. | ||
|
@@ -220,6 +348,8 @@ def decrypt( | |
if self.configuration.simulate_encrypt_run_decrypt: | ||
return tuple(results) if len(results) > 1 else results[0] # type: ignore | ||
|
||
assert isinstance(self.execution_runtime.val, ExecutionRt) | ||
results = [res.result() if isinstance(res, Future) else res for res in results] | ||
return self.execution_runtime.val.client.decrypt(*results, function_name=self.name) | ||
|
||
def encrypt_run_decrypt(self, *args: Any) -> Any: | ||
|
@@ -620,7 +750,9 @@ def init_execution(): | |
execution_client = Client( | ||
execution_server.client_specs, keyset_cache_directory, is_simulated=False | ||
) | ||
return ExecutionRt(execution_client, execution_server) | ||
return ExecutionRt( | ||
execution_client, execution_server, self.configuration.auto_schedule_run | ||
) | ||
|
||
self.execution_runtime = Lazy(init_execution) | ||
if configuration.fhe_execution: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note if you have better way to force pyling to force return type to
Union[Value, Tuple[Value, ...]]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm probably you have to do something in this spirit:
And pylint will be happy after that ....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's unfortunate that
typing.overload
doesn't support specifying overloads based on specific values.