| |
| |
| from collections import defaultdict |
| |
| import caffe2.python.nomnigraph as ng |
| from caffe2.python import core, utils |
| |
| |
| def transpose_network(nn): |
| """ |
| Convert all Convolutions operators which are in the NCHW order |
| to NHWC order and also transform their inputs and outputs so that the |
| rest of the graph is not affected. |
| """ |
| # track the incoming tensors into NHWC2NCHW operators |
| incoming = {} # output tensor -> input tensor |
| # track outgoing tensors from NCHW2NHWC operators |
| outgoing = defaultdict(lambda: []) # input tensor -> list of operators |
| dfg = nn.dataFlow |
| orig_nodes = [x for x in nn.nodes] |
| for node in orig_nodes: |
| if node.isOperator() and node.name == "Conv": |
| arg_dict = utils.ArgsToDict(node.annotation.operator_def.arg) |
| # a missing "order" argument implies default NCHW order |
| if "order" in arg_dict and arg_dict["order"] != "NCHW": |
| continue |
| inputs = [x for x in node.inputs] |
| assert len(inputs) >= 2, "Conv operator should have two inputs" |
| outputs = [x for x in node.outputs] |
| assert len(outputs) >= 1, "Conv operator should have an output" |
| for inp in inputs: |
| nn.deleteEdge(inp, node) |
| for outp in outputs: |
| nn.deleteEdge(node, outp) |
| # only the first two inputs of the Convolution the data and the |
| # weights need to be transformed |
| for idx in range(2): |
| new_inp = nn.createUniqueDataNode(inputs[idx].name) |
| transp = dfg.createNode(ng.NeuralNetOperator("NCHW2NHWC")) |
| nn.createEdge(inputs[idx], transp) |
| nn.createEdge(transp, new_inp) |
| outgoing[inputs[idx]].append(transp) |
| inputs[idx] = new_inp |
| for idx in range(len(outputs)): |
| new_outp = nn.createUniqueDataNode(outputs[idx].name) |
| transp = dfg.createNode(ng.NeuralNetOperator("NHWC2NCHW")) |
| nn.createEdge(transp, outputs[idx]) |
| nn.createEdge(new_outp, transp) |
| incoming[outputs[idx]] = new_outp |
| outputs[idx] = new_outp |
| # create a new Convolution with identical arguments as the original |
| # one except for the order |
| arg_dict["order"] = "NHWC" |
| new_node = nn.createNode(core.CreateOperator("Conv", [], [], |
| **arg_dict)) |
| for inp in inputs: |
| nn.createEdge(inp, new_node) |
| for outp in outputs: |
| nn.createEdge(new_node, outp) |
| |
| nn.deleteNode(node) |
| |
| # finally, we will compress |
| # case 1: |
| # X -> NHWC2NCHW -> Y -> NCHW2NHWC -> Z1 ; Y -> NCHW2NHWC -> Z2 |
| # to: |
| # X -> NHWC2NCHW -> Y and replace Z1 with X and replace Z2 with X |
| # And case 2: |
| # Y -> NCHW2NHWC -> Z1 ; Y -> NCHW2NHWC -> Z2 |
| # to: |
| # Y -> NCHW2NHWC -> Z1 and replace Z2 with Z1 |
| |
| # orig_tensor is one of the tensors in the original graph in NCHW order |
| for orig_tensor in outgoing: |
| # new_tensor is identical to orig_tensor except the order is NHWC |
| if orig_tensor in incoming: # case 1 (see above) |
| new_tensor = incoming[orig_tensor] |
| else: # case 2 (see above) |
| out_ops = outgoing[orig_tensor] |
| new_tensor = out_ops[0].outputs[0] |
| outgoing[orig_tensor] = out_ops[1:] |
| |
| for opnode in outgoing[orig_tensor]: |
| # there should only be one output, so this iteration is overkill |
| for out in opnode.outputs: |
| nn.replaceAllUsesWith(out, new_tensor) |
| nn.deleteNode(out) |
| nn.deleteNode(opnode) |