-
Notifications
You must be signed in to change notification settings - Fork 1
Lazy evaluation #582
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
base: master
Are you sure you want to change the base?
Lazy evaluation #582
Changes from all commits
92d8bc5
e27d079
7c0ae22
ad5fb4d
9354bf3
19fee1f
8c9a6e3
c468d0c
c307e75
fb019bd
900efd3
6003ce6
6bc2dc3
28bb22a
78c3d00
82b809a
b5fb986
6f98892
9816462
c2f35ad
f0f45e5
d39b916
8311c57
341a2f9
82a31f8
d9a708e
1f6e99d
1298a37
d8b75ce
30ea479
6e43255
be72582
483eab6
b5d0f24
fe3cd02
81bedc6
a34b002
4ca2c90
13401ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
from abc import ABCMeta, abstractmethod | ||
|
||
from functools import wraps | ||
from contextlib import contextmanager | ||
|
||
import numpy | ||
|
||
|
@@ -28,7 +29,7 @@ | |
|
||
from distarray.globalapi.ipython_utils import IPythonClient | ||
from distarray.utils import uid, nonce, has_exactly_one | ||
from distarray.localapi.proxyize import Proxy | ||
from distarray.localapi.proxyize import Proxy, lazy_proxyize, lazy_name | ||
|
||
# mpi context | ||
from distarray.mpionly_utils import (make_targets_comm, | ||
|
@@ -71,7 +72,8 @@ def make_subcomm(self, new_targets): | |
pass | ||
|
||
@abstractmethod | ||
def apply(self, func, args=None, kwargs=None, targets=None, autoproxyize=False): | ||
def apply(self, func, args=None, kwargs=None, targets=None, nresults=None, | ||
autoproxyize=False): | ||
pass | ||
|
||
@abstractmethod | ||
|
@@ -206,7 +208,7 @@ def local_allclose(la, lb, rtol, atol): | |
from numpy import allclose | ||
return allclose(la.ndarray, lb.ndarray, rtol, atol) | ||
|
||
local_results = self.apply(local_allclose, | ||
local_results = self.apply(local_allclose, | ||
(a.key, b.key, rtol, atol), | ||
targets=a.targets) | ||
return all(local_results) | ||
|
@@ -580,7 +582,7 @@ def is_NoneType(pxy): | |
return pxy.type_str == str(type(None)) | ||
|
||
def is_LocalArray(pxy): | ||
return (isinstance(pxy, Proxy) and | ||
return (isinstance(pxy, Proxy) and | ||
pxy.type_str == "<class 'distarray.localapi.localarray.LocalArray'>") | ||
|
||
if all(is_LocalArray(r) for r in results): | ||
|
@@ -743,7 +745,8 @@ def _execute(self, lines, targets): | |
def _push(self, d, targets): | ||
return self.view.push(d, targets=targets, block=True) | ||
|
||
def apply(self, func, args=None, kwargs=None, targets=None, autoproxyize=False): | ||
def apply(self, func, args=None, kwargs=None, targets=None, | ||
autoproxyize=False, nresults=1): | ||
""" | ||
Analogous to IPython.parallel.view.apply_sync | ||
|
||
|
@@ -758,6 +761,8 @@ def apply(self, func, args=None, kwargs=None, targets=None, autoproxyize=False): | |
engines func is to be run on. | ||
autoproxyize: bool, default False | ||
If True, implicitly return a Proxy object from the function. | ||
nresults: int, default 1 | ||
Number of return values. Only implemented for MPIContext. | ||
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. Anything preventing us from allowing 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. Well |
||
|
||
Returns | ||
------- | ||
|
@@ -860,7 +865,7 @@ def delete_key(self, key, targets=None): | |
if MPIContext.INTERCOMM: | ||
self._send_msg(msg, targets=targets) | ||
|
||
def __init__(self, targets=None): | ||
def __init__(self, targets=None, lazy=False): | ||
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. So does this allow you to create a context that is always lazy? 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. Yeah, or at least lazy from the get-go. It really just sets |
||
|
||
if MPIContext.INTERCOMM is None: | ||
MPIContext.INTERCOMM = initial_comm_setup() | ||
|
@@ -870,6 +875,16 @@ def __init__(self, targets=None): | |
self.all_targets = list(range(self.nengines)) | ||
self.targets = self.all_targets if targets is None else sorted(targets) | ||
|
||
self.lazy = lazy # is the context in lazy-communication mode? | ||
|
||
# message queues used for lazy mode | ||
# mapping: target -> queue of messages for that target | ||
|
||
# _sendq: batches up messages to send upon sync() | ||
self._sendq = dict([(t, []) for t in self.targets]) | ||
# _recvq: stores proxy objects for expected return values | ||
self._recvq = dict([(t, []) for t in self.targets]) | ||
|
||
# make/get comms | ||
# this is the object we want to use with push, pull, etc' | ||
self._comm_from_targets = {} | ||
|
@@ -912,16 +927,45 @@ def close(self): | |
|
||
# End of key management routines. | ||
|
||
@contextmanager | ||
def lazy_eval(self): | ||
"""Context manager that enables lazy evaluation. | ||
|
||
On exit, call `sync()` and set `self.lazy` to False | ||
""" | ||
self.lazy = True | ||
yield self | ||
self.sync() | ||
self.lazy = False | ||
|
||
def _send_msg(self, msg, targets=None): | ||
targets = self.targets if targets is None else targets | ||
for t in targets: | ||
MPIContext.INTERCOMM.send(msg, dest=t) | ||
if self.lazy and msg[0] != 'process_message_queue': | ||
for t in targets: | ||
self._sendq[t].append(msg) | ||
else: | ||
for t in targets: | ||
MPIContext.INTERCOMM.send(msg, dest=t) | ||
|
||
def _recv_msg(self, targets=None): | ||
def _recv_msg(self, targets=None, nresults=1, sync=False): | ||
res = [] | ||
targets = self.targets if targets is None else targets | ||
for t in targets: | ||
res.append(MPIContext.INTERCOMM.recv(source=t)) | ||
if self.lazy and not sync: | ||
result_names = [lazy_name() for n in range(nresults)] | ||
for t in targets: | ||
if nresults == 0: | ||
res.append(None) | ||
elif nresults == 1: | ||
res.append(lazy_proxyize(name=result_names[0])) | ||
else: | ||
target_results = [] | ||
for name in result_names: | ||
target_results.append(lazy_proxyize(name)) | ||
res.append(target_results) | ||
self._recvq[t].append(res[-1]) | ||
else: | ||
for t in targets: | ||
res.append(MPIContext.INTERCOMM.recv(source=t)) | ||
return res | ||
|
||
def make_subcomm(self, targets): | ||
|
@@ -952,7 +996,8 @@ def _push(self, d, targets=None): | |
msg = ('push', d) | ||
return self._send_msg(msg, targets=targets) | ||
|
||
def apply(self, func, args=None, kwargs=None, targets=None, autoproxyize=False): | ||
def apply(self, func, args=None, kwargs=None, targets=None, | ||
autoproxyize=False, nresults=1): | ||
""" | ||
Analogous to IPython.parallel.view.apply_sync | ||
|
||
|
@@ -967,6 +1012,8 @@ def apply(self, func, args=None, kwargs=None, targets=None, autoproxyize=False): | |
engines func is to be run on. | ||
autoproxyize: bool, default False | ||
If True, implicitly return a Proxy object from the function. | ||
nresults: int, default 1 | ||
Number of return values. Only needed for lazy evaluation. | ||
|
||
Returns | ||
------- | ||
|
@@ -1000,11 +1047,47 @@ def apply(self, func, args=None, kwargs=None, targets=None, autoproxyize=False): | |
msg = ('builtin_call', func, args, kwargs, autoproxyize) | ||
|
||
self._send_msg(msg, targets=targets) | ||
return self._recv_msg(targets=targets) | ||
return self._recv_msg(targets=targets, nresults=nresults) | ||
|
||
def push_function(self, key, func, targets=None): | ||
push_function(self, key, func, targets=targets) | ||
|
||
def sync(self, targets=None): | ||
"""Send queued messages, fill in expected result values.""" | ||
targets = self.targets if targets is None else targets | ||
for t in targets: | ||
msg = ('process_message_queue', self._recvq[t], self._sendq[t]) | ||
self._send_msg(msg, targets=[t]) | ||
self._sendq[t] = [] # empty the send queue | ||
|
||
for t in targets: | ||
results = self._recv_msg(targets=[t], sync=True)[0] | ||
lresults = self._recvq[t] | ||
for lres, res in zip(lresults, results): | ||
# multiple return values | ||
if isinstance(res, collections.Sequence): | ||
if len(lres) != len(res): | ||
msg = ("Reserved lazy result object isn't the same" | ||
" size as the actual result object: {} != {}") | ||
raise TypeError(msg.format(len(lres), len(res))) | ||
for sublres, subres in zip(lres, res): | ||
if isinstance(subres, Proxy): | ||
sublres.__dict__ = subres.__dict__ | ||
else: | ||
msg = ("Only DistArray return values are " | ||
"supported in lazy mode. Type is: {}" | ||
"".format(type(subres))) | ||
raise TypeError(msg) | ||
# single return value | ||
else: | ||
if isinstance(res, Proxy): | ||
lres.__dict__ = res.__dict__ | ||
else: | ||
msg = ("Only DistArray return values are " | ||
"currently supported in lazy mode.") | ||
raise TypeError(msg) | ||
self._recvq[t] = [] # empty the recv queue | ||
|
||
|
||
class ContextCreationError(RuntimeError): | ||
pass | ||
|
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.
There isn't time to implement it, but it feels like
nresults
should be an attribute on the local function somehow, either with a decorator or a function annotation for the return value or something. Perhaps we can write it up in an issue...