debug_runtime.py 8.74 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# 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.
17 18 19 20 21 22 23
"""Graph debug runtime executes TVM debug packed functions."""

import os
import tempfile
import shutil
from tvm._ffi.base import string_types
from tvm._ffi.function import get_global_func
24
from tvm.contrib import graph_runtime
25
from tvm.ndarray import array
26
from tvm.rpc import base as rpc_base
27 28 29 30 31
from . import debug_result

_DUMP_ROOT_PREFIX = "tvmdbg_"
_DUMP_PATH_PREFIX = "_tvmdbg_"

32

33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
def create(graph_json_str, libmod, ctx, dump_root=None):
    """Create a runtime executor module given a graph and module.

    Parameters
    ----------
    graph_json_str : str or graph class
        The graph to be deployed in json format output by nnvm graph.
        The graph can only contain one operator(tvm_op) that
        points to the name of PackedFunc in the libmod.

    libmod : tvm.Module
        The module of the corresponding function.

    ctx : TVMContext
        The context to deploy the module, can be local or remote.

    dump_root : str
        To select which folder the outputs should be kept.
        None will make a temp folder in /tmp/tvmdbg<rand_string> and does the dumping
    Returns
    -------
    graph_module : GraphModuleDebug
        Debug Runtime graph module that can be used to execute the graph.
    """
    if not isinstance(graph_json_str, string_types):
        try:
            graph_json_str = graph_json_str._tvm_graph_json()
        except AttributeError:
            raise ValueError("Type %s is not supported" % type(graph_json_str))
    try:
        fcreate = get_global_func("tvm.graph_runtime_debug.create")
    except ValueError:
65 66 67 68
        raise ValueError(
            "Please set '(USE_GRAPH_RUNTIME_DEBUG ON)' in "
            "config.cmake and rebuild TVM to enable debug mode"
        )
69 70 71

    ctx, num_rpc_ctx, device_type_id = graph_runtime.get_device_ctx(libmod, ctx)
    if num_rpc_ctx == len(ctx):
72 73
        libmod = rpc_base._ModuleHandle(libmod)
        try:
74 75 76
            fcreate = ctx[0]._rpc_sess.get_function(
                "tvm.graph_runtime_debug.remote_create"
            )
77
        except ValueError:
78 79 80 81
            raise ValueError(
                "Please set '(USE_GRAPH_RUNTIME_DEBUG ON)' in "
                "config.cmake and rebuild TVM to enable debug mode"
            )
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
    func_obj = fcreate(graph_json_str, libmod, *device_type_id)
    return GraphModuleDebug(func_obj, ctx, graph_json_str, dump_root)


class GraphModuleDebug(graph_runtime.GraphModule):
    """Graph debug runtime module.

    This is a debug wrapper over the TVM runtime.
    Runtime interfaces are wrapped with debug functionalities.
    Manage the debug framework to format the debug data and
    trigger the user interfaces.

    Parameters
    ----------
    module : Module
        The interal tvm module that holds the actual graph functions.

    ctx : TVMContext
        The context this module is under.

    graph_json_str : str or graph class
        Content of graph json file in string format

    dump_root : str
        To select which folder the outputs should be kept.
        None will make a temp folder in /tmp/tvmdbg<rand_string> and does the dumping
    """
109

110 111 112 113
    def __init__(self, module, ctx, graph_json_str, dump_root):
        self._dump_root = dump_root
        self._dump_path = None
        self._get_output_by_layer = module["get_output_by_layer"]
114
        self._run_individual = module["run_individual"]
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
        graph_runtime.GraphModule.__init__(self, module)
        self._create_debug_env(graph_json_str, ctx)

    def _format_context(self, ctx):
        return str(ctx[0]).upper().replace("(", ":").replace(")", "")

    def _ensure_dir(self, directory):
        """Create a directory if not exists

        Parameters
        ----------

        directory : str
            File path to create
        """
        if not os.path.exists(directory):
            os.makedirs(directory, 0o700)

    def _get_dump_path(self, ctx):
        """Make the graph and tensor dump folder and return the path.

        Parameters
        ----------
        ctx : TVMContext
            The context this module is under.

        Returns
        -------
        path : str
            Directory path where the graph and node outputs will be stored.
        """
        # save to file
        folder_name = _DUMP_PATH_PREFIX + "ctx_"
        folder_name = folder_name + ctx.replace(":", "_")
        path = os.path.join(self._dump_root, folder_name)
        self._ensure_dir(path)
        return path

    def _remove_dump_root(self):
        if os.path.isdir(self._dump_root):
            shutil.rmtree(self._dump_root)

    def _create_debug_env(self, graph_json, ctx):
        """Create UI wrapper framework to handle multiple UI frontends for tvmdbg

        Parameters
        ----------
        graph_json : json format
            json formatted NNVM graph contain list of each node's name, shape and type.

        nodes_list : list
            List of all the nodes presented in the graph

        ctx : TVMContext
            The context this module is under.
        """
        # make the dump folder if not given
        if not self._dump_root:
173
            self._dump_root = tempfile.mkdtemp(prefix=_DUMP_ROOT_PREFIX)
174 175 176 177 178 179 180 181 182 183 184

        # format the context
        ctx = self._format_context(ctx)

        # updates the dumping directories
        self._dump_path = self._get_dump_path(ctx)

        # init the debug dumping environment
        self.debug_datum = debug_result.DebugResult(graph_json, self._dump_path)

    def _run_debug(self):
185
        """Execute the node specified with index will be executed.
186
        Each debug output will be copied to the buffer
187
        Time consumed for each execution will be set as debug output.
188 189

        """
190 191 192
        self.debug_datum._time_list = [
            [float(t) * 1e-6] for t in self.run_individual(10, 1, 1)
        ]
193 194 195 196
        for i, node in enumerate(self.debug_datum.get_graph_nodes()):
            num_outputs = self.debug_datum.get_graph_node_output_num(node)
            for j in range(num_outputs):
                out_tensor = self._get_output_by_layer(i, j)
197
                out_tensor = array(out_tensor)
198
                self.debug_datum._output_tensor_list.append(out_tensor)
199 200

    def debug_get_output(self, node, out):
201
        """Run graph up to node and get the output to out
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217

        Parameters
        ----------
        node : int / str
            The node index or name

        out : NDArray
            The output array container
        """
        ret = None
        if isinstance(node, str):
            output_tensors = self.debug_datum.get_output_tensors()
            try:
                ret = output_tensors[node]
            except:
                node_list = output_tensors.keys()
218 219 220 221 222 223 224
                raise RuntimeError(
                    "Node "
                    + node
                    + " not found, available nodes are: "
                    + str(node_list)
                    + "."
                )
225 226 227 228 229 230 231
        elif isinstance(node, int):
            output_tensors = self.debug_datum._output_tensor_list
            ret = output_tensors[node]
        else:
            raise RuntimeError("Require node index or name only.")
        return ret

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
    def run(self, **input_dict):
        """Run forward execution of the graph with debug

        Parameters
        ----------
        input_dict : dict of str to NDArray
            List of input values to be feed to
        """
        if input_dict:
            self.set_input(**input_dict)

        # Step 1. Execute the graph
        self._run_debug()
        # Step 2. Dump the output tensors to the dump folder
        self.debug_datum.dump_output_tensor()
247 248 249
        # Step 3. Dump the Chrome trace to the dump folder
        self.debug_datum.dump_chrome_trace()
        # Step 4. Display the collected information
250 251
        self.debug_datum.display_debug_result()

252
    def run_individual(self, number, repeat=1, min_repeat_ms=0):
253 254 255
        ret = self._run_individual(number, repeat, min_repeat_ms)
        return ret.strip(",").split(",") if ret else []

256

257 258 259
    def exit(self):
        """Exits the dump folder and all its contents"""
        self._remove_dump_root()