Ejemplo n.º 1
0
def test_get_pipeline_input_node():
    # 1) No InputNode 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 InputNode"):
        get_pipeline_input_node(serve_dag)

    # 2) More than one InputNode found
    with InputNode() as dag_input:
        a = combine.bind(dag_input[0], dag_input[1])
    with InputNode() as dag_input_2:
        b = combine.bind(dag_input_2[0], dag_input_2[1])
        ray_dag = combine.bind(a, b)
    with pytest.raises(AssertionError,
                       match="Each DAG should only have one unique InputNode"):
        serve_dag = ray_dag.apply_recursive(transform_ray_dag_to_serve_dag)
        get_pipeline_input_node(serve_dag)
Ejemplo n.º 2
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)
Ejemplo n.º 3
0
def test_get_pipeline_input_node():
    # 1) No InputNode found
    ray_dag = combine.bind(1, 2)
    with DeploymentNameGenerator() as deployment_name_generator:
        serve_dag = ray_dag.apply_recursive(
            lambda node: transform_ray_dag_to_serve_dag(
                node, deployment_name_generator))
    with pytest.raises(AssertionError,
                       match="There should be one and only one InputNode"):
        get_pipeline_input_node(serve_dag)

    # 2) More than one InputNode found
    with InputNode() as dag_input:
        a = combine.bind(dag_input[0], dag_input[1])
    with InputNode() as dag_input_2:
        b = combine.bind(dag_input_2[0], dag_input_2[1])
        ray_dag = combine.bind(a, b)
    with pytest.raises(AssertionError,
                       match="Each DAG should only have one unique InputNode"):
        with DeploymentNameGenerator() as deployment_name_generator:
            serve_dag = ray_dag.apply_recursive(
                lambda node: transform_ray_dag_to_serve_dag(
                    node, deployment_name_generator))
        get_pipeline_input_node(serve_dag)
Ejemplo n.º 4
0
def build(ray_dag_root_node: DAGNode):
    """Do all the DAG transformation, extraction and generation needed to
    produce a runnable and deployable serve pipeline application from a valid
    DAG authored with Ray DAG API.

    This should be the only user facing API that user interacts with.

    Assumptions:
        Following enforcements are only applied at generating and applying
        pipeline artifact, but not blockers for local development and testing.

        - ALL args and kwargs used in DAG building should be JSON serializable.
            This means in order to ensure your pipeline application can run on
            a remote cluster potentially with different runtime environment,
            among all options listed:

                1) binding in-memory objects
                2) Rely on pickling
                3) Enforce JSON serialibility on all args used

            We believe both 1) & 2) rely on unstable in-memory objects or
            cross version pickling / closure capture, where JSON serialization
            provides the right contract needed for proper deployment.

        - ALL classes and methods used should be visible on top of the file and
            importable via a fully qualified name. Thus no inline class or
            function definitions should be used.

    Args:
        ray_dag_root_node: DAGNode acting as root of a Ray authored DAG. It
            should be executable via `ray_dag_root_node.execute(user_input)`
            and should have `PipelineInputNode` in it.

    Returns:
        app: The Ray Serve application object that wraps all deployments needed
            along with ingress deployment for an e2e runnable serve pipeline,
            accessible via python .remote() call and HTTP.

    Examples:
        >>> with ServeInputNode(preprocessor=request_to_data_int) 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 = ensemble.bind(m1_output, m2_output)

        Assuming we have non-JSON serializable or inline defined class or
        function in local pipeline development.

        >>> app = serve.pipeline.build(ray_dag) # This works
        >>> handle = app.deploy()
        >>> # This also works, we're simply executing the transformed serve_dag.
        >>> ray.get(handle.remote(data)
        >>> # This will fail where enforcements are applied.
        >>> deployment_yaml = app.to_yaml()
    """
    serve_root_dag = ray_dag_root_node.apply_recursive(transform_ray_dag_to_serve_dag)
    deployments = extract_deployments_from_serve_dag(serve_root_dag)
    pipeline_input_node = get_pipeline_input_node(serve_root_dag)
    ingress_deployment = get_ingress_deployment(serve_root_dag, pipeline_input_node)
    deployments.insert(0, ingress_deployment)

    # TODO (jiaodong): Call into Application once Shreyas' PR is merged
    # TODO (jiaodong): Apply enforcements at serve app to_yaml level
    return deployments