nomnigraph_transformations.py 3.7 KB

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