예제 #1
0
def test_get_pipeline_input_node():
    # 1) No PipelineInputNode found
    ray_dag = combine.bind(1, 2)
    serve_dag = ray_dag.apply_recursive(transform_ray_dag_to_serve_dag)
    with pytest.raises(
            AssertionError,
            match="There should be one and only one PipelineInputNode"):
        get_pipeline_input_node(serve_dag)

    # 2) More than one PipelineInputNode found
    with PipelineInputNode(preprocessor=request_to_data_int) as dag_input:
        a = combine.bind(dag_input[0], dag_input[1])
    with PipelineInputNode(preprocessor=request_to_data_int) as dag_input_2:
        b = combine.bind(dag_input_2[0], dag_input_2[1])
        ray_dag = combine.bind(a, b)
    serve_dag = ray_dag.apply_recursive(transform_ray_dag_to_serve_dag)
    with pytest.raises(
            AssertionError,
            match="There should be one and only one PipelineInputNode"):
        get_pipeline_input_node(serve_dag)

    # 3) User forgot to change InputNode to PipelineInputNode
    with InputNode() as dag_input:
        ray_dag = combine.bind(dag_input[0], dag_input[1])
    serve_dag = ray_dag.apply_recursive(transform_ray_dag_to_serve_dag)
    with pytest.raises(
            ValueError,
            match="Please change Ray DAG InputNode to PipelineInputNode"):
        get_pipeline_input_node(serve_dag)
예제 #2
0
def test_no_inline_class_or_func(serve_instance):
    # 1) Inline function
    @ray.remote
    def inline_func(val):
        return val

    with PipelineInputNode(preprocessor=request_to_data_int) as dag_input:
        ray_dag = inline_func.bind(dag_input)

    assert ray.get(ray_dag.execute(1)) == 1
    with pytest.raises(
            AssertionError,
            match="Function used in DAG should not be in-line defined",
    ):
        _ = json.dumps(ray_dag, cls=DAGNodeEncoder)

    # 2) Inline class
    @ray.remote
    class InlineClass:
        def __init__(self, val):
            self.val = val

        def get(self, input):
            return self.val + input

    with PipelineInputNode(preprocessor=request_to_data_int) as dag_input:
        node = InlineClass.bind(1)
        ray_dag = node.get.bind(dag_input)

    with pytest.raises(
            AssertionError,
            match="Class used in DAG should not be in-line defined",
    ):
        _ = json.dumps(ray_dag, cls=DAGNodeEncoder)

    # 3) Inline preprocessor fn
    def inline_preprocessor_fn(input):
        return input

    with PipelineInputNode(preprocessor=inline_preprocessor_fn) as dag_input:
        ray_dag = combine.bind(dag_input[0], 2)

    with pytest.raises(
            AssertionError,
            match="Preprocessor used in DAG should not be in-line defined",
    ):
        _ = json.dumps(ray_dag, cls=DAGNodeEncoder)

    # 4) Class factory that function returns class object
    with PipelineInputNode(preprocessor=request_to_data_int) as dag_input:
        instance = ray.remote(class_factory()).bind()
        ray_dag = instance.get.bind()
    with pytest.raises(
            AssertionError,
            match="Class used in DAG should not be in-line defined",
    ):
        _ = json.dumps(ray_dag, cls=DAGNodeEncoder)
예제 #3
0
def get_shared_deployment_handle_dag():
    with PipelineInputNode(preprocessor=request_to_data_int) 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
예제 #4
0
def test_model_wrappers_in_pipeline(serve_instance):
    _, path = tempfile.mkstemp()
    with open(path, "w") as f:
        json.dump(2, f)

    predictor_cls = "ray.serve.tests.test_model_wrappers.AdderPredictor"
    checkpoint_cls = "ray.serve.tests.test_model_wrappers.AdderCheckpoint"
    schema_cls = "ray.serve.http_adapters.array_to_databatch"

    with PipelineInputNode(preprocessor=schema_cls) as dag_input:
        m1 = ray.remote(ModelWrapper).bind(
            predictor_cls=predictor_cls,  # TODO: can't be the raw class right now?
            checkpoint={  # TODO: can't be the raw object right now?
                "checkpoint_cls": checkpoint_cls,
                "uri": path,
            },
        )
        dag = m1.predict.bind(dag_input)
    deployments = build(dag)
    for d in deployments:
        d.deploy()

    resp = requests.post("http://127.0.0.1:8000/ingress", json={"array": [40]})
    resp.raise_for_status()
    return resp.json() == {"value": [42], "batch_size": 1}
예제 #5
0
def get_multi_instantiation_class_nested_deployment_arg_dag():
    with PipelineInputNode(preprocessor=request_to_data_int) 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
예제 #6
0
def get_multi_instantiation_class_deployment_in_init_args_dag():
    with PipelineInputNode(preprocessor=request_to_data_int) 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
예제 #7
0
def get_func_class_with_class_method_dag():
    with PipelineInputNode(preprocessor=request_to_data_obj) 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
예제 #8
0
def dagnode_from_json(input_json: Any) -> Union[DAGNode, RayServeHandle, Any]:
    """
    Decode a DAGNode from given input json dictionary. JSON serialization is
    only used and enforced in ray serve from ray core API authored DAGNode(s).

    Covers both RayServeHandle and DAGNode types.

    Assumptions:
        - User object's JSON dict does not have keys that collide with our
            reserved DAGNODE_TYPE_KEY
        - RayServeHandle and Deployment can be re-constructed without losing
            states needed for their functionality or correctness.
        - DAGNode type can be re-constructed with new stable_uuid upon each
            deserialization without effective correctness of execution.
            - Only exception is ClassNode used as parent of ClassMethodNode
                that we perserve the same parent node.
        - .options() does not contain any DAGNode type
    """
    # Deserialize RayServeHandle type
    if SERVE_HANDLE_JSON_KEY in input_json:
        return json.loads(input_json, object_hook=serve_handle_object_hook)
    # Base case for plain objects
    elif DAGNODE_TYPE_KEY not in input_json:
        try:
            return json.loads(input_json)
        except Exception:
            return input_json
    # Deserialize DAGNode type
    elif input_json[DAGNODE_TYPE_KEY] == InputNode.__name__:
        return InputNode.from_json(input_json, object_hook=dagnode_from_json)
    elif input_json[DAGNODE_TYPE_KEY] == InputAtrributeNode.__name__:
        return InputAtrributeNode.from_json(input_json, object_hook=dagnode_from_json)
    elif input_json[DAGNODE_TYPE_KEY] == PipelineInputNode.__name__:
        return PipelineInputNode.from_json(input_json, object_hook=dagnode_from_json)
    elif input_json[DAGNODE_TYPE_KEY] == ClassMethodNode.__name__:
        return ClassMethodNode.from_json(input_json, object_hook=dagnode_from_json)
    elif input_json[DAGNODE_TYPE_KEY] == DeploymentNode.__name__:
        return DeploymentNode.from_json(input_json, object_hook=dagnode_from_json)
    elif input_json[DAGNODE_TYPE_KEY] == DeploymentMethodNode.__name__:
        return DeploymentMethodNode.from_json(input_json, object_hook=dagnode_from_json)
    else:
        # Class and Function nodes require original module as body.
        print(f"import_path: {input_json['import_path']}")
        module_name, attr_name = parse_import_path(input_json["import_path"])
        module = getattr(import_module(module_name), attr_name)
        if input_json[DAGNODE_TYPE_KEY] == FunctionNode.__name__:
            return FunctionNode.from_json(
                input_json, module, object_hook=dagnode_from_json
            )
        elif input_json[DAGNODE_TYPE_KEY] == ClassNode.__name__:
            return ClassNode.from_json(
                input_json, module, object_hook=dagnode_from_json
            )
예제 #9
0
def test_single_class_with_invalid_deployment_options(serve_instance):
    with PipelineInputNode(preprocessor=request_to_data_int) as dag_input:
        model = Model.options(name="my_deployment").bind(2, ratio=0.3)
        ray_dag = model.forward.bind(dag_input)

    serve_root_dag = ray_dag.apply_recursive(transform_ray_dag_to_serve_dag)
    deployments = extract_deployments_from_serve_dag(serve_root_dag)
    assert len(deployments) == 1
    with pytest.raises(
            ValueError,
            match="Specifying name in ray_actor_options is not allowed"):
        deployments[0].deploy()
예제 #10
0
def test_multi_instantiation_class_nested_deployment_arg(serve_instance):
    with PipelineInputNode(preprocessor=request_to_data_int) 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))
예제 #11
0
def test_nested_deployment_node_json_serde(serve_instance):
    with PipelineInputNode(preprocessor=request_to_data_int) 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))
예제 #12
0
def test_single_class_with_valid_ray_options(serve_instance):
    with PipelineInputNode(preprocessor=request_to_data_int) as dag_input:
        model = Model.options(num_cpus=1, memory=1000).bind(2, ratio=0.3)
        ray_dag = model.forward.bind(dag_input)

    serve_root_dag = ray_dag.apply_recursive(transform_ray_dag_to_serve_dag)
    deployments = extract_deployments_from_serve_dag(serve_root_dag)
    assert len(deployments) == 1
    deployments[0].deploy()
    _validate_consistent_python_output(deployments[0],
                                       ray_dag,
                                       deployments[0].name,
                                       input=1,
                                       output=0.6)

    deployment = serve.get_deployment(deployments[0].name)
    assert deployment.ray_actor_options.get("num_cpus") == 1
    assert deployment.ray_actor_options.get("memory") == 1000
    assert deployment.ray_actor_options.get("runtime_env") == {}
예제 #13
0
def get_ingress_deployment(
    serve_dag_root_node: DAGNode, pipeline_input_node: PipelineInputNode
) -> Deployment:
    """Return an Ingress deployment to handle user HTTP inputs.

    Args:
        serve_dag_root_node (DAGNode): Transformed  as serve DAG's root. User
            inputs are translated to serve_dag_root_node.execute().
        pipeline_input_node (DAGNode): Singleton PipelineInputNode instance that
            contains input preprocessor info.
    Returns:
        ingress (Deployment): Generated pipeline ingress deployment to serve
            user HTTP requests.
    """
    serve_dag_root_json = json.dumps(serve_dag_root_node, cls=DAGNodeEncoder)
    preprocessor_import_path = pipeline_input_node.get_preprocessor_import_path()
    serve_dag_root_deployment = make_ingress_deployment(
        DEFAULT_INGRESS_DEPLOYMENT_NAME,
        serve_dag_root_json,
        preprocessor_import_path,
    )
    return serve_dag_root_deployment
예제 #14
0
def get_simple_class_with_class_method_dag():
    with PipelineInputNode(preprocessor=request_to_data_int) as dag_input:
        model = Model.bind(2, ratio=0.3)
        ray_dag = model.forward.bind(dag_input)

    return ray_dag, dag_input
예제 #15
0
def get_simple_func_dag():
    with PipelineInputNode(preprocessor=request_to_data_obj) as dag_input:
        ray_dag = combine.bind(dag_input[0], dag_input[1], kwargs_output=1)

    return ray_dag, dag_input