Пример #1
0
def test_no_args_to_input_node(shared_ray_instance):
    @ray.remote
    def f(input):
        return input

    with pytest.raises(ValueError,
                       match="InputNode should not take any args or kwargs"):
        with InputNode(0) as dag_input:
            f.bind(dag_input)
    with pytest.raises(
            ValueError,
            match="InputNode should not take any args or kwargs",
    ):
        with InputNode(key=1) as dag_input:
            f.bind(dag_input)
Пример #2
0
def get_shared_deployment_handle_dag():
    with InputNode() as dag_input:
        m = Model.bind(2)
        combine = Combine.bind(m, m2=m)
        ray_dag = combine.__call__.bind(dag_input)

    return ray_dag, dag_input
Пример #3
0
def test_class_method_input(shared_ray_instance):
    @ray.remote
    class Model:
        def __init__(self, weight: int):
            self.weight = weight

        def forward(self, input: "RayHandleLike"):
            return self.weight * input

    @ray.remote
    class FeatureProcessor:
        def __init__(self, scale):
            self.scale = scale

        def process(self, input: int):
            return input * self.scale

    with InputNode() as dag_input:
        preprocess = FeatureProcessor.bind(0.5)
        feature = preprocess.process.bind(dag_input)
        model = Model.bind(4)
        dag = model.forward.bind(feature)

    # 2 * 0.5 * 4
    assert ray.get(dag.execute(2)) == 4
    # 6 * 0.5 * 4
    assert ray.get(dag.execute(6)) == 12
Пример #4
0
def test_func_dag(shared_ray_instance):
    @ray.remote
    def a(user_input):
        return user_input

    @ray.remote
    def b(x):
        return x * 2

    @ray.remote
    def c(x):
        return x + 1

    @ray.remote
    def d(x, y):
        return x + y

    with InputNode() as dag_input:
        a_ref = a.bind(dag_input)
        b_ref = b.bind(a_ref)
        c_ref = c.bind(a_ref)
        d_ref = d.bind(b_ref, c_ref)
        d1_ref = d.bind(d_ref, d_ref)
        d2_ref = d.bind(d1_ref, d_ref)
        dag = d.bind(d2_ref, d_ref)

    # [(2*2 + 2+1) + (2*2 + 2+1)] + [(2*2 + 2+1) + (2*2 + 2+1)]
    assert ray.get(dag.execute(2)) == 28
    # [(3*2 + 3+1) + (3*2 + 3+1)] + [(3*2 + 3+1) + (3*2 + 3+1)]
    assert ray.get(dag.execute(3)) == 40
Пример #5
0
def test_func_class_mixed_input(shared_ray_instance):
    """
    Test both class method and function are used as input in the
    same dag.
    """
    @ray.remote
    class Model:
        def __init__(self, weight: int):
            self.weight = weight

        def forward(self, input: int):
            return self.weight * input

    @ray.remote
    def model_func(input: int):
        return input * 2

    @ray.remote
    def combine(m1: "RayHandleLike", m2: "RayHandleLike"):
        return m1 + m2

    with InputNode() as dag_input:
        m1 = Model.bind(3)
        m1_output = m1.forward.bind(dag_input)
        m2_output = model_func.bind(dag_input)

    dag = combine.bind(m1_output, m2_output)
    # 2*3 + 2*2
    assert ray.get(dag.execute(2)) == 10
    # 3*3 + 3*2
    assert ray.get(dag.execute(3)) == 15
Пример #6
0
def test_multi_class_method_input(shared_ray_instance):
    """
    Test a multiple class methods can all be used as inputs in a dag.
    """
    @ray.remote
    class Model:
        def __init__(self, weight: int):
            self.weight = weight

        def forward(self, input: int):
            return self.weight * input

    @ray.remote
    def combine(m1: "RayHandleLike", m2: "RayHandleLike"):
        return m1 + m2

    with InputNode() as dag_input:
        m1 = Model.bind(2)
        m2 = Model.bind(3)

        m1_output = m1.forward.bind(dag_input)
        m2_output = m2.forward.bind(dag_input)

        dag = combine.bind(m1_output, m2_output)

    # 1*2 + 1*3
    assert ray.get(dag.execute(1)) == 5
    # 2*2 + 2*3
    assert ray.get(dag.execute(2)) == 10
Пример #7
0
def test_autoscaling_0_replica(serve_instance):
    autoscaling_config = {
        "metrics_interval_s": 0.1,
        "min_replicas": 0,
        "max_replicas": 2,
        "look_back_period_s": 0.4,
        "downscale_delay_s": 0,
        "upscale_delay_s": 0,
    }

    @serve.deployment(
        autoscaling_config=autoscaling_config, )
    class Model:
        def __init__(self, weight):
            self.weight = weight

        def forward(self, input):
            return input + self.weight

    with InputNode() as user_input:
        model = Model.bind(1)
        output = model.forward.bind(user_input)
        serve_dag = DAGDriver.options(
            route_prefix="/my-dag",
            autoscaling_config=autoscaling_config,
        ).bind(output)
    dag_handle = serve.run(serve_dag)
    assert 2 == ray.get(dag_handle.predict.remote(1))
Пример #8
0
def get_multi_instantiation_class_deployment_in_init_args_dag():
    with InputNode() as dag_input:
        m1 = Model.bind(2)
        m2 = Model.bind(3)
        combine = Combine.bind(m1, m2=m2)
        ray_dag = combine.__call__.bind(dag_input)

    return ray_dag, dag_input
Пример #9
0
def get_multi_instantiation_class_nested_deployment_arg_dag():
    with InputNode() as dag_input:
        m1 = Model.bind(2)
        m2 = Model.bind(3)
        combine = Combine.bind(m1, m2={NESTED_HANDLE_KEY: m2}, m2_nested=True)
        ray_dag = combine.__call__.bind(dag_input)

    return ray_dag, dag_input
Пример #10
0
def test_ensure_input_node_singleton(shared_ray_instance):
    @ray.remote
    def f(input):
        return input

    @ray.remote
    def combine(a, b):
        return a + b

    with InputNode() as input_1:
        a = f.bind(input_1)
    with InputNode() as input_2:
        b = f.bind(input_2)
        dag = combine.bind(a, b)

    with pytest.raises(AssertionError,
                       match="Each DAG should only have one unique InputNode"):
        _ = ray.get(dag.execute(2))
Пример #11
0
def get_func_class_with_class_method_dag():
    with InputNode() as dag_input:
        m1 = Model.bind(1)
        m2 = Model.bind(2)
        m1_output = m1.forward.bind(dag_input[0])
        m2_output = m2.forward.bind(dag_input[1])
        ray_dag = combine.bind(m1_output,
                               m2_output,
                               kwargs_output=dag_input[2])

    return ray_dag, dag_input
Пример #12
0
def test_multi_instantiation_class_nested_deployment_arg(serve_instance):
    with InputNode() as dag_input:
        m1 = Model.bind(2)
        m2 = Model.bind(3)
        combine = Combine.bind(m1, m2={NESTED_HANDLE_KEY: m2}, m2_nested=True)
        ray_dag = combine.__call__.bind(dag_input)

    (
        serve_root_dag,
        deserialized_serve_root_dag_node,
    ) = _test_deployment_json_serde_helper(ray_dag,
                                           input=1,
                                           expected_num_deployments=3)
    assert ray.get(serve_root_dag.execute(1)) == ray.get(
        deserialized_serve_root_dag_node.execute(1))
Пример #13
0
def test_ensure_in_context_manager(shared_ray_instance):
    # No enforcement on creation given __enter__ executes after __init__
    input = InputNode()
    with pytest.raises(
            AssertionError,
            match=(
                "InputNode is a singleton instance that should be only used "
                "in context manager"),
    ):
        input.execute()

    @ray.remote
    def f(input):
        return input

    # No enforcement on creation given __enter__ executes after __init__
    dag = f.bind(InputNode())
    with pytest.raises(
            AssertionError,
            match=(
                "InputNode is a singleton instance that should be only used "
                "in context manager"),
    ):
        dag.execute()
Пример #14
0
def test_simple_func(shared_ray_instance):
    @ray.remote
    def a(input: str):
        return f"{input} -> a"

    @ray.remote
    def b(a: "RayHandleLike"):
        # At runtime, a is replaced with execution result of a.
        return f"{a} -> b"

    # input -> a - > b -> ouput
    with InputNode() as dag_input:
        a_node = a.bind(dag_input)
        dag = b.bind(a_node)

    assert ray.get(dag.execute("input")) == "input -> a -> b"
    assert ray.get(dag.execute("test")) == "test -> a -> b"
Пример #15
0
def test_nested_deployment_node_json_serde(serve_instance):
    with InputNode() as dag_input:
        m1 = Model.bind(2)
        m2 = Model.bind(3)

        m1_output = m1.forward.bind(dag_input)
        m2_output = m2.forward.bind(dag_input)

        ray_dag = combine.bind(m1_output, m2_output)
    (
        serve_root_dag,
        deserialized_serve_root_dag_node,
    ) = _test_deployment_json_serde_helper(ray_dag,
                                           input=1,
                                           expected_num_deployments=2)
    assert ray.get(serve_root_dag.execute(1)) == ray.get(
        deserialized_serve_root_dag_node.execute(1))
Пример #16
0
def test_invalid_input_node_as_class_constructor(shared_ray_instance):
    @ray.remote
    class Actor:
        def __init__(self, val):
            self.val = val

        def get(self):
            return self.val

    with pytest.raises(
            ValueError,
            match=("InputNode handles user dynamic input the the DAG, and "
                   "cannot be used as args, kwargs, or other_args_to_resolve "
                   "in ClassNode constructor because it is not available at "
                   "class construction or binding time."),
    ):
        with InputNode() as dag_input:
            Actor.bind(dag_input)
Пример #17
0
def test_multi_input_func_dag(shared_ray_instance):
    @ray.remote
    def a(user_input):
        return user_input * 2

    @ray.remote
    def b(user_input):
        return user_input + 1

    @ray.remote
    def c(x, y):
        return x + y

    with InputNode() as dag_input:
        a_ref = a.bind(dag_input)
        b_ref = b.bind(dag_input)
        dag = c.bind(a_ref, b_ref)

    # (2*2) + (2*1)
    assert ray.get(dag.execute(2)) == 7
    # (3*2) + (3*1)
    assert ray.get(dag.execute(3)) == 10
Пример #18
0
def test_input_attr_partial_access(shared_ray_instance):
    @ray.remote
    class Model:
        def __init__(self, weight: int):
            self.weight = weight

        def forward(self, input: int):
            return self.weight * input

    @ray.remote
    def combine(a, b, c, d=None):
        if not d:
            return a + b + c
        else:
            return a + b + c + d["deep"]["nested"]

    # 1) Test default wrapping of args and kwargs into internal python object
    with InputNode() as dag_input:
        m1 = Model.bind(1)
        m2 = Model.bind(2)
        m1_output = m1.forward.bind(dag_input[0])
        m2_output = m2.forward.bind(dag_input[1])
        dag = combine.bind(m1_output, m2_output, dag_input.m3, dag_input.m4)
    # 1*1 + 2*2 + 3 + 4 = 12
    assert ray.get(dag.execute(1, 2, m3=3, m4={"deep": {"nested": 4}})) == 12

    # 2) Test user passed data object as only input to the dag.execute()
    class UserDataObj:
        user_object_field_0: Any
        user_object_field_1: Any
        field_3: Any

        def __init__(self, user_object_field_0: Any, user_object_field_1: Any,
                     field_3: Any) -> None:
            self.user_object_field_0 = user_object_field_0
            self.user_object_field_1 = user_object_field_1
            self.field_3 = field_3

    with InputNode() as dag_input:
        m1 = Model.bind(1)
        m2 = Model.bind(2)
        m1_output = m1.forward.bind(dag_input.user_object_field_0)
        m2_output = m2.forward.bind(dag_input.user_object_field_1)
        dag = combine.bind(m1_output, m2_output, dag_input.field_3)

    # 1*1 + 2*2 + 3
    assert ray.get(dag.execute(UserDataObj(1, 2, 3))) == 8

    # 3) Test user passed only one list object with regular list index accessor
    with InputNode() as dag_input:
        m1 = Model.bind(1)
        m2 = Model.bind(2)
        m1_output = m1.forward.bind(dag_input[0])
        m2_output = m2.forward.bind(dag_input[1])
        dag = combine.bind(m1_output, m2_output, dag_input[2])
    # 1*1 + 2*2 + 3 + 4 = 12
    assert ray.get(dag.execute([1, 2, 3])) == 8

    # 4) Test user passed only one dict object with key str accessor
    with InputNode() as dag_input:
        m1 = Model.bind(1)
        m2 = Model.bind(2)
        m1_output = m1.forward.bind(dag_input["m1"])
        m2_output = m2.forward.bind(dag_input["m2"])
        dag = combine.bind(m1_output, m2_output, dag_input["m3"])
    # 1*1 + 2*2 + 3 + 4 = 12
    assert ray.get(dag.execute({"m1": 1, "m2": 2, "m3": 3})) == 8

    with pytest.raises(
            AssertionError,
            match="Please only use int index or str as first-level key",
    ):
        with InputNode() as dag_input:
            m1 = Model.bind(1)
            dag = m1.forward.bind(dag_input[(1, 2)])
def test_autoscaling_with_ensemble_nodes(serve_instance):

    signal = SignalActor.remote()
    autoscaling_config = {
        "metrics_interval_s": 0.1,
        "min_replicas": 0,
        "max_replicas": 2,
        "look_back_period_s": 0.4,
        "downscale_delay_s": 30,
        "upscale_delay_s": 0,
    }

    @serve.deployment(
        _autoscaling_config=autoscaling_config,
        _graceful_shutdown_timeout_s=1,
    )
    class Model:
        def __init__(self, weight):
            self.weight = weight

        def forward(self, input):
            return input + self.weight

    @serve.deployment(
        _autoscaling_config=autoscaling_config,
        _graceful_shutdown_timeout_s=1,
    )
    def combine(value_refs):
        ray.get(signal.wait.remote())
        return sum(ray.get(value_refs))

    with InputNode() as user_input:
        model1 = Model.bind(0)
        model2 = Model.bind(1)
        output1 = model1.forward.bind(user_input)
        output2 = model2.forward.bind(user_input)
        output = combine.bind([output1, output2])
        serve_dag = DAGDriver.options(
            route_prefix="/my-dag",
            _autoscaling_config=autoscaling_config,
            _graceful_shutdown_timeout_s=1,
        ).bind(output)

    dag_handle = serve.run(serve_dag)
    controller = serve_instance._controller

    assert get_num_running_replicas(controller, "Model") == 0
    assert get_num_running_replicas(controller, "Model_1") == 0
    assert get_num_running_replicas(controller, "combine") == 0

    # upscaling
    [dag_handle.predict.remote(0) for _ in range(10)]
    wait_for_condition(
        lambda: get_num_running_replicas(controller, DAGDriver.name) >= 1)

    wait_for_condition(
        lambda: get_num_running_replicas(controller, "Model") >= 1, timeout=40)
    wait_for_condition(
        lambda: get_num_running_replicas(controller, "Model_1") >= 1,
        timeout=40)
    wait_for_condition(
        lambda: get_num_running_replicas(controller, "combine") >= 2,
        timeout=40)
    signal.send.remote()
    # downscaling
    wait_for_condition(
        lambda: get_num_running_replicas(controller, DAGDriver.name) == 0,
        timeout=60,
    )
    wait_for_condition(
        lambda: get_num_running_replicas(controller, "Model") == 0,
        timeout=60,
    )
    wait_for_condition(
        lambda: get_num_running_replicas(controller, "Model_1") == 0,
        timeout=60,
    )
    wait_for_condition(
        lambda: get_num_running_replicas(controller, "combine") == 0,
        timeout=60)
Пример #20
0
def test_autoscaling_with_chain_nodes(min_replicas, serve_instance):

    signal = SignalActor.remote()

    autoscaling_config = {
        "metrics_interval_s": 0.1,
        "min_replicas": min_replicas,
        "max_replicas": 2,
        "look_back_period_s": 0.4,
        "downscale_delay_s": 30,
        "upscale_delay_s": 0,
    }

    @serve.deployment(
        autoscaling_config=autoscaling_config,
        _graceful_shutdown_timeout_s=1,
    )
    class Model1:
        def __init__(self, weight):
            self.weight = weight

        def forward(self, input):
            ray.get(signal.wait.remote())
            return input + self.weight

    @serve.deployment(
        autoscaling_config=autoscaling_config,
        _graceful_shutdown_timeout_s=1,
    )
    class Model2:
        def __init__(self, weight):
            self.weight = weight

        def forward(self, input):
            return input + self.weight

    with InputNode() as user_input:
        model1 = Model1.bind(0)
        model2 = Model2.bind(1)
        output = model1.forward.bind(user_input)
        output2 = model2.forward.bind(output)
        serve_dag = DAGDriver.options(
            route_prefix="/my-dag",
            autoscaling_config=autoscaling_config,
            _graceful_shutdown_timeout_s=1,
        ).bind(output2)

    dag_handle = serve.run(serve_dag)
    controller = serve_instance._controller

    # upscaling
    [dag_handle.predict.remote(0) for _ in range(10)]
    wait_for_condition(
        lambda: get_num_running_replicas(controller, DAGDriver.name) >= 1)
    [dag_handle.predict.remote(0) for _ in range(10)]
    wait_for_condition(
        lambda: get_num_running_replicas(controller, DAGDriver.name) >= 2)
    wait_for_condition(
        lambda: get_num_running_replicas(controller, Model1.name) >= 1,
        timeout=40)
    wait_for_condition(
        lambda: get_num_running_replicas(controller, Model1.name) >= 2,
        timeout=40)
    signal.send.remote()
    wait_for_condition(
        lambda: get_num_running_replicas(controller, Model2.name) >= 1,
        timeout=40)

    # downscaling
    wait_for_condition(
        lambda: get_num_running_replicas(controller, DAGDriver.name) ==
        min_replicas,
        timeout=60,
    )
    wait_for_condition(
        lambda: get_num_running_replicas(controller, Model1.name) ==
        min_replicas,
        timeout=60,
    )
    wait_for_condition(
        lambda: get_num_running_replicas(controller, Model2.name) ==
        min_replicas,
        timeout=60,
    )
Пример #21
0
def get_simple_func_dag():
    with InputNode() as dag_input:
        ray_dag = combine.bind(dag_input[0], dag_input[1], kwargs_output=1)

    return ray_dag, dag_input
ray.init()


@serve.deployment
class Model:
    def __init__(self, weight):
        self.weight = weight

    def forward(self, input):
        return input + self.weight


@serve.deployment
def combine(value1, value2, combine_type):
    if combine_type == "sum":
        return sum([value1, value2])
    else:
        return max([value1, value2])


with InputNode() as user_input:
    model1 = Model.bind(0)
    model2 = Model.bind(1)
    output1 = model1.forward.bind(user_input[0])
    output2 = model2.forward.bind(user_input[0])
    dag = combine.bind(output1, output2, user_input[1])

print(ray.get(dag.execute(1, "max")))
print(ray.get(dag.execute(1, "sum")))
Пример #23
0
def get_simple_class_with_class_method_dag():
    with InputNode() as dag_input:
        model = Model.bind(2, ratio=0.3)
        ray_dag = model.forward.bind(dag_input)

    return ray_dag, dag_input