Caffe2 - C++ API
A deep learning, cross platform ML framework
converter.cc
1 #include <limits>
2 
3 #include "caffe2/core/logging.h"
4 #include "caffe2/opt/converter.h"
5 
6 #include "nomnigraph/Graph/Algorithms.h"
7 
8 #include "nomnigraph/Support/Casting.h"
9 #include "nomnigraph/Support/Pointer.h"
10 
11 using namespace nom;
12 
13 namespace {
14 
15 std::vector<int> getStrides(std::map<std::string, caffe2::Argument> argMap) {
16  std::vector<int> strides;
17  // TODO: include all the other ways of adding these args.
18  // e.g. strides, stride_h, etc.
19  if (argMap.count("stride")) {
20  CAFFE_ENFORCE(argMap["stride"].has_i(), "Invalid stride argument");
21  int stride = static_cast<int>(argMap["stride"].i());
22  strides = {stride, stride};
23  }
24  return strides;
25 }
26 
27 std::vector<int> getPads(std::map<std::string, caffe2::Argument> argMap) {
28  std::vector<int> pads;
29  if (argMap.count("pad")) {
30  CAFFE_ENFORCE(argMap["pad"].has_i(), "Invalid pad argument");
31  int pad = static_cast<int>(argMap["pad"].i());
32  pads = {pad, pad, pad, pad};
33  }
34  return pads;
35 }
36 
37 std::vector<int> getDilations(std::map<std::string, caffe2::Argument> argMap) {
38  std::vector<int> dilations;
39  if (argMap.count("dilation")) {
40  CAFFE_ENFORCE(argMap["dilation"].has_i(), "Invalid dilation argument");
41  int dilation = static_cast<int>(argMap["dilation"].i());
42  dilations = {dilation, dilation};
43  }
44  return dilations;
45 }
46 
47 int getGroup(std::map<std::string, caffe2::Argument>& argMap) {
48  if (argMap.count("group")) {
49  CAFFE_ENFORCE(argMap["group"].has_i() && "Invalid group argument");
50  return static_cast<int>(argMap["group"].i());
51  }
52  return 1;
53 }
54 
55 } // namespace
56 
57 namespace caffe2 {
58 
59 C10_DEFINE_REGISTRY(ConverterRegistry, Converter);
60 
61 std::map<std::string, caffe2::Argument> Converter::getArgumentsFromOperator(
62  caffe2::OperatorDef op) {
63  std::map<std::string, caffe2::Argument> argMap;
64  for (auto arg : op.arg()) {
65  argMap[arg.name()] = arg;
66  }
67  return argMap;
68 }
69 
71  std::map<std::string, caffe2::Argument> argMap) {
72  auto arg = argMap.find("order");
73  if (arg != argMap.end()) {
74  auto order = argMap["order"].s();
75  if (order == "NCHW" || order == "nchw") {
76  return repr::NeuralNetOperator::NNLayout::NCHW;
77  } else if (order == "NHWC" || order == "nhwc") {
78  return repr::NeuralNetOperator::NNLayout::NHWC;
79  }
80  }
81  return repr::NeuralNetOperator::NNLayout::Undefined;
82 }
83 
84 OperatorDef Converter::convertToOperatorDef(
85  const nom::repr::NeuralNetOperator* nnOp) {
86  auto* annotation = nnOp->getAnnotation();
87  // Default to using the stored operator.
88  if (annotation && isa<Caffe2Annotation>(annotation)) {
89  return dyn_cast<Caffe2Annotation>(annotation)->getOperatorDef();
90  }
91  LOG(WARNING)
92  << "Cannot instantiate this OperatorDef from nomnigraph, falling back";
93  caffe2::OperatorDef op;
94  op.set_type(nnOp->getName());
95  return op;
96 }
97 
98 std::vector<int> getKernelShape(
99  std::map<std::string, caffe2::Argument> argMap) {
100  // There are literally three ways to define shapes in Conv in Caffe2
101  std::vector<int> kernelShape;
102  if (argMap.count("kernel")) {
103  CAFFE_ENFORCE(argMap["kernel"].has_i(), "Invalid kernel argument");
104  int kernel = static_cast<int>(argMap["kernel"].i());
105  kernelShape = {kernel, kernel};
106  } else if (argMap.count("kernels")) {
107  for (auto i : argMap["kernels"].ints()) {
108  kernelShape.push_back(static_cast<int>(i));
109  }
110  } else if (argMap.count("kernel_h") && argMap.count("kernel_w")) {
111  CAFFE_ENFORCE(argMap["kernel_h"].has_i(), "Invalid kernel argument");
112  CAFFE_ENFORCE(argMap["kernel_w"].has_i(), "Invalid kernel argument");
113  int kernelH = static_cast<int>(argMap["kernel_h"].i());
114  int kernelW = static_cast<int>(argMap["kernel_w"].i());
115  kernelShape = {kernelH, kernelW};
116  }
117  return kernelShape;
118 }
119 
120 namespace {
121 
122 class ConvConverter : public Converter {
123  std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator(
124  const OperatorDef& op) override {
125  std::unique_ptr<repr::NeuralNetOperator> nnOp;
126  auto argMap = getArgumentsFromOperator(op);
127  auto kernelShape = getKernelShape(argMap);
128  nnOp = util::make_unique<repr::Conv>(kernelShape);
129  auto c = dyn_cast<repr::Conv>(nnOp.get());
130 
131  c->setStrides(getStrides(argMap));
132  c->setPads(getPads(argMap));
133  c->setDilations(getDilations(argMap));
134  c->setGroup(getGroup(argMap));
135 
136  return nnOp;
137  }
138  // Does not override default converter to OperatorDef
139 
140  ~ConvConverter() override {}
141 };
142 
143 REGISTER_CONVERTER(Conv, ConvConverter);
144 
145 TRIVIAL_CONVERTER(Relu);
146 REGISTER_CONVERTER(Relu, ReluConverter);
147 
148 TRIVIAL_CONVERTER(Sum);
149 REGISTER_CONVERTER(Sum, SumConverter);
150 
151 TRIVIAL_CONVERTER(BatchNormalization);
152 REGISTER_CONVERTER(SpatialBN, BatchNormalizationConverter);
153 
154 TRIVIAL_CONVERTER(Flatten);
155 REGISTER_CONVERTER(Flatten, FlattenConverter);
156 
157 class ClipConverter : public Converter {
158  std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator(
159  const OperatorDef& op) override {
160  auto argMap = getArgumentsFromOperator(op);
161  float min = std::numeric_limits<float>::lowest();
162  float max = std::numeric_limits<float>::max();
163 
164  if (argMap.count("min")) {
165  CAFFE_ENFORCE(argMap["min"].has_f(), "Invalid 'min' argument");
166  min = static_cast<float>(argMap["min"].f());
167  }
168 
169  if (argMap.count("max")) {
170  CAFFE_ENFORCE(argMap["max"].has_f(), "Invalid 'max' argument");
171  max = static_cast<float>(argMap["max"].f());
172  }
173 
174  return util::make_unique<repr::Clip>(min, max);
175  }
176  // Does not override default converter to OperatorDef
177 
178  ~ClipConverter() override {}
179 };
180 REGISTER_CONVERTER(Clip, ClipConverter);
181 
182 class AveragePoolConverter : public Converter {
183  std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator(
184  const OperatorDef& op) override {
185  std::unique_ptr<repr::NeuralNetOperator> nnOp;
186  auto argMap = getArgumentsFromOperator(op);
187  auto kernelShape = getKernelShape(argMap);
188  nnOp = util::make_unique<repr::AveragePool>(kernelShape);
189  return nnOp;
190  }
191  // Does not override default converter to OperatorDef
192 
193  ~AveragePoolConverter() override {}
194 };
195 REGISTER_CONVERTER(AveragePool, AveragePoolConverter);
196 
197 class MaxPoolConverter : public Converter {
198  std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator(
199  const OperatorDef& op) override {
200  std::unique_ptr<repr::NeuralNetOperator> nnOp;
201  auto argMap = getArgumentsFromOperator(op);
202  auto kernelShape = getKernelShape(argMap);
203  nnOp = util::make_unique<repr::MaxPool>(kernelShape);
204  return nnOp;
205  }
206  // Does not override default converter to OperatorDef
207 
208  ~MaxPoolConverter() override {}
209 };
210 REGISTER_CONVERTER(MaxPool, MaxPoolConverter);
211 
212 class ConcatConverter : public Converter {
213  std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator(
214  const OperatorDef& op) override {
215  std::unique_ptr<repr::NeuralNetOperator> nnOp =
216  util::make_unique<repr::Concat>();
217  auto argMap = getArgumentsFromOperator(op);
218 
219  auto c = dyn_cast<repr::Concat>(nnOp.get());
220  if (argMap.count("axis")) {
221  CAFFE_ENFORCE(argMap["axis"].has_i(), "Invalid axis argument");
222  int axis = static_cast<int>(argMap["axis"].i());
223  c->setAxis(axis);
224  }
225  if (argMap.count("add_axis")) {
226  CAFFE_ENFORCE(argMap["add_axis"].has_i(), "Invalid add_axis argument");
227  int add_axis = static_cast<int>(argMap["add_axis"].i());
228  c->setAddAxis(!!add_axis);
229  }
230  return nnOp;
231  }
232  // Does not override default converter to OperatorDef
233 
234  ~ConcatConverter() override {}
235 };
236 REGISTER_CONVERTER(Concat, ConcatConverter);
237 
238 class FCConverter : public Converter {
239  std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator(
240  const OperatorDef& op) override {
241  std::unique_ptr<repr::NeuralNetOperator> nnOp =
242  util::make_unique<repr::FC>();
243  auto argMap = getArgumentsFromOperator(op);
244 
245  auto c = dyn_cast<repr::FC>(nnOp.get());
246  if (argMap.count("axis")) {
247  CAFFE_ENFORCE(argMap["axis"].has_i(), "Invalid axis argument");
248  int axis = static_cast<int>(argMap["axis"].i());
249  c->setAxis(axis);
250  }
251  if (argMap.count("axis_w")) {
252  CAFFE_ENFORCE(argMap["axis_w"].has_i(), "Invalid axis_w argument");
253  int axis_w = static_cast<int>(argMap["axis_w"].i());
254  c->setAxisW(axis_w);
255  }
256 
257  return nnOp;
258  }
259  // Does not override default converter to OperatorDef
260 
261  ~FCConverter() override {}
262 };
263 REGISTER_CONVERTER(FC, FCConverter);
264 
265 } // namespace
266 
267 std::unique_ptr<repr::NeuralNetOperator> convertToNeuralNetOperator(
268  const caffe2::OperatorDef& op) {
269  auto argMap = Converter::getArgumentsFromOperator(op);
270 
271  std::unique_ptr<repr::NeuralNetOperator> nnOp;
272 
273  if (ConverterRegistry()->Has(op.type())) {
274  nnOp =
275  ConverterRegistry()->Create(op.type())->convertToNeuralNetOperator(op);
276  }
277 
278  if (!nnOp) {
279  nnOp = util::make_unique<repr::GenericOperator>(op.type());
280  }
281 
282  // Generic attributes associated with Ops here
283  nnOp->setLayout(getLayout(argMap));
284 
285  auto annotation = util::make_unique<Caffe2Annotation>();
286  annotation->setOperatorDef(op);
287 
288  auto device_name = op.device_option().node_name();
289  if (device_name != "") {
290  annotation->setDevice(device_name);
291  }
292  annotation->setDeviceType(op.device_option().device_type());
293 
294  nnOp->setAnnotation(std::move(annotation));
295 
296  return nnOp;
297 }
298 
302  const caffe2::NetDef& net,
303  bool strict,
304  std::vector<repr::NNGraph::NodeRef>* opNodeVec) {
305  repr::NNModule module;
306  repr::NNGraph& dfg = module.dataFlow;
307  repr::NNCFGraph& cfg = module.controlFlow;
314  std::unordered_map<std::string, repr::NNGraph::NodeRef> blobMap;
315 
316  std::unordered_set<std::string> externalInputNames;
317  for (const auto& inputName : net.external_input()) {
318  externalInputNames.insert(inputName);
319  }
320 
324  auto bbNode = cfg.createNamedFunction("main");
325 
326  for (const auto& op : net.op()) {
327  auto opNode = dfg.createNode(); // Create an empty node for the operator.
328  // First calculate in-edges (data dependencies).
329  for (const auto& input : op.input()) {
330  // If we've never seen this tensor, make one.
331  if (!blobMap.count(input)) {
332  auto tensor = util::make_unique<repr::Tensor>(input);
333  blobMap[input] =
334  dfg.createNode(unique_dyn_cast<repr::NeuralNetData>(tensor));
335  if (externalInputNames.count(input)) {
336  module.inputs.insert(blobMap[input]);
337  externalInputNames.erase(input);
338  }
339  }
340 
341  auto tensorNode = blobMap[input];
342  dfg.createEdge(tensorNode, opNode);
343  }
344 
345  // Then save outputs into the blobMap for later consumption.
346  for (const auto& output : op.output()) {
347  auto tensor = util::make_unique<repr::Tensor>(output);
348  auto tensorNode =
349  dfg.createNode(unique_dyn_cast<repr::NeuralNetData>(tensor));
350  dfg.createEdge(opNode, tensorNode);
351  blobMap[output] = tensorNode;
352  }
353 
354  opNode->resetData(convertToNeuralNetOperator(op));
355  if (opNodeVec) {
356  opNodeVec->emplace_back(opNode);
357  }
358  auto currentBasicBlock = bbNode->mutableData();
359  currentBasicBlock->pushInstructionNode(opNode);
360  }
361 
362  if (externalInputNames.size()) {
363  // In strict mode we ensure the input names are valid
364  if (strict) {
365  std::ostringstream os;
366  for (const auto& inputName : externalInputNames) {
367  os << "\"" << inputName << "\" ";
368  }
369 
370  CAFFE_ENFORCE(
371  externalInputNames.size() == 0,
372  "Attempting to convert an ill-formed network: ",
373  "external_input contains ",
374  externalInputNames.size(),
375  " unused blobs: ",
376  os.str());
377  // Otherwise, we add the blobs to the graph as no-ops
378  } else {
379  for (const auto& input : externalInputNames) {
380  blobMap[input] = dfg.createNode(util::make_unique<repr::Tensor>(input));
381  }
382  }
383  }
384 
385  for (const auto& outputName : net.external_output()) {
386  CAFFE_ENFORCE(
387  !strict || blobMap.count(outputName),
388  "NetDef has ill-formed external_output:",
389  outputName);
390  if (!blobMap.count(outputName)) {
391  LOG(ERROR) << "NetDef has ill-formed external_output: " << outputName;
392  continue;
393  }
394  module.outputs.insert(blobMap[outputName]);
395  }
396 
397  return module;
398 }
399 
400 caffe2::OperatorDef convertToOperatorDef(
401  const repr::NNGraph::NodeRef& instrNode) {
402  auto* nnOp = repr::nn::get<repr::NeuralNetOperator>(instrNode);
403  auto op_type = nnOp->getName();
404  auto* annotation = nnOp->getAnnotation();
405  caffe2::OperatorDef op;
406 
407  if (ConverterRegistry()->Has(op_type)) {
408  op = ConverterRegistry()->Create(op_type)->convertToOperatorDef(nnOp);
409  } else if (!annotation) {
410  op.set_type(op_type);
411  } else {
412  if (isa<Caffe2Annotation>(annotation)) {
413  auto c2_annotation = dyn_cast<Caffe2Annotation>(annotation);
414  op = c2_annotation->getOperatorDef();
415  op.mutable_device_option()->set_device_type(
416  c2_annotation->getDeviceType());
417  } else {
418  CAFFE_THROW(
419  "Couldn't convert operator annotation to Caffe2 operator def");
420  }
421  }
422 
423  // We may have swapped out some of the edges.
424  op.clear_input();
425  op.clear_output();
426  return op;
427 }
428 
429 Caffe2Annotation* getOrAddCaffe2Annotation(
430  nom::repr::NNGraph::NodeRef& instrNode) {
431  auto* nnOp = repr::nn::get<repr::NeuralNetOperator>(instrNode);
432  auto* annotation = nnOp->getMutableAnnotation();
433  if (!annotation) {
434  auto new_annot = util::make_unique<Caffe2Annotation>();
435  new_annot->setOperatorDef(convertToOperatorDef(instrNode));
436  nnOp->setAnnotation(std::move(new_annot));
437  annotation = nnOp->getMutableAnnotation();
438  }
439  CAFFE_ENFORCE(isa<Caffe2Annotation>(annotation));
440  auto c2_annotation = dyn_cast<Caffe2Annotation>(annotation);
441  return c2_annotation;
442 }
443 
444 caffe2::NetDef convertToCaffe2Proto(repr::NNModule& m) {
445  auto predictNet = caffe2::NetDef();
446  return convertToCaffe2Proto(m, predictNet);
447 }
448 
449 std::vector<std::string> mergeExternalTensors(
450  const std::unordered_set<repr::NNGraph::NodeRef>& currExternal,
451  const std::vector<std::string>& oldExternal) {
452  std::vector<std::string> out;
453 
454  // Maximally preserve the order of external inputs and outputs.
455  std::unordered_set<std::string> newExternal;
456  for (const auto& tensorNode : currExternal) {
457  CAFFE_ENFORCE(
458  repr::nn::is<repr::NeuralNetData>(tensorNode),
459  "A non-tensor node was added to external inputs/outputs of the NNModule");
460  auto name = repr::nn::get<repr::NeuralNetData>(tensorNode)->getName();
461  newExternal.insert(name);
462  }
463 
464  for (const auto& tensorName : oldExternal) {
465  if (newExternal.count(tensorName)) {
466  out.emplace_back(tensorName);
467  newExternal.erase(tensorName);
468  }
469  }
470  for (const auto& tensorName : newExternal) {
471  out.emplace_back(tensorName);
472  }
473 
474  return out;
475 }
476 
477 caffe2::NetDef convertToCaffe2Proto(
478  repr::NNModule& m,
479  const caffe2::NetDef& oldNet) {
480  auto predictNet = caffe2::NetDef();
481  // We copy the old net rather than mutate it.
482  predictNet.CopyFrom(oldNet);
483  predictNet.mutable_op()->Clear();
484 
485  repr::nn::coalesceInsertedDataDependencies(&m);
486 
487  // Simply iterate through the CFG and populate data dependencies
488  // with the DFG
489  for (const auto& bbNode : m.controlFlow.getMutableNodes()) {
490  if (bbNode->getOutEdges().size() > 1) {
491  CAFFE_THROW("Control flow not yet supported in Caffe2 converter.");
492  }
493  auto& bb = bbNode->data();
494  for (const auto& instrNode : bb.getInstructions()) {
495  caffe2::OperatorDef op = convertToOperatorDef(instrNode);
496 
497  for (const auto& inEdge : instrNode->getInEdges()) {
498  auto* tensorNode =
499  dyn_cast<repr::NeuralNetData>(inEdge->tail()->data().get());
500  *op.add_input() = tensorNode->getName();
501  }
502  for (const auto& outEdge : instrNode->getOutEdges()) {
503  auto* tensorNode =
504  dyn_cast<repr::NeuralNetData>(outEdge->head()->data().get());
505  *op.add_output() = tensorNode->getName();
506  }
507 
508  auto* nnOp = repr::nn::get<repr::NeuralNetOperator>(instrNode);
509  if (nnOp->getLayout() != repr::NeuralNetOperator::NNLayout::Undefined) {
510  caffe2::Argument* arg = nullptr;
511  for (int i = 0; i < op.arg_size(); ++i) {
512  auto arg_ = op.mutable_arg(i);
513  if (arg_->name() == "order") {
514  arg = arg_;
515  break;
516  }
517  }
518 
519  if (!arg) {
520  arg = op.add_arg();
521  arg->set_name("order");
522  }
523 
524  auto layout = nnOp->getLayout();
525  if (layout == repr::NeuralNetOperator::NNLayout::NCHW) {
526  arg->set_s("NCHW");
527  }
528  if (layout == repr::NeuralNetOperator::NNLayout::NHWC) {
529  arg->set_s("NHWC");
530  }
531  }
532 
533  // Save the operator to the net.
534  *predictNet.add_op() = op;
535  }
536  }
537 
538  // Maximally preserve the order of external inputs and outputs.
539  std::vector<std::string> oldExternalInputs;
540  std::vector<std::string> oldExternalOutputs;
541 
542  for (const auto& inputName : predictNet.external_input()) {
543  oldExternalInputs.emplace_back(inputName);
544  }
545  for (const auto& outputName : predictNet.external_output()) {
546  oldExternalOutputs.emplace_back(outputName);
547  }
548 
549  auto newExternalInputs = mergeExternalTensors(m.inputs, oldExternalInputs);
550  auto newExternalOutputs = mergeExternalTensors(m.outputs, oldExternalOutputs);
551 
552  predictNet.clear_external_input();
553  predictNet.clear_external_output();
554 
555  for (const auto& inputName : newExternalInputs) {
556  predictNet.add_external_input(inputName);
557  }
558 
559  for (const auto& outputName : newExternalOutputs) {
560  predictNet.add_external_output(outputName);
561  }
562 
563  return predictNet;
564 }
565 
566 void pushOpToFront(caffe2::OperatorDef& op, caffe2::NetDef* net) {
567  *net->add_op() = op;
568  google::protobuf::RepeatedPtrField<caffe2::OperatorDef>* op_list(
569  net->mutable_op());
570  // Reverse iterate, swapping new element in front each time
571  for (int i(net->op_size() - 1); i > 0; --i) {
572  op_list->SwapElements(i, i - 1);
573  }
574 }
575 
576 void injectDataEdgeIndicators(caffe2::NetDef* net) {
577  for (const auto& input : net->external_input()) {
578  caffe2::OperatorDef op;
579  op.set_type("Declare");
580  op.add_output(input);
581  pushOpToFront(op, net);
582  }
583  for (const auto& output : net->external_output()) {
584  caffe2::OperatorDef op;
585  op.set_type("Export");
586  op.add_input(output);
587  *net->add_op() = op;
588  }
589  net->clear_external_input();
590  net->clear_external_output();
591 }
592 
593 void removeDataEdgeIndicators(caffe2::NetDef* net) {
594  google::protobuf::RepeatedPtrField<caffe2::OperatorDef>* op_list(
595  net->mutable_op());
596  for (auto i = 0; i < net->op_size(); ++i) {
597  auto op = net->op(i);
598  if (op.type() == "Declare") {
599  net->add_external_input(op.output(0));
600  } else if (op.type() == "Export") {
601  net->add_external_output(op.input(0));
602  } else {
603  continue;
604  }
605  // Note that this compensates for modifying the list inplace
606  op_list->DeleteSubrange(i--, 1);
607  }
608 }
609 
610 } // namespace caffe2
Definition: OpClasses.h:414
NodeRef createNode(T &&data)
Creates a node and retains ownership of it.
Definition: Graph.h:240
NNLayout
An optional tensor-type specifier.
Definition: NeuralNet.h:72
Definition: Dot.h:16
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...
Definition: blob.h:13
Definition: OpClasses.h:566
repr::NNModule convertToNNModule(const caffe2::NetDef &net, bool strict, std::vector< repr::NNGraph::NodeRef > *opNodeVec)
Ingest a caffe2 protobuf model and output an NNModule.
Definition: converter.cc:301
Definition: OpClasses.h:13
A simple graph implementation.
Definition: Graph.h:29
Definition: OpClasses.h:2
EdgeRef createEdge(NodeRef tail, NodeRef head, U...data)
Creates a directed edge and retains ownership of it.
Definition: Graph.h:415