|
| 1 | +--- |
| 2 | +title: 调试器 |
| 3 | +sidebar_position: 120 |
| 4 | +--- |
| 5 | + |
| 6 | +# 调试器 |
| 7 | + |
| 8 | +TVM 调试器是一个用于调试 TVM 计算图执行过程的接口。它有助于访问 TVM runtime 的计算图结构和张量值。 |
| 9 | + |
| 10 | +## 调试交换格式 |
| 11 | + |
| 12 | +### 1. 计算图 |
| 13 | + |
| 14 | +由 Relay 以 JSON 序列化格式构建的优化图按原样转储。包含有关计算图的全部信息。UX 可以直接使用此图,也可以将此图转换为 UX 可以理解的格式。 |
| 15 | + |
| 16 | +Graph JSON 格式解释如下 |
| 17 | + |
| 18 | +1. `nodes` 节点是占位符,或是 json 中的计算节点。节点存储为列表。一个节点包含以下信息: |
| 19 | + * `op` - 操作类型,`null` 表示它是一个占位符/变量/输入节点,`tvm_op` 表示该节点可以执行 |
| 20 | + * `name` - 节点名称 |
| 21 | + * `inputs` - 算子的输入位置,Inputs 是一个(nodeid、index、version)元组列表。(可选) |
| 22 | + * `attrs` - 包含以下信息的节点属性 |
| 23 | + * `flatten_data` - 执行前是否需要对数据进行展开 |
| 24 | + * `func_name` - 融合函数名,对应 Relay 编译过程生成的 lib 的符号。 |
| 25 | + * `num_inputs` - 节点的输入数量 |
| 26 | + * `num_outputs` - 节点产生的输出数量 |
| 27 | +2. `arg_nodes`:arg_nodes 是节点的索引列表,它是计算图的占位符/变量/输入或常量/参数。 |
| 28 | +3. `heads`:heads 是计算图输出的条目列表。 |
| 29 | +4. `node_row_ptr`:node_row_ptr 存储前向传播路径的历史记录,因此可以在推理任务中跳过构建整个计算图。 |
| 30 | +5. `attrs`:attrs 包含版本号或类似的有用信息。 |
| 31 | + * `storage_id` - 存储布局中每个节点的内存插槽 id。 |
| 32 | + * `dtype` - 每个节点的数据类型(枚举值)。 |
| 33 | + * `dltype` - 按顺序排列的每个节点的数据类型。 |
| 34 | + * `shape` - 每个节点的 shape 为 k 阶。 |
| 35 | + * `device_index` - 计算图中每个条目的设备分配。 |
| 36 | + |
| 37 | +转储图示例: |
| 38 | + |
| 39 | +``` json |
| 40 | +{ |
| 41 | + "nodes": [ # 节点列表 |
| 42 | + { |
| 43 | + "op": "null", # operation type = null,这是一个占位符/变量/输入或常量/参数节点 |
| 44 | + "name": "x", # 参数节点的名称 |
| 45 | + "inputs": [] # 此节点的输入,这里为 none,因为这是一个参数节点 |
| 46 | + }, |
| 47 | + { |
| 48 | + "op": "tvm_op", # operation type = tvm_op,这个节点可以执行 |
| 49 | + "name": "relu0", # 节点名称 |
| 50 | + "attrs": { # 节点的属性 |
| 51 | + "flatten_data": "0", # 此数据是否需要展开 |
| 52 | + "func_name": "fuse_l2_normalize_relu", # 融合函数名,对应编译过程生成的 lib 的符号 |
| 53 | + "num_inputs": "1", # 此节点的输入数量 |
| 54 | + "num_outputs": "1" # 此节点产生的输出数量 |
| 55 | + }, |
| 56 | + "inputs": [[0, 0, 0]] # 此操作的输入位置 |
| 57 | + } |
| 58 | + ], |
| 59 | + "arg_nodes": [0], # 其中所有节点都是参数节点 |
| 60 | + "node_row_ptr": [0, 1, 2], # 用于更快深度优先搜索的行索引 |
| 61 | + "heads": [[1, 0, 0]], # 此操作的输出节点的位置 |
| 62 | + "attrs": { # 计算图的属性 |
| 63 | + "storage_id": ["list_int", [1, 0]], # 存储布局中每个节点的内存插槽 ID |
| 64 | + "dtype": ["list_int", [0, 0]], # 每个节点的数据类型(枚举值) |
| 65 | + "dltype": ["list_str", [ # 按顺序排列的每个节点的数据类型 |
| 66 | + "float32", |
| 67 | + "float32"]], |
| 68 | + "shape": ["list_shape", [ # 每个节点的 shape 为 k 阶 |
| 69 | + [1, 3, 20, 20], |
| 70 | + [1, 3, 20, 20]]], |
| 71 | + "device_index": ["list_int", [1, 1]], # 按顺序为每个节点分配设备 |
| 72 | + } |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +### 2. 张量转储 |
| 77 | + |
| 78 | +执行返回的张量是 `tvm.ndarray` 类型。所有张量都将以序列化格式保存为二进制字节。结果二进制字节可以通过 API「load_params」加载。 |
| 79 | + |
| 80 | +**加载参数示例** |
| 81 | + |
| 82 | +``` python |
| 83 | +with open(path_params, “rb”) as fi: |
| 84 | + loaded_params = bytearray(fi.read()) |
| 85 | +module.load_params(loaded_params) |
| 86 | +``` |
| 87 | + |
| 88 | +## 如何使用调试器 |
| 89 | + |
| 90 | +1. 在 `config.cmake` 中把 `USE_PROFILER` 标志设置为 `ON` |
| 91 | + |
| 92 | + ``` cmake |
| 93 | + # 是否开启额外的计算图调试功能 |
| 94 | + set(USE_PROFILER ON) |
| 95 | + ``` |
| 96 | + |
| 97 | +2. 执行 `make` tvm,生成 `libtvm_runtime.so` |
| 98 | + |
| 99 | +3. 在前端脚本文件中导入 `GraphModuleDebug`,`from tvm.contrib.debugger.debug_executor import GraphModuleDebug`,而非 `from tvm.contrib import graph_executor`。 |
| 100 | + |
| 101 | + ``` python |
| 102 | + from tvm.contrib.debugger.debug_executor import GraphModuleDebug |
| 103 | + m = GraphModuleDebug( |
| 104 | + lib["debug_create"]("default", dev), |
| 105 | + [dev], |
| 106 | + lib.graph_json, |
| 107 | + dump_root="/tmp/tvmdbg", |
| 108 | + ) |
| 109 | + # 设置输入 |
| 110 | + m.set_input('data', tvm.nd.array(data.astype(dtype))) |
| 111 | + m.set_input(**params) |
| 112 | + # 执行 |
| 113 | + m.run() |
| 114 | + tvm_out = m.get_output(0, tvm.nd.empty(out_shape, dtype)).numpy() |
| 115 | + ``` |
| 116 | + |
| 117 | +4. 如果之前和共享对象文件/动态链接库一样,**用的是 `lib.export_library("network.so")` 将网络导出到外部库**,调试 runtime 的初始化会略有不同。 |
| 118 | + |
| 119 | + ``` python |
| 120 | + lib = tvm.runtime.load_module("network.so") |
| 121 | + m = graph_executor.create(lib["get_graph_json"](), lib, dev, dump_root="/tmp/tvmdbg") |
| 122 | + # 设置输入 |
| 123 | + m.set_input('data', tvm.nd.array(data.astype(dtype))) |
| 124 | + m.set_input(**params) |
| 125 | + # 执行 |
| 126 | + m.run() |
| 127 | + tvm_out = m.get_output(0, tvm.nd.empty(out_shape, dtype)).numpy() |
| 128 | + ``` |
| 129 | + |
| 130 | +输出被转储到 `/tmp` 文件夹中的临时文件夹,或创建 runtime 时指定的文件夹。 |
| 131 | + |
| 132 | +## 样本输出 |
| 133 | + |
| 134 | +以下是调试器的输出示例: |
| 135 | + |
| 136 | +``` bash |
| 137 | +Node Name Ops Time(us) Time(%) Start Time End Time Shape Inputs Outputs |
| 138 | +--------- --- -------- ------- ---------- -------- ----- ------ ------- |
| 139 | +1_NCHW1c fuse___layout_transform___4 56.52 0.02 15:24:44.177475 15:24:44.177534 (1, 1, 224, 224) 1 1 |
| 140 | +_contrib_conv2d_nchwc0 fuse__contrib_conv2d_NCHWc 12436.11 3.4 15:24:44.177549 15:24:44.189993 (1, 1, 224, 224, 1) 2 1 |
| 141 | +relu0_NCHW8c fuse___layout_transform___broadcast_add_relu___layout_transform__ 4375.43 1.2 15:24:44.190027 15:24:44.194410 (8, 1, 5, 5, 1, 8) 2 1 |
| 142 | +_contrib_conv2d_nchwc1 fuse__contrib_conv2d_NCHWc_1 213108.6 58.28 15:24:44.194440 15:24:44.407558 (1, 8, 224, 224, 8) 2 1 |
| 143 | +relu1_NCHW8c fuse___layout_transform___broadcast_add_relu___layout_transform__ 2265.57 0.62 15:24:44.407600 15:24:44.409874 (64, 1, 1) 2 1 |
| 144 | +_contrib_conv2d_nchwc2 fuse__contrib_conv2d_NCHWc_2 104623.15 28.61 15:24:44.409905 15:24:44.514535 (1, 8, 224, 224, 8) 2 1 |
| 145 | +relu2_NCHW2c fuse___layout_transform___broadcast_add_relu___layout_transform___1 2004.77 0.55 15:24:44.514567 15:24:44.516582 (8, 8, 3, 3, 8, 8) 2 1 |
| 146 | +_contrib_conv2d_nchwc3 fuse__contrib_conv2d_NCHWc_3 25218.4 6.9 15:24:44.516628 15:24:44.541856 (1, 8, 224, 224, 8) 2 1 |
| 147 | +reshape1 fuse___layout_transform___broadcast_add_reshape_transpose_reshape 1554.25 |
| 148 | +``` |
0 commit comments