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)
def test_simple_function_node_json_serde(serve_instance): """ Test the following behavior 1) Ray DAG node can go through full JSON serde cycle 2) Ray DAG node and deserialized DAG node produces same output 3) Ray DAG node can go through multiple rounds of JSON serde and still provides the same value as if it's only JSON serde once Against following test cases - Simple function with no args - Simple function with only args, all primitive types - Simple function with args + kwargs, all primitive types """ original_dag_node = combine.bind(1, 2) _test_json_serde_helper( original_dag_node, executor_fn=_test_execution_function_node, expected_json_dict={ DAGNODE_TYPE_KEY: "FunctionNode", "import_path": "ray.serve.pipeline.tests.resources.test_modules.combine", "args": "[1, 2]", "kwargs": "{}", "options": "{}", "other_args_to_resolve": "{}", "uuid": original_dag_node.get_stable_uuid(), }, ) original_dag_node = combine.bind(1, 2, kwargs_output=3) _test_json_serde_helper( original_dag_node, executor_fn=_test_execution_function_node, expected_json_dict={ DAGNODE_TYPE_KEY: "FunctionNode", "import_path": "ray.serve.pipeline.tests.resources.test_modules.combine", "args": "[1, 2]", "kwargs": '{"kwargs_output": 3}', "options": "{}", "other_args_to_resolve": "{}", "uuid": original_dag_node.get_stable_uuid(), }, ) original_dag_node = fn_hello.bind() _test_json_serde_helper( original_dag_node, executor_fn=_test_execution_function_node, expected_json_dict={ DAGNODE_TYPE_KEY: "FunctionNode", "import_path": "ray.serve.pipeline.tests.resources.test_modules.fn_hello", "args": "[]", "kwargs": "{}", "options": "{}", "other_args_to_resolve": "{}", "uuid": original_dag_node.get_stable_uuid(), }, )
def test_non_json_serializable_args(): """Use non-JSON serializable object in Ray DAG and ensure we throw exception with reasonable error messages. """ class MyNonJSONClass: def __init__(self, val): self.val = val ray_dag = combine.bind(MyNonJSONClass(1), MyNonJSONClass(2)) # General context with pytest.raises( TypeError, match=("All args and kwargs used in Ray DAG building for serve " "deployment need to be JSON serializable."), ): _ = json.dumps(ray_dag, cls=DAGNodeEncoder) # User actionable item with pytest.raises( TypeError, match=( "Please JSON serialize your args to make your ray application " "deployment ready"), ): _ = json.dumps(ray_dag, cls=DAGNodeEncoder) # Original error message with pytest.raises( TypeError, match=r"Object of type .* is not JSON serializable", ): _ = json.dumps(ray_dag, cls=DAGNodeEncoder)
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)
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)
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
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
def test_non_json_serializable_args(): """Use non-JSON serializable object in Ray DAG and ensure we throw exception with reasonable error messages. """ class MyNonJSONClass: def __init__(self, val): self.val = val ray_dag = combine.bind(MyNonJSONClass(1), MyNonJSONClass(2)) # General context with pytest.raises( TypeError, match=r"Object of type .* is not JSON serializable", ): _ = json.dumps(ray_dag, cls=DAGNodeEncoder)
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)
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) )
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
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