Caffe2 - C++ API
A deep learning, cross platform ML framework
Caffe2.cc
1 #include "nomnigraph/Converters/Caffe2.h"
2 #include "nomnigraph/Graph/Algorithms.h"
3 
4 #include "nomnigraph/Support/Casting.h"
5 #include "nomnigraph/Support/Pointer.h"
6 
7 std::map<std::string, caffe2::Argument>
8 getArgumentsFromOperator(caffe2::OperatorDef op) {
9  std::map<std::string, caffe2::Argument> argMap;
10  for (auto arg : op.arg()) {
11  argMap[arg.name()] = arg;
12  }
13  return argMap;
14 }
15 
16 namespace nom {
17 namespace converters {
18 
19 std::unique_ptr<repr::NeuralNetOperator>
20 convertOperator(caffe2::OperatorDef op) {
21  auto argMap = getArgumentsFromOperator(op);
22 
23  if (op.type() == "Conv") {
24  // There are literally three ways to define shapes in Conv in Caffe2
25  std::vector<int> kernelShape;
26  if (argMap.count("kernel")) {
27  assert(argMap["kernel"].has_i() && "Invalid kernel argument passed to Conv");
28  int kernel = static_cast<int>(argMap["kernel"].i());
29  kernelShape = {kernel, kernel};
30  } else if (argMap.count("kernels")) {
31  for (auto i : argMap["kernels"].ints()) {
32  kernelShape.push_back(static_cast<int>(i));
33  }
34  } else if (argMap.count("kernel_h") && argMap.count("kernel_w")) {
35  assert(argMap["kernel_h"].has_i() && "Invalid kernel argument passed to Conv");
36  assert(argMap["kernel_w"].has_i() && "Invalid kernel argument passed to Conv");
37  int kernelH = static_cast<int>(argMap["kernel_h"].i());
38  int kernelW = static_cast<int>(argMap["kernel_w"].i());
39  kernelShape = {kernelH, kernelW};
40  } else {
41  assert(0);
42  }
43 
44  auto c = util::make_unique<repr::Conv>(kernelShape);
45 
46  if (argMap.count("order")) {
47  auto order = argMap["order"].s();
48  if (order == "NCHW") {
49  c->setLayout(repr::Conv::NNLayout::NCHW);
50  } else if (order == "NHWC") {
51  c->setLayout(repr::Conv::NNLayout::NHWC);
52  }
53  }
54 
55  // TODO: include all the other ways of adding these args.
56  // e.g. strides, stride_h, etc.
57  if (argMap.count("stride")) {
58  assert(argMap["stride"].has_i() && "Invalid stride argument");
59  int stride = static_cast<int>(argMap["stride"].i());
60  c->setStrides({stride, stride});
61  }
62 
63  if (argMap.count("pad")) {
64  assert(argMap["pad"].has_i() && "Invalid pad argument");
65  int pad = static_cast<int>(argMap["pad"].i());
66  c->setPads({pad, pad, pad, pad});
67  }
68 
69  if (argMap.count("dilation")) {
70  assert(argMap["dilation"].has_i() && "Invalid dilation argument");
71  int dilation = static_cast<int>(argMap["dilation"].i());
72  c->setDilations({dilation, dilation});
73  }
74 
75  return std::move(c);
76  }
77 
78  if (op.type() == "Relu") {
79  auto relu = util::make_unique<repr::Relu>();
80  return std::move(relu);
81  }
82 
83  return util::make_unique<repr::GenericOperator>(op.type());
84 }
85 
86 
90 repr::NNModule convertFromCaffe2Proto(const caffe2::NetDef &net, std::unordered_map<std::string, repr::NNGraph::NodeRef>* blobMapOut) {
91  repr::NNGraph dfg;
92  repr::NNCFGraph cfg;
99  std::unordered_map<std::string, repr::NNGraph::NodeRef> blobMap;
100 
104  // std::unique_ptr<repr::BasicBlockType<repr::NNGraph>> currentBasicBlock =
105  auto bbNode =
106  cfg.createNode(util::make_unique<repr::BasicBlockType<repr::NNGraph>>());
107 
108  for (const auto &op : net.op()) {
109  auto opNode = dfg.createNode(); // Create an empty node for the operator.
110  // First calculate in-edges (data dependencies).
111  for (const auto &input : op.input()) {
112  // If we've never seen this tensor, make one.
113  if (!blobMap.count(input)) {
114  auto tensor = util::make_unique<repr::Tensor>(input);
115  blobMap[input] =
116  dfg.createNode(unique_dyn_cast<repr::NeuralNetData>(tensor));
117  }
118 
119  auto tensorNode = blobMap[input];
120  dfg.createEdge(tensorNode, opNode);
121  }
122 
123  // Then save outputs into the blobMap for later consumption.
124  for (const auto &output : op.output()) {
125  auto tensor = util::make_unique<repr::Tensor>(output);
126  auto tensorNode =
127  dfg.createNode(unique_dyn_cast<repr::NeuralNetData>(tensor));
128  dfg.createEdge(opNode, tensorNode);
129  blobMap[output] = tensorNode;
130  }
131 
132  if (op.type() == "While") {
133  opNode->resetData(util::make_unique<repr::While>());
134  auto argMap = getArgumentsFromOperator(op);
135  std::string bodyNetSerialized = argMap["body"].s();
136  auto bodyNet = caffe2::NetDef();
137  bodyNet.ParseFromString(bodyNetSerialized);
138 
139  std::unordered_map<std::string, repr::NNGraph::NodeRef> bodyBlobMap;
140  auto bodyNN = convertFromCaffe2Proto(bodyNet, &bodyBlobMap);
141  repr::NNGraph bodyGraph = std::move(bodyNN.dataFlow);
142  repr::NNCFGraph bodyCFGraph = std::move(bodyNN.controlFlow);
143 
144  auto rev_sorted = algorithm::tarjans(&bodyGraph);
145 
146  for (auto& k : bodyBlobMap) {
147  auto name = k.first;
148  if (blobMap.count(name)) {
149  auto oldNode = blobMap[name];
150  printf("Exit tensor %s is in the parent scope, inserting Phi node...\n", k.first.c_str());
151  auto phiNode = dfg.createNode(util::make_unique<repr::NNPhi>()); // NN variant of a Phi node
152  // Clone the operator.
153  auto tensor = dyn_cast<repr::NeuralNetData>(blobMap[name]->data().get());
154  auto* clonedTensor = tensor->clone();
155  auto phiOut = dfg.createNode(std::unique_ptr<repr::NeuralNetData>(clonedTensor));
156  dfg.createEdge(phiNode, phiOut);
157  dfg.createEdge(oldNode, phiNode);
158  dfg.createEdge(bodyBlobMap[name], phiNode);
159  blobMap[name] = phiOut;
160  for (auto& inEdge : opNode->getInEdges()) {
161  if (inEdge->tail() == oldNode) {
162  dfg.deleteEdge(inEdge);
163  dfg.createEdge(phiOut, opNode);
164  }
165  }
166  }
167  }
168 
169  // Dependencies simply have no producers
170  std::unordered_map<repr::NNGraph::NodeRef, repr::NNGraph::NodeRef> inNodeMap;
171  for (auto& n : bodyGraph.getMutableNodes()) {
172  if (!isa<repr::NeuralNetData>(n->data())) { continue; }
173  if (n->getInEdges().size() == 0) {
174  auto name = dyn_cast<repr::NeuralNetData>(n->data().get())->getName();
175  // TODO(bwasti): this may be needed, depending on constraints
176  //assert(blobMap.count(name) != 0 && "Loop body takes undefined dependency.");
177  if (blobMap.count(name)) {
178  inNodeMap[n] = blobMap[name];
179  }
180  }
181  }
182 
183  assert(rev_sorted.front().getNodes().size() == 1 &&
184  "More than one exit node.");
185  assert(rev_sorted.back().getNodes().size() == 1 &&
186  "More than one entry node.");
187 
188  auto exit_tensor = *(rev_sorted.front().getNodes().begin());
189  assert(isa<repr::NeuralNetData>(exit_tensor->data()) &&
190  "Exit node is not a tensor.");
191 
192  auto bodyNodes = bodyGraph.getMutableNodes();
193  auto bodyEdges = bodyGraph.getMutableEdges();
194 
195  for (auto node : bodyNodes) {
196  bodyGraph.swapNode(node, dfg);
197  }
198 
199  for (auto edge : bodyEdges) {
200  bodyGraph.swapEdge(edge, dfg);
201  }
202 
203  // Merge all dependencies
204  for (auto node : dfg.getMutableNodes()) {
205  if (inNodeMap.count(node)) {
206  dfg.replaceNode(node, inNodeMap[node]);
207  dfg.deleteNode(node);
208  }
209  }
210 
211  for (const auto& inEdge : opNode->getInEdges()) {
212  auto* inputData = dyn_cast<repr::NeuralNetData>(inEdge->tail()->data().get());
213  auto* exitData = dyn_cast<repr::NeuralNetData>(exit_tensor->data().get());
214  if (inputData->getName() == exitData->getName()) {
215  dfg.replaceNode(exit_tensor, inEdge->tail());
216  dfg.deleteNode(exit_tensor);
217  }
218  }
219 
220  // CFG Handling
221  auto bodyCFNodes = bodyCFGraph.getMutableNodes();
222  auto bodyCFEdges = bodyCFGraph.getMutableEdges();
223 
224  // Create a while loop CFG node.
225  auto whileBasicBlock = util::make_unique<repr::BasicBlockType<repr::NNGraph>>();
226  for (auto& inEdge : opNode->getInEdges()) {
227  auto node = inEdge->tail();
228  for (auto& parentInEdge : node->getInEdges()) {
229  auto parentNode = parentInEdge->tail();
230  if (isa<repr::Phi>(parentNode->data().get())) {
231  whileBasicBlock->pushInstructionNode(parentNode);
232  }
233  }
234  }
235  whileBasicBlock->pushInstructionNode(opNode);
236 
237  auto whileCFNode = cfg.createNode(std::move(whileBasicBlock));
238  cfg.createEdge(bbNode, whileCFNode, 0);
239 
240  // The true path executes the body of the loop, so we
241  // take that BB and point to it.
242  for (auto cfNode : bodyCFNodes) {
243  bodyCFGraph.swapNode(cfNode, cfg);
244  // If the CFG node has no children, we loop back to the top of the
245  // while loop.
246  if (cfNode->getOutEdges().size() == 0) {
247  cfg.createEdge(cfNode, whileCFNode, 0);
248  }
249  // TODO check for a single entry point
250  if (cfNode->getInEdges().size() == 0) {
251  cfg.createEdge(whileCFNode, cfNode, 1);
252  }
253  }
254  for (auto cfEdge : bodyCFEdges) {
255  bodyCFGraph.swapEdge(cfEdge, cfg);
256  }
257 
258  // Now create the false case.
259  bbNode =
260  cfg.createNode(util::make_unique<repr::BasicBlockType<repr::NNGraph>>());
261  cfg.createEdge(whileCFNode, bbNode, -1);
262  } else {
263  opNode->resetData(convertOperator(op));
264  auto currentBasicBlock = bbNode->mutableData()->get();
265  currentBasicBlock->pushInstructionNode(opNode);
266  }
267  auto opRef = dyn_cast<repr::NeuralNetOperator>(opNode->data().get());
268 
269  assert(opNode->data());
270 
271  auto device_name = op.device_option().node_name();
272  if (device_name != "") {
273  auto device = util::make_unique<repr::DeviceAnnotation>(device_name);
274  opRef->setAnnotation(std::move(device));
275  } else {
276  opRef->setAnnotation(util::make_unique<repr::Annotation>());
277  }
278 
279  opRef->getMutableAnnotation()->setSaved((void *)&op);
280  }
281 
282  repr::NNModule module;
283  module.dataFlow = std::move(dfg);
284  module.controlFlow = std::move(cfg);
285  if (blobMapOut) {
286  *blobMapOut = blobMap;
287  }
288  return module;
289 }
290 
291 caffe2::NetDef convertToCaffe2Proto(repr::NNModule &m) {
292  auto predictNet = caffe2::NetDef();
293 
294  repr::nn::coalesceInsertedDataDependencies(&m);
295 
296  // Simply iterate through the CFG and populate data dependencies
297  // with the DFG
298  for (const auto &bbNode : m.controlFlow.getMutableNodes()) {
299  if (bbNode->getOutEdges().size() > 1) {
300  assert(0 && "Control flow not yet supported in Caffe2 converter.");
301  }
302  auto bb = bbNode->data().get();
303  for (const auto &instrNode : bb->getInstructions()) {
304  auto *nnOp = dyn_cast<repr::NeuralNetOperator>(instrNode->data().get());
305  auto *annotation = nnOp->getAnnotation();
306  assert(annotation->getSaved() &&
307  "Generating Caffe2 operators from IR not yet supported.\n");
308  auto *op =
309  reinterpret_cast<caffe2::OperatorDef *>(annotation->getSaved());
310 
311  // We may have swapped out some of the edges.
312  op->clear_input();
313  op->clear_output();
314  for (const auto &inEdge : instrNode->getInEdges()) {
315  auto *tensorNode =
316  dyn_cast<repr::NeuralNetData>(inEdge->tail()->data().get());
317  *op->add_input() = tensorNode->getName();
318  }
319  for (const auto &outEdge : instrNode->getOutEdges()) {
320  auto *tensorNode =
321  dyn_cast<repr::NeuralNetData>(outEdge->head()->data().get());
322  *op->add_output() = tensorNode->getName();
323  }
324  // Save the operator to the net.
325  *predictNet.add_op() = *op;
326  }
327  }
328 
329  return predictNet;
330 }
331 
332 } // namespace converters
333 } // namespace nom
Definition: Caffe2.cc:16