Caffe2 - Python API
A deep learning, cross platform ML framework
nomnigraph_transformations.py
1 from __future__ import absolute_import, division, print_function, unicode_literals
2 
3 from collections import defaultdict
4 
5 import caffe2.python.nomnigraph as ng
6 from caffe2.python import core, utils
7 
8 
9 def transpose_network(nn):
10  """
11  Convert all Convolutions operators which are in the NCHW order
12  to NHWC order and also transform their inputs and outputs so that the
13  rest of the graph is not affected.
14  """
15  # track the incoming tensors into NHWC2NCHW operators
16  incoming = {} # output tensor -> input tensor
17  # track outgoing tensors from NCHW2NHWC operators
18  outgoing = defaultdict(lambda: []) # input tensor -> list of operators
19  dfg = nn.dataFlow
20  orig_nodes = [x for x in nn.nodes]
21  for node in orig_nodes:
22  if node.isOperator() and node.name == "Conv":
23  arg_dict = utils.ArgsToDict(node.annotation.operator_def.arg)
24  # a missing "order" argument implies default NCHW order
25  if "order" in arg_dict and arg_dict["order"] != "NCHW":
26  continue
27  inputs = [x for x in node.inputs]
28  assert len(inputs) >= 2, "Conv operator should have two inputs"
29  outputs = [x for x in node.outputs]
30  assert len(outputs) >= 1, "Conv operator should have an output"
31  for inp in inputs:
32  nn.deleteEdge(inp, node)
33  for outp in outputs:
34  nn.deleteEdge(node, outp)
35  # only the first two inputs of the Convolution the data and the
36  # weights need to be transformed
37  for idx in range(2):
38  new_inp = nn.createUniqueDataNode(inputs[idx].name)
39  transp = dfg.createNode(ng.NeuralNetOperator("NCHW2NHWC"))
40  nn.createEdge(inputs[idx], transp)
41  nn.createEdge(transp, new_inp)
42  outgoing[inputs[idx]].append(transp)
43  inputs[idx] = new_inp
44  for idx in range(len(outputs)):
45  new_outp = nn.createUniqueDataNode(outputs[idx].name)
46  transp = dfg.createNode(ng.NeuralNetOperator("NHWC2NCHW"))
47  nn.createEdge(transp, outputs[idx])
48  nn.createEdge(new_outp, transp)
49  incoming[outputs[idx]] = new_outp
50  outputs[idx] = new_outp
51  # create a new Convolution with identical arguments as the original
52  # one except for the order
53  arg_dict["order"] = "NHWC"
54  new_node = nn.createNode(core.CreateOperator("Conv", [], [],
55  **arg_dict))
56  for inp in inputs:
57  nn.createEdge(inp, new_node)
58  for outp in outputs:
59  nn.createEdge(new_node, outp)
60 
61  nn.deleteNode(node)
62 
63  # finally, we will compress
64  # case 1:
65  # X -> NHWC2NCHW -> Y -> NCHW2NHWC -> Z1 ; Y -> NCHW2NHWC -> Z2
66  # to:
67  # X -> NHWC2NCHW -> Y and replace Z1 with X and replace Z2 with X
68  # And case 2:
69  # Y -> NCHW2NHWC -> Z1 ; Y -> NCHW2NHWC -> Z2
70  # to:
71  # Y -> NCHW2NHWC -> Z1 and replace Z2 with Z1
72 
73  # orig_tensor is one of the tensors in the original graph in NCHW order
74  for orig_tensor in outgoing:
75  # new_tensor is identical to orig_tensor except the order is NHWC
76  if orig_tensor in incoming: # case 1 (see above)
77  new_tensor = incoming[orig_tensor]
78  else: # case 2 (see above)
79  out_ops = outgoing[orig_tensor]
80  new_tensor = out_ops[0].outputs[0]
81  outgoing[orig_tensor] = out_ops[1:]
82 
83  for opnode in outgoing[orig_tensor]:
84  # there should only be one output, so this iteration is overkill
85  for out in opnode.outputs:
86  nn.replaceAllUsesWith(out, new_tensor)
87  nn.deleteNode(out)
88  nn.deleteNode(opnode)