diff --git a/CMakeLists.txt b/CMakeLists.txt index 59786af38a9f6..517467f3d983e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -377,6 +377,12 @@ if(USE_PROFILER) list(APPEND RUNTIME_SRCS ${RUNTIME_VM_PROFILER_SRCS}) endif(USE_PROFILER) +if(USE_PIPELINE_EXECUTOR) + message(STATUS "Build with Pipeline Executor support...") + file(GLOB RUNTIME_PIPELINE_SRCS src/runtime/pipeline/*.cc) + list(APPEND RUNTIME_SRCS ${RUNTIME_PIPELINE_SRCS}) +endif(USE_PIPELINE_EXECUTOR) + # Module rules include(cmake/modules/VTA.cmake) include(cmake/modules/StandaloneCrt.cmake) diff --git a/cmake/config.cmake b/cmake/config.cmake index daa0f1e84315b..91746c77f0327 100644 --- a/cmake/config.cmake +++ b/cmake/config.cmake @@ -105,6 +105,9 @@ set(USE_GRAPH_EXECUTOR ON) # Whether enable tiny graph executor with CUDA Graph set(USE_GRAPH_EXECUTOR_CUDA_GRAPH OFF) +# Whether enable subgraph runtime. +set(USE_PIPELINE_EXECUTOR ON) + # Whether to enable the profiler for the graph executor and vm set(USE_PROFILER ON) diff --git a/python/tvm/contrib/pipeline_executor.py b/python/tvm/contrib/pipeline_executor.py new file mode 100644 index 0000000000000..671d14c19f555 --- /dev/null +++ b/python/tvm/contrib/pipeline_executor.py @@ -0,0 +1,176 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +"""Pipeline executor that executes pipeline containing TVM PackedFunc.""" +import json +import tvm._ffi +from tvm import relay +from tvm.contrib import graph_executor + + +def pipeline_executor_enabled(): + """check if pipeline executor enabled. + Return + ------ + enable: bool + return pipeline executor get enabled or not + """ + pipeline_enabled = False + try: + pipelinecreate = tvm._ffi.get_global_func("tvm.pipeline_executor.create") + assert pipelinecreate + pipeline_enabled = True + except ValueError: + print("pipeline executor not enabled!") + + return pipeline_enabled + + +def build_pipeline(mod_n_configs): + """build module list that can use for pipeline execution. + + Parameters + ---------- + mod_n_configs: Dict[IRModule, Dict[str, Any]] + build configuration informaton, structure like following. + {IRModule: {"target":target, + "target_host":target_host, + "params":params, + "mod_name"mod_name, + "build":build}} + + Returns + ------- + ret: List[IRModule] + list of IRModule + string_config: Dict[int, Dict[str, any]] + pipeline configuration + """ + mods = {} + config_len = len(mod_n_configs) + string_config = [{} for _ in range(config_len)] + for _, (ir_mod, mod_config) in enumerate(mod_n_configs.items()): + # init lib_name and json_name params with empty + lib_name = "" + json_name = "" + params_name = "" + # Get module configuration + assert "pipeline" in mod_config and "mod_indx" in mod_config["pipeline"] + # Get module index in pipeline configuration + mconf = mod_config["pipeline"].copy() + # Get mod device config + dev = mod_config["dev"] + mod_indx = mconf["mod_indx"] - 1 + target = mod_config["target"] + assert mod_indx < config_len + build_func = relay.build + # if there is a self defined build function then use it. + if "build" in mod_config and mod_config["build"]: + build_func = mod_config["build"] + + # build IRModule + mod = build_func( + ir_mod, + target, + params=mod_config["params"], + target_host=mod_config["target_host"], + mod_name=mod_config["mod_name"], + ) + + mconf["lib_name"] = lib_name + mconf["json_name"] = json_name + mconf["params_name"] = params_name + mconf["dev"] = "{},{}".format(dev.device_type, dev.device_id) + # Create pipeline configuration + string_config[mod_indx] = mconf + # associate mod with device + mods[mod] = {"dev": dev} + + # return IRModule list and pipeline configuration + return mods, string_config + + +def create(pipeline_mods, mod_config): + """Create a pipeline runtime executor. + + Parameters + ---------- + pipeline_mods : List[IRModule] + list of IRModule + + mod_config : Dict[int, Dict[str, Any]] + modules and modules dependency configuration informaiton. + + Returns + ------- + submodule : PipelineModule + Runtime pipeline module. + """ + + submodule = PipelineModule(pipeline_mods, mod_config) + return submodule + + +class PipelineModule(object): + """Wrapper runtime module. This is a thin wrapper of the underlying TVM module. + you can also directly call set_input, run, and get_output of underlying module functions. + + Parameters + ---------- + graph_module : List[GraphModule] + The internal tvm module that holds the actual graph functions. + + pipeline_config : Dict[IRModule, Dict[str, Any]] + modules and modules dependency configuration informaiton. + + """ + + def graph_executor_create(self, pipeline_mods, mod_config): + """Create a pipeline runtime executor. + + Parameters + ---------- + pipeline_mods : List[IRModule] + list of IRModule + + mod_config : Dict[int, Dict[str, Any]] + modules and modules dependency configuration informaiton. + + Returns + ------- + mods : GreaphModule + Runtime graph module. + """ + + mods = [] + for pipeline_mod in pipeline_mods: + mod = graph_executor.GraphModule( + pipeline_mod["default"](pipeline_mods[pipeline_mod]["dev"]) + ) + mods.append(mod.module) + + return mods, json.dumps(mod_config) + + def __init__(self, pipeline_mods, mod_config): + self.pipeline_mods = pipeline_mods + self.mod_config = mod_config + mods, config = self.graph_executor_create(pipeline_mods, mod_config) + + pipelinecreate = tvm._ffi.get_global_func("tvm.pipeline_executor.create") + assert pipelinecreate + module = pipelinecreate(mods, config) + + self.module_ = module diff --git a/src/runtime/pipeline/pipeline_executor.cc b/src/runtime/pipeline/pipeline_executor.cc new file mode 100644 index 0000000000000..44440f3418056 --- /dev/null +++ b/src/runtime/pipeline/pipeline_executor.cc @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \file pipeline_executor.cc + */ +#include "pipeline_executor.h" + +namespace tvm { +namespace runtime { + +void SubGraphRuntime::Init(const Array& modules, + const std::string& pipeline_json) { + return; +} + +PackedFunc SubGraphRuntime::GetFunction(const std::string& name, + const ObjectPtr& sptr_to_self) { + return PackedFunc(); +} + +Module PipelineRuntimeCreate(const Array& m, + const std::string& pipeline_json) { + auto exec = make_object(); + exec->Init(m, pipeline_json); + return Module(exec); +} + +TVM_REGISTER_GLOBAL("tvm.pipeline_executor.create").set_body([](TVMArgs args, TVMRetValue* rv) { + *rv = PipelineRuntimeCreate(args[0], args[1]); +}); +} // namespace runtime +} // namespace tvm diff --git a/src/runtime/pipeline/pipeline_executor.h b/src/runtime/pipeline/pipeline_executor.h new file mode 100644 index 0000000000000..fe92874975e93 --- /dev/null +++ b/src/runtime/pipeline/pipeline_executor.h @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \brief pipeline executor + * \file pipeline_executor.h + */ +#ifndef TVM_RUNTIME_PIPELINE_PIPELINE_EXECUTOR_H_ +#define TVM_RUNTIME_PIPELINE_PIPELINE_EXECUTOR_H_ +#include + +#include +#include +#include +#include + +#include "../file_utils.h" +using namespace std; +namespace tvm { +namespace runtime { + +/*! + * \brief pipeline runtime. + * + * This runtime can be acccesibly in various language via + * TVM runtime PackedFunc API. + */ +class TVM_DLL SubGraphRuntime : public ModuleNode { + public: + /*! + * \return The type key of the executor. + */ + const char* type_key() const final { return "SubGraphRuntime"; } + /*! + * \brief Initialize the graph executor with graph and context. + * \param graph_json The execution graph. + * \param module The module containing the compiled functions for the host + * processor. + * \param ctxs The context of the host and devices where graph nodes will be + * executed on. + * \param lookup_linked_param_func If given, a PackedFunc invoked to lookup linked parameters + * by storage_id. If not given, linked parameters are looked-up using an internal implementation, + * which is not compatible with RPCModules. + */ + void Init(const Array& modules, const std::string& pipeline_json); + /*! + * \brief Get member function to front-end + * \param name The name of the function. + * \param sptr_to_self The pointer to the module node. + * \return The corresponding member function. + */ + virtual PackedFunc GetFunction(const std::string& name, const ObjectPtr& sptr_to_self); +}; +} // namespace runtime +} // namespace tvm +#endif // TVM_RUNTIME_PIPELINE_PIPELINE_EXECUTOR_H_ diff --git a/tests/python/relay/test_pipeline_executor.py b/tests/python/relay/test_pipeline_executor.py new file mode 100644 index 0000000000000..2a043918542f9 --- /dev/null +++ b/tests/python/relay/test_pipeline_executor.py @@ -0,0 +1,136 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import numpy as np +import tvm +import tvm.testing +from tvm import relay +from tvm.relay import transform +from tvm.contrib import graph_executor, pipeline_executor + + +def get_mannual_mod(): + mods = [] + dshape = (3, 3) + data = relay.var("data_0", relay.TensorType(dshape, "float32")) + data21 = relay.var("data_1", relay.TensorType(dshape, "float32")) + data_net1_output_1 = relay.var("data_0", relay.TensorType(dshape, "float32")) + data_net1_output_2 = relay.var("data_1", relay.TensorType(dshape, "float32")) + data_net2_output_1 = relay.var("data_0", relay.TensorType(dshape, "float32")) + mvalue1 = np.full((1), 1).astype("float32") + mvalue2 = np.full((1), 2).astype("float32") + mvalue3 = np.full((1), 3).astype("float32") + mv1 = relay.Constant(tvm.nd.array(mvalue1)) + mv2 = relay.Constant(tvm.nd.array(mvalue2)) + mv3 = relay.Constant(tvm.nd.array(mvalue3)) + + # net1 have three output, output3 is final output + net_output1 = relay.add(data, mv1) + net_output2 = relay.subtract(data, mv2) + net_output3 = relay.multiply(data, mv3) + + # net2 use net1 output1 as input + net2 = relay.add(data_net1_output_1, mv2) + net2 = relay.add(net2, data21) + net2 = relay.add(net2, mv3) + + # net3 use net2 output1 and net1 outpu2 as input + net3 = relay.multiply(data_net2_output_1, mv3) + net3 = relay.add(net3, data_net1_output_2) + + mods.append( + tvm.IRModule.from_expr( + relay.Function([data], relay.Tuple([net_output1, net_output2, net_output3])) + ) + ) + mods.append(tvm.IRModule.from_expr(relay.Function([data_net1_output_1, data21], net2))) + mods.append( + tvm.IRModule.from_expr(relay.Function([data_net1_output_2, data_net2_output_1], net3)) + ) + + return mods, dshape + + +def pipeline(target): + """ + #Get 4 pipeline module. + """ + mods, dshape = get_mannual_mod() + """ + #Prepare batch data for pipeline feeding + """ + datas = [] + for i in range(len(mods) + 1): + datas.append(np.full(dshape, 3 + i).astype("float32")) + + # set configure + indx = 0 + mod_config = {} + mconfig = {"target_host": None, "mod_name": "default", "build": None, "params": None} + mconfig1 = mconfig.copy() + mconfig1["target"] = target[0] + mconfig1["dev"] = target[1] + # third output is final output, second output for mod3, first for mod2 + # input + mconfig1["pipeline"] = { + "mod_indx": 1, + "output": [ + {"output_indx": 1, "dependent": [{"mod_indx": 2, "input_name": "data_0"}]}, + {"output_indx": 2, "dependent": [{"mod_indx": 3, "input_name": "data_0"}]}, + {"output_indx": 3, "dependent": [{"mod_indx": 0, "input_name": "1"}]}, + ], + } + mod_config[mods[0]] = mconfig1 + + mconfig2 = mconfig.copy() + mconfig2["target"] = "llvm" + mconfig2["dev"] = tvm.cpu(0) + mconfig2["pipeline"] = { + "mod_indx": 2, + "output": [ + {"output_indx": 1, "dependent": [{"mod_indx": 3, "input_name": "data_1"}]}, + ], + } + mod_config[mods[1]] = mconfig2 + + mconfig3 = mconfig.copy() + mconfig3["target"] = "llvm" + mconfig3["dev"] = tvm.cpu(0) + + mconfig3["pipeline"] = { + "mod_indx": 3, + "output": [{"output_indx": 1, "dependent": [{"mod_indx": 0, "input_name": "2"}]}], + } + mod_config[mods[2]] = mconfig3 + """ + #build and create pipeline module + """ + with relay.build_config(opt_level=3): + pipeline_mods, string_config = pipeline_executor.build_pipeline(mod_config) + + pipeline_module = pipeline_executor.create(pipeline_mods, string_config) + + +def test_pipeline(): + if pipeline_executor.pipeline_executor_enabled(): + target_list = tvm.testing.enabled_targets() + for target in target_list: + pipeline(target) + + +if __name__ == "__main__": + test_pipeline()