Parse an optimized PyTorch model graph and produces a list of nodes and node stats. Useful for eventual conversion to TensorBoard protobuf format. Args: graph (PyTorch module): The model graph to be parsed. trace (PyTorch JIT TracedModule): The model trace to be parsed. a
(graph, trace, args=None, omit_useless_nodes=True)
| 230 | |
| 231 | |
| 232 | def parse(graph, trace, args=None, omit_useless_nodes=True): |
| 233 | """Parse an optimized PyTorch model graph and produces a list of nodes and node stats. |
| 234 | |
| 235 | Useful for eventual conversion to TensorBoard protobuf format. |
| 236 | |
| 237 | Args: |
| 238 | graph (PyTorch module): The model graph to be parsed. |
| 239 | trace (PyTorch JIT TracedModule): The model trace to be parsed. |
| 240 | args (tuple): input tensor[s] for the model. |
| 241 | omit_useless_nodes (boolean): Whether to remove nodes from the graph. |
| 242 | """ |
| 243 | n_inputs = len(args) |
| 244 | |
| 245 | scope = {} |
| 246 | nodes_py = GraphPy() |
| 247 | for node in graph.inputs(): |
| 248 | if omit_useless_nodes: |
| 249 | if ( |
| 250 | len(node.uses()) == 0 |
| 251 | ): # number of user of the node (= number of outputs/ fanout) |
| 252 | continue |
| 253 | |
| 254 | if node.type().kind() != CLASSTYPE_KIND: |
| 255 | nodes_py.append(NodePyIO(node, "input")) |
| 256 | |
| 257 | attr_to_scope: Dict[Any, str] = {} |
| 258 | for node in graph.nodes(): |
| 259 | if node.kind() == GETATTR_KIND: |
| 260 | attr_name = node.s("name") |
| 261 | attr_key = node.output().debugName() |
| 262 | parent = node.input().node() |
| 263 | if ( |
| 264 | parent.kind() == GETATTR_KIND |
| 265 | ): # If the parent node is not the top-level "self" node |
| 266 | parent_attr_name = parent.s("name") |
| 267 | parent_attr_key = parent.output().debugName() |
| 268 | parent_scope = attr_to_scope[parent_attr_key] |
| 269 | attr_scope = parent_scope.split("/")[-1] |
| 270 | attr_to_scope[attr_key] = f"{parent_scope}/{attr_scope}.{attr_name}" |
| 271 | else: |
| 272 | attr_to_scope[attr_key] = f"__module.{attr_name}" |
| 273 | # We don't need classtype nodes; scope will provide this information |
| 274 | if node.output().type().kind() != CLASSTYPE_KIND: |
| 275 | node_py = NodePyOP(node) |
| 276 | node_py.scopeName = attr_to_scope[attr_key] # type: ignore[attr-defined] |
| 277 | nodes_py.append(node_py) |
| 278 | else: |
| 279 | nodes_py.append(NodePyOP(node)) |
| 280 | |
| 281 | for i, node in enumerate(graph.outputs()): # Create sink nodes for output ops |
| 282 | node_pyio = NodePyIO(node, "output") |
| 283 | node_pyio.debugName = f"output.{i + 1}" |
| 284 | node_pyio.inputs = [node.debugName()] |
| 285 | nodes_py.append(node_pyio) |
| 286 | |
| 287 | def parse_traced_name(module): |
| 288 | if isinstance(module, torch.jit.TracedModule): |
| 289 | module_name = module._name |
no test coverage detected
searching dependent graphs…