mirror of
https://github.com/wassname/ray.git
synced 2026-06-28 13:37:39 +08:00
[xlang] Cross language Python support (#6709)
This commit is contained in:
@@ -107,6 +107,7 @@ from ray._raylet import (
|
||||
ObjectID,
|
||||
TaskID,
|
||||
UniqueID,
|
||||
Language,
|
||||
) # noqa: E402
|
||||
|
||||
_config = _Config()
|
||||
@@ -141,6 +142,7 @@ import ray.projects # noqa: E402
|
||||
import ray.actor # noqa: F401
|
||||
from ray.actor import method # noqa: E402
|
||||
from ray.runtime_context import _get_runtime_context # noqa: E402
|
||||
from ray.cross_language import java_function, java_actor_class # noqa: E402
|
||||
|
||||
# Ray version string.
|
||||
__version__ = "0.9.0.dev0"
|
||||
@@ -182,6 +184,9 @@ __all__ = [
|
||||
"shutdown",
|
||||
"show_in_webui",
|
||||
"wait",
|
||||
"Language",
|
||||
"java_function",
|
||||
"java_actor_class",
|
||||
]
|
||||
|
||||
# ID types
|
||||
|
||||
@@ -20,6 +20,9 @@ from ray.includes.unique_ids cimport (
|
||||
CObjectID,
|
||||
CActorID
|
||||
)
|
||||
from ray.includes.function_descriptor cimport (
|
||||
CFunctionDescriptor,
|
||||
)
|
||||
|
||||
cdef class Buffer:
|
||||
cdef:
|
||||
@@ -66,4 +69,6 @@ cdef class CoreWorker:
|
||||
self, worker, outputs, const c_vector[CObjectID] return_ids,
|
||||
c_vector[shared_ptr[CRayObject]] *returns)
|
||||
|
||||
cdef c_vector[c_string] string_vector_from_list(list string_list)
|
||||
cdef class FunctionDescriptor:
|
||||
cdef:
|
||||
CFunctionDescriptor descriptor
|
||||
|
||||
+23
-32
@@ -2,6 +2,7 @@
|
||||
# distutils: language = c++
|
||||
# cython: embedsignature = True
|
||||
# cython: language_level = 3
|
||||
# cython: c_string_encoding = default
|
||||
|
||||
from cpython.exc cimport PyErr_CheckSignals
|
||||
|
||||
@@ -87,7 +88,6 @@ from ray.exceptions import (
|
||||
RayTimeoutError,
|
||||
)
|
||||
from ray.experimental.no_return import NoReturn
|
||||
from ray.function_manager import FunctionDescriptor
|
||||
from ray.utils import decode
|
||||
from ray.ray_constants import (
|
||||
DEFAULT_PUT_OBJECT_DELAY,
|
||||
@@ -106,6 +106,7 @@ cimport cpython
|
||||
|
||||
include "includes/unique_ids.pxi"
|
||||
include "includes/ray_config.pxi"
|
||||
include "includes/function_descriptor.pxi"
|
||||
include "includes/task.pxi"
|
||||
include "includes/buffer.pxi"
|
||||
include "includes/common.pxi"
|
||||
@@ -206,6 +207,7 @@ def compute_task_id(ObjectID object_id):
|
||||
return TaskID(object_id.native().TaskId().Binary())
|
||||
|
||||
|
||||
@cython.auto_pickle(False)
|
||||
cdef class Language:
|
||||
cdef CLanguage lang
|
||||
|
||||
@@ -218,7 +220,7 @@ cdef class Language:
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, Language) and
|
||||
(<int32_t>self.lang) == (<int32_t>other.lang))
|
||||
(<int32_t>self.lang) == (<int32_t>(<Language>other).lang))
|
||||
|
||||
def __repr__(self):
|
||||
if <int32_t>self.lang == <int32_t>LANGUAGE_PYTHON:
|
||||
@@ -230,11 +232,12 @@ cdef class Language:
|
||||
else:
|
||||
raise Exception("Unexpected error")
|
||||
|
||||
def __reduce__(self):
|
||||
return Language, (<int32_t>self.lang,)
|
||||
|
||||
# Programming language enum values.
|
||||
cdef Language LANG_PYTHON = Language.from_native(LANGUAGE_PYTHON)
|
||||
cdef Language LANG_CPP = Language.from_native(LANGUAGE_CPP)
|
||||
cdef Language LANG_JAVA = Language.from_native(LANGUAGE_JAVA)
|
||||
PYTHON = Language.from_native(LANGUAGE_PYTHON)
|
||||
CPP = Language.from_native(LANGUAGE_CPP)
|
||||
JAVA = Language.from_native(LANGUAGE_JAVA)
|
||||
|
||||
|
||||
cdef int prepare_resources(
|
||||
@@ -261,17 +264,8 @@ cdef int prepare_resources(
|
||||
return 0
|
||||
|
||||
|
||||
cdef c_vector[c_string] string_vector_from_list(list string_list):
|
||||
cdef:
|
||||
c_vector[c_string] out
|
||||
for s in string_list:
|
||||
if not isinstance(s, bytes):
|
||||
raise TypeError("string_list elements must be bytes")
|
||||
out.push_back(s)
|
||||
return out
|
||||
|
||||
cdef void prepare_args(
|
||||
CoreWorker core_worker, list args, c_vector[CTaskArg] *args_vector):
|
||||
CoreWorker core_worker, args, c_vector[CTaskArg] *args_vector):
|
||||
cdef:
|
||||
size_t size
|
||||
int64_t put_threshold
|
||||
@@ -345,11 +339,10 @@ cdef execute_task(
|
||||
# Automatically restrict the GPUs available to this task.
|
||||
ray.utils.set_cuda_visible_devices(ray.get_gpu_ids())
|
||||
|
||||
descriptor = tuple(ray_function.GetFunctionDescriptor())
|
||||
function_descriptor = CFunctionDescriptorToPython(
|
||||
ray_function.GetFunctionDescriptor())
|
||||
|
||||
if <int>task_type == <int>TASK_TYPE_ACTOR_CREATION_TASK:
|
||||
function_descriptor = FunctionDescriptor.from_bytes_list(
|
||||
ray_function.GetFunctionDescriptor())
|
||||
actor_class = manager.load_actor_class(job_id, function_descriptor)
|
||||
actor_id = core_worker.get_actor_id()
|
||||
worker.actors[actor_id] = actor_class.__new__(actor_class)
|
||||
@@ -359,13 +352,11 @@ cdef execute_task(
|
||||
last_checkpoint_timestamp=int(1000 * time.time()),
|
||||
checkpoint_ids=[]))
|
||||
|
||||
execution_info = execution_infos.get(descriptor)
|
||||
execution_info = execution_infos.get(function_descriptor)
|
||||
if not execution_info:
|
||||
function_descriptor = FunctionDescriptor.from_bytes_list(
|
||||
ray_function.GetFunctionDescriptor())
|
||||
execution_info = manager.get_execution_info(
|
||||
job_id, function_descriptor)
|
||||
execution_infos[descriptor] = execution_info
|
||||
execution_infos[function_descriptor] = execution_info
|
||||
|
||||
function_name = execution_info.function_name
|
||||
extra_data = (b'{"name": ' + function_name.encode("ascii") +
|
||||
@@ -500,9 +491,6 @@ cdef execute_task(
|
||||
ray_signal.reset()
|
||||
|
||||
if execution_info.max_calls != 0:
|
||||
function_descriptor = FunctionDescriptor.from_bytes_list(
|
||||
ray_function.GetFunctionDescriptor())
|
||||
|
||||
# Reset the state of the worker for the next task to execute.
|
||||
# Increase the task execution counter.
|
||||
manager.increase_task_counter(job_id, function_descriptor)
|
||||
@@ -772,7 +760,8 @@ cdef class CoreWorker:
|
||||
message.decode("utf-8")))
|
||||
|
||||
def submit_task(self,
|
||||
function_descriptor,
|
||||
Language language,
|
||||
FunctionDescriptor function_descriptor,
|
||||
args,
|
||||
int num_return_vals,
|
||||
c_bool is_direct_call,
|
||||
@@ -790,7 +779,7 @@ cdef class CoreWorker:
|
||||
task_options = CTaskOptions(
|
||||
num_return_vals, is_direct_call, c_resources)
|
||||
ray_function = CRayFunction(
|
||||
LANGUAGE_PYTHON, string_vector_from_list(function_descriptor))
|
||||
language.lang, function_descriptor.descriptor)
|
||||
prepare_args(self, args, &args_vector)
|
||||
|
||||
with nogil:
|
||||
@@ -801,7 +790,8 @@ cdef class CoreWorker:
|
||||
return VectorToObjectIDs(return_ids)
|
||||
|
||||
def create_actor(self,
|
||||
function_descriptor,
|
||||
Language language,
|
||||
FunctionDescriptor function_descriptor,
|
||||
args,
|
||||
uint64_t max_reconstructions,
|
||||
resources,
|
||||
@@ -822,7 +812,7 @@ cdef class CoreWorker:
|
||||
prepare_resources(resources, &c_resources)
|
||||
prepare_resources(placement_resources, &c_placement_resources)
|
||||
ray_function = CRayFunction(
|
||||
LANGUAGE_PYTHON, string_vector_from_list(function_descriptor))
|
||||
language.lang, function_descriptor.descriptor)
|
||||
prepare_args(self, args, &args_vector)
|
||||
|
||||
with nogil:
|
||||
@@ -837,8 +827,9 @@ cdef class CoreWorker:
|
||||
return ActorID(c_actor_id.Binary())
|
||||
|
||||
def submit_actor_task(self,
|
||||
Language language,
|
||||
ActorID actor_id,
|
||||
function_descriptor,
|
||||
FunctionDescriptor function_descriptor,
|
||||
args,
|
||||
int num_return_vals,
|
||||
double num_method_cpus):
|
||||
@@ -856,7 +847,7 @@ cdef class CoreWorker:
|
||||
c_resources[b"CPU"] = num_method_cpus
|
||||
task_options = CTaskOptions(num_return_vals, False, c_resources)
|
||||
ray_function = CRayFunction(
|
||||
LANGUAGE_PYTHON, string_vector_from_list(function_descriptor))
|
||||
language.lang, function_descriptor.descriptor)
|
||||
prepare_args(self, args, &args_vector)
|
||||
|
||||
with nogil:
|
||||
|
||||
+140
-67
@@ -7,12 +7,13 @@ import weakref
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections import namedtuple
|
||||
|
||||
from ray.function_manager import FunctionDescriptor
|
||||
import ray.ray_constants as ray_constants
|
||||
import ray._raylet
|
||||
import ray.signature as signature
|
||||
import ray.worker
|
||||
from ray import ActorID, ActorClassID
|
||||
from ray import ActorID, ActorClassID, Language
|
||||
from ray._raylet import PythonFunctionDescriptor
|
||||
from ray import cross_language
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -143,8 +144,11 @@ class ActorClassMetadata:
|
||||
"""Metadata for an actor class.
|
||||
|
||||
Attributes:
|
||||
language: The actor language, e.g. Python, Java.
|
||||
modified_class: The original class that was decorated (with some
|
||||
additional methods added like __ray_terminate__).
|
||||
actor_creation_function_descriptor: The function descriptor for
|
||||
the actor creation task.
|
||||
class_id: The ID of this actor class.
|
||||
class_name: The name of this class.
|
||||
num_cpus: The default number of CPUs required by the actor creation
|
||||
@@ -154,7 +158,6 @@ class ActorClassMetadata:
|
||||
memory: The heap memory quota for this actor.
|
||||
object_store_memory: The object store memory quota for this actor.
|
||||
resources: The default resources required by the actor creation task.
|
||||
actor_method_cpus: The number of CPUs required by actor method tasks.
|
||||
last_export_session_and_job: A pair of the last exported session
|
||||
and job to help us to know whether this function was exported.
|
||||
This is an imperfect mechanism used to determine if we need to
|
||||
@@ -172,11 +175,17 @@ class ActorClassMetadata:
|
||||
each actor method.
|
||||
"""
|
||||
|
||||
def __init__(self, modified_class, class_id, max_reconstructions, num_cpus,
|
||||
num_gpus, memory, object_store_memory, resources):
|
||||
def __init__(self, language, modified_class,
|
||||
actor_creation_function_descriptor, class_id,
|
||||
max_reconstructions, num_cpus, num_gpus, memory,
|
||||
object_store_memory, resources):
|
||||
self.language = language
|
||||
self.modified_class = modified_class
|
||||
self.actor_creation_function_descriptor = \
|
||||
actor_creation_function_descriptor
|
||||
self.class_name = actor_creation_function_descriptor.class_name
|
||||
self.is_cross_language = language != Language.PYTHON
|
||||
self.class_id = class_id
|
||||
self.class_name = modified_class.__name__
|
||||
self.max_reconstructions = max_reconstructions
|
||||
self.num_cpus = num_cpus
|
||||
self.num_gpus = num_gpus
|
||||
@@ -192,7 +201,8 @@ class ActorClassMetadata:
|
||||
]
|
||||
|
||||
constructor_name = "__init__"
|
||||
if constructor_name not in self.actor_method_names:
|
||||
if not self.is_cross_language and \
|
||||
constructor_name not in self.actor_method_names:
|
||||
# Add __init__ if it does not exist.
|
||||
# Actor creation will be executed with __init__ together.
|
||||
|
||||
@@ -290,7 +300,10 @@ class ActorClass:
|
||||
def _ray_from_modified_class(cls, modified_class, class_id,
|
||||
max_reconstructions, num_cpus, num_gpus,
|
||||
memory, object_store_memory, resources):
|
||||
for attribute in ["remote", "_remote", "_ray_from_modified_class"]:
|
||||
for attribute in [
|
||||
"remote", "_remote", "_ray_from_modified_class",
|
||||
"_ray_from_function_descriptor"
|
||||
]:
|
||||
if hasattr(modified_class, attribute):
|
||||
logger.warning("Creating an actor from class {} overwrites "
|
||||
"attribute {} of that class".format(
|
||||
@@ -307,10 +320,28 @@ class ActorClass:
|
||||
DerivedActorClass.__qualname__ = name
|
||||
# Construct the base object.
|
||||
self = DerivedActorClass.__new__(DerivedActorClass)
|
||||
# Actor creation function descriptor.
|
||||
actor_creation_function_descriptor = PythonFunctionDescriptor(
|
||||
modified_class.__module__, "__init__", modified_class.__name__)
|
||||
|
||||
self.__ray_metadata__ = ActorClassMetadata(
|
||||
modified_class, class_id, max_reconstructions, num_cpus, num_gpus,
|
||||
memory, object_store_memory, resources)
|
||||
Language.PYTHON, modified_class,
|
||||
actor_creation_function_descriptor, class_id, max_reconstructions,
|
||||
num_cpus, num_gpus, memory, object_store_memory, resources)
|
||||
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def _ray_from_function_descriptor(cls, language,
|
||||
actor_creation_function_descriptor,
|
||||
max_reconstructions, num_cpus, num_gpus,
|
||||
memory, object_store_memory, resources):
|
||||
self = ActorClass.__new__(ActorClass)
|
||||
|
||||
self.__ray_metadata__ = ActorClassMetadata(
|
||||
language, None, actor_creation_function_descriptor, None,
|
||||
max_reconstructions, num_cpus, num_gpus, memory,
|
||||
object_store_memory, resources)
|
||||
|
||||
return self
|
||||
|
||||
@@ -460,29 +491,33 @@ class ActorClass:
|
||||
if meta.num_cpus is None else meta.num_cpus)
|
||||
actor_method_cpu = ray_constants.DEFAULT_ACTOR_METHOD_CPU_SPECIFIED
|
||||
|
||||
function_name = "__init__"
|
||||
function_descriptor = FunctionDescriptor(
|
||||
meta.modified_class.__module__, function_name,
|
||||
meta.modified_class.__name__)
|
||||
|
||||
# Do not export the actor class or the actor if run in LOCAL_MODE
|
||||
# Instead, instantiate the actor locally and add it to the worker's
|
||||
# dictionary
|
||||
if worker.mode == ray.LOCAL_MODE:
|
||||
assert not meta.is_cross_language, \
|
||||
"Cross language ActorClass cannot be executed locally."
|
||||
actor_id = ActorID.from_random()
|
||||
worker.actors[actor_id] = meta.modified_class(
|
||||
*copy.deepcopy(args), **copy.deepcopy(kwargs))
|
||||
else:
|
||||
# Export the actor.
|
||||
if (meta.last_export_session_and_job !=
|
||||
worker.current_session_and_job):
|
||||
if not meta.is_cross_language and (meta.last_export_session_and_job
|
||||
!=
|
||||
worker.current_session_and_job):
|
||||
# If this actor class was not exported in this session and job,
|
||||
# we need to export this function again, because current GCS
|
||||
# doesn't have it.
|
||||
meta.last_export_session_and_job = (
|
||||
worker.current_session_and_job)
|
||||
# After serialize / deserialize modified class, the __module__
|
||||
# of modified class will be ray.cloudpickle.cloudpickle.
|
||||
# So, here pass actor_creation_function_descriptor to make
|
||||
# sure export actor class correct.
|
||||
worker.function_actor_manager.export_actor_class(
|
||||
meta.modified_class, meta.actor_method_names)
|
||||
meta.modified_class,
|
||||
meta.actor_creation_function_descriptor,
|
||||
meta.actor_method_names)
|
||||
|
||||
resources = ray.utils.resources_from_resource_arguments(
|
||||
cpus_to_use, meta.num_gpus, meta.memory,
|
||||
@@ -497,24 +532,28 @@ class ActorClass:
|
||||
if actor_method_cpu == 1:
|
||||
actor_placement_resources = resources.copy()
|
||||
actor_placement_resources["CPU"] += 1
|
||||
function_signature = meta.method_signatures[function_name]
|
||||
creation_args = signature.flatten_args(function_signature, args,
|
||||
kwargs)
|
||||
if meta.is_cross_language:
|
||||
creation_args = cross_language.format_args(
|
||||
worker, args, kwargs)
|
||||
else:
|
||||
function_signature = meta.method_signatures["__init__"]
|
||||
creation_args = signature.flatten_args(function_signature,
|
||||
args, kwargs)
|
||||
actor_id = worker.core_worker.create_actor(
|
||||
function_descriptor.get_function_descriptor_list(),
|
||||
meta.language, meta.actor_creation_function_descriptor,
|
||||
creation_args, meta.max_reconstructions, resources,
|
||||
actor_placement_resources, is_direct_call, max_concurrency,
|
||||
detached, is_asyncio)
|
||||
|
||||
actor_handle = ActorHandle(
|
||||
meta.language,
|
||||
actor_id,
|
||||
meta.modified_class.__module__,
|
||||
meta.class_name,
|
||||
meta.actor_method_names,
|
||||
meta.method_decorators,
|
||||
meta.method_signatures,
|
||||
meta.actor_method_num_return_vals,
|
||||
actor_method_cpu,
|
||||
meta.is_cross_language,
|
||||
meta.actor_creation_function_descriptor,
|
||||
worker.current_session_and_job,
|
||||
original_handle=True)
|
||||
|
||||
@@ -536,9 +575,8 @@ class ActorHandle:
|
||||
cloudpickle).
|
||||
|
||||
Attributes:
|
||||
_ray_actor_language: The actor language.
|
||||
_ray_actor_id: Actor ID.
|
||||
_ray_module_name: The module name of this actor.
|
||||
_ray_actor_method_names: The names of the actor methods.
|
||||
_ray_method_decorators: Optional decorators for the function
|
||||
invocation. This can be used to change the behavior on the
|
||||
invocation side, whereas a regular decorator can be used to change
|
||||
@@ -546,48 +584,55 @@ class ActorHandle:
|
||||
_ray_method_signatures: The signatures of the actor methods.
|
||||
_ray_method_num_return_vals: The default number of return values for
|
||||
each method.
|
||||
_ray_class_name: The name of the actor class.
|
||||
_ray_actor_method_cpus: The number of CPUs required by actor methods.
|
||||
_ray_original_handle: True if this is the original actor handle for a
|
||||
given actor. If this is true, then the actor will be destroyed when
|
||||
this handle goes out of scope.
|
||||
_ray_is_cross_language: Whether this actor is cross language.
|
||||
_ray_actor_creation_function_descriptor: The function descriptor
|
||||
of the actor creation task.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
language,
|
||||
actor_id,
|
||||
module_name,
|
||||
class_name,
|
||||
actor_method_names,
|
||||
method_decorators,
|
||||
method_signatures,
|
||||
method_num_return_vals,
|
||||
actor_method_cpus,
|
||||
is_cross_language,
|
||||
actor_creation_function_descriptor,
|
||||
session_and_job,
|
||||
original_handle=False):
|
||||
self._ray_actor_language = language
|
||||
self._ray_actor_id = actor_id
|
||||
self._ray_module_name = module_name
|
||||
self._ray_original_handle = original_handle
|
||||
self._ray_actor_method_names = actor_method_names
|
||||
self._ray_method_decorators = method_decorators
|
||||
self._ray_method_signatures = method_signatures
|
||||
self._ray_method_num_return_vals = method_num_return_vals
|
||||
self._ray_class_name = class_name
|
||||
self._ray_actor_method_cpus = actor_method_cpus
|
||||
self._ray_session_and_job = session_and_job
|
||||
self._ray_function_descriptor_lists = {
|
||||
method_name: FunctionDescriptor(
|
||||
self._ray_module_name, method_name,
|
||||
self._ray_class_name).get_function_descriptor_list()
|
||||
for method_name in self._ray_method_signatures.keys()
|
||||
}
|
||||
self._ray_is_cross_language = is_cross_language
|
||||
self._ray_actor_creation_function_descriptor = \
|
||||
actor_creation_function_descriptor
|
||||
self._ray_function_descriptor = {}
|
||||
|
||||
for method_name in actor_method_names:
|
||||
method = ActorMethod(
|
||||
self,
|
||||
method_name,
|
||||
self._ray_method_num_return_vals[method_name],
|
||||
decorator=self._ray_method_decorators.get(method_name))
|
||||
setattr(self, method_name, method)
|
||||
if not self._ray_is_cross_language:
|
||||
assert isinstance(actor_creation_function_descriptor,
|
||||
PythonFunctionDescriptor)
|
||||
module_name = actor_creation_function_descriptor.module_name
|
||||
class_name = actor_creation_function_descriptor.class_name
|
||||
for method_name in self._ray_method_signatures.keys():
|
||||
function_descriptor = PythonFunctionDescriptor(
|
||||
module_name, method_name, class_name)
|
||||
self._ray_function_descriptor[
|
||||
method_name] = function_descriptor
|
||||
method = ActorMethod(
|
||||
self,
|
||||
method_name,
|
||||
self._ray_method_num_return_vals[method_name],
|
||||
decorator=self._ray_method_decorators.get(method_name))
|
||||
setattr(self, method_name, method)
|
||||
|
||||
def _actor_method_call(self,
|
||||
method_name,
|
||||
@@ -615,22 +660,34 @@ class ActorHandle:
|
||||
|
||||
args = args or []
|
||||
kwargs = kwargs or {}
|
||||
function_signature = self._ray_method_signatures[method_name]
|
||||
|
||||
if not args and not kwargs and not function_signature:
|
||||
list_args = []
|
||||
if self._ray_is_cross_language:
|
||||
list_args = cross_language.format_args(worker, args, kwargs)
|
||||
function_descriptor = \
|
||||
cross_language.get_function_descriptor_for_actor_method(
|
||||
self._ray_actor_language,
|
||||
self._ray_actor_creation_function_descriptor, method_name)
|
||||
else:
|
||||
list_args = signature.flatten_args(function_signature, args,
|
||||
kwargs)
|
||||
function_signature = self._ray_method_signatures[method_name]
|
||||
|
||||
if not args and not kwargs and not function_signature:
|
||||
list_args = []
|
||||
else:
|
||||
list_args = signature.flatten_args(function_signature, args,
|
||||
kwargs)
|
||||
function_descriptor = self._ray_function_descriptor[method_name]
|
||||
|
||||
if worker.mode == ray.LOCAL_MODE:
|
||||
assert not self._ray_is_cross_language,\
|
||||
"Cross language remote actor method " \
|
||||
"cannot be executed locally."
|
||||
function = getattr(worker.actors[self._actor_id], method_name)
|
||||
object_ids = worker.local_mode_manager.execute(
|
||||
function, method_name, args, kwargs, num_return_vals)
|
||||
else:
|
||||
object_ids = worker.core_worker.submit_actor_task(
|
||||
self._ray_actor_id,
|
||||
self._ray_function_descriptor_lists[method_name], list_args,
|
||||
num_return_vals, self._ray_actor_method_cpus)
|
||||
self._ray_actor_language, self._ray_actor_id,
|
||||
function_descriptor, list_args, num_return_vals,
|
||||
self._ray_actor_method_cpus)
|
||||
|
||||
if len(object_ids) == 1:
|
||||
object_ids = object_ids[0]
|
||||
@@ -639,13 +696,28 @@ class ActorHandle:
|
||||
|
||||
return object_ids
|
||||
|
||||
def __getattr__(self, item):
|
||||
if not self._ray_is_cross_language:
|
||||
raise AttributeError("'{}' object has no attribute '{}'".format(
|
||||
type(self).__name__, item))
|
||||
|
||||
return ActorMethod(
|
||||
self,
|
||||
item,
|
||||
ray_constants.
|
||||
# Currently, we use default num returns
|
||||
DEFAULT_ACTOR_METHOD_NUM_RETURN_VALS,
|
||||
# Currently, cross-lang actor method not support decorator
|
||||
decorator=None)
|
||||
|
||||
# Make tab completion work.
|
||||
def __dir__(self):
|
||||
return self._ray_actor_method_names
|
||||
return self._ray_method_signatures.keys()
|
||||
|
||||
def __repr__(self):
|
||||
return "Actor({}, {})".format(self._ray_class_name,
|
||||
self._actor_id.hex())
|
||||
return "Actor({}, {})".format(
|
||||
self._ray_actor_creation_function_descriptor.class_name,
|
||||
self._actor_id.hex())
|
||||
|
||||
def __del__(self):
|
||||
"""Terminate the worker that is running this actor."""
|
||||
@@ -666,7 +738,7 @@ class ActorHandle:
|
||||
logger.warning(
|
||||
"Actor is garbage collected in the wrong driver." +
|
||||
" Actor id = %s, class name = %s.", self._ray_actor_id,
|
||||
self._ray_class_name)
|
||||
self._ray_actor_creation_function_descriptor.class_name)
|
||||
return
|
||||
if worker.connected and self._ray_original_handle:
|
||||
# Note: in py2 the weakref is destroyed prior to calling __del__
|
||||
@@ -708,17 +780,18 @@ class ActorHandle:
|
||||
worker = ray.worker.get_global_worker()
|
||||
worker.check_connected()
|
||||
state = {
|
||||
"actor_language": self._ray_actor_language,
|
||||
# Local mode just uses the actor ID.
|
||||
"core_handle": worker.core_worker.serialize_actor_handle(
|
||||
self._ray_actor_id)
|
||||
if hasattr(worker, "core_worker") else self._ray_actor_id,
|
||||
"module_name": self._ray_module_name,
|
||||
"class_name": self._ray_class_name,
|
||||
"actor_method_names": self._ray_actor_method_names,
|
||||
"method_decorators": self._ray_method_decorators,
|
||||
"method_signatures": self._ray_method_signatures,
|
||||
"method_num_return_vals": self._ray_method_num_return_vals,
|
||||
"actor_method_cpus": self._ray_actor_method_cpus
|
||||
"actor_method_cpus": self._ray_actor_method_cpus,
|
||||
"is_cross_language": self._ray_is_cross_language,
|
||||
"actor_creation_function_descriptor": self.
|
||||
_ray_actor_creation_function_descriptor,
|
||||
}
|
||||
|
||||
return state
|
||||
@@ -738,16 +811,16 @@ class ActorHandle:
|
||||
# TODO(swang): Accessing the worker's current task ID is not
|
||||
# thread-safe.
|
||||
# Local mode just uses the actor ID.
|
||||
state["actor_language"],
|
||||
worker.core_worker.deserialize_and_register_actor_handle(
|
||||
state["core_handle"])
|
||||
if hasattr(worker, "core_worker") else state["core_handle"],
|
||||
state["module_name"],
|
||||
state["class_name"],
|
||||
state["actor_method_names"],
|
||||
state["method_decorators"],
|
||||
state["method_signatures"],
|
||||
state["method_num_return_vals"],
|
||||
state["actor_method_cpus"],
|
||||
state["is_cross_language"],
|
||||
state["actor_creation_function_descriptor"],
|
||||
worker.current_session_and_job)
|
||||
|
||||
def __getstate__(self):
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from ray import Language
|
||||
from ray._raylet import JavaFunctionDescriptor
|
||||
|
||||
__all__ = [
|
||||
"java_function",
|
||||
"java_actor_class",
|
||||
]
|
||||
|
||||
|
||||
def format_args(worker, args, kwargs):
|
||||
"""Format args for various languages.
|
||||
|
||||
Args:
|
||||
worker: The global worker instance.
|
||||
args: The arguments for cross language.
|
||||
kwargs: The keyword arguments for cross language.
|
||||
|
||||
Returns:
|
||||
List of args and kwargs (if supported).
|
||||
"""
|
||||
if not worker.load_code_from_local:
|
||||
raise Exception("Cross language feature needs "
|
||||
"--load-code-from-local to be set.")
|
||||
if kwargs:
|
||||
raise Exception("Cross language remote functions "
|
||||
"does not support kwargs.")
|
||||
return args
|
||||
|
||||
|
||||
def get_function_descriptor_for_actor_method(
|
||||
language, actor_creation_function_descriptor, method_name):
|
||||
"""Get function descriptor for cross language actor method call.
|
||||
|
||||
Args:
|
||||
language: Target language.
|
||||
actor_creation_function_descriptor:
|
||||
The function signature for actor creation.
|
||||
method_name: The name of actor method.
|
||||
|
||||
Returns:
|
||||
Function descriptor for cross language actor method call.
|
||||
"""
|
||||
if language == Language.JAVA:
|
||||
return JavaFunctionDescriptor(
|
||||
actor_creation_function_descriptor.class_name,
|
||||
method_name,
|
||||
# Currently not support call actor method with signature.
|
||||
"")
|
||||
else:
|
||||
raise NotImplementedError("Cross language remote actor method "
|
||||
"not support language {}".format(language))
|
||||
|
||||
|
||||
def java_function(class_name, function_name):
|
||||
from ray.remote_function import RemoteFunction
|
||||
return RemoteFunction(
|
||||
Language.JAVA,
|
||||
lambda *args, **kwargs: None,
|
||||
JavaFunctionDescriptor(class_name, function_name, ""),
|
||||
None, # num_cpus,
|
||||
None, # num_gpus,
|
||||
None, # memory,
|
||||
None, # object_store_memory,
|
||||
None, # resources,
|
||||
None, # num_return_vals,
|
||||
None, # max_calls,
|
||||
None) # max_retries)
|
||||
|
||||
|
||||
def java_actor_class(class_name):
|
||||
from ray.actor import ActorClass
|
||||
return ActorClass._ray_from_function_descriptor(
|
||||
Language.JAVA,
|
||||
JavaFunctionDescriptor(class_name, "<init>", ""),
|
||||
0, # max_reconstructions,
|
||||
None, # num_cpus,
|
||||
None, # num_gpus,
|
||||
None, # memory,
|
||||
None, # object_store_memory,
|
||||
None) # resources,
|
||||
@@ -441,12 +441,6 @@ class NodeStats(threading.Thread):
|
||||
if addr in self._addr_to_actor_id:
|
||||
actor_info = flattened_tree[self._addr_to_actor_id[
|
||||
addr]]
|
||||
if "currentTaskFuncDesc" in core_worker_stats:
|
||||
core_worker_stats[
|
||||
"currentTaskFuncDesc"] = list(
|
||||
map(
|
||||
b64_decode, core_worker_stats[
|
||||
"currentTaskFuncDesc"]))
|
||||
format_reply_id(core_worker_stats)
|
||||
actor_info.update(core_worker_stats)
|
||||
actor_info["averageTaskExecutionSpeed"] = round(
|
||||
@@ -464,8 +458,6 @@ class NodeStats(threading.Thread):
|
||||
caller_id = self._addr_to_actor_id.get(caller_addr, "root")
|
||||
child_to_parent[actor_id] = caller_id
|
||||
infeasible_task["state"] = -1
|
||||
infeasible_task["functionDescriptor"] = list(
|
||||
map(b64_decode, infeasible_task["functionDescriptor"]))
|
||||
format_reply_id(infeasible_tasks)
|
||||
flattened_tree[actor_id] = infeasible_task
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ import ray
|
||||
from ray import profiling
|
||||
from ray import ray_constants
|
||||
from ray import cloudpickle as pickle
|
||||
from ray._raylet import PythonFunctionDescriptor
|
||||
from ray.utils import (
|
||||
binary_to_hex,
|
||||
is_function_or_method,
|
||||
is_class_method,
|
||||
is_static_method,
|
||||
@@ -36,226 +36,6 @@ FunctionExecutionInfo = namedtuple("FunctionExecutionInfo",
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FunctionDescriptor:
|
||||
"""A class used to describe a python function.
|
||||
|
||||
Attributes:
|
||||
module_name: the module name that the function belongs to.
|
||||
class_name: the class name that the function belongs to if exists.
|
||||
It could be empty is the function is not a class method.
|
||||
function_name: the function name of the function.
|
||||
function_hash: the hash code of the function source code if the
|
||||
function code is available.
|
||||
function_id: the function id calculated from this descriptor.
|
||||
is_for_driver_task: whether this descriptor is for driver task.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
module_name,
|
||||
function_name,
|
||||
class_name="",
|
||||
function_source_hash=b""):
|
||||
self._module_name = module_name
|
||||
self._class_name = class_name
|
||||
self._function_name = function_name
|
||||
self._function_source_hash = function_source_hash
|
||||
self._function_id = self._get_function_id()
|
||||
|
||||
def __repr__(self):
|
||||
return ("FunctionDescriptor:" + self._module_name + "." +
|
||||
self._class_name + "." + self._function_name + "." +
|
||||
binary_to_hex(self._function_source_hash))
|
||||
|
||||
@classmethod
|
||||
def from_bytes_list(cls, function_descriptor_list):
|
||||
"""Create a FunctionDescriptor instance from list of bytes.
|
||||
|
||||
This function is used to create the function descriptor from
|
||||
backend data.
|
||||
|
||||
Args:
|
||||
cls: Current class which is required argument for classmethod.
|
||||
function_descriptor_list: list of bytes to represent the
|
||||
function descriptor.
|
||||
|
||||
Returns:
|
||||
The FunctionDescriptor instance created from the bytes list.
|
||||
"""
|
||||
assert isinstance(function_descriptor_list, list)
|
||||
if len(function_descriptor_list) == 0:
|
||||
# This is a function descriptor of driver task.
|
||||
return FunctionDescriptor.for_driver_task()
|
||||
elif (len(function_descriptor_list) == 3
|
||||
or len(function_descriptor_list) == 4):
|
||||
module_name = ensure_str(function_descriptor_list[0])
|
||||
class_name = ensure_str(function_descriptor_list[1])
|
||||
function_name = ensure_str(function_descriptor_list[2])
|
||||
if len(function_descriptor_list) == 4:
|
||||
return cls(module_name, function_name, class_name,
|
||||
function_descriptor_list[3])
|
||||
else:
|
||||
return cls(module_name, function_name, class_name)
|
||||
else:
|
||||
raise Exception(
|
||||
"Invalid input for FunctionDescriptor.from_bytes_list")
|
||||
|
||||
@classmethod
|
||||
def from_function(cls, function, pickled_function):
|
||||
"""Create a FunctionDescriptor from a function instance.
|
||||
|
||||
This function is used to create the function descriptor from
|
||||
a python function. If a function is a class function, it should
|
||||
not be used by this function.
|
||||
|
||||
Args:
|
||||
cls: Current class which is required argument for classmethod.
|
||||
function: the python function used to create the function
|
||||
descriptor.
|
||||
pickled_function: This is factored in to ensure that any
|
||||
modifications to the function result in a different function
|
||||
descriptor.
|
||||
|
||||
Returns:
|
||||
The FunctionDescriptor instance created according to the function.
|
||||
"""
|
||||
module_name = function.__module__
|
||||
function_name = function.__name__
|
||||
class_name = ""
|
||||
|
||||
pickled_function_hash = hashlib.sha1(pickled_function).digest()
|
||||
|
||||
return cls(module_name, function_name, class_name,
|
||||
pickled_function_hash)
|
||||
|
||||
@classmethod
|
||||
def from_class(cls, target_class):
|
||||
"""Create a FunctionDescriptor from a class.
|
||||
|
||||
Args:
|
||||
cls: Current class which is required argument for classmethod.
|
||||
target_class: the python class used to create the function
|
||||
descriptor.
|
||||
|
||||
Returns:
|
||||
The FunctionDescriptor instance created according to the class.
|
||||
"""
|
||||
module_name = target_class.__module__
|
||||
class_name = target_class.__name__
|
||||
return cls(module_name, "__init__", class_name)
|
||||
|
||||
@classmethod
|
||||
def for_driver_task(cls):
|
||||
"""Create a FunctionDescriptor instance for a driver task."""
|
||||
return cls("", "", "", b"")
|
||||
|
||||
@property
|
||||
def is_for_driver_task(self):
|
||||
"""See whether this function descriptor is for a driver or not.
|
||||
|
||||
Returns:
|
||||
True if this function descriptor is for driver tasks.
|
||||
"""
|
||||
return all(
|
||||
len(x) == 0
|
||||
for x in [self.module_name, self.class_name, self.function_name])
|
||||
|
||||
@property
|
||||
def module_name(self):
|
||||
"""Get the module name of current function descriptor.
|
||||
|
||||
Returns:
|
||||
The module name of the function descriptor.
|
||||
"""
|
||||
return self._module_name
|
||||
|
||||
@property
|
||||
def class_name(self):
|
||||
"""Get the class name of current function descriptor.
|
||||
|
||||
Returns:
|
||||
The class name of the function descriptor. It could be
|
||||
empty if the function is not a class method.
|
||||
"""
|
||||
return self._class_name
|
||||
|
||||
@property
|
||||
def function_name(self):
|
||||
"""Get the function name of current function descriptor.
|
||||
|
||||
Returns:
|
||||
The function name of the function descriptor.
|
||||
"""
|
||||
return self._function_name
|
||||
|
||||
@property
|
||||
def function_hash(self):
|
||||
"""Get the hash code of the function source code.
|
||||
|
||||
Returns:
|
||||
The bytes with length of ray_constants.ID_SIZE if the source
|
||||
code is available. Otherwise, the bytes length will be 0.
|
||||
"""
|
||||
return self._function_source_hash
|
||||
|
||||
@property
|
||||
def function_id(self):
|
||||
"""Get the function id calculated from this descriptor.
|
||||
|
||||
Returns:
|
||||
The value of ray.ObjectID that represents the function id.
|
||||
"""
|
||||
return self._function_id
|
||||
|
||||
def _get_function_id(self):
|
||||
"""Calculate the function id of current function descriptor.
|
||||
|
||||
This function id is calculated from all the fields of function
|
||||
descriptor.
|
||||
|
||||
Returns:
|
||||
ray.ObjectID to represent the function descriptor.
|
||||
"""
|
||||
if self.is_for_driver_task:
|
||||
return ray.FunctionID.nil()
|
||||
function_id_hash = hashlib.sha1()
|
||||
# Include the function module and name in the hash.
|
||||
function_id_hash.update(self.module_name.encode("ascii"))
|
||||
function_id_hash.update(self.function_name.encode("ascii"))
|
||||
function_id_hash.update(self.class_name.encode("ascii"))
|
||||
function_id_hash.update(self._function_source_hash)
|
||||
# Compute the function ID.
|
||||
function_id = function_id_hash.digest()
|
||||
return ray.FunctionID(function_id)
|
||||
|
||||
def get_function_descriptor_list(self):
|
||||
"""Return a list of bytes representing the function descriptor.
|
||||
|
||||
This function is used to pass this function descriptor to backend.
|
||||
|
||||
Returns:
|
||||
A list of bytes.
|
||||
"""
|
||||
descriptor_list = []
|
||||
if self.is_for_driver_task:
|
||||
# Driver task returns an empty list.
|
||||
return descriptor_list
|
||||
else:
|
||||
descriptor_list.append(self.module_name.encode("ascii"))
|
||||
descriptor_list.append(self.class_name.encode("ascii"))
|
||||
descriptor_list.append(self.function_name.encode("ascii"))
|
||||
if len(self._function_source_hash) != 0:
|
||||
descriptor_list.append(self._function_source_hash)
|
||||
return descriptor_list
|
||||
|
||||
def is_actor_method(self):
|
||||
"""Wether this function descriptor is an actor method.
|
||||
|
||||
Returns:
|
||||
True if it's an actor method, False if it's a normal function.
|
||||
"""
|
||||
return len(self._class_name) > 0
|
||||
|
||||
|
||||
class FunctionActorManager:
|
||||
"""A class used to export/load remote functions and actors.
|
||||
|
||||
@@ -488,7 +268,7 @@ class FunctionActorManager:
|
||||
self._num_task_executions[job_id][function_id] = 0
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Failed to load function %s.".format(function_name))
|
||||
"Failed to load function {}.".format(function_name))
|
||||
raise Exception(
|
||||
"Function {} failed to be loaded from local code.".format(
|
||||
function_descriptor))
|
||||
@@ -551,10 +331,10 @@ class FunctionActorManager:
|
||||
self._worker.redis_client.hmset(key, actor_class_info)
|
||||
self._worker.redis_client.rpush("Exports", key)
|
||||
|
||||
def export_actor_class(self, Class, actor_method_names):
|
||||
def export_actor_class(self, Class, actor_creation_function_descriptor,
|
||||
actor_method_names):
|
||||
if self._worker.load_code_from_local:
|
||||
return
|
||||
function_descriptor = FunctionDescriptor.from_class(Class)
|
||||
# `current_job_id` shouldn't be NIL, unless:
|
||||
# 1) This worker isn't an actor;
|
||||
# 2) And a previous task started a background thread, which didn't
|
||||
@@ -565,10 +345,10 @@ class FunctionActorManager:
|
||||
"please make sure the thread finishes before the task finishes.")
|
||||
job_id = self._worker.current_job_id
|
||||
key = (b"ActorClass:" + job_id.binary() + b":" +
|
||||
function_descriptor.function_id.binary())
|
||||
actor_creation_function_descriptor.function_id.binary())
|
||||
actor_class_info = {
|
||||
"class_name": Class.__name__,
|
||||
"module": Class.__module__,
|
||||
"class_name": actor_creation_function_descriptor.class_name,
|
||||
"module": actor_creation_function_descriptor.module_name,
|
||||
"class": pickle.dumps(Class),
|
||||
"job_id": job_id.binary(),
|
||||
"collision_identifier": self.compute_collision_identifier(Class),
|
||||
@@ -617,7 +397,7 @@ class FunctionActorManager:
|
||||
actor_methods = inspect.getmembers(
|
||||
actor_class, predicate=is_function_or_method)
|
||||
for actor_method_name, actor_method in actor_methods:
|
||||
method_descriptor = FunctionDescriptor(
|
||||
method_descriptor = PythonFunctionDescriptor(
|
||||
module_name, actor_method_name, actor_class_name)
|
||||
method_id = method_descriptor.function_id
|
||||
executor = self._make_actor_method_executor(
|
||||
|
||||
@@ -13,6 +13,9 @@ from ray.includes.unique_ids cimport (
|
||||
CObjectID,
|
||||
CTaskID,
|
||||
)
|
||||
from ray.includes.function_descriptor cimport (
|
||||
CFunctionDescriptor,
|
||||
)
|
||||
|
||||
|
||||
cdef extern from * namespace "polyfill":
|
||||
@@ -201,9 +204,9 @@ cdef extern from "ray/core_worker/common.h" nogil:
|
||||
cdef cppclass CRayFunction "ray::RayFunction":
|
||||
CRayFunction()
|
||||
CRayFunction(CLanguage language,
|
||||
const c_vector[c_string] function_descriptor)
|
||||
const CFunctionDescriptor &function_descriptor)
|
||||
CLanguage GetLanguage()
|
||||
const c_vector[c_string]& GetFunctionDescriptor()
|
||||
const CFunctionDescriptor GetFunctionDescriptor()
|
||||
|
||||
cdef cppclass CTaskArg "ray::TaskArg":
|
||||
@staticmethod
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
from libc.stdint cimport uint8_t, uint64_t
|
||||
from libcpp cimport bool as c_bool
|
||||
from libcpp.memory cimport unique_ptr, shared_ptr
|
||||
from libcpp.string cimport string as c_string
|
||||
from libcpp.unordered_map cimport unordered_map
|
||||
from libcpp.vector cimport vector as c_vector
|
||||
|
||||
from ray.includes.common cimport (
|
||||
CLanguage,
|
||||
ResourceSet,
|
||||
)
|
||||
from ray.includes.unique_ids cimport (
|
||||
CActorID,
|
||||
CJobID,
|
||||
CObjectID,
|
||||
CTaskID,
|
||||
)
|
||||
|
||||
cdef extern from "ray/protobuf/common.pb.h" nogil:
|
||||
cdef cppclass CFunctionDescriptorType \
|
||||
"ray::FunctionDescriptorType":
|
||||
pass
|
||||
|
||||
cdef CFunctionDescriptorType EmptyFunctionDescriptorType \
|
||||
"ray::FunctionDescriptorType::FUNCTION_DESCRIPTOR_NOT_SET"
|
||||
cdef CFunctionDescriptorType JavaFunctionDescriptorType \
|
||||
"ray::FunctionDescriptorType::kJavaFunctionDescriptor"
|
||||
cdef CFunctionDescriptorType PythonFunctionDescriptorType \
|
||||
"ray::FunctionDescriptorType::kPythonFunctionDescriptor"
|
||||
|
||||
|
||||
cdef extern from "ray/common/function_descriptor.h" nogil:
|
||||
cdef cppclass CFunctionDescriptorInterface \
|
||||
"ray::CFunctionDescriptorInterface":
|
||||
CFunctionDescriptorType Type()
|
||||
c_string ToString()
|
||||
c_string Serialize()
|
||||
|
||||
ctypedef shared_ptr[CFunctionDescriptorInterface] CFunctionDescriptor \
|
||||
"ray::FunctionDescriptor"
|
||||
|
||||
cdef cppclass CFunctionDescriptorBuilder "ray::FunctionDescriptorBuilder":
|
||||
@staticmethod
|
||||
CFunctionDescriptor Empty()
|
||||
|
||||
@staticmethod
|
||||
CFunctionDescriptor BuildJava(const c_string &class_name,
|
||||
const c_string &function_name,
|
||||
const c_string &signature)
|
||||
|
||||
@staticmethod
|
||||
CFunctionDescriptor BuildPython(const c_string &module_name,
|
||||
const c_string &class_name,
|
||||
const c_string &function_name,
|
||||
const c_string &function_source_hash)
|
||||
|
||||
@staticmethod
|
||||
CFunctionDescriptor Deserialize(const c_string &serialized_binary)
|
||||
|
||||
cdef cppclass CJavaFunctionDescriptor "ray::JavaFunctionDescriptor":
|
||||
c_string ClassName()
|
||||
c_string FunctionName()
|
||||
c_string Signature()
|
||||
|
||||
cdef cppclass CPythonFunctionDescriptor "ray::PythonFunctionDescriptor":
|
||||
c_string ModuleName()
|
||||
c_string ClassName()
|
||||
c_string FunctionName()
|
||||
c_string FunctionHash()
|
||||
@@ -0,0 +1,285 @@
|
||||
from ray.includes.function_descriptor cimport (
|
||||
CFunctionDescriptor,
|
||||
CFunctionDescriptorBuilder,
|
||||
CPythonFunctionDescriptor,
|
||||
CJavaFunctionDescriptor,
|
||||
EmptyFunctionDescriptorType,
|
||||
JavaFunctionDescriptorType,
|
||||
PythonFunctionDescriptorType,
|
||||
)
|
||||
|
||||
import hashlib
|
||||
import cython
|
||||
import inspect
|
||||
|
||||
|
||||
ctypedef object (*FunctionDescriptor_from_cpp)(const CFunctionDescriptor &)
|
||||
cdef unordered_map[int, FunctionDescriptor_from_cpp] \
|
||||
FunctionDescriptor_constructor_map
|
||||
cdef CFunctionDescriptorToPython(CFunctionDescriptor function_descriptor):
|
||||
cdef int function_descriptor_type = <int>function_descriptor.get().Type()
|
||||
it = FunctionDescriptor_constructor_map.find(function_descriptor_type)
|
||||
if it == FunctionDescriptor_constructor_map.end():
|
||||
raise Exception("Can't construct FunctionDescriptor from type {}"
|
||||
.format(function_descriptor_type))
|
||||
else:
|
||||
constructor = dereference(it).second
|
||||
return constructor(function_descriptor)
|
||||
|
||||
|
||||
@cython.auto_pickle(False)
|
||||
cdef class FunctionDescriptor:
|
||||
def __cinit__(self, *args, **kwargs):
|
||||
if type(self) == FunctionDescriptor:
|
||||
raise Exception("type {} is abstract".format(type(self).__name__))
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.descriptor.get().ToString())
|
||||
|
||||
def __eq__(self, other):
|
||||
return (type(self) == type(other) and
|
||||
self.descriptor.get().ToString() ==
|
||||
(<FunctionDescriptor>other).descriptor.get().ToString())
|
||||
|
||||
def __repr__(self):
|
||||
return <str>self.descriptor.get().ToString()
|
||||
|
||||
def to_dict(self):
|
||||
d = {"type": type(self).__name__}
|
||||
for k, v in vars(type(self)).items():
|
||||
if inspect.isgetsetdescriptor(v):
|
||||
d[k] = v.__get__(self)
|
||||
return d
|
||||
|
||||
|
||||
FunctionDescriptor_constructor_map[<int>EmptyFunctionDescriptorType] = \
|
||||
EmptyFunctionDescriptor.from_cpp
|
||||
|
||||
|
||||
@cython.auto_pickle(False)
|
||||
cdef class EmptyFunctionDescriptor(FunctionDescriptor):
|
||||
def __cinit__(self):
|
||||
self.descriptor = CFunctionDescriptorBuilder.Empty()
|
||||
|
||||
def __reduce__(self):
|
||||
return EmptyFunctionDescriptor, ()
|
||||
|
||||
@staticmethod
|
||||
cdef from_cpp(const CFunctionDescriptor &c_function_descriptor):
|
||||
return EmptyFunctionDescriptor()
|
||||
|
||||
|
||||
FunctionDescriptor_constructor_map[<int>JavaFunctionDescriptorType] = \
|
||||
JavaFunctionDescriptor.from_cpp
|
||||
|
||||
|
||||
@cython.auto_pickle(False)
|
||||
cdef class JavaFunctionDescriptor(FunctionDescriptor):
|
||||
cdef:
|
||||
CJavaFunctionDescriptor *typed_descriptor
|
||||
|
||||
def __cinit__(self,
|
||||
class_name,
|
||||
function_name,
|
||||
signature):
|
||||
self.descriptor = CFunctionDescriptorBuilder.BuildJava(
|
||||
class_name, function_name, signature)
|
||||
self.typed_descriptor = <CJavaFunctionDescriptor*>(
|
||||
self.descriptor.get())
|
||||
|
||||
def __reduce__(self):
|
||||
return JavaFunctionDescriptor, (self.typed_descriptor.ClassName(),
|
||||
self.typed_descriptor.FunctionName(),
|
||||
self.typed_descriptor.Signature())
|
||||
|
||||
@staticmethod
|
||||
cdef from_cpp(const CFunctionDescriptor &c_function_descriptor):
|
||||
cdef CJavaFunctionDescriptor *typed_descriptor = \
|
||||
<CJavaFunctionDescriptor*>(c_function_descriptor.get())
|
||||
return JavaFunctionDescriptor(typed_descriptor.ClassName(),
|
||||
typed_descriptor.FunctionName(),
|
||||
typed_descriptor.Signature())
|
||||
|
||||
@property
|
||||
def class_name(self):
|
||||
"""Get the class name of current function descriptor.
|
||||
|
||||
Returns:
|
||||
The class name of the function descriptor. It could be
|
||||
empty if the function is not a class method.
|
||||
"""
|
||||
return <str>self.typed_descriptor.ClassName()
|
||||
|
||||
@property
|
||||
def function_name(self):
|
||||
"""Get the function name of current function descriptor.
|
||||
|
||||
Returns:
|
||||
The function name of the function descriptor.
|
||||
"""
|
||||
return <str>self.typed_descriptor.FunctionName()
|
||||
|
||||
@property
|
||||
def signature(self):
|
||||
"""Get the signature of current function descriptor.
|
||||
|
||||
Returns:
|
||||
The signature of the function descriptor.
|
||||
"""
|
||||
return <str>self.typed_descriptor.Signature()
|
||||
|
||||
|
||||
FunctionDescriptor_constructor_map[<int>PythonFunctionDescriptorType] = \
|
||||
PythonFunctionDescriptor.from_cpp
|
||||
|
||||
|
||||
@cython.auto_pickle(False)
|
||||
cdef class PythonFunctionDescriptor(FunctionDescriptor):
|
||||
cdef:
|
||||
CPythonFunctionDescriptor *typed_descriptor
|
||||
object _function_id
|
||||
|
||||
def __cinit__(self,
|
||||
module_name,
|
||||
function_name,
|
||||
class_name="",
|
||||
function_source_hash=""):
|
||||
self.descriptor = CFunctionDescriptorBuilder.BuildPython(
|
||||
module_name, class_name, function_name, function_source_hash)
|
||||
self.typed_descriptor = <CPythonFunctionDescriptor*>(
|
||||
self.descriptor.get())
|
||||
|
||||
def __reduce__(self):
|
||||
return PythonFunctionDescriptor, (self.typed_descriptor.ModuleName(),
|
||||
self.typed_descriptor.FunctionName(),
|
||||
self.typed_descriptor.ClassName(),
|
||||
self.typed_descriptor.FunctionHash())
|
||||
|
||||
@staticmethod
|
||||
cdef from_cpp(const CFunctionDescriptor &c_function_descriptor):
|
||||
cdef CPythonFunctionDescriptor *typed_descriptor = \
|
||||
<CPythonFunctionDescriptor*>(c_function_descriptor.get())
|
||||
return PythonFunctionDescriptor(typed_descriptor.ModuleName(),
|
||||
typed_descriptor.FunctionName(),
|
||||
typed_descriptor.ClassName(),
|
||||
typed_descriptor.FunctionHash())
|
||||
|
||||
@classmethod
|
||||
def from_function(cls, function, pickled_function):
|
||||
"""Create a FunctionDescriptor from a function instance.
|
||||
|
||||
This function is used to create the function descriptor from
|
||||
a python function. If a function is a class function, it should
|
||||
not be used by this function.
|
||||
|
||||
Args:
|
||||
cls: Current class which is required argument for classmethod.
|
||||
function: the python function used to create the function
|
||||
descriptor.
|
||||
pickled_function: This is factored in to ensure that any
|
||||
modifications to the function result in a different function
|
||||
descriptor.
|
||||
|
||||
Returns:
|
||||
The FunctionDescriptor instance created according to the function.
|
||||
"""
|
||||
module_name = function.__module__
|
||||
function_name = function.__name__
|
||||
class_name = ""
|
||||
|
||||
pickled_function_hash = hashlib.sha1(pickled_function).hexdigest()
|
||||
|
||||
return cls(module_name, function_name, class_name,
|
||||
pickled_function_hash)
|
||||
|
||||
@classmethod
|
||||
def from_class(cls, target_class):
|
||||
"""Create a FunctionDescriptor from a class.
|
||||
|
||||
Args:
|
||||
cls: Current class which is required argument for classmethod.
|
||||
target_class: the python class used to create the function
|
||||
descriptor.
|
||||
|
||||
Returns:
|
||||
The FunctionDescriptor instance created according to the class.
|
||||
"""
|
||||
module_name = target_class.__module__
|
||||
class_name = target_class.__name__
|
||||
return cls(module_name, "__init__", class_name)
|
||||
|
||||
@property
|
||||
def module_name(self):
|
||||
"""Get the module name of current function descriptor.
|
||||
|
||||
Returns:
|
||||
The module name of the function descriptor.
|
||||
"""
|
||||
return <str>self.typed_descriptor.ModuleName()
|
||||
|
||||
@property
|
||||
def class_name(self):
|
||||
"""Get the class name of current function descriptor.
|
||||
|
||||
Returns:
|
||||
The class name of the function descriptor. It could be
|
||||
empty if the function is not a class method.
|
||||
"""
|
||||
return <str>self.typed_descriptor.ClassName()
|
||||
|
||||
@property
|
||||
def function_name(self):
|
||||
"""Get the function name of current function descriptor.
|
||||
|
||||
Returns:
|
||||
The function name of the function descriptor.
|
||||
"""
|
||||
return <str>self.typed_descriptor.FunctionName()
|
||||
|
||||
@property
|
||||
def function_hash(self):
|
||||
"""Get the hash string of the function source code.
|
||||
|
||||
Returns:
|
||||
The hex of function hash if the source code is available.
|
||||
Otherwise, it will be an empty string.
|
||||
"""
|
||||
return <str>self.typed_descriptor.FunctionHash()
|
||||
|
||||
@property
|
||||
def function_id(self):
|
||||
"""Get the function id calculated from this descriptor.
|
||||
|
||||
Returns:
|
||||
The value of ray.ObjectID that represents the function id.
|
||||
"""
|
||||
if not self._function_id:
|
||||
self._function_id = self._get_function_id()
|
||||
return self._function_id
|
||||
|
||||
def _get_function_id(self):
|
||||
"""Calculate the function id of current function descriptor.
|
||||
|
||||
This function id is calculated from all the fields of function
|
||||
descriptor.
|
||||
|
||||
Returns:
|
||||
ray.ObjectID to represent the function descriptor.
|
||||
"""
|
||||
function_id_hash = hashlib.sha1()
|
||||
# Include the function module and name in the hash.
|
||||
function_id_hash.update(self.typed_descriptor.ModuleName())
|
||||
function_id_hash.update(self.typed_descriptor.FunctionName())
|
||||
function_id_hash.update(self.typed_descriptor.ClassName())
|
||||
function_id_hash.update(self.typed_descriptor.FunctionHash())
|
||||
# Compute the function ID.
|
||||
function_id = function_id_hash.digest()
|
||||
return ray.FunctionID(function_id)
|
||||
|
||||
def is_actor_method(self):
|
||||
"""Wether this function descriptor is an actor method.
|
||||
|
||||
Returns:
|
||||
True if it's an actor method, False if it's a normal function.
|
||||
"""
|
||||
return not self.typed_descriptor.ClassName().empty()
|
||||
@@ -15,6 +15,9 @@ from ray.includes.unique_ids cimport (
|
||||
CObjectID,
|
||||
CTaskID,
|
||||
)
|
||||
from ray.includes.function_descriptor cimport (
|
||||
CFunctionDescriptor,
|
||||
)
|
||||
|
||||
cdef extern from "ray/protobuf/common.pb.h" nogil:
|
||||
cdef cppclass RpcTaskSpec "ray::rpc::TaskSpec":
|
||||
@@ -44,7 +47,7 @@ cdef extern from "ray/common/task/task_spec.h" nogil:
|
||||
CJobID JobId() const
|
||||
CTaskID ParentTaskId() const
|
||||
uint64_t ParentCounter() const
|
||||
c_vector[c_string] FunctionDescriptor() const
|
||||
CFunctionDescriptor FunctionDescriptor() const
|
||||
c_string FunctionDescriptorString() const
|
||||
uint64_t NumArgs() const
|
||||
uint64_t NumReturns() const
|
||||
|
||||
@@ -64,14 +64,10 @@ cdef class TaskSpec:
|
||||
"""Return the parent counter of this task."""
|
||||
return self.task_spec.get().ParentCounter()
|
||||
|
||||
def function_descriptor_list(self):
|
||||
def function_descriptor(self):
|
||||
"""Return the function descriptor for this task."""
|
||||
cdef c_vector[c_string] function_descriptor = (
|
||||
return CFunctionDescriptorToPython(
|
||||
self.task_spec.get().FunctionDescriptor())
|
||||
results = []
|
||||
for i in range(function_descriptor.size()):
|
||||
results.append(function_descriptor[i])
|
||||
return results
|
||||
|
||||
def arguments(self):
|
||||
"""Return the arguments for the task."""
|
||||
|
||||
@@ -3,7 +3,8 @@ from functools import wraps
|
||||
|
||||
from ray import cloudpickle as pickle
|
||||
from ray import ray_constants
|
||||
from ray.function_manager import FunctionDescriptor
|
||||
from ray._raylet import PythonFunctionDescriptor
|
||||
from ray import cross_language, Language
|
||||
import ray.signature
|
||||
|
||||
# Default parameters for remote functions.
|
||||
@@ -23,6 +24,7 @@ class RemoteFunction:
|
||||
This is a decorated function. It can be used to spawn tasks.
|
||||
|
||||
Attributes:
|
||||
_language: The target language.
|
||||
_function: The original function.
|
||||
_function_descriptor: The function descriptor. This is not defined
|
||||
until the remote function is first invoked because that is when the
|
||||
@@ -57,12 +59,15 @@ class RemoteFunction:
|
||||
different workers.
|
||||
"""
|
||||
|
||||
def __init__(self, function, num_cpus, num_gpus, memory,
|
||||
object_store_memory, resources, num_return_vals, max_calls,
|
||||
max_retries):
|
||||
def __init__(self, language, function, function_descriptor, num_cpus,
|
||||
num_gpus, memory, object_store_memory, resources,
|
||||
num_return_vals, max_calls, max_retries):
|
||||
self._language = language
|
||||
self._function = function
|
||||
self._function_name = (
|
||||
self._function.__module__ + "." + self._function.__name__)
|
||||
self._function_descriptor = function_descriptor
|
||||
self._is_cross_language = language != Language.PYTHON
|
||||
self._num_cpus = (DEFAULT_REMOTE_FUNCTION_CPUS
|
||||
if num_cpus is None else num_cpus)
|
||||
self._num_gpus = num_gpus
|
||||
@@ -80,11 +85,11 @@ class RemoteFunction:
|
||||
if max_retries is None else max_retries)
|
||||
self._decorator = getattr(function, "__ray_invocation_decorator__",
|
||||
None)
|
||||
|
||||
self._function_signature = ray.signature.extract_signature(
|
||||
self._function)
|
||||
|
||||
self._last_export_session_and_job = None
|
||||
|
||||
# Override task.remote's signature and docstring
|
||||
@wraps(function)
|
||||
def _remote_proxy(*args, **kwargs):
|
||||
@@ -152,7 +157,9 @@ class RemoteFunction:
|
||||
|
||||
# If this function was not exported in this session and job, we need to
|
||||
# export this function again, because the current GCS doesn't have it.
|
||||
if self._last_export_session_and_job != worker.current_session_and_job:
|
||||
if not self._is_cross_language and \
|
||||
self._last_export_session_and_job != \
|
||||
worker.current_session_and_job:
|
||||
# There is an interesting question here. If the remote function is
|
||||
# used by a subsequent driver (in the same script), should the
|
||||
# second driver pickle the function again? If yes, then the remote
|
||||
@@ -164,10 +171,8 @@ class RemoteFunction:
|
||||
# which we do here.
|
||||
self._pickled_function = pickle.dumps(self._function)
|
||||
|
||||
self._function_descriptor = FunctionDescriptor.from_function(
|
||||
self._function_descriptor = PythonFunctionDescriptor.from_function(
|
||||
self._function, self._pickled_function)
|
||||
self._function_descriptor_list = (
|
||||
self._function_descriptor.get_function_descriptor_list())
|
||||
|
||||
self._last_export_session_and_job = worker.current_session_and_job
|
||||
worker.function_actor_manager.export(self)
|
||||
@@ -188,20 +193,25 @@ class RemoteFunction:
|
||||
memory, object_store_memory, resources)
|
||||
|
||||
def invocation(args, kwargs):
|
||||
if not args and not kwargs and not self._function_signature:
|
||||
if self._is_cross_language:
|
||||
list_args = cross_language.format_args(worker, args, kwargs)
|
||||
elif not args and not kwargs and not self._function_signature:
|
||||
list_args = []
|
||||
else:
|
||||
list_args = ray.signature.flatten_args(
|
||||
self._function_signature, args, kwargs)
|
||||
|
||||
if worker.mode == ray.worker.LOCAL_MODE:
|
||||
assert not self._is_cross_language, \
|
||||
"Cross language remote function " \
|
||||
"cannot be executed locally."
|
||||
object_ids = worker.local_mode_manager.execute(
|
||||
self._function, self._function_descriptor, args, kwargs,
|
||||
num_return_vals)
|
||||
else:
|
||||
object_ids = worker.core_worker.submit_task(
|
||||
self._function_descriptor_list, list_args, num_return_vals,
|
||||
is_direct_call, resources, max_retries)
|
||||
self._language, self._function_descriptor, list_args,
|
||||
num_return_vals, is_direct_call, resources, max_retries)
|
||||
|
||||
if len(object_ids) == 1:
|
||||
return object_ids[0]
|
||||
|
||||
+2
-9
@@ -5,7 +5,6 @@ import sys
|
||||
import time
|
||||
|
||||
import ray
|
||||
from ray.function_manager import FunctionDescriptor
|
||||
|
||||
from ray import (
|
||||
gcs_utils,
|
||||
@@ -393,9 +392,7 @@ class GlobalState:
|
||||
|
||||
task = ray._raylet.TaskSpec.from_string(
|
||||
task_table_data.task.task_spec.SerializeToString())
|
||||
function_descriptor_list = task.function_descriptor_list()
|
||||
function_descriptor = FunctionDescriptor.from_bytes_list(
|
||||
function_descriptor_list)
|
||||
function_descriptor = task.function_descriptor()
|
||||
|
||||
task_spec_info = {
|
||||
"JobID": task.job_id().hex(),
|
||||
@@ -412,11 +409,7 @@ class GlobalState:
|
||||
"Args": task.arguments(),
|
||||
"ReturnObjectIDs": task.returns(),
|
||||
"RequiredResources": task.required_resources(),
|
||||
"FunctionID": function_descriptor.function_id.hex(),
|
||||
"FunctionHash": binary_to_hex(function_descriptor.function_hash),
|
||||
"ModuleName": function_descriptor.module_name,
|
||||
"ClassName": function_descriptor.class_name,
|
||||
"FunctionName": function_descriptor.function_name,
|
||||
"FunctionDescriptor": function_descriptor.to_dict(),
|
||||
}
|
||||
|
||||
execution_spec = ray._raylet.TaskExecutionSpec.from_string(
|
||||
|
||||
@@ -151,14 +151,13 @@ def test_global_state_api(shutdown_only):
|
||||
assert len(task_table) == 1
|
||||
assert driver_task_id == list(task_table.keys())[0]
|
||||
task_spec = task_table[driver_task_id]["TaskSpec"]
|
||||
nil_unique_id_hex = ray.UniqueID.nil().hex()
|
||||
nil_actor_id_hex = ray.ActorID.nil().hex()
|
||||
|
||||
assert task_spec["TaskID"] == driver_task_id
|
||||
assert task_spec["ActorID"] == nil_actor_id_hex
|
||||
assert task_spec["Args"] == []
|
||||
assert task_spec["JobID"] == job_id.hex()
|
||||
assert task_spec["FunctionID"] == nil_unique_id_hex
|
||||
assert task_spec["FunctionDescriptor"]["type"] == "EmptyFunctionDescriptor"
|
||||
assert task_spec["ReturnObjectIDs"] == []
|
||||
|
||||
client_table = ray.nodes()
|
||||
@@ -172,7 +171,7 @@ def test_global_state_api(shutdown_only):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
_ = Actor.remote()
|
||||
_ = Actor.remote() # noqa: F841
|
||||
# Wait for actor to be created
|
||||
wait_for_num_actors(1)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import string
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import pickle
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
@@ -351,6 +352,23 @@ def test_complex_serialization_with_pickle(shutdown_only):
|
||||
complex_serialization(use_pickle=True)
|
||||
|
||||
|
||||
def test_function_descriptor():
|
||||
python_descriptor = ray._raylet.PythonFunctionDescriptor(
|
||||
"module_name", "function_name", "class_name", "function_hash")
|
||||
python_descriptor2 = pickle.loads(pickle.dumps(python_descriptor))
|
||||
assert python_descriptor == python_descriptor2
|
||||
assert hash(python_descriptor) == hash(python_descriptor2)
|
||||
assert python_descriptor.function_id == python_descriptor2.function_id
|
||||
java_descriptor = ray._raylet.JavaFunctionDescriptor(
|
||||
"class_name", "function_name", "signature")
|
||||
java_descriptor2 = pickle.loads(pickle.dumps(java_descriptor))
|
||||
assert java_descriptor == java_descriptor2
|
||||
assert python_descriptor != java_descriptor
|
||||
assert python_descriptor != object()
|
||||
d = {python_descriptor: 123}
|
||||
assert d.get(python_descriptor2) == 123
|
||||
|
||||
|
||||
def test_nested_functions(ray_start_regular):
|
||||
# Make sure that remote functions can use other values that are defined
|
||||
# after the remote function but before the first function invocation.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import pytest
|
||||
|
||||
import ray
|
||||
import ray.cluster_utils
|
||||
import ray.test_utils
|
||||
|
||||
|
||||
def test_cross_language_raise_kwargs(shutdown_only):
|
||||
ray.init(load_code_from_local=True, include_java=True)
|
||||
|
||||
with pytest.raises(Exception, match="kwargs"):
|
||||
ray.java_function("a", "b").remote(x="arg1")
|
||||
|
||||
with pytest.raises(Exception, match="kwargs"):
|
||||
ray.java_actor_class("a").remote(x="arg1")
|
||||
@@ -189,7 +189,13 @@ def test_raylet_info_endpoint(shutdown_only):
|
||||
try:
|
||||
webui_url = addresses["webui_url"]
|
||||
webui_url = webui_url.replace("localhost", "http://127.0.0.1")
|
||||
raylet_info = requests.get(webui_url + "/api/raylet_info").json()
|
||||
response = requests.get(webui_url + "/api/raylet_info")
|
||||
response.raise_for_status()
|
||||
try:
|
||||
raylet_info = response.json()
|
||||
except Exception as ex:
|
||||
print("failed response: {}".format(response.text))
|
||||
raise ex
|
||||
actor_info = raylet_info["result"]["actors"]
|
||||
try:
|
||||
assert len(actor_info) == 1
|
||||
|
||||
@@ -36,6 +36,7 @@ from ray import (
|
||||
ActorID,
|
||||
JobID,
|
||||
ObjectID,
|
||||
Language,
|
||||
)
|
||||
from ray import import_thread
|
||||
from ray import profiling
|
||||
@@ -553,6 +554,7 @@ def init(address=None,
|
||||
redis_password=ray_constants.REDIS_DEFAULT_PASSWORD,
|
||||
plasma_directory=None,
|
||||
huge_pages=False,
|
||||
include_java=False,
|
||||
include_webui=None,
|
||||
webui_host="localhost",
|
||||
job_id=None,
|
||||
@@ -632,6 +634,7 @@ def init(address=None,
|
||||
be created.
|
||||
huge_pages: Boolean flag indicating whether to start the Object
|
||||
Store with hugetlbfs support. Requires plasma_directory.
|
||||
include_java: Boolean flag indicating whether to enable java worker.
|
||||
include_webui: Boolean flag indicating whether to start the web
|
||||
UI, which displays the status of the Ray cluster. If this argument
|
||||
is None, then the UI will be started if the relevant dependencies
|
||||
@@ -725,6 +728,7 @@ def init(address=None,
|
||||
redis_password=redis_password,
|
||||
plasma_directory=plasma_directory,
|
||||
huge_pages=huge_pages,
|
||||
include_java=include_java,
|
||||
include_webui=include_webui,
|
||||
webui_host=webui_host,
|
||||
memory=memory,
|
||||
@@ -1684,9 +1688,9 @@ def make_decorator(num_return_vals=None,
|
||||
"allowed for remote functions.")
|
||||
|
||||
return ray.remote_function.RemoteFunction(
|
||||
function_or_class, num_cpus, num_gpus, memory,
|
||||
object_store_memory, resources, num_return_vals, max_calls,
|
||||
max_retries)
|
||||
Language.PYTHON, function_or_class, None, num_cpus, num_gpus,
|
||||
memory, object_store_memory, resources, num_return_vals,
|
||||
max_calls, max_retries)
|
||||
|
||||
if inspect.isclass(function_or_class):
|
||||
if num_return_vals is not None:
|
||||
|
||||
Reference in New Issue
Block a user