mirror of
https://github.com/wassname/ray.git
synced 2026-07-09 14:16:13 +08:00
Fix class attributes and methods for actor classes (#5802)
This commit is contained in:
+143
-113
@@ -117,39 +117,105 @@ class ActorMethod(object):
|
||||
return invocation(args, kwargs)
|
||||
|
||||
|
||||
class ActorClassMetadata(object):
|
||||
"""Metadata for an actor class.
|
||||
|
||||
Attributes:
|
||||
modified_class: The original class that was decorated (with some
|
||||
additional methods added like __ray_terminate__).
|
||||
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
|
||||
task.
|
||||
num_gpus: The default number of GPUs required by the actor creation
|
||||
task.
|
||||
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
|
||||
export the remote function again. It is imperfect in the sense that
|
||||
the actor class definition could be exported multiple times by
|
||||
different workers.
|
||||
actor_methods: The actor methods.
|
||||
method_decorators: Optional decorators that should be applied to the
|
||||
method invocation function before invoking the actor methods. These
|
||||
can be set by attaching the attribute
|
||||
"__ray_invocation_decorator__" to the actor method.
|
||||
method_signatures: The signatures of the methods.
|
||||
actor_method_names: The names of the actor methods.
|
||||
actor_method_num_return_vals: The default number of return values for
|
||||
each actor method.
|
||||
"""
|
||||
|
||||
def __init__(self, modified_class, class_id, max_reconstructions, num_cpus,
|
||||
num_gpus, memory, object_store_memory, resources):
|
||||
self.modified_class = modified_class
|
||||
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
|
||||
self.memory = memory
|
||||
self.object_store_memory = object_store_memory
|
||||
self.resources = resources
|
||||
self.last_export_session_and_job = None
|
||||
|
||||
self.actor_methods = inspect.getmembers(
|
||||
self.modified_class, ray.utils.is_function_or_method)
|
||||
self.actor_method_names = [
|
||||
method_name for method_name, _ in self.actor_methods
|
||||
]
|
||||
|
||||
constructor_name = "__init__"
|
||||
if constructor_name not in self.actor_method_names:
|
||||
# Add __init__ if it does not exist.
|
||||
# Actor creation will be executed with __init__ together.
|
||||
|
||||
# Assign an __init__ function will avoid many checks later on.
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
self.modified_class.__init__ = __init__
|
||||
self.actor_method_names.append(constructor_name)
|
||||
self.actor_methods.append((constructor_name, __init__))
|
||||
|
||||
# Extract the signatures of each of the methods. This will be used
|
||||
# to catch some errors if the methods are called with inappropriate
|
||||
# arguments.
|
||||
self.method_decorators = {}
|
||||
self.method_signatures = {}
|
||||
self.actor_method_num_return_vals = {}
|
||||
for method_name, method in self.actor_methods:
|
||||
# Print a warning message if the method signature is not
|
||||
# supported. We don't raise an exception because if the actor
|
||||
# inherits from a class that has a method whose signature we
|
||||
# don't support, there may not be much the user can do about it.
|
||||
signature.check_signature_supported(method, warn=True)
|
||||
self.method_signatures[method_name] = signature.extract_signature(
|
||||
method, ignore_first=not ray.utils.is_class_method(method))
|
||||
# Set the default number of return values for this method.
|
||||
if hasattr(method, "__ray_num_return_vals__"):
|
||||
self.actor_method_num_return_vals[method_name] = (
|
||||
method.__ray_num_return_vals__)
|
||||
else:
|
||||
self.actor_method_num_return_vals[method_name] = (
|
||||
ray_constants.DEFAULT_ACTOR_METHOD_NUM_RETURN_VALS)
|
||||
|
||||
if hasattr(method, "__ray_invocation_decorator__"):
|
||||
self.method_decorators[method_name] = (
|
||||
method.__ray_invocation_decorator__)
|
||||
|
||||
|
||||
class ActorClass(object):
|
||||
"""An actor class.
|
||||
|
||||
This is a decorated class. It can be used to create actors.
|
||||
|
||||
Attributes:
|
||||
_modified_class: The original class that was decorated (with some
|
||||
additional methods added like __ray_terminate__).
|
||||
_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
|
||||
task.
|
||||
_num_gpus: The default number of GPUs required by the actor creation
|
||||
task.
|
||||
_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
|
||||
export the remote function again. It is imperfect in the sense that
|
||||
the actor class definition could be exported multiple times by
|
||||
different workers.
|
||||
_actor_methods: The actor methods.
|
||||
_method_decorators: Optional decorators that should be applied to the
|
||||
method invocation function before invoking the actor methods. These
|
||||
can be set by attaching the attribute
|
||||
"__ray_invocation_decorator__" to the actor method.
|
||||
_method_signatures: The signatures of the methods.
|
||||
_actor_method_names: The names of the actor methods.
|
||||
_actor_method_num_return_vals: The default number of return values for
|
||||
each actor method.
|
||||
__ray_metadata__: Contains metadata for the actor.
|
||||
"""
|
||||
|
||||
def __init__(cls, name, bases, attr):
|
||||
@@ -157,7 +223,7 @@ class ActorClass(object):
|
||||
|
||||
This will be called when a class is defined with an ActorClass object
|
||||
as one of its base classes. To intentionally construct an ActorClass,
|
||||
use the '_from_modified_class' classmethod.
|
||||
use the '_ray_from_modified_class' classmethod.
|
||||
|
||||
Raises:
|
||||
TypeError: Always.
|
||||
@@ -169,7 +235,8 @@ class ActorClass(object):
|
||||
"not currently supported. You can instead "
|
||||
"inherit from a non-actor base class and make "
|
||||
"the derived class an actor class (with "
|
||||
"@ray.remote).".format(name, base._class_name))
|
||||
"@ray.remote).".format(
|
||||
name, base.__ray_metadata__.class_name))
|
||||
|
||||
# This shouldn't be reached because one of the base classes must be
|
||||
# an actor class if this was meant to be subclassed.
|
||||
@@ -188,70 +255,35 @@ class ActorClass(object):
|
||||
"""
|
||||
raise Exception("Actors cannot be instantiated directly. "
|
||||
"Instead of '{}()', use '{}.remote()'.".format(
|
||||
self._class_name, self._class_name))
|
||||
self.__ray_metadata__.class_name,
|
||||
self.__ray_metadata__.class_name))
|
||||
|
||||
@classmethod
|
||||
def _from_modified_class(cls, modified_class, class_id,
|
||||
max_reconstructions, num_cpus, num_gpus, memory,
|
||||
object_store_memory, resources):
|
||||
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"]:
|
||||
if hasattr(modified_class, attribute):
|
||||
logger.warning("Creating an actor from class {} overwrites "
|
||||
"attribute {} of that class".format(
|
||||
modified_class.__name__, attribute))
|
||||
|
||||
# Make sure the actor class we are constructing inherits from the
|
||||
# original class so it retains all class properties.
|
||||
class DerivedActorClass(cls, modified_class):
|
||||
pass
|
||||
|
||||
name = "ActorClass({})".format(modified_class.__name__)
|
||||
DerivedActorClass.__module__ = modified_class.__module__
|
||||
DerivedActorClass.__name__ = name
|
||||
DerivedActorClass.__qualname__ = name
|
||||
|
||||
# Construct the base object.
|
||||
self = cls.__new__(cls)
|
||||
self = DerivedActorClass.__new__(DerivedActorClass)
|
||||
|
||||
self._modified_class = modified_class
|
||||
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
|
||||
self._memory = memory
|
||||
self._object_store_memory = object_store_memory
|
||||
self._resources = resources
|
||||
self._last_export_session_and_job = None
|
||||
|
||||
self._actor_methods = inspect.getmembers(
|
||||
self._modified_class, ray.utils.is_function_or_method)
|
||||
self._actor_method_names = [
|
||||
method_name for method_name, _ in self._actor_methods
|
||||
]
|
||||
|
||||
constructor_name = "__init__"
|
||||
if constructor_name not in self._actor_method_names:
|
||||
# Add __init__ if it does not exist.
|
||||
# Actor creation will be executed with __init__ together.
|
||||
|
||||
# Assign an __init__ function will avoid many checks later on.
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
self._modified_class.__init__ = __init__
|
||||
self._actor_method_names.append(constructor_name)
|
||||
self._actor_methods.append((constructor_name, __init__))
|
||||
|
||||
# Extract the signatures of each of the methods. This will be used
|
||||
# to catch some errors if the methods are called with inappropriate
|
||||
# arguments.
|
||||
self._method_decorators = {}
|
||||
self._method_signatures = {}
|
||||
self._actor_method_num_return_vals = {}
|
||||
for method_name, method in self._actor_methods:
|
||||
# Print a warning message if the method signature is not
|
||||
# supported. We don't raise an exception because if the actor
|
||||
# inherits from a class that has a method whose signature we
|
||||
# don't support, there may not be much the user can do about it.
|
||||
signature.check_signature_supported(method, warn=True)
|
||||
self._method_signatures[method_name] = signature.extract_signature(
|
||||
method, ignore_first=not ray.utils.is_class_method(method))
|
||||
# Set the default number of return values for this method.
|
||||
if hasattr(method, "__ray_num_return_vals__"):
|
||||
self._actor_method_num_return_vals[method_name] = (
|
||||
method.__ray_num_return_vals__)
|
||||
else:
|
||||
self._actor_method_num_return_vals[method_name] = (
|
||||
ray_constants.DEFAULT_ACTOR_METHOD_NUM_RETURN_VALS)
|
||||
|
||||
if hasattr(method, "__ray_invocation_decorator__"):
|
||||
self._method_decorators[method_name] = (
|
||||
method.__ray_invocation_decorator__)
|
||||
self.__ray_metadata__ = ActorClassMetadata(
|
||||
modified_class, class_id, max_reconstructions, num_cpus, num_gpus,
|
||||
memory, object_store_memory, resources)
|
||||
|
||||
return self
|
||||
|
||||
@@ -307,12 +339,14 @@ class ActorClass(object):
|
||||
raise Exception("Actors cannot be created before ray.init() "
|
||||
"has been called.")
|
||||
|
||||
meta = self.__ray_metadata__
|
||||
|
||||
# Set the actor's default resources if not already set. First three
|
||||
# conditions are to check that no resources were specified in the
|
||||
# decorator. Last three conditions are to check that no resources were
|
||||
# specified when _remote() was called.
|
||||
if (self._num_cpus is None and self._num_gpus is None
|
||||
and self._resources is None and num_cpus is None
|
||||
if (meta.num_cpus is None and meta.num_gpus is None
|
||||
and meta.resources is None and num_cpus is None
|
||||
and num_gpus is None and resources is None):
|
||||
# In the default case, actors acquire no resources for
|
||||
# their lifetime, and actor methods will require 1 CPU.
|
||||
@@ -323,13 +357,13 @@ class ActorClass(object):
|
||||
# all resources are acquired for the actor's lifetime and no
|
||||
# resources are associated with methods.
|
||||
cpus_to_use = (ray_constants.DEFAULT_ACTOR_CREATION_CPU_SPECIFIED
|
||||
if self._num_cpus is None else self._num_cpus)
|
||||
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(
|
||||
self._modified_class.__module__, function_name,
|
||||
self._modified_class.__name__)
|
||||
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
|
||||
@@ -338,26 +372,26 @@ class ActorClass(object):
|
||||
actor_id = ActorID.of(worker.current_job_id,
|
||||
worker.current_task_id,
|
||||
worker.task_context.task_index + 1)
|
||||
worker.actors[actor_id] = self._modified_class(
|
||||
worker.actors[actor_id] = meta.modified_class(
|
||||
*copy.deepcopy(args), **copy.deepcopy(kwargs))
|
||||
core_handle = ray._raylet.ActorHandle(
|
||||
actor_id, ActorHandleID.nil(), worker.current_job_id,
|
||||
function_descriptor.get_function_descriptor_list())
|
||||
else:
|
||||
# Export the actor.
|
||||
if (self._last_export_session_and_job !=
|
||||
if (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.
|
||||
self._last_export_session_and_job = (
|
||||
meta.last_export_session_and_job = (
|
||||
worker.current_session_and_job)
|
||||
worker.function_actor_manager.export_actor_class(
|
||||
self._modified_class, self._actor_method_names)
|
||||
meta.modified_class, meta.actor_method_names)
|
||||
|
||||
resources = ray.utils.resources_from_resource_arguments(
|
||||
cpus_to_use, self._num_gpus, self._memory,
|
||||
self._object_store_memory, self._resources, num_cpus, num_gpus,
|
||||
cpus_to_use, meta.num_gpus, meta.memory,
|
||||
meta.object_store_memory, meta.resources, num_cpus, num_gpus,
|
||||
memory, object_store_memory, resources)
|
||||
|
||||
# If the actor methods require CPU resources, then set the required
|
||||
@@ -369,32 +403,28 @@ class ActorClass(object):
|
||||
actor_placement_resources = resources.copy()
|
||||
actor_placement_resources["CPU"] += 1
|
||||
|
||||
function_signature = self._method_signatures[function_name]
|
||||
function_signature = meta.method_signatures[function_name]
|
||||
creation_args = signature.extend_args(function_signature, args,
|
||||
kwargs)
|
||||
core_handle = worker.core_worker.create_actor(
|
||||
function_descriptor.get_function_descriptor_list(),
|
||||
creation_args, self._max_reconstructions, resources,
|
||||
creation_args, meta.max_reconstructions, resources,
|
||||
actor_placement_resources)
|
||||
|
||||
actor_handle = ActorHandle(
|
||||
core_handle,
|
||||
self._modified_class.__module__,
|
||||
self._class_name,
|
||||
self._actor_method_names,
|
||||
self._method_decorators,
|
||||
self._method_signatures,
|
||||
self._actor_method_num_return_vals,
|
||||
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,
|
||||
worker.current_session_and_job,
|
||||
original_handle=True)
|
||||
|
||||
return actor_handle
|
||||
|
||||
@property
|
||||
def class_id(self):
|
||||
return self._class_id
|
||||
|
||||
|
||||
class ActorHandle(object):
|
||||
"""A handle to an actor.
|
||||
@@ -672,7 +702,7 @@ def make_actor(cls, num_cpus, num_gpus, memory, object_store_memory, resources,
|
||||
Class.__module__ = cls.__module__
|
||||
Class.__name__ = cls.__name__
|
||||
|
||||
return ActorClass._from_modified_class(
|
||||
return ActorClass._ray_from_modified_class(
|
||||
Class, ActorClassID.from_random(), max_reconstructions, num_cpus,
|
||||
num_gpus, memory, object_store_memory, resources)
|
||||
|
||||
|
||||
@@ -660,7 +660,7 @@ class FunctionActorManager(object):
|
||||
module = importlib.import_module(module_name)
|
||||
actor_class = getattr(module, class_name)
|
||||
if isinstance(actor_class, ray.actor.ActorClass):
|
||||
return actor_class._modified_class
|
||||
return actor_class.__ray_metadata__.modified_class
|
||||
else:
|
||||
return actor_class
|
||||
except Exception:
|
||||
|
||||
@@ -264,6 +264,41 @@ def test_custom_classes(ray_start_regular):
|
||||
assert results2[2].x == 3
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info < (3, 0), reason="This test requires Python 3.")
|
||||
def test_actor_class_attributes(ray_start_regular):
|
||||
class Grandparent(object):
|
||||
GRANDPARENT = 2
|
||||
|
||||
class Parent1(Grandparent):
|
||||
PARENT1 = 6
|
||||
|
||||
class Parent2(object):
|
||||
PARENT2 = 7
|
||||
|
||||
@ray.remote
|
||||
class TestActor(Parent1, Parent2):
|
||||
X = 3
|
||||
|
||||
@classmethod
|
||||
def f(cls):
|
||||
assert TestActor.GRANDPARENT == 2
|
||||
assert TestActor.PARENT1 == 6
|
||||
assert TestActor.PARENT2 == 7
|
||||
assert TestActor.X == 3
|
||||
return 4
|
||||
|
||||
def g(self):
|
||||
assert TestActor.GRANDPARENT == 2
|
||||
assert TestActor.PARENT1 == 6
|
||||
assert TestActor.PARENT2 == 7
|
||||
assert TestActor.f() == 4
|
||||
return TestActor.X
|
||||
|
||||
t = TestActor.remote()
|
||||
assert ray.get(t.g.remote()) == 3
|
||||
|
||||
|
||||
def test_caching_actors(shutdown_only):
|
||||
# Test defining actors before ray.init() has been called.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user