This is an automated email from the ASF dual-hosted git repository.

masahi pushed a commit to branch unity
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/unity by this push:
     new 8f24a272a0 [Unity][MSC][M2.1] Add Manager for compile pipeline (#16163)
8f24a272a0 is described below

commit 8f24a272a08551ce5044e9c66c0e46b0f11aff8c
Author: Archermmt <[email protected]>
AuthorDate: Tue Nov 28 18:38:57 2023 +0800

    [Unity][MSC][M2.1] Add Manager for compile pipeline (#16163)
    
    * add manager as compile pipeline
    
    * minor change
    
    * format fix
---
 python/tvm/contrib/msc/core/runtime/runner.py      | 201 ++++--
 python/tvm/contrib/msc/core/utils/dataset.py       | 366 ++++++++--
 python/tvm/contrib/msc/core/utils/file.py          |  43 +-
 python/tvm/contrib/msc/core/utils/info.py          |  55 +-
 python/tvm/contrib/msc/core/utils/message.py       |  48 +-
 .../msc/framework/tensorflow/runtime/runner.py     |  33 +-
 .../msc/framework/tensorrt/codegen/codegen.py      |   6 +
 .../msc/framework/tensorrt/frontend/translate.py   |  38 +-
 .../msc/framework/tensorrt/runtime/runner.py       |  35 +-
 .../contrib/msc/framework/tvm/runtime/runner.py    |  29 +-
 .../runtime/runner.py => pipeline/__init__.py}     |  30 +-
 python/tvm/contrib/msc/pipeline/manager.py         | 804 +++++++++++++++++++++
 src/contrib/msc/core/codegen/base_codegen.h        |  16 +-
 src/contrib/msc/core/codegen/code_stack.h          |   8 +-
 src/contrib/msc/core/codegen/codegen_utils.cc      |   2 +-
 src/contrib/msc/core/codegen/codegen_utils.h       |  72 +-
 src/contrib/msc/core/codegen/cpp_codegen.h         |  77 +-
 src/contrib/msc/core/codegen/py_codegen.h          |  59 +-
 src/contrib/msc/core/printer/python_printer.cc     |   2 +-
 src/contrib/msc/core/transform/set_expr_name.cc    | 110 ++-
 src/contrib/msc/framework/tensorflow/codegen.cc    |   4 +-
 src/contrib/msc/framework/tensorrt/codegen.cc      |   3 +
 src/contrib/msc/framework/tensorrt/codegen_utils.h |   8 +-
 .../msc/framework/tensorrt/tensorrt_opcode.h       |   2 +-
 src/contrib/msc/framework/torch/codegen.cc         |  22 +-
 src/contrib/msc/framework/torch/codegen_utils.h    |   4 +-
 src/contrib/msc/framework/torch/torch_opcode.cc    |  46 ++
 src/contrib/msc/framework/torch/torch_opcode.h     |   5 +-
 src/contrib/msc/framework/tvm/codegen.cc           |  40 +-
 src/contrib/msc/framework/tvm/relax_opcode.cc      |  11 +-
 tests/python/contrib/test_msc/test_graph_build.py  |  25 +-
 tests/python/contrib/test_msc/test_manager.py      | 263 +++++++
 tests/python/contrib/test_msc/test_runner.py       |   4 +-
 .../contrib/test_msc/test_translate_relax.py       |   2 +-
 34 files changed, 2133 insertions(+), 340 deletions(-)

diff --git a/python/tvm/contrib/msc/core/runtime/runner.py 
b/python/tvm/contrib/msc/core/runtime/runner.py
index 3c8212f02d..cc4b56eae4 100644
--- a/python/tvm/contrib/msc/core/runtime/runner.py
+++ b/python/tvm/contrib/msc/core/runtime/runner.py
@@ -46,6 +46,8 @@ class BaseRunner(object):
         The config for translate IRModule to MSCGraph.
     codegen_config: dict
         The config for build MSCGraph to runnable model.
+    stage: str
+        The stage of runner.
     name: str
         The name of the runner
     device: str
@@ -61,7 +63,8 @@ class BaseRunner(object):
         mod: tvm.IRModule,
         tools_config: Optional[Dict[str, Any]] = None,
         translate_config: Optional[Dict[str, str]] = None,
-        load_config: Optional[Dict[str, str]] = None,
+        generate_config: Optional[Dict[str, str]] = None,
+        stage: str = "default",
         name: str = "main",
         device: str = "cpu",
         is_training: bool = False,
@@ -70,29 +73,52 @@ class BaseRunner(object):
         self._mod = mod
         self._tools_config = tools_config or {}
         self._translate_config = translate_config or {}
-        self._load_config = load_config or {}
+        self._generate_config = generate_config or {}
+        self._stage = stage
         self._name = name
         self._device = device if self._device_enabled(device) else "cpu"
         self._is_training = is_training
         self._logger = logger or msc_utils.get_global_logger()
-        self.setup()
-        config = {
-            "class": self.__class__.__name__,
+        self._logger.info(
+            msc_utils.msg_block(
+                "RUNNER.SETUP({} @ {})".format(self._stage, self.framework), 
self.setup()
+            )
+        )
+
+    def setup(self) -> dict:
+        """Setup the runner
+
+        Returns
+        -------
+        info: dict
+            The setup info.
+        """
+
+        if "build_folder" not in self._generate_config:
+            self._generate_config["build_folder"] = msc_utils.get_build_dir()
+        if self._tools_config:
+            if "codegen" not in self._generate_config:
+                self._generate_config["codegen"] = {}
+            self._generate_config["codegen"].update({"use_tools": True, 
"tools_tag": self._name})
+        self._graphs, self._weights = [], []
+        self._model, self._model_info = None, {}
+        self._runnable = None
+        self._tools = {}
+        return {
             "tools_config": self._tools_config,
             "translate_config": self._translate_config,
-            "load_config": self._load_config,
+            "generate_config": self._generate_config,
             "name": self._name,
             "device": self._device,
             "is_training": self._is_training,
         }
-        self._logger.debug(msc_utils.msg_block("RUNNER_CONFIG", config))
 
-    def setup(self):
-        """Setup the runner"""
+    def change_stage(self, stage: str):
+        """Change the stage of tools and strategy"""
 
-        self._graphs, self._weights = [], []
-        self._model, self._model_info = None, {}
-        self._runnable = None
+        self._stage = stage
+        for tool in self._tools.values():
+            tool.change_stage(stage)
 
     def build(self, cache_dir: msc_utils.MSCDirectory = None, build_graph: 
bool = False) -> Any:
         """Build the runnable object
@@ -115,6 +141,10 @@ class BaseRunner(object):
         else:
             cache_info = {}
 
+        # Create tools
+        if self._tools_config:
+            raise NotImplementedError("Tools is not supported")
+
         # Load graphs from cache
         if cache_info.get("graphs"):
             self._graphs, self._weights = self._load_graphs(cache_dir, 
cache_info["graphs"])
@@ -127,26 +157,18 @@ class BaseRunner(object):
             self._graphs, self._weights = self._translate()
             self._logger.debug("Translate {} graphs from 
module".format(len(self._graphs)))
 
-        # Save graphs for debug
-        for graph in self._graphs:
-            graph.visualize(msc_utils.get_debug_dir().relpath(graph.name + 
".prototxt"))
-
-        # Create tools
-        if self._tools_config:
-            raise NotImplementedError("Build runner with tools is not 
supported")
-
         if cache_info.get("model") and not build_graph:
             # Load model from cache
             self._model = self._load_model(cache_dir, cache_info["model"])
         else:
-            # Generate and save model
+            # Generate model
             self._model = self._generate_model()
-            if "loader" in self._load_config:
-                loader, load_config = self._load_config["loader"]
-                self._model = loader(self._model, **load_config)
+            if "loader" in self._generate_config:
+                loader, generate_config = self._generate_config["loader"]
+                self._model = loader(self._model, **generate_config)
                 self._logger.info(
                     "Model({}) processed by customize loader {}({})".format(
-                        self.framework, loader, load_config
+                        self.framework, loader, generate_config
                     )
                 )
         self._model_info = self._inspect_model()
@@ -181,8 +203,9 @@ class BaseRunner(object):
         }
         with open(cache_dir.relpath("cache_info.json"), "w") as f:
             f.write(json.dumps(cache_info, indent=2))
-        self._logger.debug("Runner save cache -> " + str(cache_dir.path))
-        self._logger.debug(msc_utils.msg_block("CACHE_INFO", cache_info))
+        self._logger.debug(
+            msc_utils.msg_block("CACHE_INFO", {"folder": cache_dir, "info": 
cache_info})
+        )
 
     def run(
         self, inputs: Union[List[np.ndarray], Dict[str, np.ndarray]], 
ret_type="dict"
@@ -240,6 +263,20 @@ class BaseRunner(object):
             outputs = [msc_utils.cast_array(data) for data in outputs]
         return outputs
 
+    def visualize(self, visual_dir: msc_utils.MSCDirectory):
+        """Visualize MSCGraphs
+
+        Parameters
+        -------
+        visual_dir: MSCDirectory
+            Visualize path for saving graph
+        """
+
+        for graph in self._graphs:
+            graph.visualize(visual_dir.relpath(graph.name + ".prototxt"))
+        for tool in self._tools.values():
+            tool.visualize(visual_dir)
+
     def get_inputs(self) -> List[Dict[str, str]]:
         """Get the inputs of the model
 
@@ -266,7 +303,11 @@ class BaseRunner(object):
         """Destory runner"""
 
         if self._model:
-            del self._model
+            self._model = None
+        if self._runnable:
+            self._runnable = None
+        for tool in self._tools.values():
+            tool.destory()
 
     def _translate(self) -> Tuple[List[MSCGraph], Dict[str, tvm.nd.array]]:
         """Translate IRModule to MSCgraphs
@@ -319,9 +360,18 @@ class BaseRunner(object):
 
         raise NotImplementedError("_save_graphs is not implemented for " + 
str(self.__class__))
 
-    def _generate_model(self) -> Any:
+    def _generate_model(
+        self, graphs: List[MSCGraph] = None, weights: List[Dict[str, 
tvm.nd.array]] = None
+    ) -> Any:
         """Codegen the model according to framework
 
+        Parameters
+        -------
+        graphs: list<MSCgraph>
+            The msc graphs.
+        weights: list<dic<str, tvm.nd.array>>
+            The weights
+
         Returns
         -------
         model: Any
@@ -464,6 +514,10 @@ class BaseRunner(object):
 
         return True
 
+    @property
+    def stage(self):
+        return self._stage
+
     @property
     def model(self):
         return self._model
@@ -472,6 +526,10 @@ class BaseRunner(object):
     def runnable(self):
         return self._runnable
 
+    @property
+    def model_info(self):
+        return self._model_info
+
     @property
     def device(self):
         return self._device
@@ -558,9 +616,18 @@ class ModelRunner(BaseRunner):
                 f_params.write(tvm.runtime.save_param_dict(self._weights[0]))
         return {"main": main_info}
 
-    def _generate_model(self) -> Any:
+    def _generate_model(
+        self, graphs: List[MSCGraph] = None, weights: List[Dict[str, 
tvm.nd.array]] = None
+    ) -> Any:
         """Codegen the model according to framework
 
+        Parameters
+        -------
+        graphs: list<MSCgraph>
+            The msc graphs.
+        weights: list<dic<str, tvm.nd.array>>
+            The weights
+
         Returns
         -------
         model: Any
@@ -568,11 +635,11 @@ class ModelRunner(BaseRunner):
         """
 
         return self.codegen_func(
-            self._graphs[0],
-            self._weights[0],
-            codegen_config=self._load_config.get("codegen"),
-            print_config=self._load_config.get("build"),
-            build_folder=self._load_config.get("build_folder", 
msc_utils.get_build_dir()),
+            graphs or self._graphs[0],
+            weights or self._weights[0],
+            codegen_config=self._generate_config.get("codegen"),
+            print_config=self._generate_config.get("build"),
+            build_folder=self._generate_config["build_folder"],
         )
 
     def _inspect_model(self) -> dict:
@@ -590,12 +657,29 @@ class ModelRunner(BaseRunner):
 class BYOCRunner(BaseRunner):
     """BYOC runner of MSC"""
 
-    def setup(self):
-        """Setup the runner"""
+    def setup(self) -> dict:
+        """Setup the runner
+
+        Returns
+        -------
+        info: dict
+            The setup info.
+        """
 
-        super().setup()
         self._byoc_mod, self._byoc_graph = None, None
-        self._graph_infos = {}
+        return super().setup()
+
+    def visualize(self, visual_dir: msc_utils.MSCDirectory):
+        """Visualize MSCGraphs
+
+        Parameters
+        -------
+        visual_dir: MSCDirectory
+            Visualize path for saving graph
+        """
+
+        super().visualize(visual_dir)
+        self._byoc_graph.visualize(visual_dir.relpath(self._byoc_graph.name + 
".prototxt"))
 
     def _translate(self) -> Tuple[List[MSCGraph], Dict[str, tvm.nd.array]]:
         """Translate IRModule to MSCgraphs
@@ -608,21 +692,18 @@ class BYOCRunner(BaseRunner):
             The translated weights
         """
 
-        self._byoc_mod, self._graph_infos = self.partition_func(
+        self._byoc_mod, graph_infos = self.partition_func(
             self._mod,
             trans_config=self._translate_config.get("transform"),
             build_config=self._translate_config.get("build"),
         )
         graphs, weights = [], []
-        for graph, sub_weights in self._graph_infos:
+        for graph, sub_weights in graph_infos:
             graphs.append(graph)
             weights.append(sub_weights)
         self._byoc_graph = _ffi_api.BuildFromRelax(
             self._byoc_mod, "main", 
msc_utils.dump_dict(self._translate_config.get("build"))
         )
-        self._byoc_graph.visualize(
-            msc_utils.get_debug_dir().relpath(self._byoc_graph.name + 
".prototxt")
-        )
         return graphs, weights
 
     def _load_graphs(
@@ -655,17 +736,14 @@ class BYOCRunner(BaseRunner):
             cache_info
         )
 
-        self._byoc_mod = 
tvm.ir.load_json(cache_dir.relpath(cache_info["byoc_mod"]))
+        with open(cache_dir.relpath(cache_info["byoc_mod"]), "r") as f:
+            self._byoc_mod = tvm.ir.load_json(f.read())
         graphs, weights = [], []
         for f_graph, f_weights in cache_info["sub_graphs"]:
             graphs.append(MSCGraph.from_json(cache_dir.relpath(f_graph)))
             with open(cache_dir.relpath(f_weights), "rb") as f:
                 weights = tvm.runtime.load_param_dict(f.read())
-        self._graph_infos = list(zip(graphs, weights))
         self._byoc_graph = 
MSCGraph.from_json(cache_dir.relpath(cache_info["byoc_graph"]))
-        self._byoc_graph.visualize(
-            msc_utils.get_debug_dir().relpath(self._byoc_graph.name + 
".prototxt")
-        )
         return graphs, weights
 
     def _save_graphs(self, cache_dir: msc_utils.MSCDirectory) -> dict:
@@ -701,22 +779,35 @@ class BYOCRunner(BaseRunner):
             "byoc_mod": "byoc_module.json",
         }
 
-    def _generate_model(self) -> tvm.IRModule:
+    def _generate_model(
+        self, graphs: List[MSCGraph] = None, weights: List[Dict[str, 
tvm.nd.array]] = None
+    ) -> Any:
         """Codegen the model according to framework
 
+        Parameters
+        -------
+        graphs: list<MSCgraph>
+            The msc graphs.
+        weights: list<dic<str, tvm.nd.array>>
+            The weights
+
         Returns
         -------
         model: tvm.IRModule
             The relax module
         """
 
+        graph_infos = list(zip(graphs or self._graphs, weights or 
self._weights))
+        extra_option = self._generate_config.get("extra_option", {})
+        extra_option["tool_tag"] = self._name
         return self.codegen_func(
             self._byoc_mod,
-            self._graph_infos,
-            codegen_config=self._load_config.get("codegen"),
-            print_config=self._load_config.get("build"),
-            build_folder=self._load_config.get("build_folder", 
msc_utils.get_build_dir()),
-            output_folder=self._load_config.get("output_folder", 
msc_utils.get_output_dir()),
+            graph_infos,
+            codegen_config=self._generate_config.get("codegen"),
+            print_config=self._generate_config.get("build"),
+            extra_option=extra_option,
+            build_folder=self._generate_config["build_folder"],
+            output_folder=self._generate_config.get("output_folder", 
msc_utils.get_output_dir()),
         )
 
     def _to_runnable(self, model: Any, device: str, is_training: bool) -> Any:
diff --git a/python/tvm/contrib/msc/core/utils/dataset.py 
b/python/tvm/contrib/msc/core/utils/dataset.py
index 68760f07ae..7835eb346e 100644
--- a/python/tvm/contrib/msc/core/utils/dataset.py
+++ b/python/tvm/contrib/msc/core/utils/dataset.py
@@ -14,19 +14,20 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+# pylint: disable=unused-argument
 """tvm.contrib.msc.core.utils.dataset"""
 
 import os
 import shutil
 import json
-from typing import List, Union, Dict
+from typing import List, Union, Dict, Any
 import numpy as np
 
 from .info import load_dict
 
 
-class MSCDataLoader(object):
-    """Dataset Loader for MSC
+class BaseDataLoader(object):
+    """Basic dataset loader for MSC
 
     Parameters
     ----------
@@ -43,31 +44,26 @@ class MSCDataLoader(object):
         self._start = start
         self._current = 0
         assert os.path.isdir(folder), "Dataset {} is not folder".format(folder)
-        self._info = load_dict(os.path.join(folder, "msc_info.json"))
+        self._info = load_dict(os.path.join(folder, "datas_info.json"))
         if end == -1:
             self._end = self._info["num_datas"]
         else:
             self._end = min(end, self._info["num_datas"])
 
+    def __str__(self):
+        return "<{}> @ {}".format(self._class__.__name__, self._folder)
+
     def __getitem__(self, idx):
         if idx + self._start >= self._end:
             raise StopIteration("Reach End")
-        if "inputs" in self._info:
-            inputs = {n: self._load_data(n, idx, i) for n, i in 
self._info["inputs"].items()}
-        else:
-            inputs = {}
-        if "outputs" in self._info:
-            outputs = {n: self._load_data(n, idx, i) for n, i in 
self._info["outputs"].items()}
-        else:
-            outputs = {}
-        return inputs, outputs
+        return self._load_batch(idx)
 
     def __next__(self):
         if self._current + self._start >= self._end:
             raise StopIteration("Reach End")
-        inputs, outputs = self.__getitem__(self._current)
+        batch = self._load_batch(self._current)
         self._current += 1
-        return inputs, outputs
+        return batch
 
     def __len__(self):
         return self._end - self._start
@@ -75,6 +71,47 @@ class MSCDataLoader(object):
     def reset(self):
         self._current = 0
 
+    def has_data(self, name: str, index: int) -> bool:
+        """Check if data exist.
+
+        Parameters
+        -------
+        name: str
+            The name of the data.
+        index: int
+            The index of the data.
+
+        Returns
+        -------
+        has_data: bool
+           Whether the data can be load.
+        """
+
+        info = self._data_info(name)
+        if not info:
+            return False
+        save_name = info.get("save_name", name)
+        f_path = os.path.join(self._folder, save_name, 
"batch_{}.bin".format(self._start + index))
+        return os.path.isfile(f_path)
+
+    def load_data(self, name: str, index: int) -> np.ndarray:
+        """Load data by name.
+
+        Parameters
+        -------
+        name: str
+            The name of the data.
+        index: int
+            The index of the data.
+
+        Returns
+        -------
+        data: np.ndarray
+           The loaded data.
+        """
+
+        return self._load_data(name, index, self._data_info(name))
+
     def _load_data(self, name: str, index: int, info: dict) -> np.ndarray:
         """Load data from file.
 
@@ -98,22 +135,135 @@ class MSCDataLoader(object):
         assert os.path.isfile(f_path), "Can not find data file " + str(f_path)
         return np.fromfile(f_path, dtype=info["dtype"]).reshape(info["shape"])
 
+    def _load_batch(self, index: int) -> Any:
+        """Get batch data
+
+        Parameters
+        -------
+        index: int
+            The index for the batch.
+
+        Returns
+        -------
+        batch: Any
+           The batch data.
+        """
+
+        raise NotImplementedError("_load_batch is not implemented for 
BaseDataLoader")
+
+    def _data_info(self, name: str) -> dict:
+        """Get info of data
+
+        Parameters
+        -------
+        name: str
+            The name of data.
+
+        Returns
+        -------
+        info: dict
+           The info of data.
+        """
+
+        raise NotImplementedError("_data_info is not implemented for 
BaseDataLoader")
+
     @property
     def info(self):
         return self._info
 
 
-class MSCDataSaver(object):
+class SimpleDataLoader(BaseDataLoader):
+    """Dataset Loader for simple datas"""
+
+    def _load_batch(self, index: int) -> Any:
+        """Get batch data
+
+        Parameters
+        -------
+        index: int
+            The index for the batch.
+
+        Returns
+        -------
+        batch: Any
+           The batch data.
+        """
+
+        assert "datas" in self._info, "datas shoule be given to load batch"
+        return {n: self._load_data(n, index, i) for n, i in 
self._info["datas"].items()}
+
+    def _data_info(self, name: str) -> dict:
+        """Get info of data
+
+        Parameters
+        -------
+        name: str
+            The name of data.
+
+        Returns
+        -------
+        info: dict
+           The info of data.
+        """
+
+        return self._info["datas"].get(name)
+
+
+class IODataLoader(BaseDataLoader):
+    """Dataset Loader for Input/Output datas"""
+
+    def _load_batch(self, index: int) -> Any:
+        """Get batch data
+
+        Parameters
+        -------
+        index: int
+            The index for the batch.
+
+        Returns
+        -------
+        batch: Any
+           The batch data.
+        """
+
+        if "inputs" in self._info:
+            inputs = {n: self._load_data(n, index, i) for n, i in 
self._info["inputs"].items()}
+        else:
+            inputs = {}
+        if "outputs" in self._info:
+            outputs = {n: self._load_data(n, index, i) for n, i in 
self._info["outputs"].items()}
+        else:
+            outputs = {}
+        return inputs, outputs
+
+    def _data_info(self, name: str) -> dict:
+        """Get info of data
+
+        Parameters
+        -------
+        name: str
+            The name of data.
+
+        Returns
+        -------
+        info: dict
+           The info of data.
+        """
+
+        if name in self._info["inputs"]:
+            return self._info["inputs"][name]
+        return self._info["outputs"].get(name)
+
+
+class BaseDataSaver(object):
     """Dataset Saver for MSC
 
     Parameters
     ----------
     folder: string
         The dataset folder path.
-    input_names: list<string>
-        The input names.
-    output_names: list<string>
-        The output names.
+    options: dict
+        The extra options for the data saver
     start: int
         The start position.
     max_size: int
@@ -123,8 +273,7 @@ class MSCDataSaver(object):
     def __init__(
         self,
         folder: str,
-        input_names: List[str],
-        output_names: List[str],
+        options: dict = None,
         start: int = 0,
         max_size: int = -1,
     ):
@@ -132,30 +281,130 @@ class MSCDataSaver(object):
             shutil.rmtree(folder)
         os.mkdir(folder)
         self._folder = folder
-        self._input_names = input_names
-        self._output_names = output_names
         self._start = start
         self._max_size = max_size
         self._current = 0
         assert os.path.isdir(folder), "Dataset {} is not folder".format(folder)
-        self._info = {"inputs": {}, "outputs": {}, "num_datas": 0}
+        self._info = self.setup(options)
+
+    def setup(self, options: dict):
+        return {"num_datas": 0}
 
     def __enter__(self):
         return self
 
     def __exit__(self, exception_type, exception_value, traceback):
         self._info["num_datas"] = self._current
-        with open(os.path.join(self._folder, "msc_info.json"), "w") as f:
+        self.finalize()
+
+    def finalize(self):
+        with open(os.path.join(self._folder, "datas_info.json"), "w") as f:
             f.write(json.dumps(self._info, indent=2))
 
     def reset(self):
         self._current = 0
 
-    def save(
+    def _save_data(self, index: int, name: str, data: np.ndarray, collect: 
str) -> str:
+        """Save data to file.
+
+        Parameters
+        -------
+        index: int
+            The index
+        name: str
+            The name of the data.
+        data: np.ndarray
+           The data to be saved.
+        collect: str
+            The collect of data.
+
+        Returns
+        -------
+        data_path: str
+           The folder that data saved to.
+        """
+
+        save_name = name.replace("/", "_").replace(":", "_")
+        sub_folder = f_path = os.path.join(self._folder, save_name)
+        if not os.path.isdir(sub_folder):
+            os.mkdir(sub_folder)
+        f_path = os.path.join(sub_folder, "batch_{}.bin".format(self._start + 
index))
+        ref_info = self._info[collect]
+        # TODO(mengtong): support dynamic datas shape
+        if name in ref_info:
+            assert (
+                ref_info[name]["dtype"] == data.dtype.name
+            ), "dtype {} mismatch with saved {}".format(data.dtype.name, 
ref_info[name]["dtype"])
+            assert ref_info[name]["shape"] == list(
+                data.shape
+            ), "shape {} mismatch with saved {}".format(data.shape, 
ref_info[name]["shape"])
+        else:
+            ref_info[name] = {
+                "shape": list(data.shape),
+                "dtype": data.dtype.name,
+                "bytes": data.size * data.itemsize,
+                "save_name": save_name,
+            }
+        data.tofile(f_path)
+        return sub_folder
+
+    def _save_batch(self, *args, **kwargs) -> dict:
+        """Save a batch data"""
+
+        raise NotImplementedError("_save_batch is not implemented for 
BaseDataSaver")
+
+    @property
+    def info(self):
+        return self._info
+
+
+class SimpleDataSaver(BaseDataSaver):
+    """Dataset Saver for simple datas"""
+
+    def save_datas(self, datas: Dict[str, np.ndarray], index: int = -1) -> 
Dict[str, str]:
+        """Save 1 simple datas.
+
+        Parameters
+        -------
+        datas: dict<str, np.ndarray>
+            The datas to be saved.
+        indec: int
+            The current index
+
+        Returns
+        -------
+        datas_path: dict<str, str>
+           The data paths.
+        """
+
+        datas_path = {}
+        current = self._current if index < 0 else index
+        for name, data in datas.items():
+            datas_path[name] = self._save_data(current, name, data, "datas")
+        if index > 0:
+            self._current = index
+        else:
+            self._current += 1
+        return datas_path
+
+    def setup(self, options: dict):
+        return {"datas": {}, "num_datas": 0}
+
+
+class IODataSaver(BaseDataSaver):
+    """Dataset Saver for inputs/outputs"""
+
+    def setup(self, options: dict):
+        assert "input_names" in options, "input_names should be given to setup 
IODataSaver"
+        self._input_names = options["input_names"]
+        self._output_names = options.get("output_names", [])
+        return {"inputs": {}, "outputs": {}, "num_datas": 0}
+
+    def save_batch(
         self,
         inputs: Union[Dict[str, np.ndarray], List[np.ndarray]],
         outputs: Union[Dict[str, np.ndarray], List[np.ndarray]] = None,
-    ):
+    ) -> int:
         """Save 1 batch inputs and outputs.
 
         Parameters
@@ -164,6 +413,11 @@ class MSCDataSaver(object):
             The inputs datas.
         outputs: list<np.ndarray>/dict<str, np.ndarray>
             The outputs datas.
+
+        Returns
+        -------
+        current: int
+           The current batch cnt.
         """
 
         if isinstance(inputs, dict):
@@ -176,7 +430,7 @@ class MSCDataSaver(object):
             ), "Inputs size {} mismatch with input_names 
{}".format(len(inputs), self._input_names)
             inputs = dict(zip(self._input_names, inputs))
         for name, data in inputs.items():
-            self._save_data(name, data, True)
+            self._save_data(self._current, name, data, "inputs")
         if outputs:
             if isinstance(outputs, dict):
                 assert set(outputs.keys()) == set(
@@ -190,52 +444,24 @@ class MSCDataSaver(object):
                 )
                 outputs = dict(zip(self._output_names, outputs))
             for name, data in outputs.items():
-                self._save_data(name, data, False)
+                self._save_data(self._current, name, data, "outputs")
         self._current += 1
         return self._current
 
-    def _save_data(self, name: str, data: np.ndarray, is_input: bool):
-        """Save data to file.
 
-        Parameters
-        -------
-        name: str
-            The name of the data.
-        data: np.ndarray
-           The data to be saved.
-        is_input: bool
-            Whether the data is input.
-        """
+def is_io_dataset(folder: str) -> bool:
+    """Check if a folder is IO dataset"""
 
-        save_name = name.replace("/", "_")
-        sub_folder = f_path = os.path.join(self._folder, save_name)
-        if not os.path.isdir(sub_folder):
-            os.mkdir(sub_folder)
-        f_path = os.path.join(sub_folder, "batch_{}.bin".format(self._start + 
self._current))
-        ref_info = self._info["inputs"] if is_input else self._info["outputs"]
-        # TODO(mengtong): support dynamic datas shape
-        if name in ref_info:
-            assert (
-                ref_info[name]["dtype"] == data.dtype.name
-            ), "dtype {} mismatch with saved {}".format(data.dtype.name, 
ref_info[name]["dtype"])
-            assert ref_info[name]["shape"] == list(
-                data.shape
-            ), "shape {} mismatch with saved {}".format(data.shape, 
ref_info[name]["shape"])
-        else:
-            ref_info[name] = {
-                "shape": list(data.shape),
-                "dtype": data.dtype.name,
-                "bytes": data.size * data.itemsize,
-                "save_name": save_name,
-            }
-        data.tofile(f_path)
-
-    @property
-    def info(self):
-        return self._info
+    if not os.path.isfile(os.path.join(folder, "datas_info.json")):
+        return False
+    data_info = load_dict(os.path.join(folder, "datas_info.json"))
+    return "inputs" in data_info and "outputs" in data_info
 
 
-def is_dataset(folder: str) -> bool:
-    """Check if a folder is MSC dataset"""
+def is_simple_dataset(folder: str) -> bool:
+    """Check if a folder is simple dataset"""
 
-    return os.path.isfile(os.path.join(folder, "msc_info.json"))
+    if not os.path.isfile(os.path.join(folder, "datas_info.json")):
+        return False
+    data_info = load_dict(os.path.join(folder, "datas_info.json"))
+    return "datas" in data_info
diff --git a/python/tvm/contrib/msc/core/utils/file.py 
b/python/tvm/contrib/msc/core/utils/file.py
index 88808c61d2..f59295640c 100644
--- a/python/tvm/contrib/msc/core/utils/file.py
+++ b/python/tvm/contrib/msc/core/utils/file.py
@@ -211,6 +211,8 @@ class MSCDirectory(object):
             The content of directory
         """
 
+        if not os.path.isdir(self._path):
+            return []
         return os.listdir(self._path)
 
     def destory(self):
@@ -285,13 +287,19 @@ def get_workspace() -> MSCDirectory:
     return workspace
 
 
-def get_workspace_subdir(name: str = None) -> MSCDirectory:
+def get_workspace_subdir(
+    name: str = None, keep_history: bool = True, cleanup: bool = False
+) -> MSCDirectory:
     """Create sub dir for workspace
 
     Parameters
     ----------
     name: str
         The sub dir name under workspace.
+    keep_history: bool
+        Whether to remove files before start.
+    cleanup: bool
+        Whether to clean up before exit.
 
     Returns
     -------
@@ -299,11 +307,36 @@ def get_workspace_subdir(name: str = None) -> 
MSCDirectory:
         The created dir.
     """
 
-    return get_workspace().create_dir(name)
+    return get_workspace().create_dir(name, keep_history, cleanup)
+
+
+def to_abs_path(path: str, root_dir: MSCDirectory = None, keep_history: bool = 
True) -> str:
+    """Change path to abs path
+
+    Parameters
+    ----------
+    path: str
+        The path of the file.
+    root_dir: MSCDirectory
+        Root dir to save the file.
+    keep_history: bool
+        Whether to remove files before start.
+
+    Returns
+    -------
+    abs_path: str
+        The abspath.
+    """
+
+    root_dir = root_dir or get_workspace()
+    if os.path.abspath(path) == path:
+        return path
+    return root_dir.relpath(path, keep_history)
 
 
 get_build_dir = partial(get_workspace_subdir, name="Build")
-get_output_dir = partial(get_workspace_subdir, name="Output")
-get_dataset_dir = partial(get_workspace_subdir, name="Dataset")
-get_debug_dir = partial(get_workspace_subdir, name="Debug")
 get_cache_dir = partial(get_workspace_subdir, name="Cache")
+get_config_dir = partial(get_workspace_subdir, name="Config")
+get_dataset_dir = partial(get_workspace_subdir, name="Dataset")
+get_output_dir = partial(get_workspace_subdir, name="Output")
+get_visual_dir = partial(get_workspace_subdir, name="Visual")
diff --git a/python/tvm/contrib/msc/core/utils/info.py 
b/python/tvm/contrib/msc/core/utils/info.py
index 6053d8ddc8..5d8d4fdd5a 100644
--- a/python/tvm/contrib/msc/core/utils/info.py
+++ b/python/tvm/contrib/msc/core/utils/info.py
@@ -27,6 +27,29 @@ import tvm
 from .namespace import MSCFramework
 
 
+def inspect_array(data: np.ndarray) -> Dict[str, Any]:
+    """Inspect the array
+
+    Parameters
+    ----------
+    data: np.ndarray
+        The data to inspect
+
+    Returns
+    -------
+    info: dict
+        The data info.
+    """
+
+    return {
+        "shape": list(data.shape),
+        "dtype": data.dtype.name,
+        "max": float(data.max()),
+        "min": float(data.min()),
+        "avg": float(data.sum() / data.size),
+    }
+
+
 class MSCArray(object):
     """MSC wrapper for array like object
 
@@ -43,6 +66,8 @@ class MSCArray(object):
         return "<{}>{}".format(self._type, self.abstract())
 
     def _analysis(self, data: Any) -> Tuple[str, np.ndarray]:
+        if isinstance(data, (list, tuple)) and all(isinstance(d, (int, float)) 
for d in data):
+            return "np", np.array(data)
         if isinstance(data, np.ndarray):
             return "np", data
         if isinstance(data, tvm.runtime.NDArray):
@@ -76,7 +101,7 @@ class MSCArray(object):
         return self._data
 
 
-def cast_array(data: Any):
+def cast_array(data: Any) -> np.ndarray:
     """Cast array like object to np.ndarray
 
     Parameters
@@ -190,22 +215,28 @@ def dump_dict(dict_obj: dict, flavor: str = "dmlc") -> 
str:
         return ""
     if flavor == "dmlc":
         return json.dumps({k: int(v) if isinstance(v, bool) else v for k, v in 
dict_obj.items()})
-    if flavor == "table":
+    if flavor.startswith("table:"):
 
-        def _get_lines(value, indent=0):
+        def _get_lines(value, indent=2):
+            max_size = int(flavor.split(":")[1]) - indent - 2
             lines = []
             for k, v in value.items():
-                if isinstance(v, dict):
+                if isinstance(v, (dict, tuple, list)) and not v:
+                    continue
+                if isinstance(v, dict) and len(str(k) + str(v)) > max_size:
                     lines.append("{}{}:".format(indent * " ", k))
                     lines.extend(_get_lines(v, indent + 2))
-                elif isinstance(v, (tuple, list)) and len(str(v)) > 100:
-                    lines.append("{}{}:".format(indent * " ", k))
-                    lines.extend(
-                        [
-                            "{}<{}>{}".format((indent + 2) * " ", idx, ele)
-                            for idx, ele in enumerate(v)
-                        ]
-                    )
+                elif isinstance(v, (tuple, list)) and len(str(k) + str(v)) > 
max_size:
+                    if all(isinstance(e, (int, float)) for e in v):
+                        lines.append("{}{}: {}".format(indent * " ", k, 
MSCArray(v).abstract()))
+                    else:
+                        lines.append("{}{}:".format(indent * " ", k))
+                        lines.extend(
+                            [
+                                "{}<{}>{}".format((indent + 2) * " ", idx, ele)
+                                for idx, ele in enumerate(v)
+                            ]
+                        )
                 elif isinstance(v, bool):
                     lines.append("{}{}: {}".format(indent * " ", k, "true" if 
v else "false"))
                 elif isinstance(v, np.ndarray):
diff --git a/python/tvm/contrib/msc/core/utils/message.py 
b/python/tvm/contrib/msc/core/utils/message.py
index b3508f0e7c..69c31c807e 100644
--- a/python/tvm/contrib/msc/core/utils/message.py
+++ b/python/tvm/contrib/msc/core/utils/message.py
@@ -18,23 +18,41 @@
 
 import datetime
 import logging
+from typing import List
 
 from .info import dump_dict
 from .log import get_global_logger
 from .namespace import MSCMap, MSCKey
 
 
-def time_stamp(
-    stage: str, mark_stage: bool = False, log_stage: bool = True, logger: 
logging.Logger = None
-):
+class MSCStage(object):
+    """Enum all msc stage names"""
+
+    SETUP = "setup"
+    PREPARE = "prepare"
+    PARSE = "parse"
+    BASELINE = "baseline"
+    PRUNE = "prune"
+    QUANTIZE = "quantize"
+    DISTILL = "distill"
+    OPTIMIZE = "optimize"
+    COMPILE = "compile"
+    SUMMARY = "summary"
+    ALL = [SETUP, PREPARE, PARSE, BASELINE, PRUNE, QUANTIZE, DISTILL, 
OPTIMIZE, COMPILE, SUMMARY]
+
+    @classmethod
+    def all_stages(cls) -> List[str]:
+        """Get all stage names"""
+        return cls.ALL
+
+
+def time_stamp(stage: str, log_stage: bool = True, logger: logging.Logger = 
None):
     """Mark the stamp and record time.
 
     Parameters
     ----------
     stage: str
         The stage name.
-    mark_stage: bool
-        Whether to mark the stage.
     log_stage: bool
         Whether to log the stage
     logger: logging.Logger
@@ -45,17 +63,17 @@ def time_stamp(
     time_stamps = MSCMap.get(MSCKey.TIME_STAMPS, [])
     time_stamps.append((stage, datetime.datetime.now()))
     MSCMap.set(MSCKey.TIME_STAMPS, time_stamps)
-    if log_stage:
-        if mark_stage:
+    if stage in MSCStage.all_stages():
+        if log_stage:
             last_stage = MSCMap.get(MSCKey.MSC_STAGE)
             if last_stage:
-                end_msg = "[MSC] End {}".format(last_stage)
+                end_msg = "[MSC] End {}".format(last_stage.upper())
                 logger.info("\n{0} {1} {0}\n".format("#" * 20, 
end_msg.center(40)))
-            start_msg = "[MSC] Start {}".format(stage)
+            start_msg = "[MSC] Start {}".format(stage.upper())
             logger.info("\n{0} {1} {0}".format("#" * 20, start_msg.center(40)))
-            MSCMap.set(MSCKey.MSC_STAGE, stage)
-        else:
-            logger.debug("Start {}".format(stage))
+        MSCMap.set(MSCKey.MSC_STAGE, stage.upper())
+    elif log_stage:
+        logger.debug("Start {}".format(stage))
 
 
 def get_duration() -> dict:
@@ -106,7 +124,7 @@ def get_duration() -> dict:
     return duration
 
 
-def msg_block(title: str, msg: str):
+def msg_block(title: str, msg: str, width: int = 100):
     """Log message in block format
 
     Parameters
@@ -115,6 +133,8 @@ def msg_block(title: str, msg: str):
         The title of the block
     msg: str
         The message to log.
+    width: int
+        The max width of block message
 
     Returns
     -------
@@ -123,7 +143,7 @@ def msg_block(title: str, msg: str):
     """
 
     if isinstance(msg, dict):
-        msg = dump_dict(msg, "table")
+        msg = dump_dict(msg, "table:" + str(width))
     return "\n{0} {1} {0}\n{2}\n{3} {1} {3}".format(">" * 20, 
title.center(40), msg, "<" * 20)
 
 
diff --git a/python/tvm/contrib/msc/framework/tensorflow/runtime/runner.py 
b/python/tvm/contrib/msc/framework/tensorflow/runtime/runner.py
index e2c2e919ff..6fd26e04f1 100644
--- a/python/tvm/contrib/msc/framework/tensorflow/runtime/runner.py
+++ b/python/tvm/contrib/msc/framework/tensorflow/runtime/runner.py
@@ -24,6 +24,8 @@ import numpy as np
 from tensorflow.python.client import device_lib
 from tensorflow.python.ops import variables
 
+import tvm
+from tvm.contrib.msc.core.ir import MSCGraph
 from tvm.contrib.msc.core.runtime import ModelRunner
 from tvm.contrib.msc.core.utils.namespace import MSCFramework
 from tvm.contrib.msc.framework.tensorflow.codegen import to_tensorflow
@@ -58,26 +60,41 @@ class WrapSession(tf_v1.Session):
 class TensorflowRunner(ModelRunner):
     """Runner of Tensorflow"""
 
-    def setup(self):
-        """Setup the runner"""
+    def setup(self) -> dict:
+        """Setup the runner
+
+        Returns
+        -------
+        info: dict
+            The setup info.
+        """
 
-        super().setup()
         self._tf_graph = None
         self._tf_outputs = None
         self._session = None
+        return super().setup()
 
     def destory(self):
         """Destory runner"""
 
         self._session.close()
-        del self._tf_graph
-        del self._tf_outputs
-        del self._session
+        self._tf_graph = None
+        self._tf_outputs = None
+        self._session = None
         super().destory()
 
-    def _generate_model(self) -> Any:
+    def _generate_model(
+        self, graphs: List[MSCGraph] = None, weights: List[Dict[str, 
tvm.nd.array]] = None
+    ) -> Any:
         """Codegen the model according to framework
 
+        Parameters
+        -------
+        graphs: list<MSCgraph>
+            The msc graphs.
+        weights: list<dic<str, tvm.nd.array>>
+            The weights
+
         Returns
         -------
         model: Any
@@ -88,7 +105,7 @@ class TensorflowRunner(ModelRunner):
             del self._tf_graph
         self._tf_graph = tf_v1.Graph()
         with self._tf_graph.as_default():
-            self._tf_outputs = super()._generate_model()
+            self._tf_outputs = super()._generate_model(graphs, weights)
         return self._tf_graph
 
     def _to_runnable(self, model: Any, device: str, is_training: bool) -> Any:
diff --git a/python/tvm/contrib/msc/framework/tensorrt/codegen/codegen.py 
b/python/tvm/contrib/msc/framework/tensorrt/codegen/codegen.py
index 5539e614bf..1ff74f27b9 100644
--- a/python/tvm/contrib/msc/framework/tensorrt/codegen/codegen.py
+++ b/python/tvm/contrib/msc/framework/tensorrt/codegen/codegen.py
@@ -113,6 +113,7 @@ def to_sub_tensorrt(
     engine_file = codegen.load([], pre_load=_create_depends, 
post_load=_build_engine)
     return {
         "graph_json": graph.to_json(),
+        "graph_name": graph.name,
         "engine": engine_file,
     }
 
@@ -122,6 +123,7 @@ def to_tensorrt(
     graph_infos: List[Tuple[str, MSCGraph, Dict[str, tvm.nd.array]]],
     codegen_config: Optional[Dict[str, str]] = None,
     print_config: Optional[Dict[str, str]] = None,
+    extra_option: Optional[Dict[str, str]] = None,
     build_folder: msc_utils.MSCDirectory = None,
     output_folder: msc_utils.MSCDirectory = None,
 ) -> Dict[str, str]:
@@ -137,6 +139,8 @@ def to_tensorrt(
         The config for codegen.
     print_config: dict
         The config for print.
+    extra_option: dict
+        The extra option for sub engine.
     build_folder: MSCDirectory
         The folder for saving sources and datas.
     export_folder: MSCDirectory
@@ -153,6 +157,8 @@ def to_tensorrt(
         options = to_sub_tensorrt(
             graph, weights, codegen_config, print_config, build_folder, 
output_folder
         )
+        if extra_option:
+            options.update(extra_option)
         target_options[graph.name] = msc_utils.dump_dict(options)
     mod = tvm.transform.Sequential(
         [
diff --git a/python/tvm/contrib/msc/framework/tensorrt/frontend/translate.py 
b/python/tvm/contrib/msc/framework/tensorrt/frontend/translate.py
index a165a106ec..8758fdb630 100644
--- a/python/tvm/contrib/msc/framework/tensorrt/frontend/translate.py
+++ b/python/tvm/contrib/msc/framework/tensorrt/frontend/translate.py
@@ -26,6 +26,35 @@ from tvm.contrib.msc.core.frontend import byoc_partition
 from tvm.contrib.msc.framework.tensorrt import transform as trt_transform
 
 
+def transform_for_tensorrt(
+    mod: tvm.IRModule,
+    trans_config: Optional[Dict[str, str]] = None,
+) -> tvm.IRModule:
+    """Transform module to tensorrt.
+
+    Parameters
+    ----------
+    mod: IRModule
+        The IRModule of relax.
+    trans_config: dict
+        The config for transform IRModule.
+
+    Returns
+    -------
+    mod: IRModule
+        The transformed IRModule of relax.
+    """
+
+    trans_config = trans_config or {}
+    return tvm.transform.Sequential(
+        [
+            msc_transform.SetExprName(),
+            trt_transform.TransformTensorRT(trans_config.get("version")),
+            relax.transform.FoldConstant(),
+        ]
+    )(mod)
+
+
 def partition_for_tensorrt(
     mod: tvm.IRModule,
     params: Optional[Dict[str, tvm.nd.array]] = None,
@@ -53,12 +82,5 @@ def partition_for_tensorrt(
         The func <MSCGraph and weights> list, each element for a sub graph.
     """
 
-    trans_config = trans_config or {}
-    mod = tvm.transform.Sequential(
-        [
-            msc_transform.SetExprName(),
-            trt_transform.TransformTensorRT(trans_config.get("version")),
-            relax.transform.FoldConstant(),
-        ]
-    )(mod)
+    mod = transform_for_tensorrt(mod, trans_config)
     return byoc_partition("msc_tensorrt", mod, params, trans_config, 
build_config)
diff --git a/python/tvm/contrib/msc/framework/tensorrt/runtime/runner.py 
b/python/tvm/contrib/msc/framework/tensorrt/runtime/runner.py
index 88c45c786b..615cc4ba31 100644
--- a/python/tvm/contrib/msc/framework/tensorrt/runtime/runner.py
+++ b/python/tvm/contrib/msc/framework/tensorrt/runtime/runner.py
@@ -16,21 +16,48 @@
 # under the License.
 """tvm.contrib.msc.framework.tensorrt.runtime.runner"""
 
+import tvm
 from tvm.contrib.msc.core.runtime import BYOCRunner
 from tvm.contrib.msc.core.utils.namespace import MSCFramework
-from tvm.contrib.msc.framework.tensorrt.frontend import partition_for_tensorrt
+from tvm.contrib.msc.framework.tensorrt.frontend import (
+    partition_for_tensorrt,
+    transform_for_tensorrt,
+)
 from tvm.contrib.msc.framework.tensorrt.codegen import to_tensorrt
 
 
 class TensorRTRunner(BYOCRunner):
     """Runner of tensorrt"""
 
-    def setup(self):
-        """Setup the runner"""
+    def setup(self) -> dict:
+        """Setup the runner
+
+        Returns
+        -------
+        info: dict
+            The setup info.
+        """
 
-        super().setup()
         if not self._device.startswith("cuda"):
             self._device = "cuda"
+        return super().setup()
+
+    @classmethod
+    def target_transform(cls, mod: tvm.IRModule):
+        """Transform the mod by target.
+
+        Parameters
+        ----------
+        mod: IRModule
+            The IRModule of relax.
+
+        Returns
+        -------
+        mod: IRModule
+            The IRModule of partitioned relax.
+        """
+
+        return transform_for_tensorrt(mod)
 
     @property
     def codegen_func(self):
diff --git a/python/tvm/contrib/msc/framework/tvm/runtime/runner.py 
b/python/tvm/contrib/msc/framework/tvm/runtime/runner.py
index c5240ca229..b3a3f3bf70 100644
--- a/python/tvm/contrib/msc/framework/tvm/runtime/runner.py
+++ b/python/tvm/contrib/msc/framework/tvm/runtime/runner.py
@@ -25,6 +25,25 @@ from tvm.contrib.msc.core.utils.namespace import MSCFramework
 from tvm.contrib.msc.framework.tvm.codegen import to_relax
 
 
+class WrapRunnable(object):
+    """Wrapped runnable for tools
+
+    Parameters
+    -------
+    runnable: tvm.relax.VirtualMachine
+        The virtual machine.
+    entry: str
+        The entry funcname.
+    """
+
+    def __init__(self, runnable: tvm.relax.VirtualMachine, entry: str = 
"main"):
+        self._runnable = runnable
+        self._entry = entry
+
+    def __call__(self, *inputs) -> List[tvm.nd.array]:
+        return self._runnable[self._entry](*inputs)
+
+
 class TVMRunner(ModelRunner):
     """Runner of Relax"""
 
@@ -46,8 +65,8 @@ class TVMRunner(ModelRunner):
             The runnable
         """
 
-        if "builder" in self._load_config:
-            builder, build_config = self._load_config["builder"]
+        if "builder" in self._generate_config:
+            builder, build_config = self._generate_config["builder"]
             runnable = builder(model, **build_config)
             self._logger.info(
                 "Model({}) processed by customize builder {}({})".format(
@@ -70,10 +89,10 @@ class TVMRunner(ModelRunner):
                     runnable = tvm.relax.VirtualMachine(relax_exec, tvm.cuda())
             else:
                 raise NotImplementedError("Unsupported device " + str(device))
-        return runnable
+        return WrapRunnable(runnable)
 
     def _call_runnable(
-        self, runnable: tvm.relax.VirtualMachine, inputs: Dict[str, 
np.ndarray], device: str
+        self, runnable: WrapRunnable, inputs: Dict[str, np.ndarray], device: 
str
     ) -> Union[List[np.ndarray], Dict[str, np.ndarray]]:
         """Call the runnable to get outputs
 
@@ -102,7 +121,7 @@ class TVMRunner(ModelRunner):
             ]
         else:
             raise NotImplementedError("Unsupported device " + str(device))
-        return runnable["main"](*tvm_inputs)
+        return runnable(*tvm_inputs)
 
     def _device_enabled(self, device: str) -> bool:
         """Check if the device is enabled
diff --git a/python/tvm/contrib/msc/framework/tensorrt/runtime/runner.py 
b/python/tvm/contrib/msc/pipeline/__init__.py
similarity index 50%
copy from python/tvm/contrib/msc/framework/tensorrt/runtime/runner.py
copy to python/tvm/contrib/msc/pipeline/__init__.py
index 88c45c786b..99a8699ad9 100644
--- a/python/tvm/contrib/msc/framework/tensorrt/runtime/runner.py
+++ b/python/tvm/contrib/msc/pipeline/__init__.py
@@ -14,32 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-"""tvm.contrib.msc.framework.tensorrt.runtime.runner"""
+"""tvm.contrib.msc.pipeline"""
 
-from tvm.contrib.msc.core.runtime import BYOCRunner
-from tvm.contrib.msc.core.utils.namespace import MSCFramework
-from tvm.contrib.msc.framework.tensorrt.frontend import partition_for_tensorrt
-from tvm.contrib.msc.framework.tensorrt.codegen import to_tensorrt
-
-
-class TensorRTRunner(BYOCRunner):
-    """Runner of tensorrt"""
-
-    def setup(self):
-        """Setup the runner"""
-
-        super().setup()
-        if not self._device.startswith("cuda"):
-            self._device = "cuda"
-
-    @property
-    def codegen_func(self):
-        return to_tensorrt
-
-    @property
-    def partition_func(self):
-        return partition_for_tensorrt
-
-    @property
-    def framework(self):
-        return MSCFramework.TENSORRT
+from .manager import *
diff --git a/python/tvm/contrib/msc/pipeline/manager.py 
b/python/tvm/contrib/msc/pipeline/manager.py
new file mode 100644
index 0000000000..f571884860
--- /dev/null
+++ b/python/tvm/contrib/msc/pipeline/manager.py
@@ -0,0 +1,804 @@
+# 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.
+# pylint: disable=import-outside-toplevel
+"""tvm.contrib.msc.pipeline.manager"""
+
+import os
+import time
+from typing import Dict, Any
+import traceback
+import numpy as np
+
+import tvm
+from tvm.contrib.msc.core.runtime import BaseRunner
+from tvm.contrib.msc.core.utils.namespace import MSCFramework, MSCMap, MSCKey
+from tvm.contrib.msc.core.utils.message import MSCStage
+from tvm.contrib.msc.core import utils as msc_utils
+
+
+class BaseManager(object):
+    """Base Manager of MSC
+
+    Parameters
+    ----------
+    model: Any
+        The raw model in framwork.
+    config: dict
+        The config for pipeline.
+    """
+
+    def __init__(self, model, config):
+        # check config
+        for stage in ["inputs", "outputs", "dataset", "prepare", "compile"]:
+            assert stage in config, "{} should be given to run the 
pipeline".format(stage)
+        self._model = model
+        self._workspace = msc_utils.set_workspace(config.get("workspace"))
+        log_path = config.get("log_path") or 
self._workspace.relpath("MSC_LOG", keep_history=False)
+        if config.get("debug", False) and "verbose" not in config:
+            verbose = "debug"
+        else:
+            verbose = config.get("verbose", "info")
+        self._logger = msc_utils.set_global_logger(verbose, log_path)
+        msc_utils.time_stamp(MSCStage.SETUP)
+        self._logger.info(msc_utils.msg_block("SETUP", self.setup(config)))
+
+    def setup(self, config: dict) -> dict:
+        """Setup the manager
+
+        Parameters
+        ----------
+        config: dict
+            The config for manager.
+
+        Returns
+        -------
+        info: dict
+            The setup info.
+        """
+
+        self._config, self._debug_config = self.update_config(config)
+        self._tools_config = {}
+        self._relax_mod, self._runner = None, None
+        self._data_loader, self._sample_inputs = None, None
+        self._report = {
+            "success": False,
+            "info": {
+                "workspace": self._workspace.path,
+                "model_type": self._config["model_type"],
+            },
+            "duration": {},
+            "profile": {},
+        }
+        return {"workspace": self._workspace.path, "config": config}
+
+    def update_config(self, config: dict) -> dict:
+        """Update config
+
+        Parameters
+        ----------
+        config: dict
+            The config for manager.
+
+        Returns
+        -------
+        config: dict
+            The updated config.
+        """
+
+        # update prepare and parse
+        assert "inputs" in config, "inputs should be given to run manager"
+        assert "outputs" in config, "outputs should be given to run manager"
+        config = msc_utils.copy_dict(config)
+        for stage in ["prepare", "parse"]:
+            if stage not in config:
+                config[stage] = {}
+        config = self._update_prepare_config(config)
+        config = self._update_parse_config(config)
+        for stage in ["baseline", "optimize", "compile"]:
+            config = self._update_runner_config(config, stage)
+        config = self._update_tool_config(config)
+        debug_config = {}
+
+        def _set_debug(stage, stage_config, default=None):
+            if "debug" in stage_config:
+                debug_config[stage] = stage_config.pop("debug")
+            elif default is not None:
+                debug_config[stage] = default
+            return debug_config
+
+        if "debug" in config:
+            for stage in ["baseline", "optimize", "compile"]:
+                if stage not in config:
+                    continue
+                debug_config = _set_debug(stage, config[stage], 
config["debug"])
+        else:
+            for stage in ["baseline", "optimize", "compile"]:
+                if stage not in config:
+                    continue
+                debug_config = _set_debug(stage, config[stage])
+        ordered_keys = [
+            "model_type",
+            "inputs",
+            "outputs",
+            "dataset",
+            "prepare",
+            "parse",
+            "baseline",
+            "optimize",
+            "compile",
+        ]
+        return {k: config[k] for k in ordered_keys if k in config}, 
debug_config
+
+    def run_pipe(self) -> dict:
+        """Run the pipeline and return object.
+
+        Returns
+        -------
+        report:
+            The pipeline report.
+        """
+
+        err_msg = None
+        use_cache = self._config.get("use_cache", True)
+        try:
+            self._data_loader, self._sample_inputs = self.prepare(
+                self._config["prepare"], use_cache
+            )
+            self._relax_mod = self.parse(self._config["parse"], use_cache)
+            if "baseline" in self._config:
+                self._runner = self.baseline(self._config["baseline"], 
use_cache)
+            if "optimize" in self._config:
+                self._runner = self.optimize(self._config["optimize"], 
use_cache)
+            self._runner = self.compile(self._config["compile"], use_cache)
+        except Exception as exc:  # pylint: disable=broad-exception-caught
+            err_msg = "Pipeline failed:{}\nTrace: {}".format(exc, 
traceback.format_exc())
+        report = self.summary(err_msg)
+        self._logger.info(msc_utils.msg_block("SUMMARY", report, 0))
+        return report
+
+    def prepare(self, stage_config: dict, use_cache: bool = False) -> 
Dict[str, np.ndarray]:
+        """Prepare datas for the pipeline.
+
+        Parameters
+        ----------
+        stage_config: dict
+            The config of this stage.
+        use_cache: bool
+            Whether to use cache.
+
+        Returns
+        -------
+        sample_inputs: dict<str,np.ndarray>
+            The sample inputs.
+        """
+
+        msc_utils.time_stamp(MSCStage.PREPARE)
+
+        # create data loader
+        source_loader = self._config["dataset"].get("loader")
+        max_batch = self._config["dataset"].get("max_batch", 5)
+        assert source_loader, "Dataset loader should be given for msc pipeline"
+        if source_loader.startswith("from_random"):
+
+            def get_random():
+                for _ in range(max_batch):
+                    yield {i[0]: np.random.rand(*i[1]).astype(i[2]) for i in 
self._config["inputs"]}
+
+            data_loader, source_type = get_random, "Random"
+        elif msc_utils.is_io_dataset(source_loader):
+
+            def load_datas():
+                for inputs, _ in msc_utils.IODataLoader(data_loader, 
end=max_batch):
+                    yield inputs
+
+            data_loader, source_type = load_datas, "IOData"
+        elif callable(source_loader):
+
+            def get_source():
+                for idx, inputs in enumerate(source_loader()):
+                    if idx >= max_batch:
+                        break
+                    yield inputs
+
+            data_loader, source_type = get_source, "Custom"
+        else:
+            raise TypeError(
+                "Unexpected source loader {}({})".format(source_loader, 
type(source_loader))
+            )
+        self._logger.info("Create data loader(%s) %s", source_type, 
data_loader)
+
+        # create golden
+        golden_folder = msc_utils.get_dataset_dir().relpath("Golden", 
use_cache)
+        input_names, sample_inputs = [i[0] for i in self._config["inputs"]], 
None
+        report = {"golden_folder": golden_folder}
+        runner_cls = self._get_runner_cls(self._config["model_type"])
+        run_func = runner_cls.run_native if hasattr(runner_cls, "run_native") 
else None
+        if use_cache and msc_utils.is_io_dataset(golden_folder):
+            golden_loader, source_type = 
msc_utils.IODataLoader(golden_folder), "Cache"
+            report["datas_info"] = golden_loader.info
+            sample_inputs = golden_loader[0][0]
+            self._logger.debug("Load %d cached golden from %s", 
len(golden_loader), golden_folder)
+        else:
+            # save golden
+            golden_cnt, max_golden = 0, 
self._config["dataset"].get("max_golden", 5)
+            saver_options = {"input_names": input_names, "output_names": 
self._config["outputs"]}
+            if run_func:
+                with msc_utils.IODataSaver(golden_folder, saver_options) as 
saver:
+                    for inputs in data_loader():
+                        if golden_cnt >= max_golden:
+                            break
+                        if not sample_inputs:
+                            sample_inputs = inputs
+                        outputs, _ = run_func(
+                            self._model, inputs, input_names, 
self._config["outputs"]
+                        )
+                        golden_cnt = saver.save_batch(inputs, outputs)
+                    report["datas_info"] = saver.info
+            elif isinstance(data_loader, msc_utils.IODataLoader):
+                with msc_utils.IODataSaver(golden_folder, saver_options) as 
saver:
+                    for inputs, outputs in data_loader():
+                        if golden_cnt >= max_golden:
+                            break
+                        if not sample_inputs:
+                            sample_inputs = inputs
+                        golden_cnt = saver.save_batch(inputs, outputs)
+                    report["datas_info"] = saver.info
+            else:
+                raise Exception("golden or runner should given in prepare to 
save golden")
+            self._logger.debug("Saved %d golden to %s", golden_cnt, 
golden_folder)
+
+        def _to_abstract(info: dict) -> dict:
+            def _to_tensor_str(info):
+                return "{},{}".format(";".join([str(s) for s in 
info["shape"]]), info["dtype"])
+
+            return {
+                "num_datas": info["num_datas"],
+                "inputs": {n: _to_tensor_str(i) for n, i in 
info["inputs"].items()},
+                "outputs": {n: _to_tensor_str(o) for n, o in 
info["outputs"].items()},
+            }
+
+        report["datas_info"] = _to_abstract(report["datas_info"])
+        report["sample_inputs"] = sample_inputs
+        
self._logger.info(msc_utils.msg_block("GOLDEN({})".format(source_type), report))
+
+        # profile
+        if "profile" in stage_config and run_func:
+            benchmark = stage_config["profile"].get("benchmark", {})
+            repeat = benchmark.get("repeat", 100)
+            self._logger.debug("Prepare profile with %s(%s)", run_func, 
benchmark)
+            _, avg_time = run_func(
+                self._model, sample_inputs, input_names, 
self._config["outputs"], **benchmark
+            )
+            self._logger.info("Profile(prepare) {} times -> {:.2f} 
ms".format(repeat, avg_time))
+            self._report["profile"]["prepare"] = {"latency": "{:.2f} 
ms".format(avg_time)}
+        return data_loader, sample_inputs
+
+    def parse(self, stage_config: dict, use_cache: bool = False) -> 
tvm.IRModule:
+        """Parse the model to IRModule.
+
+        Parameters
+        ----------
+        stage_config: dict
+            The config of this stage.
+        use_cache: bool
+            Whether to use cache.
+
+        Returns
+        -------
+        relax_mod: tvm.IRModule
+            The parsed module.
+        """
+
+        msc_utils.time_stamp(MSCStage.PARSE)
+        cache_path = msc_utils.get_cache_dir().relpath("parsed_relax.json") if 
use_cache else None
+        if cache_path and os.path.isfile(cache_path):
+            with open(cache_path, "r") as f:
+                relax_mod = tvm.ir.load_json(f.read())
+            self._logger.info("Load parsed mod from %s", cache_path)
+        else:
+            parse_config = stage_config.get("parse_config", {})
+            runner_cls = 
self._get_runner_cls(self._config["compile"]["run_type"])
+            trans_func = (
+                runner_cls.target_transform if hasattr(runner_cls, 
"target_transform") else None
+            )
+            parse_info = {
+                "parser": stage_config["parser"],
+                "config": parse_config,
+                "trans_func": trans_func,
+            }
+            self._logger.info(msc_utils.msg_block("PARSE", parse_info))
+            relax_mod, _ = stage_config["parser"](self._model, as_msc=False, 
**parse_config)
+            if trans_func:
+                relax_mod = trans_func(relax_mod)
+            if cache_path:
+                with open(cache_path, "w") as f:
+                    f.write(tvm.ir.save_json(relax_mod))
+                self._logger.debug("Save parsed mod to %s", cache_path)
+        return relax_mod
+
+    def baseline(self, stage_config: dict, use_cache: bool = False) -> 
BaseRunner:
+        """Run the baseline.
+
+        Parameters
+        ----------
+        stage_config: dict
+            The config of this stage.
+        use_cache: bool
+            Whether to use cache.
+
+        Returns
+        -------
+        runner: BaseRunner
+            The runner.
+        """
+
+        msc_utils.time_stamp(MSCStage.BASELINE)
+        return self._create_runner(MSCStage.BASELINE, stage_config, 
use_cache=use_cache)
+
+    def optimize(self, stage_config: dict, use_cache: bool = False) -> 
BaseRunner:
+        """Run the optimize and return object.
+
+        Parameters
+        ----------
+        stage_config: dict
+            The config of this stage.
+        use_cache: bool
+            Whether to use cache.
+
+        Returns
+        -------
+        runner: BaseRunner
+            The runner.
+        """
+
+        # optimize and get the runner
+        msc_utils.time_stamp(MSCStage.OPTIMIZE)
+        return self._create_runner(
+            MSCStage.OPTIMIZE, stage_config, tools_config=self._tools_config, 
use_cache=use_cache
+        )
+
+    def compile(self, stage_config: dict, use_cache: bool = False) -> 
BaseRunner:
+        """Run the compile and return object.
+
+        Parameters
+        ----------
+        stage_config: dict
+            The config of this stage.
+        use_cache: bool
+            Whether to use cache.
+        ret_type: str
+            The return type runner| model.
+
+        Returns
+        -------
+        runner: BaseRunner
+            The runner.
+        """
+
+        msc_utils.time_stamp(MSCStage.COMPILE)
+        return self._create_runner(
+            MSCStage.COMPILE, stage_config, tools_config=self._tools_config, 
use_cache=use_cache
+        )
+
+    def summary(self, err_msg=None):
+        """Summary the pipeline.
+
+        Parameters
+        ----------
+        err_msg: str
+            The error message.
+
+        Returns
+        -------
+        report: dict
+            The report of the pipeline.
+        """
+
+        msc_utils.time_stamp(MSCStage.SUMMARY, False)
+        if err_msg:
+            self._report.update({"success": False, "err_msg": err_msg})
+        else:
+            self._report["success"] = True
+        self._report["duration"] = msc_utils.get_duration()
+        return self._report
+
+    def destory(self, keep_workspace: bool = False):
+        """Destroy the manager
+
+        Parameters
+        ----------
+        keep_workspace: bool
+            Whether to keep workspace.
+        """
+
+        MSCMap.delete(MSCKey.TIME_STAMPS)
+        if self._runner:
+            self._runner.destory()
+        if not keep_workspace:
+            self._workspace.destory()
+
+    def _create_runner(
+        self,
+        stage: str,
+        stage_config: dict,
+        tools_config: dict = None,
+        visualize: bool = True,
+        profile: bool = True,
+        use_cache: bool = True,
+    ) -> BaseRunner:
+        """Create runner.
+
+        Parameters
+        ----------
+        stage: str
+            The stage name
+        stage_config: dict
+            The config of this stage.
+        tools_config: dict
+            The config of the tools
+        visualize: bool
+            Whether to visualize the runner
+        profile: bool
+            Whether to profile the runner.
+        use_cache: bool
+            Whether to use cache.
+
+        Returns
+        -------
+        runner: BaseRunner
+            The runner.
+        """
+
+        if self._runner:
+            self._runner.destory()
+        on_debug = self._debug_config.get(stage, False)
+        cache_dir = msc_utils.get_cache_dir().create_dir(stage) if use_cache 
else None
+        tools_config = tools_config or {}
+        msc_utils.time_stamp(stage + ".build", False)
+        runner_cls = self._get_runner_cls(stage_config["run_type"])
+        run_config = msc_utils.copy_dict(stage_config.get("run_config"))
+        if "generate_config" not in run_config:
+            run_config["generate_config"] = {}
+        run_config["generate_config"].update(
+            {
+                "build_folder": msc_utils.get_build_dir().create_dir(stage, 
cleanup=not on_debug),
+            }
+        )
+        self._logger.debug("Create runner(%s) by %s(%s)", stage, 
runner_cls.__name__, run_config)
+        runner = runner_cls(
+            self._relax_mod,
+            tools_config=tools_config,
+            stage=stage,
+            logger=self._logger,
+            **run_config,
+        )
+        runner.build(cache_dir=cache_dir)
+        self._report["info"][stage + "_by"] = 
"{}({})".format(runner.framework, runner.device)
+        if visualize:
+            runner.visualize(msc_utils.get_visual_dir().create_dir(stage))
+        if profile and "profile" in stage_config:
+            self._report["profile"][stage] = self._profile_runner(runner, 
stage_config)
+        if use_cache:
+            runner.save_cache(cache_dir)
+        return runner
+
+    def _create_tool_runner(self, tool_type: str, stage_config: dict) -> 
BaseRunner:
+        """Create runner with tool.
+
+        Parameters
+        ----------
+        tool_type: str
+            The tool type.
+        stage_config: dict
+            The config of this stage.
+
+        Returns
+        -------
+        runner: BaseRunner
+            The runner.
+        """
+
+        t_stage_config = {
+            "run_type": stage_config["run_type"],
+            "run_config": stage_config["run_config"],
+        }
+        return self._create_runner(
+            tool_type,
+            t_stage_config,
+            tools_config=self._tools_config,
+            profile=False,
+            use_cache=False,
+        )
+
+    def _profile_runner(self, runner: BaseRunner, stage_config: str) -> dict:
+        """Profile the runner.
+
+        Parameters
+        ----------
+        runner: BaseRunner
+            The runner to be profiled
+        stage_config: dict
+            The config of this stage.
+
+        Returns
+        -------
+        report: dict
+            The profile report.
+        """
+
+        stage = runner.stage
+        msc_utils.time_stamp(stage + ".profile", False)
+        profile_config = stage_config["profile"]
+        msg, report = "Profile({})".format(stage), {}
+
+        # check accuracy
+        check_config = profile_config.get("check", {})
+        if check_config:
+            loader = 
msc_utils.IODataLoader(msc_utils.get_dataset_dir().relpath("Golden"))
+            total, passed = 0, 0
+            acc_report = {}
+            for idx, (inputs, outputs) in enumerate(loader):
+                results = runner.run(inputs)
+                iter_report = msc_utils.compare_arrays(outputs, results)
+                total += iter_report["total"]
+                passed += iter_report["passed"]
+                acc_report["iter_" + str(idx)] = iter_report["info"]
+            pass_rate = float(passed) / total
+            report["accuracy"] = "{}/{}({:.2f}%)".format(passed, total, 
pass_rate * 100)
+            title = "Check({}) pass {}".format(stage, report["accuracy"])
+            self._logger.debug(msc_utils.msg_block(title, acc_report))
+            msg += " acc {} iters -> {}".format(len(loader), 
report["accuracy"])
+            required_err, err_rate = check_config.get("err_rate", 0), (1 - 
pass_rate)
+            if err_rate > required_err >= 0:
+                raise Exception(
+                    "Failed to profile the runner({}), err_rate {} > required 
{}".format(
+                        stage, err_rate, required_err
+                    )
+                )
+
+        # benchmark model
+        benchmark_config = profile_config.get("benchmark", {})
+        if benchmark_config:
+            for _ in range(benchmark_config.get("warm_up", 10)):
+                runner.run(self._sample_inputs)
+            start = time.time()
+            repeat = benchmark_config.get("repeat", 100)
+            for _ in range(repeat):
+                runner.run(self._sample_inputs)
+            avg_time = (time.time() - start) * 1000 / repeat
+            report["latency"] = "{:.2f} ms @ {}".format(avg_time, 
runner.device)
+            msg += " latency {} times -> {}".format(repeat, report["latency"])
+        self._logger.info(msg)
+        return report
+
+    def _update_prepare_config(self, config: dict) -> dict:
+        """Update prepare in stage config.
+
+        Parameters
+        ----------
+        config: dict
+            The config of a pipeline.
+
+        Returns
+        -------
+        config: dict
+            The updated config.
+        """
+
+        if config["model_type"] == MSCFramework.TORCH:
+            import torch
+
+            assert isinstance(
+                self._model, torch.nn.Module
+            ), "Model for torch should be nn.Module, get {}({})".format(
+                self._model, type(self._model)
+            )
+        elif config["model_type"] == MSCFramework.TENSORFLOW:
+            from tvm.contrib.msc.framework.tensorflow import tf_v1
+
+            assert isinstance(
+                self._model, tf_v1.GraphDef
+            ), "Model for tenosrflow should be tf.GraphDef, get {}({})".format(
+                self._model, type(self._model)
+            )
+        else:
+            raise Exception("Unexpect model_type " + str(config["model_type"]))
+        return config
+
+    def _update_parse_config(self, config: dict) -> dict:
+        """Update parse in stage config.
+
+        Parameters
+        ----------
+        config: dict
+            The config of a pipeline.
+
+        Returns
+        -------
+        config: dict
+            The updated config.
+        """
+
+        if config["model_type"] == MSCFramework.TORCH:
+            from tvm.contrib.msc.framework.torch.frontend import from_torch
+
+            config["parse"]["parser"] = from_torch
+            parse_config = config["parse"].get("parse_config", {})
+            parse_config.update(
+                {
+                    "input_info": [[i[1], i[2]] for i in config["inputs"]],
+                    "input_names": [i[0] for i in config["inputs"]],
+                }
+            )
+            config["parse"]["parse_config"] = parse_config
+        elif config["model_type"] == MSCFramework.TENSORFLOW:
+            from tvm.contrib.msc.framework.tensorflow.frontend import 
from_tensorflow
+
+            config["parse"]["parser"] = from_tensorflow
+            parse_config = config["parse"].get("parse_config", {})
+            parse_config.update(
+                {
+                    "shape_dict": {i[0]: i[1] for i in config["inputs"]},
+                    "outputs": config["outputs"],
+                }
+            )
+            config["parse"]["parse_config"] = parse_config
+        else:
+            raise Exception("Unexpect model_type " + str(config["model_type"]))
+        return config
+
+    def _update_runner_config(self, config: dict, stage: str) -> dict:
+        """Update runtime stage in stage config.
+
+        Parameters
+        ----------
+        config: dict
+            The config of a pipeline.
+        stage: str
+            The stage to be updated
+        """
+
+        if stage not in config:
+            return config
+        model_type = config["model_type"]
+        if "run_type" not in config[stage]:
+            config[stage]["run_type"] = model_type
+        # update run config
+        run_config = config[stage].get("run_config", {})
+        if "translate_config" not in run_config:
+            run_config["translate_config"] = {}
+        if "build" not in run_config["translate_config"]:
+            run_config["translate_config"]["build"] = {}
+        if "generate_config" not in run_config:
+            run_config["generate_config"] = {}
+        run_config["translate_config"]["build"]["input_aliases"] = [i[0] for i 
in config["inputs"]]
+        run_config["translate_config"]["build"]["output_aliases"] = 
config["outputs"]
+        if model_type == MSCFramework.TORCH:
+            parameters = list(self._model.parameters())
+            if parameters:
+                ref_device = parameters[0].device
+                if ref_device.type == "cpu":
+                    device = "cpu"
+                else:
+                    device = "{}:{}".format(ref_device.type, ref_device.index)
+            else:
+                device = "cpu"
+            run_config.update({"device": device, "is_training": 
self._model.training})
+        if config[stage]["run_type"] == MSCFramework.TENSORRT:
+            if "extra_option" not in run_config["generate_config"]:
+                run_config["generate_config"]["extra_option"] = {}
+            run_config["generate_config"]["extra_option"]["stage"] = stage
+        config[stage]["run_config"] = run_config
+        return config
+
+    def _update_tool_config(self, config: dict) -> dict:
+        """Update tool in stage config.
+
+        Parameters
+        ----------
+        config: dict
+            The config of a pipeline.
+
+        Returns
+        -------
+        config: dict
+            The updated config.
+        """
+
+        if "optimize" not in config:
+            return config
+        return config
+
+    def get_runnable(self, ret_type: str = "runner") -> Any:
+        """Return object by type.
+
+        Parameters
+        ----------
+        ret_type: str
+            The return type runner| model.
+
+        Returns
+        -------
+        runnable:
+            The runner or model.
+        """
+
+        if ret_type == "runner":
+            return self._runner
+        elif ret_type == "runnable":
+            return self._runner.runnable
+        elif ret_type == "model":
+            return self._runner.model
+        raise Exception("Unexpect return type " + str(ret_type))
+
+    def _get_runner_cls(self, run_type: str) -> BaseRunner:
+        """Get the runner cls by type
+
+        Parameters
+        ----------
+        run_type: str
+            The run type.
+
+        Returns
+        -------
+        runner_cls: class
+            The runner class.
+        """
+
+        raise NotImplementedError("_get_runner_cls is not implemented for 
BaseManager")
+
+    @property
+    def runner(self):
+        return self._runner
+
+
+class MSCManager(BaseManager):
+    """Normal manager in MSC"""
+
+    def _get_runner_cls(self, run_type: str) -> BaseRunner:
+        """Get the runner cls by type
+
+        Parameters
+        ----------
+        run_type: str
+            The run type.
+
+        Returns
+        -------
+        runner_cls: class
+            The runner class.
+        """
+
+        if run_type == MSCFramework.TVM:
+            from tvm.contrib.msc.framework.tvm.runtime import TVMRunner
+
+            runner_cls = TVMRunner
+        elif run_type == MSCFramework.TORCH:
+            from tvm.contrib.msc.framework.torch.runtime import TorchRunner
+
+            runner_cls = TorchRunner
+        elif run_type == MSCFramework.TENSORFLOW:
+            from tvm.contrib.msc.framework.tensorflow.runtime import 
TensorflowRunner
+
+            runner_cls = TensorflowRunner
+        elif run_type == MSCFramework.TENSORRT:
+            from tvm.contrib.msc.framework.tensorrt.runtime import 
TensorRTRunner
+
+            runner_cls = TensorRTRunner
+        else:
+            raise Exception("Unexpect run_type " + str(run_type))
+        return runner_cls
diff --git a/src/contrib/msc/core/codegen/base_codegen.h 
b/src/contrib/msc/core/codegen/base_codegen.h
index c90763b668..26c9de5d8b 100644
--- a/src/contrib/msc/core/codegen/base_codegen.h
+++ b/src/contrib/msc/core/codegen/base_codegen.h
@@ -66,21 +66,19 @@ class BaseOpCode {
   virtual const Array<Doc> GetDocs() = 0;
 
   /*! \brief Get return describe for default node*/
-  virtual const String IdxNode(bool as_raw = true) { return IdxNodeBase(node_, 
as_raw); }
+  virtual const String IdxNode() { return IdxNodeBase(node_); }
 
   /*! \brief Get describe for default node input*/
-  const String IdxInput(int idx = 0, bool as_raw = false) {
-    return IdxInputBase(node_, idx, as_raw);
+  const String IdxInput(int idx = 0, bool process = true) {
+    return IdxInputBase(node_, idx, process);
   }
 
   /*! \brief Get describe for default node output*/
-  const String IdxOutput(int idx = 0, bool as_raw = false) {
-    return IdxOutputBase(node_, idx, as_raw);
-  }
+  const String IdxOutput(int idx = 0) { return IdxOutputBase(node_, idx); }
 
   /*! \brief Get describe for default node weight*/
-  const String IdxWeight(const String& wtype, bool as_raw = false) {
-    return IdxWeightBase(node_, wtype, as_raw);
+  const String IdxWeight(const String& wtype, bool process = true) {
+    return IdxWeightBase(node_, wtype, process);
   }
 
   /*! \brief Get comment for default node*/
@@ -93,7 +91,7 @@ class BaseOpCode {
   virtual const String callee_name() { return func_name(); }
 
   /*! \brief Get valid return name for the default node*/
-  virtual const String ret_name() { return IdxNode(true); }
+  virtual const String ret_name() { return IdxNode(); }
 
   /*! \brief Get the default node*/
   const MSCJoint node() { return node_; }
diff --git a/src/contrib/msc/core/codegen/code_stack.h 
b/src/contrib/msc/core/codegen/code_stack.h
index bf659f927d..1ddc21aff5 100644
--- a/src/contrib/msc/core/codegen/code_stack.h
+++ b/src/contrib/msc/core/codegen/code_stack.h
@@ -447,14 +447,14 @@ class OpCodeStack : public BaseStack {
 
   /*! \brief Cache input as argument*/
   OpCodeStack<OpCodeGenType>& op_input_arg(int idx = 0, const String& key = 
"") {
-    return call_arg(codegen_->IdxInput(idx, false), key);
+    return call_arg(codegen_->IdxInput(idx, true), key);
   }
 
   /*! \brief Cache inputs as argument*/
   OpCodeStack<OpCodeGenType>& op_inputs_arg(bool as_list = true, const String& 
key = "") {
     Array<String> inputs;
     for (size_t i = 0; i < codegen_->node()->inputs.size(); i++) {
-      inputs.push_back(codegen_->IdxInput(i, false));
+      inputs.push_back(codegen_->IdxInput(i, true));
     }
     if (as_list) {
       return call_arg(DocUtils::ToListDoc(inputs), key);
@@ -465,13 +465,13 @@ class OpCodeStack : public BaseStack {
 
   /*! \brief Cache output as argument*/
   OpCodeStack<OpCodeGenType>& op_output_arg(int idx = 0, const String& key = 
"") {
-    return call_arg(codegen_->IdxOutput(idx, false), key);
+    return call_arg(codegen_->IdxOutput(idx), key);
   }
 
   /*! \brief Cache weight as argument*/
   OpCodeStack<OpCodeGenType>& op_weight_arg(const String& wtype, const String& 
key = "") {
     if (codegen_->node()->weights.count(wtype)) {
-      return call_arg(codegen_->IdxWeight(wtype, false), key);
+      return call_arg(codegen_->IdxWeight(wtype, true), key);
     }
     return *this;
   }
diff --git a/src/contrib/msc/core/codegen/codegen_utils.cc 
b/src/contrib/msc/core/codegen/codegen_utils.cc
index 0c751a8fbf..44626debe1 100644
--- a/src/contrib/msc/core/codegen/codegen_utils.cc
+++ b/src/contrib/msc/core/codegen/codegen_utils.cc
@@ -36,7 +36,7 @@ const String CodeGenUtils::IdxOutput(const MSCJoint& node, 
const String& prefix,
                                      const String& suffix) {
   const auto& idx_node = IdxNode(node, prefix, suffix);
   size_t output_size = node->outputs.size();
-  if (output_size == 1) {
+  if (output_size == 1 && node->optype != "tuple") {
     return idx_node;
   }
   size_t v_index = CommonUtils::GetIndex(idx, output_size);
diff --git a/src/contrib/msc/core/codegen/codegen_utils.h 
b/src/contrib/msc/core/codegen/codegen_utils.h
index d2c1e2aabc..126e9847d6 100644
--- a/src/contrib/msc/core/codegen/codegen_utils.h
+++ b/src/contrib/msc/core/codegen/codegen_utils.h
@@ -41,12 +41,10 @@ using namespace tvm::script::printer;
 
 #define CODEGEN_CONFIG_MEMBERS             \
   bool is_train{false};                    \
-  bool need_prune{false};                  \
-  bool need_quantize{false};               \
-  bool need_collect{false};                \
-  bool need_distill{false};                \
-  bool need_process{false};                \
+  bool use_tools{false};                   \
   bool need_test{true};                    \
+  std::string tools_scope{""};             \
+  std::string tools_tag{"main"};           \
   std::string test_device{"cpu"};          \
   std::string prefix{"res_"};              \
   std::string baseline_folder{"baseline"}; \
@@ -55,20 +53,14 @@ using namespace tvm::script::printer;
 #define CODEGEN_CONFIG_PARSE                    \
   if (key == "is_train") {                      \
     reader->Read(&is_train);                    \
-  } else if (key == "need_prune") {             \
-    reader->Read(&need_prune);                  \
-    need_process |= need_prune;                 \
-  } else if (key == "need_quantize") {          \
-    reader->Read(&need_quantize);               \
-    need_process |= need_quantize;              \
-  } else if (key == "need_collect") {           \
-    reader->Read(&need_collect);                \
-    need_process |= need_collect;               \
-  } else if (key == "need_distill") {           \
-    reader->Read(&need_distill);                \
-    need_process |= need_distill;               \
+  } else if (key == "use_tools") {              \
+    reader->Read(&use_tools);                   \
   } else if (key == "need_test") {              \
     reader->Read(&need_test);                   \
+  } else if (key == "tools_scope") {            \
+    reader->Read(&tools_scope);                 \
+  } else if (key == "tools_tag") {              \
+    reader->Read(&tools_tag);                   \
   } else if (key == "test_device") {            \
     reader->Read(&test_device);                 \
   } else if (key == "prefix") {                 \
@@ -87,21 +79,18 @@ using namespace tvm::script::printer;
                                                                                
                   \
  protected:                                                                    
                   \
   const std::shared_ptr<ConfigType> config() { return config_; }               
                   \
-  const String GetSuffix(bool as_raw = false) {                                
                   \
-    const String& suffix = as_raw && config()->need_process ? "_raw" : "";     
                   \
-    return suffix;                                                             
                   \
+  const String IdxNodeBase(const MSCJoint& node) {                             
                   \
+    return helper_.IdxNodeBase(node, config()->prefix, "");                    
                   \
   }                                                                            
                   \
-  const String IdxNodeBase(const MSCJoint& node, bool as_raw = true) {         
                   \
-    return helper_.IdxNodeBase(node, config()->prefix, GetSuffix(as_raw));     
                   \
+  const String IdxInputBase(const MSCJoint& node, int idx = 0, bool process = 
true) {             \
+    return helper_.IdxInputBase(node, config()->prefix, idx, "", process && 
config()->use_tools); \
   }                                                                            
                   \
-  const String IdxInputBase(const MSCJoint& node, int idx = 0, bool as_raw = 
false) {             \
-    return helper_.IdxInputBase(node, config()->prefix, idx, 
GetSuffix(as_raw));                  \
+  const String IdxOutputBase(const MSCJoint& node, int idx = 0, bool mark_exit 
= false) {         \
+    return helper_.IdxOutputBase(node, config()->prefix, idx, "",              
                   \
+                                 mark_exit && config()->use_tools);            
                   \
   }                                                                            
                   \
-  const String IdxOutputBase(const MSCJoint& node, int idx = 0, bool as_raw = 
false) {            \
-    return helper_.IdxOutputBase(node, config()->prefix, idx, 
GetSuffix(as_raw));                 \
-  }                                                                            
                   \
-  const String IdxWeightBase(const MSCJoint& node, const String& wtype, bool 
as_raw = false) {    \
-    return helper_.IdxWeightBase(node, wtype, GetSuffix(as_raw));              
                   \
+  const String IdxWeightBase(const MSCJoint& node, const String& wtype, bool 
process = true) {    \
+    return helper_.IdxWeightBase(node, wtype, "", process && 
config()->use_tools);                \
   }                                                                            
                   \
   const String Comment(const MSCJoint& node) { return helper_.Comment(node, 
config()->prefix); }  \
                                                                                
                   \
@@ -154,21 +143,36 @@ class CodeGenUtils {
  */
 class BaseCodeGenHelper {
  public:
+  const String GetSuffix(const MSCJoint& node, bool process = false) {
+    return process ? "c" + std::to_string(node->index) : "";
+  }
+
   virtual const String IdxNodeBase(const MSCJoint& node, const String& prefix 
= "",
                                    const String& suffix = "") {
     return CodeGenUtils::IdxNode(node, prefix, suffix);
   }
   virtual const String IdxInputBase(const MSCJoint& node, const String& prefix 
= "", int idx = 0,
-                                    const String& suffix = "") {
-    return CodeGenUtils::IdxInput(node, prefix, idx, suffix);
+                                    const String& suffix = "", bool process = 
false) {
+    const auto& pair = node->ProducerAndIdxOf(idx);
+    size_t output_size = pair.first->outputs.size();
+    if (process && (output_size > 1 || pair.first->optype == "tuple")) {
+      return CodeGenUtils::IdxNode(pair.first, prefix, suffix) + "_" + 
std::to_string(pair.second);
+    }
+    return CodeGenUtils::IdxInput(node, prefix, idx, suffix + GetSuffix(node, 
process));
   }
   virtual const String IdxOutputBase(const MSCJoint& node, const String& 
prefix = "", int idx = 0,
-                                     const String& suffix = "") {
+                                     const String& suffix = "", bool mark_exit 
= false) {
+    if (mark_exit) {
+      if (node->outputs.size() > 1 || node->optype == "tuple") {
+        return CodeGenUtils::IdxNode(node, prefix, suffix) + "_" + 
std::to_string(idx) + "_exit";
+      }
+      return CodeGenUtils::IdxOutput(node, prefix, idx, suffix + "_exit");
+    }
     return CodeGenUtils::IdxOutput(node, prefix, idx, suffix);
   }
   virtual const String IdxWeightBase(const MSCJoint& node, const String& wtype,
-                                     const String& suffix = "") {
-    return CodeGenUtils::IdxWeight(node, wtype, suffix);
+                                     const String& suffix = "", bool process = 
false) {
+    return CodeGenUtils::IdxWeight(node, wtype, suffix + GetSuffix(node, 
process));
   }
   virtual const String Comment(const MSCJoint& node, const String& prefix = 
"") {
     return CodeGenUtils::CommentNode(node, prefix);
diff --git a/src/contrib/msc/core/codegen/cpp_codegen.h 
b/src/contrib/msc/core/codegen/cpp_codegen.h
index 0f4f68c636..97b5c221f5 100644
--- a/src/contrib/msc/core/codegen/cpp_codegen.h
+++ b/src/contrib/msc/core/codegen/cpp_codegen.h
@@ -27,6 +27,7 @@
 #include <dmlc/json.h>
 #include <tvm/script/printer/doc.h>
 
+#include <set>
 #include <string>
 
 #include "../printer/cpp_printer.h"
@@ -49,7 +50,11 @@ class CppCodeGen : public BaseCodeGen<ConfigType, 
HelperType> {
    * \param config the options for codegen.
    */
   explicit CppCodeGen(const MSCGraph& graph, const std::string& config = "")
-      : BaseCodeGen<ConfigType, HelperType>(graph, config) {}
+      : BaseCodeGen<ConfigType, HelperType>(graph, config) {
+    for (const auto& output : graph->GetOutputs()) {
+      graph_outputs_.insert(output);
+    }
+  }
 
   /*! \brief Stack the docs for the class declare*/
   virtual void CodeGenClassDeclare() = 0;
@@ -90,6 +95,73 @@ class CppCodeGen : public BaseCodeGen<ConfigType, 
HelperType> {
   }
 
  protected:
+  /*! \brief Stack the docs for the node*/
+  virtual void CodeGenNode(const MSCJoint& node, bool use_tools) {
+    this->stack_.comment(this->Comment(node));
+    // process inputs and weights by tools
+    if (use_tools) {
+      const auto* pf = runtime::Registry::Get("msc_tool.codegen_tensor");
+      ICHECK(pf != nullptr) << "Cannot find codegen_tensor func.";
+      for (size_t i = 0; i < node->inputs.size(); i++) {
+        const auto& input = node->InputAt(i);
+        const Array<String>& lines = (*pf)(GetTensorCtx(input), input->name, 
node->name,
+                                           this->config()->tools_scope, 
this->config()->tools_tag);
+        for (const auto& l : lines) {
+          this->stack_.line(l);
+        }
+      }
+      for (const auto& pair : node->weights) {
+        const Array<String>& lines = (*pf)(GetTensorCtx(pair.second), 
pair.second->name, node->name,
+                                           this->config()->tools_scope, 
this->config()->tools_tag);
+        for (const auto& l : lines) {
+          this->stack_.line(l);
+        }
+      }
+    }
+    for (const auto& d : this->GetOpCodes(node)) {
+      this->stack_.line(d);
+    }
+    // process graph outputs by tools
+    if (use_tools) {
+      const auto* pf = runtime::Registry::Get("msc_tool.codegen_tensor");
+      ICHECK(pf != nullptr) << "Cannot find codegen_tensor func.";
+      for (size_t i = 0; i < node->outputs.size(); i++) {
+        int index = static_cast<int>(i);
+        if (graph_outputs_.count(node->OutputAt(index))) {
+          const auto& output = node->OutputAt(index);
+          const Array<String>& lines =
+              (*pf)(GetTensorCtx(output), output->name, node->name, 
this->config()->tools_scope,
+                    this->config()->tools_tag);
+          for (const auto& l : lines) {
+            this->stack_.line(l);
+          }
+        }
+      }
+    }
+  }
+
+  virtual Map<String, String> GetTensorCtx(const MSCTensor& tensor) {
+    Map<String, String> tensor_ctx;
+    MSCJoint producer;
+    if (this->graph()->weight_holders.count(tensor->name)) {
+      producer = this->graph()->FindProducer(tensor);
+      for (const auto& pair : producer->weights) {
+        if (pair.second == tensor) {
+          tensor_ctx.Set("tensor", this->IdxWeightBase(producer, pair.first));
+          break;
+        }
+      }
+      ICHECK(tensor_ctx.count("tensor"))
+          << "Can not find weight " << tensor << " from " << producer;
+    } else {
+      const auto& pair = this->graph()->FindProducerAndIdx(tensor);
+      producer = pair.first;
+      tensor_ctx.Set("tensor", this->IdxOutputBase(pair.first, pair.second));
+    }
+    tensor_ctx.Set("producer", this->IdxNodeBase(producer));
+    return tensor_ctx;
+  }
+
   void StartNamespace() {
     this->stack_.line("namespace tvm {").line("namespace contrib 
{").line("namespace msc {").line();
   }
@@ -101,6 +173,9 @@ class CppCodeGen : public BaseCodeGen<ConfigType, 
HelperType> {
         .line("} // namespace msc")
         .line();
   }
+
+ private:
+  std::set<MSCTensor> graph_outputs_;
 };
 
 }  // namespace msc
diff --git a/src/contrib/msc/core/codegen/py_codegen.h 
b/src/contrib/msc/core/codegen/py_codegen.h
index 275cd995a4..040b6df11f 100644
--- a/src/contrib/msc/core/codegen/py_codegen.h
+++ b/src/contrib/msc/core/codegen/py_codegen.h
@@ -27,6 +27,7 @@
 #include <dmlc/json.h>
 #include <tvm/script/printer/doc.h>
 
+#include <set>
 #include <string>
 
 #include "../printer/python_printer.h"
@@ -49,7 +50,11 @@ class PyCodeGen : public BaseCodeGen<ConfigType, HelperType> 
{
    * \param config the options for codegen.
    */
   explicit PyCodeGen(const MSCGraph& graph, const std::string& config = "")
-      : BaseCodeGen<ConfigType, HelperType>(graph, config) {}
+      : BaseCodeGen<ConfigType, HelperType>(graph, config) {
+    for (const auto& output : graph->GetOutputs()) {
+      graph_outputs_.insert(output);
+    }
+  }
 
   /*! \brief Stack the docs for the script*/
   virtual void CodeGenScript() {
@@ -82,18 +87,15 @@ class PyCodeGen : public BaseCodeGen<ConfigType, 
HelperType> {
     this->stack_.line("import os")
         .line("import numpy as np")
         .line("from typing import List, Dict")
-        .line("import tvm")
-        .line("from tvm.contrib.msc.core import utils as msc_utils");
+        .line("import tvm");
+    if (this->config()->use_tools) {
+      this->stack_.line("from tvm.contrib.msc.core import tools as msc_tools");
+    }
+    this->stack_.line("from tvm.contrib.msc.core import utils as msc_utils");
   }
 
   /*! \brief Stack the docs for the helpers*/
   virtual void CodeGenHelper() {
-    this->stack_.func_def("process_tensor", TensorType())
-        .func_arg("tensor", TensorType())
-        .func_arg("name", "str")
-        .func_arg("consumer", "str")
-        .func_start()
-        .func_end("tensor");
     if (this->config()->need_test) {
       this->stack_.func_def("load_data", "np.ndarray")
           .func_arg("name", "str")
@@ -145,26 +147,46 @@ class PyCodeGen : public BaseCodeGen<ConfigType, 
HelperType> {
   }
 
   /*! \brief Stack the docs for the node*/
-  virtual void CodeGenNode(const MSCJoint& node) {
+  virtual void CodeGenNode(const MSCJoint& node, bool use_tools) {
     this->stack_.comment(this->Comment(node));
-    if (this->config()->need_process) {
+    // process inputs and weights by tools
+    if (use_tools) {
       for (size_t i = 0; i < node->inputs.size(); i++) {
         const auto& input = node->InputAt(i);
-        this->stack_.func_call("process_tensor", this->IdxInputBase(node, i, 
false))
-            .call_arg(this->IdxInputBase(node, i, true))
+        this->stack_.func_call("msc_tools.process_tensor", 
this->IdxInputBase(node, i, true))
+            .call_arg(this->IdxInputBase(node, i, false))
             .call_arg(DocUtils::ToStrDoc(input->name))
-            .call_arg(DocUtils::ToStrDoc(node->name));
+            .call_arg(DocUtils::ToStrDoc(node->name))
+            .call_arg(DocUtils::ToStrDoc(this->config()->tools_scope))
+            .call_arg(DocUtils::ToStrDoc(this->config()->tools_tag));
       }
       for (const auto& pair : node->weights) {
-        this->stack_.func_call("process_tensor", this->IdxWeightBase(node, 
pair.first, false))
-            .call_arg(this->IdxWeightBase(node, pair.first, true))
+        this->stack_
+            .func_call("msc_tools.process_tensor", this->IdxWeightBase(node, 
pair.first, true))
+            .call_arg(this->IdxWeightBase(node, pair.first, false))
             .call_arg(DocUtils::ToStrDoc(pair.second->name))
-            .call_arg(DocUtils::ToStrDoc(node->name));
+            .call_arg(DocUtils::ToStrDoc(node->name))
+            .call_arg(DocUtils::ToStrDoc(this->config()->tools_scope))
+            .call_arg(DocUtils::ToStrDoc(this->config()->tools_tag));
       }
     }
     for (const auto& d : this->GetOpCodes(node)) {
       this->stack_.line(d);
     }
+    // process graph outputs by tools
+    if (use_tools) {
+      for (size_t i = 0; i < node->outputs.size(); i++) {
+        int index = static_cast<int>(i);
+        if (graph_outputs_.count(node->OutputAt(index))) {
+          this->stack_.func_call("msc_tools.process_tensor", 
this->IdxOutputBase(node, index, true))
+              .call_arg(this->IdxOutputBase(node, index, false))
+              .call_arg(DocUtils::ToStrDoc(node->OutputAt(index)->name))
+              .call_arg(DocUtils::ToStrDoc("exit"))
+              .call_arg(DocUtils::ToStrDoc(this->config()->tools_scope))
+              .call_arg(DocUtils::ToStrDoc(this->config()->tools_tag));
+        }
+      }
+    }
   }
 
   /*! \brief Stack the docs for the graph*/
@@ -175,6 +197,9 @@ class PyCodeGen : public BaseCodeGen<ConfigType, 
HelperType> {
 
   /*! \brief Get tensor type of the framework*/
   virtual const String TensorType() const { return "np.ndarray"; }
+
+ private:
+  std::set<MSCTensor> graph_outputs_;
 };
 
 }  // namespace msc
diff --git a/src/contrib/msc/core/printer/python_printer.cc 
b/src/contrib/msc/core/printer/python_printer.cc
index db198aaa56..e272c04e98 100644
--- a/src/contrib/msc/core/printer/python_printer.cc
+++ b/src/contrib/msc/core/printer/python_printer.cc
@@ -196,7 +196,7 @@ void PythonPrinter::PrintIndentedBlock(const 
Array<StmtDoc>& docs) {
 void PythonPrinter::PrintDecorators(const Array<ExprDoc>& decorators) {
   for (const ExprDoc& decorator : decorators) {
     output_ << "@";
-    PrintDoc(decorator);
+    PrintDoc(decorator, false);
     NewLine();
   }
 }
diff --git a/src/contrib/msc/core/transform/set_expr_name.cc 
b/src/contrib/msc/core/transform/set_expr_name.cc
index 41529a7f39..97850c70e8 100644
--- a/src/contrib/msc/core/transform/set_expr_name.cc
+++ b/src/contrib/msc/core/transform/set_expr_name.cc
@@ -36,6 +36,49 @@ using namespace tvm::contrib::msc;
 
 namespace relax {
 
+class FuncNameGetter : public ExprVisitor {
+ public:
+  explicit FuncNameGetter(const Array<String>& arg_names) : 
arg_names_(arg_names) {}
+
+  /*! \brief Get the attributes from prim value as Map<String, String>*/
+  String HintName(const Expr& expr) {
+    name_ = "";
+    ExprVisitor::VisitExpr(expr);
+    return name_;
+  }
+
+  void VisitBinding_(const VarBindingNode* binding, const CallNode* val) {
+    if (name_.size() == 0) {
+      name_ = SpanUtils::GetAttr(val->span, "name");
+    }
+    if (name_.size() == 0) {
+      ExprVisitor::VisitBinding_(binding, val);
+    }
+  }
+
+  void VisitBinding_(const VarBindingNode* binding, const TupleNode* val) {
+    if (name_.size() == 0) {
+      name_ = SpanUtils::GetAttr(val->span, "name");
+    }
+    if (name_.size() == 0) {
+      ExprVisitor::VisitBinding_(binding, val);
+    }
+  }
+
+  void VisitBinding_(const VarBindingNode* binding, const TupleGetItemNode* 
val) {
+    if (name_.size() == 0 && arg_names_[0].size() > 0) {
+      name_ = arg_names_[0] + "." + std::to_string(val->index);
+    }
+    if (name_.size() == 0) {
+      ExprVisitor::VisitBinding_(binding, val);
+    }
+  }
+
+ private:
+  String name_;
+  Array<String> arg_names_;
+};
+
 /*!
  * \brief Name setter for Relax
  */
@@ -125,6 +168,7 @@ class RelaxExprNameSetter : public ExprVisitor {
   void VisitBinding_(const VarBindingNode* binding, const CallNode* val) {
     ExprVisitor::VisitBinding_(binding, val);
     String name_hint, optype;
+    bool use_unique = true;
     if (const auto* op_node = val->op.as<OpNode>()) {
       const std::string& op_name = op_node->name;
       int rpos = op_name.rfind(".");
@@ -134,23 +178,18 @@ class RelaxExprNameSetter : public ExprVisitor {
       const auto& func = 
Downcast<Function>(ref_module_->Lookup(v_node->name_hint));
       ExprVisitor::VisitExpr(func);
       optype = GetFuncType(func);
-      if (optype == "extern_func") {
-        name_hint = v_node->name_hint;
-      } else {
-        name_hint = optype;
-      }
+      name_hint = GetFuncName(GetRef<Call>(val), func);
+      use_unique = false;
     } else if (local_funcs_.count(val->op)) {
-      optype = GetFuncType(local_funcs_[val->op]);
       ExprVisitor::VisitExpr(local_funcs_[val->op]);
-      if (optype == "extern_func") {
-        name_hint = Downcast<Var>(val->op)->name_hint();
-      } else {
-        name_hint = optype;
-      }
+      optype = GetFuncType(local_funcs_[val->op]);
+      name_hint = GetFuncName(GetRef<Call>(val), local_funcs_[val->op]);
+      use_unique = false;
     }
     if (name_hint.size() > 0) {
       // set name
-      const String& unique_name = GetUniqueName(GetRef<Expr>(val), name_hint);
+      const String& unique_name =
+          use_unique ? GetUniqueName(GetRef<Expr>(val), name_hint) : name_hint;
       if (unique_name != SpanUtils::GetAttr(val->span, "name")) {
         val->span = SpanUtils::SetAttr(val->span, "name", unique_name);
       }
@@ -208,18 +247,53 @@ class RelaxExprNameSetter : public ExprVisitor {
 
   const String GetFuncType(const Function& func) {
     String optype;
-    const auto& name_opt = func->GetAttr<runtime::String>(attr::kComposite);
-    if (name_opt.defined()) {
-      optype = name_opt.value();
-      if (target_.size() > 0) {
-        optype = StringUtils::Replace(optype, target_ + ".", "");
-      }
+    const auto& comp_opt = func->GetAttr<runtime::String>(attr::kComposite);
+    const auto& code_opt = func->GetAttr<runtime::String>(attr::kCodegen);
+    if (comp_opt.defined()) {
+      optype = comp_opt.value();
+    } else if (code_opt.defined()) {
+      optype = code_opt.value();
     } else {
       optype = "extern_func";
     }
+    if (target_.size() > 0) {
+      optype = StringUtils::Replace(optype, target_ + ".", "");
+    }
     return optype;
   }
 
+  const String GetFuncName(const Call& call, const Function& func) {
+    String name;
+    // get from byoc_name
+    if (target_.size() > 0) {
+      const auto& byoc_name_opt = func->GetAttr<runtime::String>("byoc_name");
+      if (byoc_name_opt.defined()) {
+        return byoc_name_opt.value();
+      }
+    }
+    // get from attribute
+    const auto& name_opt = func->GetAttr<runtime::String>("unique_name");
+    if (name_opt.defined()) {
+      return name_opt.value();
+    }
+    // get from exprs in the func
+    Array<String> arg_names;
+    for (const auto& a : call->args) {
+      arg_names.push_back(expr_names_.count(a) ? expr_names_[a] : "");
+    }
+    name = FuncNameGetter(arg_names).HintName(local_funcs_[call->op]);
+    if (name.size() > 0) {
+      return name;
+    }
+    const auto& optype = GetFuncType(func);
+    if (optype == "extern_func") {
+      name = Downcast<Var>(call->op)->name_hint();
+    } else {
+      name = optype;
+    }
+    return GetUniqueName(call, name);
+  }
+
   Map<String, Expr> setted_names_;
   Map<String, String> constant_consumers_;
   std::set<String> setted_blocks_;
diff --git a/src/contrib/msc/framework/tensorflow/codegen.cc 
b/src/contrib/msc/framework/tensorflow/codegen.cc
index 30f06b43e7..b99b1a1363 100644
--- a/src/contrib/msc/framework/tensorflow/codegen.cc
+++ b/src/contrib/msc/framework/tensorflow/codegen.cc
@@ -68,7 +68,7 @@ void TensorflowCodeGen::CodeGenGraph() {
       continue;
     }
     for (const auto& pair : node->weights) {
-      stack_.func_call("get_variable", IdxWeightBase(node, pair.first))
+      stack_.func_call("get_variable", IdxWeightBase(node, pair.first, false))
           .call_arg(DocUtils::ToStrDoc(pair.second->name))
           .call_arg(DocUtils::ToListDoc(pair.second->shape, true))
           .call_arg(DocUtils::ToStrDoc(pair.second->DTypeName()))
@@ -82,7 +82,7 @@ void TensorflowCodeGen::CodeGenGraph() {
     if (node->optype == "input") {
       continue;
     }
-    CodeGenNode(node);
+    CodeGenNode(node, config()->use_tools);
   }
   Array<String> idx_outputs;
   for (const auto& o : graph()->GetOutputs()) {
diff --git a/src/contrib/msc/framework/tensorrt/codegen.cc 
b/src/contrib/msc/framework/tensorrt/codegen.cc
index a59697377a..4f73767b2e 100644
--- a/src/contrib/msc/framework/tensorrt/codegen.cc
+++ b/src/contrib/msc/framework/tensorrt/codegen.cc
@@ -110,9 +110,12 @@ void TensorRTCodeGen::CodeGenClassDefine() {
   // build layers
   for (const auto& n : graph()->node_names) {
     const auto& node = graph()->FindNode(n);
+    CodeGenNode(node, config()->use_tools);
+    /*
     for (const auto& d : GetOpCodes(node)) {
       stack_.line(d);
     }
+    */
   }
   // mark outputs
   stack_.comment("Mark outputs");
diff --git a/src/contrib/msc/framework/tensorrt/codegen_utils.h 
b/src/contrib/msc/framework/tensorrt/codegen_utils.h
index d598396f6f..eab2ec616d 100644
--- a/src/contrib/msc/framework/tensorrt/codegen_utils.h
+++ b/src/contrib/msc/framework/tensorrt/codegen_utils.h
@@ -40,7 +40,7 @@ class TensorRTCodeGenHelper : public BaseCodeGenHelper {
  public:
   /*! \brief Get describe for default node input*/
   const String IdxInputBase(const MSCJoint& node, const String& prefix = "", 
int idx = 0,
-                            const String& suffix = "") final {
+                            const String& suffix = "", bool process = false) 
final {
     const auto& pair = node->ProducerAndIdxOf(idx);
     if (pair.first->optype == "input") {
       return "*" + IdxNodeBase(pair.first, prefix, suffix);
@@ -53,7 +53,7 @@ class TensorRTCodeGenHelper : public BaseCodeGenHelper {
 
   /*! \brief Get describe for default node output*/
   const String IdxOutputBase(const MSCJoint& node, const String& prefix = "", 
int idx = 0,
-                             const String& suffix = "") final {
+                             const String& suffix = "", bool mark_exit = 
false) final {
     if (node->optype == "argmax" || node->optype == "argmin") {
       ICHECK_EQ(idx, 0) << "argmax and argmin only has 1 output, get " << idx;
       return IdxNodeBase(node, prefix, suffix) + "->getOutput(1)";
@@ -69,8 +69,8 @@ class TensorRTCodeGenHelper : public BaseCodeGenHelper {
   }
 
   /*! \brief Get describe for default node weight*/
-  const String IdxWeightBase(const MSCJoint& node, const String& wtype,
-                             const String& suffix = "") final {
+  const String IdxWeightBase(const MSCJoint& node, const String& wtype, const 
String& suffix = "",
+                             bool process = false) final {
     return "mWeights[\"" + node->WeightAt(wtype)->name + "\"]";
   }
 };
diff --git a/src/contrib/msc/framework/tensorrt/tensorrt_opcode.h 
b/src/contrib/msc/framework/tensorrt/tensorrt_opcode.h
index 89942930ac..2d9bcb6acf 100644
--- a/src/contrib/msc/framework/tensorrt/tensorrt_opcode.h
+++ b/src/contrib/msc/framework/tensorrt/tensorrt_opcode.h
@@ -61,7 +61,7 @@ class TensorRTOpCode : public 
BaseOpCode<TensorRTCodeGenConfig, TensorRTCodeGenH
   }
 
   /*! \brief Get valid return name for the default node*/
-  const String ret_name() final { return "auto " + IdxNode(true); }
+  const String ret_name() final { return "auto " + IdxNode(); }
 
   /*! \brief Get the dtype from the datatype*/
   const String DType(const DataType& dtype) final;
diff --git a/src/contrib/msc/framework/torch/codegen.cc 
b/src/contrib/msc/framework/torch/codegen.cc
index 6e4ceea0f7..6d21f590d0 100644
--- a/src/contrib/msc/framework/torch/codegen.cc
+++ b/src/contrib/msc/framework/torch/codegen.cc
@@ -40,6 +40,9 @@ void TorchCodeGen::CodeGenGraph() {
   // Write init
   is_init_ = true;
   stack_.func_def("__init__", "torch.nn.Module");
+  if (config()->use_tools) {
+    stack_.func_decorator("msc_tools.wrap_step(\"build\",\"" + 
config()->tools_tag + "\")");
+  }
   stack_.func_arg("self", "torch.nn.Module");
   stack_.func_start();
   
stack_.func_call("super").call_arg(graph()->name).call_arg("self").method_call("__init__");
@@ -48,30 +51,43 @@ void TorchCodeGen::CodeGenGraph() {
     if (node->optype == "input") {
       continue;
     }
-    CodeGenNode(node);
+    CodeGenNode(node, false);
   }
   stack_.func_end();
 
   // Write forward
   is_init_ = false;
   stack_.func_def("forward", "List[torch.Tensor]");
+  if (config()->use_tools) {
+    stack_.func_decorator("msc_tools.wrap_step(\"forward\",\"" + 
config()->tools_tag + "\")");
+  }
   stack_.func_arg("self", "torch.nn.Module");
   for (const auto& i : graph()->GetInputs()) {
     const auto& pair = graph()->FindProducerAndIdx(i);
     stack_.func_arg(IdxOutputBase(pair.first, pair.second), "torch.Tensor");
   }
   stack_.func_start();
+  if (config()->use_tools) {
+    stack_.comment("Define all weights");
+    for (const auto& n : graph()->node_names) {
+      const auto& node = graph()->FindNode(n);
+      for (const auto& pair : node->weights) {
+        stack_.assign(IdxWeightBase(node, pair.first, false), "self." + 
pair.second->alias);
+      }
+    }
+    stack_.comment("End of define all weights").line();
+  }
   for (const auto& n : graph()->node_names) {
     const auto& node = graph()->FindNode(n);
     if (node->optype == "input") {
       continue;
     }
-    CodeGenNode(node);
+    CodeGenNode(node, config()->use_tools);
   }
   Array<String> idx_outputs;
   for (const auto& o : graph()->GetOutputs()) {
     const auto& pair = graph()->FindProducerAndIdx(o);
-    idx_outputs.push_back(IdxOutputBase(pair.first, pair.second));
+    idx_outputs.push_back(IdxOutputBase(pair.first, pair.second, true));
   }
   if (idx_outputs.size() == 1) {
     stack_.assign("outputs", idx_outputs[0]);
diff --git a/src/contrib/msc/framework/torch/codegen_utils.h 
b/src/contrib/msc/framework/torch/codegen_utils.h
index 67b4c8bb52..b80ea51f15 100644
--- a/src/contrib/msc/framework/torch/codegen_utils.h
+++ b/src/contrib/msc/framework/torch/codegen_utils.h
@@ -40,12 +40,12 @@ class TorchCodeGenHelper : public BaseCodeGenHelper {
  public:
   /*! \brief Get describe for default node input*/
   const String IdxOutputBase(const MSCJoint& node, const String& prefix = "", 
int idx = 0,
-                             const String& suffix = "") final {
+                             const String& suffix = "", bool mark_exit = 
false) final {
     if ((node->optype == "max" || node->optype == "min") && 
node->OutputAt(0)->Ndim() > 0) {
       ICHECK(idx == 0) << "max and min op only support 1 outputs, get " << 
node;
       return IdxNodeBase(node, prefix, suffix) + ".values";
     }
-    return BaseCodeGenHelper::IdxOutputBase(node, prefix, idx, suffix);
+    return BaseCodeGenHelper::IdxOutputBase(node, prefix, idx, suffix, 
mark_exit);
   }
 };
 
diff --git a/src/contrib/msc/framework/torch/torch_opcode.cc 
b/src/contrib/msc/framework/torch/torch_opcode.cc
index 4b92959fbc..341181bc2a 100644
--- a/src/contrib/msc/framework/torch/torch_opcode.cc
+++ b/src/contrib/msc/framework/torch/torch_opcode.cc
@@ -166,6 +166,22 @@ class TorchBatchNormCodeGen : public TorchOpCode {
     const auto& gamma = node()->WeightAt("gamma");
     stack_.op_call().call_arg(gamma->DimAt(0), 
"num_features").op_arg<float>("epsilon", "eps");
   }
+
+  void CodeGenForward() final {
+    if (config()->use_tools) {
+      stack_.op_call(func_name())
+          .op_input_arg()
+          .op_weight_arg("mean")
+          .op_weight_arg("var")
+          .op_weight_arg("gamma")
+          .op_weight_arg("beta")
+          .call_arg(DocUtils::ToAttrAccessDoc(module_ref(), "training"))
+          .call_arg(DocUtils::ToAttrAccessDoc(module_ref(), "momentum"))
+          .call_arg(DocUtils::ToAttrAccessDoc(module_ref(), "eps"));
+    } else {
+      TorchOpCode::CodeGenForward();
+    }
+  }
 };
 
 class TorchBroadcastToCodeGen : public TorchOpCode {
@@ -236,6 +252,23 @@ class TorchConvCodeGen : public TorchOpCode {
         .call_arg(use_bias_, "bias");
   }
 
+  void CodeGenForward() final {
+    if (config()->use_tools) {
+      stack_.op_call(func_name()).op_input_arg().op_weight_arg("weight");
+      if (use_bias_) {
+        stack_.op_weight_arg("bias");
+      } else {
+        stack_.call_arg("None");
+      }
+      stack_.call_arg(DocUtils::ToAttrAccessDoc(module_ref(), "stride"))
+          .call_arg(DocUtils::ToAttrAccessDoc(module_ref(), "padding"))
+          .call_arg(DocUtils::ToAttrAccessDoc(module_ref(), "dilation"))
+          .call_arg(DocUtils::ToAttrAccessDoc(module_ref(), "groups"));
+    } else {
+      TorchOpCode::CodeGenForward();
+    }
+  }
+
  private:
   bool use_bias_;
 };
@@ -351,6 +384,19 @@ class TorchLinearCodeGen : public TorchOpCode {
         .call_arg(use_bias_, "bias");
   }
 
+  void CodeGenForward() final {
+    if (config()->use_tools) {
+      stack_.op_call(func_name()).op_input_arg().op_weight_arg("weight");
+      if (use_bias_) {
+        stack_.op_weight_arg("bias");
+      } else {
+        stack_.call_arg("None");
+      }
+    } else {
+      TorchOpCode::CodeGenForward();
+    }
+  }
+
  private:
   bool use_bias_;
 };
diff --git a/src/contrib/msc/framework/torch/torch_opcode.h 
b/src/contrib/msc/framework/torch/torch_opcode.h
index 85d3cbf958..6fe5cf5f96 100644
--- a/src/contrib/msc/framework/torch/torch_opcode.h
+++ b/src/contrib/msc/framework/torch/torch_opcode.h
@@ -63,9 +63,8 @@ class TorchOpCode : public BaseOpCode<TorchCodeGenConfig, 
TorchCodeGenHelper> {
   }
 
   /*! \brief Get return describe for default node*/
-  const String IdxNode(bool as_raw = true) final {
-    return is_init_ ? module_ref_
-                    : BaseOpCode<TorchCodeGenConfig, 
TorchCodeGenHelper>::IdxNode(as_raw);
+  const String IdxNode() final {
+    return is_init_ ? module_ref_ : BaseOpCode<TorchCodeGenConfig, 
TorchCodeGenHelper>::IdxNode();
   };
 
   /*! \brief Get dtype string*/
diff --git a/src/contrib/msc/framework/tvm/codegen.cc 
b/src/contrib/msc/framework/tvm/codegen.cc
index 88aea30f3f..c8956ca399 100644
--- a/src/contrib/msc/framework/tvm/codegen.cc
+++ b/src/contrib/msc/framework/tvm/codegen.cc
@@ -46,7 +46,7 @@ void RelaxCodeGen::CodeGenGraph() {
   for (const auto& n : graph()->node_names) {
     const auto& node = graph()->FindNode(n);
     for (const auto& pair : node->weights) {
-      const auto& idx_weight = IdxWeightBase(node, pair.first);
+      const auto& idx_weight = IdxWeightBase(node, pair.first, false);
       stack_.func_call("relax.Var", idx_weight)
           .call_arg(DocUtils::ToStrDoc(pair.second->name))
           .func_call("relax.TensorStructInfo")
@@ -60,6 +60,11 @@ void RelaxCodeGen::CodeGenGraph() {
   stack_.comment("Define the module");
   stack_.assign("block_builder", "relax.BlockBuilder()")
       .scope_start("block_builder.function(name=\"" + graph()->name + "\", 
params=inputs.copy())");
+  if (config()->use_tools) {
+    stack_.func_call("msc_tools.execute_step")
+        .call_arg(DocUtils::ToStrDoc("before_build"))
+        .call_arg("block_builder");
+  }
   for (const auto& n : graph()->node_names) {
     const auto& node = graph()->FindNode(n);
     if (node->optype == "input") {
@@ -71,7 +76,7 @@ void RelaxCodeGen::CodeGenGraph() {
     } else if (scope_level == -1) {
       stack_.scope_end();
     }
-    CodeGenNode(node);
+    CodeGenNode(node, config()->use_tools);
   }
   if (scopes().size() > 1) {
     // end left scopes
@@ -86,16 +91,41 @@ void RelaxCodeGen::CodeGenGraph() {
   stack_.comment("Emit the outputs");
   Array<String> idx_exits;
   for (const auto& e : graph()->GetExits()) {
-    const auto& idx_exit = IdxNodeBase(e, false);
+    const auto& idx_exit = IdxNodeBase(e) + (config()->use_tools ? "_exit" : 
"");
+    if (config()->use_tools) {
+      if (e->outputs.size() > 1) {
+        Array<String> tuple_outputs;
+        for (size_t o_idx = 0; o_idx < e->outputs.size(); o_idx++) {
+          const auto& t_output = IdxOutputBase(e, o_idx, true);
+          tuple_outputs.push_back(t_output);
+        }
+        stack_.func_call("relax.Tuple", 
idx_exit).call_arg(DocUtils::ToListDoc(tuple_outputs));
+        stack_.func_call("block_builder.emit", idx_exit).call_arg(idx_exit);
+        stack_.call_arg(DocUtils::ToStrDoc(e->name + "_exit"), "name_hint");
+      }
+    }
     stack_.func_call("block_builder.emit_output", idx_exit).call_arg(idx_exit);
     idx_exits.push_back(idx_exit);
   }
-  stack_.scope_end().func_call("block_builder.emit_func_output");
-  if (idx_exits.size() == 1) {
+  stack_.scope_end();
+  if (config()->use_tools) {
+    stack_.func_call("msc_tools.execute_step", "output")
+        .call_arg(DocUtils::ToStrDoc("after_build"));
+    if (idx_exits.size() == 1) {
+      stack_.call_arg(idx_exits[0]);
+    } else {
+      stack_.call_arg(DocUtils::ToListDoc(idx_exits));
+    }
+  }
+  stack_.func_call("block_builder.emit_func_output");
+  if (config()->use_tools) {
+    stack_.call_arg("output");
+  } else if (idx_exits.size() == 1) {
     stack_.call_arg(idx_exits[0]);
   } else {
     stack_.call_arg(DocUtils::ToListDoc(idx_exits));
   }
+
   stack_.scope_end().assign("mod", "block_builder.get()").func_end("mod");
 }
 
diff --git a/src/contrib/msc/framework/tvm/relax_opcode.cc 
b/src/contrib/msc/framework/tvm/relax_opcode.cc
index 869f7f595d..7960253741 100644
--- a/src/contrib/msc/framework/tvm/relax_opcode.cc
+++ b/src/contrib/msc/framework/tvm/relax_opcode.cc
@@ -41,7 +41,7 @@ const Array<Doc> RelaxOpCode::GetDocs() {
   }
   if (emit_var) {
     const auto& name = config()->explicit_name ? node()->name : "";
-    BuilderEmit(IdxNode(true), name);
+    BuilderEmit(IdxNode(), name);
   }
   return stack_.GetDocs();
 }
@@ -334,14 +334,7 @@ class RelaxEinsumCodeGen : public RelaxOpCode {
  protected:
   void CodeGenBuild() final {
     const String& key = config()->from_relay ? "equation" : "subscripts";
-    const auto& producer = node()->ProducerOf(0);
-    stack_.op_call();
-    if (node()->inputs.size() == 1 && producer->optype == "tuple") {
-      stack_.op_input_arg();
-    } else {
-      stack_.op_inputs_arg();
-    }
-    stack_.op_str_arg(key, "subscripts");
+    stack_.op_call().op_inputs_arg().op_str_arg(key, "subscripts");
   }
 };
 
diff --git a/tests/python/contrib/test_msc/test_graph_build.py 
b/tests/python/contrib/test_msc/test_graph_build.py
index 4b92f08253..3b1cfc4057 100644
--- a/tests/python/contrib/test_msc/test_graph_build.py
+++ b/tests/python/contrib/test_msc/test_graph_build.py
@@ -23,7 +23,7 @@ from torch.nn import Module
 
 import tvm.testing
 from tvm.relax.frontend.torch import from_fx
-from tvm.contrib.msc.core.ir import translate
+from tvm.contrib.msc.core.frontend import translate
 from tvm.contrib.msc.core import utils as msc_utils
 
 
@@ -51,9 +51,7 @@ def test_conv1d():
 
     expected1 = {
         "inputs": [{"name": "inp_0", "shape": [1, 3, 10], "dtype": "float32", 
"layout": "NCW"}],
-        "outputs": [
-            {"name": "msc.conv1d_bias", "shape": [1, 6, 4], "dtype": 
"float32", "layout": "NCW"}
-        ],
+        "outputs": [{"name": "conv1d", "shape": [1, 6, 4], "dtype": "float32", 
"layout": "NCW"}],
         "nodes": {"total": 2, "input": 1, "msc.conv1d_bias": 1},
     }
 
@@ -93,7 +91,7 @@ def test_conv2d():
         ],
         "outputs": [
             {
-                "name": "msc.conv2d_bias",
+                "name": "conv2d",
                 "shape": [1, 6, 4, 4],
                 "dtype": "float32",
                 "layout": "NCHW",
@@ -141,7 +139,7 @@ def test_linear():
         ],
         "outputs": [
             {
-                "name": "msc.linear_bias",
+                "name": "matmul",
                 "shape": [1, 3, 10, 7],
                 "dtype": "float32",
                 "layout": "NCHW",
@@ -163,7 +161,7 @@ def test_linear():
             {"name": "inp_0", "shape": [1, 3, 10, 10], "dtype": "float32", 
"layout": "NCHW"}
         ],
         "outputs": [
-            {"name": "msc.linear", "shape": [1, 3, 10, 7], "dtype": "float32", 
"layout": "NCHW"}
+            {"name": "matmul", "shape": [1, 3, 10, 7], "dtype": "float32", 
"layout": "NCHW"}
         ],
         "nodes": {"total": 2, "input": 1, "msc.linear": 1},
     }
@@ -499,15 +497,13 @@ def test_embedding():
 
     expected1 = {
         "inputs": [{"name": "inp_0", "shape": [4], "dtype": "int64", "layout": 
"A"}],
-        "outputs": [{"name": "msc.embedding", "shape": [4, 3], "dtype": 
"float32", "layout": "NA"}],
+        "outputs": [{"name": "take", "shape": [4, 3], "dtype": "float32", 
"layout": "NA"}],
         "nodes": {"total": 2, "input": 1, "msc.embedding": 1},
     }
 
     expected2 = {
         "inputs": [{"name": "inp_0", "shape": [4, 5], "dtype": "int64", 
"layout": "AB"}],
-        "outputs": [
-            {"name": "msc.embedding", "shape": [4, 5, 3], "dtype": "float32", 
"layout": "CNB"}
-        ],
+        "outputs": [{"name": "take", "shape": [4, 5, 3], "dtype": "float32", 
"layout": "CNB"}],
         "nodes": {"total": 2, "input": 1, "msc.embedding": 1},
     }
 
@@ -1750,7 +1746,7 @@ def test_keep_params():
         ],
         "outputs": [
             {
-                "name": "msc.conv2d_bias",
+                "name": "conv2d",
                 "shape": [1, 6, 4, 4],
                 "dtype": "float32",
                 "layout": "NCHW",
@@ -1982,7 +1978,7 @@ def test_attention():
         ],
         "outputs": [
             {
-                "name": "msc.attention",
+                "name": "attention",
                 "shape": [32, 128, 8, 64],
                 "dtype": "float32",
                 "layout": "ABCD",
@@ -2012,7 +2008,7 @@ def test_attention():
         ],
         "outputs": [
             {
-                "name": "msc.attention",
+                "name": "attention_bias",
                 "shape": [32, 128, 8, 64],
                 "dtype": "float32",
                 "layout": "ABCD",
@@ -2020,7 +2016,6 @@ def test_attention():
         ],
         "nodes": {"total": 5, "input": 4, "msc.attention": 1},
     }
-
     verify_model(
         Attention3(),
         [
diff --git a/tests/python/contrib/test_msc/test_manager.py 
b/tests/python/contrib/test_msc/test_manager.py
new file mode 100644
index 0000000000..de846c10eb
--- /dev/null
+++ b/tests/python/contrib/test_msc/test_manager.py
@@ -0,0 +1,263 @@
+# 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.
+
+""" Test Managers in MSC. """
+
+import pytest
+import torch
+
+import tvm.testing
+from tvm.contrib.msc.pipeline import MSCManager
+from tvm.contrib.msc.core.utils.namespace import MSCFramework
+from tvm.contrib.msc.core import utils as msc_utils
+
+requires_tensorrt = pytest.mark.skipif(
+    tvm.get_global_func("relax.ext.tensorrt", True) is None,
+    reason="TENSORRT is not enabled",
+)
+
+
+def _get_config(model_type, deploy_type, inputs, outputs, atol=1e-2, 
rtol=1e-2):
+    """Get msc config"""
+    return {
+        "model_type": model_type,
+        "inputs": inputs,
+        "outputs": outputs,
+        "dataset": {"loader": "from_random", "max_iter": 5},
+        "prepare": {"profile": {"benchmark": {"repeat": 10}}},
+        "baseline": {
+            "run_type": model_type,
+            "profile": {"check": {"atol": atol, "rtol": rtol}, "benchmark": 
{"repeat": 10}},
+        },
+        "compile": {
+            "run_type": deploy_type,
+            "profile": {"check": {"atol": atol, "rtol": rtol}, "benchmark": 
{"repeat": 10}},
+        },
+    }
+
+
+def _get_torch_model(name, is_training=False):
+    """Get model from torch vision"""
+    # pylint: disable=import-outside-toplevel
+    try:
+        import torchvision
+
+        model = getattr(torchvision.models, name)(pretrained=True)
+        if is_training:
+            model = model.train()
+        else:
+            model = model.eval()
+        return model
+    except:  # pylint: disable=bare-except
+        print("please install torchvision package")
+        return None
+
+
+def _get_tf_graph():
+    """Get graph from tensorflow"""
+    # pylint: disable=import-outside-toplevel
+    try:
+        from tvm.contrib.msc.framework.tensorflow import tf_v1
+        import tvm.relay.testing.tf as tf_testing
+
+        tf_graph = tf_v1.Graph()
+        with tf_graph.as_default():
+            graph_def = tf_testing.get_workload(
+                
"https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.4_224.tgz";,
+                "mobilenet_v2_1.4_224_frozen.pb",
+            )
+            # Call the utility to import the graph definition into default 
graph.
+            graph_def = tf_testing.ProcessGraphDefParam(graph_def)
+        return graph_def
+    except:  # pylint: disable=bare-except
+        print("please install tensorflow package")
+        return None
+
+
+def _test_from_torch(deploy_type, expected_info, is_training=False, atol=1e-2, 
rtol=1e-2):
+    torch_model = _get_torch_model("resnet50", is_training)
+    if torch_model:
+        if torch.cuda.is_available():
+            torch_model = torch_model.to(torch.device("cuda:0"))
+        config = _get_config(
+            MSCFramework.TORCH,
+            deploy_type,
+            inputs=[["input_0", [1, 3, 224, 224], "float32"]],
+            outputs=["output"],
+            atol=atol,
+            rtol=rtol,
+        )
+        manager = MSCManager(torch_model, config)
+        report = manager.run_pipe()
+        assert report["success"], "Failed to run pipe for torch -> 
{}".format(deploy_type)
+        model_info = manager.runner.model_info
+        assert msc_utils.dict_equal(
+            model_info, expected_info
+        ), "Model info {} mismatch with expected {}".format(model_info, 
expected_info)
+        manager.destory()
+
+
+def _test_from_tf(deploy_type, expected_info, atol=1e-2, rtol=1e-2):
+    graphdef = _get_tf_graph()
+    if graphdef:
+        config = _get_config(
+            MSCFramework.TENSORFLOW,
+            deploy_type,
+            inputs=[["input", [1, 224, 224, 3], "float32"]],
+            outputs=["MobilenetV2/Predictions/Reshape_1:0"],
+            atol=atol,
+            rtol=rtol,
+        )
+        config["compile"]["profile"]["check"]["err_rate"] = -1
+        manager = MSCManager(graphdef, config)
+        report = manager.run_pipe()
+        assert report["success"], "Failed to run pipe for tensorflow -> 
{}".format(deploy_type)
+        model_info = manager.runner.model_info
+        assert msc_utils.dict_equal(
+            model_info, expected_info
+        ), "Model info {} mismatch with expected {}".format(model_info, 
expected_info)
+        manager.destory()
+
+
+def test_tvm_manager():
+    """Test manager for tvm"""
+
+    model_info = {
+        "inputs": [
+            {"name": "input_0", "shape": [1, 3, 224, 224], "dtype": "float32", 
"layout": "NCHW"}
+        ],
+        "outputs": [{"name": "output", "shape": [1, 1000], "dtype": "float32", 
"layout": "NC"}],
+        "nodes": {
+            "total": 229,
+            "input": 1,
+            "nn.conv2d": 53,
+            "nn.batch_norm": 53,
+            "get_item": 53,
+            "nn.relu": 49,
+            "nn.max_pool2d": 1,
+            "add": 16,
+            "nn.adaptive_avg_pool2d": 1,
+            "reshape": 1,
+            "msc.linear_bias": 1,
+        },
+    }
+    _test_from_torch(MSCFramework.TVM, model_info, is_training=True)
+
+    model_info = {
+        "inputs": [
+            {"name": "input", "shape": [1, 224, 224, 3], "dtype": "float32", 
"layout": "NHWC"}
+        ],
+        "outputs": [
+            {
+                "name": "MobilenetV2/Predictions/Reshape_1:0",
+                "shape": [1, 1001],
+                "dtype": "float32",
+                "layout": "NC",
+            }
+        ],
+        "nodes": {
+            "total": 138,
+            "input": 1,
+            "msc.conv2d_bias": 36,
+            "clip": 35,
+            "nn.conv2d": 17,
+            "nn.batch_norm": 17,
+            "get_item": 17,
+            "add": 10,
+            "nn.avg_pool2d": 1,
+            "squeeze": 1,
+            "reshape": 2,
+            "nn.softmax": 1,
+        },
+    }
+    _test_from_tf(MSCFramework.TVM, model_info)
+
+
+def test_torch_manager():
+    """Test manager for torch"""
+
+    model_info = {
+        "inputs": [
+            {"name": "input_0", "shape": [1, 3, 224, 224], "dtype": "float32", 
"layout": "NCHW"}
+        ],
+        "outputs": [{"name": "output", "shape": [1, 1000], "dtype": "float32", 
"layout": "NC"}],
+        "nodes": {
+            "total": 229,
+            "input": 1,
+            "nn.conv2d": 53,
+            "nn.batch_norm": 53,
+            "get_item": 53,
+            "nn.relu": 49,
+            "nn.max_pool2d": 1,
+            "add": 16,
+            "nn.adaptive_avg_pool2d": 1,
+            "reshape": 1,
+            "msc.linear_bias": 1,
+        },
+    }
+    _test_from_torch(MSCFramework.TORCH, model_info, is_training=False)
+
+
+def test_tensorflow_manager():
+    """Test manager for tensorflow"""
+
+    model_info = {
+        "inputs": [
+            {"name": "input", "shape": [1, 224, 224, 3], "dtype": "float32", 
"layout": "NHWC"}
+        ],
+        "outputs": [
+            {
+                "name": "MobilenetV2/Predictions/Reshape_1:0",
+                "shape": [1, 1001],
+                "dtype": "float32",
+                "layout": "NC",
+            }
+        ],
+        "nodes": {
+            "total": 138,
+            "input": 1,
+            "msc.conv2d_bias": 36,
+            "clip": 35,
+            "nn.conv2d": 17,
+            "nn.batch_norm": 17,
+            "get_item": 17,
+            "add": 10,
+            "nn.avg_pool2d": 1,
+            "squeeze": 1,
+            "reshape": 2,
+            "nn.softmax": 1,
+        },
+    }
+    _test_from_tf(MSCFramework.TENSORFLOW, model_info)
+
+
+@requires_tensorrt
+def test_tensorrt_manager():
+    """Test manager for tensorrt"""
+
+    model_info = {
+        "inputs": [
+            {"name": "input_0", "shape": [1, 3, 224, 224], "dtype": "float32", 
"layout": "NCHW"}
+        ],
+        "outputs": [{"name": "output", "shape": [1, 1000], "dtype": "float32", 
"layout": ""}],
+        "nodes": {"total": 2, "input": 1, "msc_tensorrt": 1},
+    }
+    _test_from_torch(MSCFramework.TENSORRT, model_info, is_training=False)
+
+
+if __name__ == "__main__":
+    tvm.testing.main()
diff --git a/tests/python/contrib/test_msc/test_runner.py 
b/tests/python/contrib/test_msc/test_runner.py
index 3f2d0d0c90..a6005f5d41 100644
--- a/tests/python/contrib/test_msc/test_runner.py
+++ b/tests/python/contrib/test_msc/test_runner.py
@@ -25,7 +25,6 @@ from torch import fx
 from tvm.contrib.msc.framework.tensorflow import tf_v1
 
 import tvm.testing
-import tvm.relay.testing.tf as tf_testing
 from tvm.relax.frontend.torch import from_fx
 from tvm.contrib.msc.framework.tvm.runtime import TVMRunner
 from tvm.contrib.msc.framework.torch.runtime import TorchRunner
@@ -60,7 +59,10 @@ def _get_torch_model(name, is_training=False):
 def _get_tf_graph():
     """Get tensorflow graphdef"""
 
+    # pylint: disable=import-outside-toplevel
     try:
+        import tvm.relay.testing.tf as tf_testing
+
         tf_graph = tf_v1.Graph()
         with tf_graph.as_default():
             graph_def = tf_testing.get_workload(
diff --git a/tests/python/contrib/test_msc/test_translate_relax.py 
b/tests/python/contrib/test_msc/test_translate_relax.py
index fcf12947f0..cff2ee18ca 100644
--- a/tests/python/contrib/test_msc/test_translate_relax.py
+++ b/tests/python/contrib/test_msc/test_translate_relax.py
@@ -23,7 +23,7 @@ from torch.nn import Module
 
 import tvm.testing
 from tvm.relax.frontend.torch import from_fx
-from tvm.contrib.msc.core.ir import translate
+from tvm.contrib.msc.core.frontend import translate
 from tvm.contrib.msc.framework.tvm import codegen as tvm_codegen
 
 


Reply via email to