1 #include "caffe2/onnx/onnx_exporter.h" 2 #include "caffe2/core/logging.h" 3 #include "caffe2/core/tensor_impl.h" 4 #include "caffe2/onnx/helper.h" 5 #include "caffe2/proto/caffe2_legacy.pb.h" 6 #include "caffe2/utils/map_utils.h" 7 #include "caffe2/utils/proto_utils.h" 10 #include <unordered_set> 18 std::unordered_map<std::string, AttributeProto>* attrs,
22 const std::string& ks =
"") {
23 std::string ks2 = ks.empty() ? (k +
"s") : ks;
24 std::string k_h, k_w, k_t, k_l, k_b, k_r;
35 std::vector<int64_t> vals;
36 if (dim == 2 && attrs->count(k_h) && attrs->count(k_w)) {
37 auto it = attrs->find(k_h);
38 vals.push_back(it->second.i());
40 it = attrs->find(k_w);
41 vals.push_back(it->second.i());
44 dim == 4 && attrs->count(k_t) && attrs->count(k_b) && attrs->count(k_l) &&
46 auto it = attrs->find(k_t);
47 vals.push_back(it->second.i());
49 it = attrs->find(k_l);
50 vals.push_back(it->second.i());
52 it = attrs->find(k_b);
53 vals.push_back(it->second.i());
55 it = attrs->find(k_r);
56 vals.push_back(it->second.i());
58 }
else if (attrs->count(k)) {
59 auto it = attrs->find(k);
60 auto tmp = it->second.i();
61 for (
int i = 0; i < dim; ++i) {
67 if (!vals.empty() && !global) {
68 attrs->emplace(ks2, MakeAttribute(ks2, vals));
72 int64_t DimProd(
const caffe2::TensorShape& shape,
int start,
int end) {
74 for (
int i = start; i < end; ++i) {
80 TensorProto CreateOnnxShapeTensor(
81 std::shared_ptr<DummyName> dummy,
82 const std::vector<int64_t>& shape) {
84 tensor.set_name(dummy->NewDummyName());
85 tensor.set_data_type(TensorProto::INT64);
86 tensor.add_dims(shape.size());
87 tensor.mutable_raw_data()->assign(
88 reinterpret_cast<const char*>(shape.data()),
89 sizeof(int64_t) * shape.size());
93 std::string SsaName(
const std::string& n,
int version) {
94 return c10::str(n,
"_", version);
97 NodeProto AddShapeNode(
const std::string& input,
const std::string& output) {
99 shape_node.set_op_type(
"Shape");
100 shape_node.add_input(input);
101 shape_node.add_output(output);
107 ::ONNX_NAMESPACE::TensorProto::DataType Caffe2TypeToOnnxType(
108 caffe2::TensorProto::DataType t) {
109 #define CAFFE2_TO_ONNX_TYPE(x) \ 110 case (caffe2::TensorProto::x): \ 111 return ::ONNX_NAMESPACE::TensorProto::x 113 CAFFE2_TO_ONNX_TYPE(FLOAT);
114 CAFFE2_TO_ONNX_TYPE(BOOL);
115 CAFFE2_TO_ONNX_TYPE(INT8);
116 CAFFE2_TO_ONNX_TYPE(UINT8);
117 CAFFE2_TO_ONNX_TYPE(UINT16);
118 CAFFE2_TO_ONNX_TYPE(INT16);
119 CAFFE2_TO_ONNX_TYPE(INT32);
120 CAFFE2_TO_ONNX_TYPE(INT64);
121 CAFFE2_TO_ONNX_TYPE(FLOAT16);
123 LOG(WARNING) <<
"Unsupported Caffe2 tensor type: " << t
124 <<
", fallback to FLOAT";
125 return ::ONNX_NAMESPACE::TensorProto::FLOAT;
127 #undef CAFFE2_TO_ONNX_TYPE 130 std::unordered_map<std::string, std::string> SsaRewrite(
131 caffe2::NetDef* init_net,
132 caffe2::NetDef* pred_net) {
133 std::unordered_map<std::string, std::string> input_mapping;
134 std::unordered_map<std::string, int> blob_versions;
144 for (
const auto& name : init_net->external_input()) {
145 input_mapping.emplace(name, name);
147 blob_versions.clear();
151 std::unordered_set<std::string> external_outputs;
152 for (
const auto& input : pred_net->external_input()) {
154 input_mapping.emplace(input, input);
156 for (
const auto& output : pred_net->external_output()) {
157 external_outputs.emplace(output);
159 for (
auto& op : *pred_net->mutable_op()) {
160 for (
auto& input : *op.mutable_input()) {
161 const auto it = blob_versions.find(input);
162 if (it != blob_versions.end()) {
163 input = SsaName(input, it->second);
171 for (
auto& output : *op.mutable_output()) {
172 auto it = blob_versions.find(output);
173 if (it != blob_versions.end()) {
175 output = SsaName(output, it->second);
177 blob_versions.emplace(output, 0);
178 output = SsaName(output, 0);
186 std::unordered_map<std::string, std::string> renamed_external_outputs;
187 for (
const auto it : blob_versions) {
188 if (external_outputs.count(it.first)) {
189 renamed_external_outputs.emplace(
190 SsaName(it.first, it.second), it.first);
196 for (
auto& op : *pred_net->mutable_op()) {
197 for (
auto& input : *op.mutable_input()) {
198 const auto it = renamed_external_outputs.find(input);
199 if (it != renamed_external_outputs.end()) {
203 for (
auto& output : *op.mutable_output()) {
204 const auto it = renamed_external_outputs.find(output);
205 if (it != renamed_external_outputs.end()) {
212 return input_mapping;
215 const std::unordered_map<std::string, std::string>&
216 OnnxExporter::get_renamed_operators()
const {
217 const static std::unordered_map<std::string, std::string> kRenamedOperators{
218 {
"SpatialBN",
"BatchNormalization"},
222 {
"ConvTranspose1D",
"ConvTranspose"},
223 {
"ConvTranspose2D",
"ConvTranspose"},
224 {
"ConvTranspose3D",
"ConvTranspose"},
225 {
"MaxPool1D",
"MaxPool"},
226 {
"MaxPool2D",
"MaxPool"},
227 {
"MaxPool3D",
"MaxPool"},
228 {
"AveragePool1D",
"AveragePool"},
229 {
"AveragePool2D",
"AveragePool"},
230 {
"AveragePool3D",
"AveragePool"}};
231 return kRenamedOperators;
234 const std::unordered_map<std::string, std::string>&
235 OnnxExporter::get_renamed_attrs()
const {
236 const static std::unordered_map<std::string, std::string> kRenamedAttrs{
237 {
"kernels",
"kernel_shape"}};
238 return kRenamedAttrs;
242 unordered_map<std::string, std::unordered_map<std::string, std::string>>&
243 OnnxExporter::get_per_op_renamed_attrs()
const {
245 unordered_map<std::string, std::unordered_map<std::string, std::string>>
246 kPerOpRenamedAttrs = {{
"Squeeze", {{
"dims",
"axes"}}},
247 {
"Unsqueeze", {{
"dims",
"axes"}}},
248 {
"Transpose", {{
"axes",
"perm"}}},
249 {
"ConvTranspose", {{
"adjs",
"output_padding"}}},
250 {
"Selu", {{
"scale",
"gamma"}}}};
252 return kPerOpRenamedAttrs;
255 const std::unordered_map<std::string, OnnxExporter::SpecialOpConverter>&
256 OnnxExporter::get_special_operators()
const {
257 const static std::unordered_map<std::string, OnnxExporter::SpecialOpConverter>
258 kSpecialOperators = {
259 {
"ArgMax", &OnnxExporter::CreateArgMaxMinOpNodes},
260 {
"ArgMin", &OnnxExporter::CreateArgMaxMinOpNodes},
261 {
"Add", &OnnxExporter::CreateBinaryElementwiseOpNodes},
262 {
"Sub", &OnnxExporter::CreateBinaryElementwiseOpNodes},
263 {
"Mul", &OnnxExporter::CreateBinaryElementwiseOpNodes},
264 {
"Div", &OnnxExporter::CreateBinaryElementwiseOpNodes},
265 {
"Pow", &OnnxExporter::CreateBinaryElementwiseOpNodes},
266 {
"And", &OnnxExporter::CreateBinaryElementwiseOpNodes},
267 {
"Or", &OnnxExporter::CreateBinaryElementwiseOpNodes},
268 {
"Xor", &OnnxExporter::CreateBinaryElementwiseOpNodes},
269 {
"Equal", &OnnxExporter::CreateBinaryElementwiseOpNodes},
270 {
"Greater", &OnnxExporter::CreateBinaryElementwiseOpNodes},
271 {
"Less", &OnnxExporter::CreateBinaryElementwiseOpNodes},
272 {
"Cast", &OnnxExporter::CreateCastNodes},
273 {
"ElementwiseLinear", &OnnxExporter::CreateElementwiseLinearNodes},
274 {
"Conv", &OnnxExporter::CreateConvPoolNodes},
275 {
"ConvTranspose", &OnnxExporter::CreateConvPoolNodes},
276 {
"MaxPool", &OnnxExporter::CreateConvPoolNodes},
277 {
"AveragePool", &OnnxExporter::CreateConvPoolNodes},
278 {
"FC", &OnnxExporter::CreateGemmNodes},
279 {
"Concat", &OnnxExporter::CreateConcatNodes},
280 {
"MergeDim", &OnnxExporter::CreateMergeDimNodes},
281 {
"LRN", &OnnxExporter::CreateLrnNodes},
282 {
"Reshape", &OnnxExporter::CreateReshapeNodes},
283 {
"Slice", &OnnxExporter::CreateSliceNodes},
284 {
"ChannelShuffle", &OnnxExporter::CreateChannelShuffleNodes},
285 {
"ReduceMean", &OnnxExporter::CreateReduceMeanNodes},
286 {
"ReduceFrontMean", &OnnxExporter::CreateReduceMeanNodes},
287 {
"ReduceBackMean", &OnnxExporter::CreateReduceMeanNodes},
288 {
"ResizeNearest", &OnnxExporter::CreateUpsampleNodes}};
289 return kSpecialOperators;
292 void OnnxExporter::CopyCaffe2ArgToOnnxAttr(
293 AttributeProto* attr,
294 const std::string& op_type,
297 caffe2::get_default(get_renamed_attrs(), arg.name(), arg.name());
298 const auto& per_op_renamed_attr_lut = get_per_op_renamed_attrs();
299 const auto it = per_op_renamed_attr_lut.find(op_type);
300 if (it != per_op_renamed_attr_lut.end()) {
302 name = caffe2::get_default(it->second, arg.name(), name);
304 attr->set_name(name);
307 attr->set_f(arg.f());
308 attr->set_type(AttributeProto::FLOAT);
309 }
else if (arg.has_i()) {
310 attr->set_i(arg.i());
311 attr->set_type(AttributeProto::INT);
312 }
else if (arg.has_s()) {
313 attr->set_s(arg.s());
314 attr->set_type(AttributeProto::STRING);
315 }
else if (arg.floats_size()) {
316 attr->mutable_floats()->CopyFrom(arg.floats());
317 attr->set_type(AttributeProto::STRINGS);
318 }
else if (arg.ints_size()) {
319 attr->mutable_ints()->CopyFrom(arg.ints());
320 attr->set_type(AttributeProto::INTS);
321 }
else if (arg.strings_size()) {
322 attr->mutable_strings()->CopyFrom(arg.strings());
323 attr->set_type(AttributeProto::STRINGS);
325 CAFFE_THROW(c10::str(
"Unsupported Caffe2 argument: ", arg.name()));
330 const static std::unordered_map<std::string, std::unordered_set<std::string>>
331 kBlackListString = {{
"order", {
"NCHW"}}};
332 const static std::unordered_map<std::string, std::unordered_set<int64_t>>
333 kBlackListInt = {{
"cudnn_exhaustive_search", {0, 1}},
334 {
"use_cudnn", {0, 1}},
335 {
"exhaustive_search", {0, 1}},
337 {
"broadcast", {0, 1}}};
340 const auto it = kBlackListInt.find(arg.name());
341 if (it != kBlackListInt.end()) {
342 return it->second.count(arg.i());
344 }
else if (arg.has_s()) {
345 const auto it = kBlackListString.find(arg.name());
346 if (it != kBlackListString.end()) {
347 return it->second.count(arg.s());
354 ConvertedResult OnnxExporter::Caffe2OpToOnnxNodes(
355 const caffe2::OperatorDef& def,
356 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
357 std::string type = def.type();
358 const auto& renamed_op_lut = get_renamed_operators();
359 const auto it = renamed_op_lut.find(type);
360 if (it != renamed_op_lut.end()) {
363 const auto& special_op_lut = get_special_operators();
364 const auto it_op = get_special_operators().find(type);
365 if (it_op != special_op_lut.end()) {
366 return (this->*(it_op->second))(def, shapes);
368 return CommonCaffe2OpToOnnxNodes(def);
372 ConvertedResult OnnxExporter::CommonCaffe2OpToOnnxNodes(
373 const caffe2::OperatorDef& def) {
374 ConvertedResult result;
375 auto& nodes = result.first;
376 nodes.emplace_back();
377 NodeProto& node = nodes.back();
378 node.set_name(def.name());
380 caffe2::get_default(get_renamed_operators(), def.type(), def.type()));
381 for (
const auto& i : def.input()) {
384 for (
const auto& o : def.output()) {
387 for (
const auto& a : def.arg()) {
388 if (!IsBlackListed(a)) {
389 auto* attr = node.add_attribute();
390 CopyCaffe2ArgToOnnxAttr(attr, def.type(), a);
396 ConvertedResult OnnxExporter::CreateArgMaxMinOpNodes(
397 const caffe2::OperatorDef& def,
398 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
399 auto result = CommonCaffe2OpToOnnxNodes(def);
400 auto& nodes = result.first;
402 CAFFE_ENFORCE_EQ(nodes.size(), 1);
403 auto& node = nodes.back();
405 if (!ArgumentHelper::HasArgument(def,
"axis")) {
406 const auto& x = def.input(0);
407 const auto& x_shape = shapes.at(x);
408 node.add_attribute()->CopyFrom(
409 MakeAttribute(
"axis", x_shape.dims().size() - 1));
415 ConvertedResult OnnxExporter::CreateBinaryElementwiseOpNodes(
416 const caffe2::OperatorDef& def,
417 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
418 caffe2::OperatorDef mdef(def);
419 const auto& x = mdef.input(0);
420 const auto& y = def.input(1);
421 const auto& x_shape = shapes.at(x);
422 const auto& y_shape = shapes.at(y);
423 for (
int i = 0; i < mdef.arg_size(); ++i) {
424 const auto& arg = mdef.arg(i);
425 if (arg.name() ==
"broadcast") {
426 ArgumentHelper::RemoveArgument(mdef, i);
430 std::vector<int64_t> axes;
431 for (
int i = 0; i < mdef.arg_size(); ++i) {
432 const auto& arg = mdef.arg(i);
433 if (arg.name() ==
"axis") {
434 int64_t axis = arg.i();
435 if (x_shape.dims().size() - axis != y_shape.dims().size()) {
438 y_shape.dims().size() - 1 - axis + x_shape.dims().size();
439 axes.resize(end_dim - y_shape.dims().size());
440 std::iota(axes.begin(), axes.end(), y_shape.dims().size());
441 mdef.set_input(1, dummy_->NewDummyName());
443 ArgumentHelper::RemoveArgument(mdef, i);
448 auto result = CommonCaffe2OpToOnnxNodes(mdef);
449 if (axes.size() != 0) {
451 result.first.begin(),
453 "Unsqueeze", {y}, {mdef.input(1)}, {MakeAttribute(
"axes", axes)}));
458 ConvertedResult OnnxExporter::CreateCastNodes(
459 const caffe2::OperatorDef& def,
460 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
461 auto result = CommonCaffe2OpToOnnxNodes(def);
462 auto* attr = result.first[0].mutable_attribute(0);
463 auto onnx_dtype = ::ONNX_NAMESPACE::TensorProto::UNDEFINED;
464 const auto& arg = def.arg(0);
466 auto c2_dtype = arg.s();
468 c2_dtype.begin(), c2_dtype.end(), c2_dtype.begin(), ::toupper);
469 if (c2_dtype ==
"FLOAT") {
470 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::FLOAT;
471 }
else if (c2_dtype ==
"INT32") {
472 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::INT32;
473 }
else if (c2_dtype ==
"BOOL") {
474 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::BOOL;
475 }
else if (c2_dtype ==
"UINT8") {
476 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::UINT8;
477 }
else if (c2_dtype ==
"INT8") {
478 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::INT8;
479 }
else if (c2_dtype ==
"UINT16") {
480 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::UINT16;
481 }
else if (c2_dtype ==
"INT16") {
482 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::INT16;
483 }
else if (c2_dtype ==
"INT64") {
484 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::INT64;
485 }
else if (c2_dtype ==
"FLOAT16") {
486 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::FLOAT16;
487 }
else if (c2_dtype ==
"DOUBLE") {
488 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::DOUBLE;
490 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::UNDEFINED;
494 ::ONNX_NAMESPACE::TensorProto::UNDEFINED,
497 "' dtype is not supported");
499 attr->set_type(AttributeProto::INT);
500 }
else if (arg.has_i()) {
501 const auto& c2_dtype = arg.i();
503 case caffe2::TensorProto::FLOAT:
504 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::FLOAT;
506 case caffe2::TensorProto::INT32:
507 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::INT32;
509 case caffe2::TensorProto::BOOL:
510 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::BOOL;
512 case caffe2::TensorProto::UINT8:
513 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::UINT8;
515 case caffe2::TensorProto::INT8:
516 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::INT8;
518 case caffe2::TensorProto::UINT16:
519 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::UINT16;
521 case caffe2::TensorProto::INT16:
522 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::INT16;
524 case caffe2::TensorProto::INT64:
525 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::INT64;
527 case caffe2::TensorProto::FLOAT16:
528 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::FLOAT16;
530 case caffe2::TensorProto::DOUBLE:
531 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::DOUBLE;
534 case caffe2::TensorProto::STRING:
535 case caffe2::TensorProto::BYTE:
536 case caffe2::TensorProto::UNDEFINED:
537 onnx_dtype = ::ONNX_NAMESPACE::TensorProto::UNDEFINED;
542 ::ONNX_NAMESPACE::TensorProto::UNDEFINED,
545 "' dtype is not supported");
547 attr->set_i(onnx_dtype);
551 ConvertedResult OnnxExporter::CreateElementwiseLinearNodes(
552 const caffe2::OperatorDef& def,
553 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
554 CAFFE_ENFORCE_EQ(def.input_size(), 3);
555 CAFFE_ENFORCE_GE(def.output_size(), 1);
556 const auto& x = def.input(0);
557 const auto& w = def.input(1);
558 const auto& b = def.input(2);
559 const auto& y = def.output(0);
560 CAFFE_ENFORCE_EQ(shapes.at(w).dims().size(), 1);
561 CAFFE_ENFORCE_EQ(shapes.at(b).dims().size(), 1);
563 ConvertedResult result;
564 auto& nodes = result.first;
565 auto& const_tensors = result.second;
566 std::unordered_map<std::string, const caffe2::Argument*> args;
567 for (
const auto& a : def.arg()) {
568 args.emplace(a.name(), &a);
571 const auto& x_shape = shapes.at(x);
572 const auto it = args.find(
"axis");
573 const int64_t axis = it == args.end() ? 1 : it->second->i();
574 const bool need_reshape = axis + 1 != x_shape.dims().size();
576 auto fma_x_input = x;
578 const auto inner = DimProd(x_shape, axis, x_shape.dims().size());
579 CAFFE_ENFORCE_EQ(shapes.at(w).dims(0), inner);
580 CAFFE_ENFORCE_EQ(shapes.at(b).dims(0), inner);
582 fma_x_input = dummy_->NewDummyName();
583 const_tensors.emplace_back(CreateOnnxShapeTensor(
584 dummy_, std::vector<int64_t>{-1, shapes.at(w).dims(0)}));
586 MakeNode(
"Reshape", {x, const_tensors.back().name()}, {fma_x_input}));
589 const auto& mul_output = dummy_->NewDummyName();
591 MakeNode(
"Mul", {fma_x_input, w}, {mul_output}, def.name()));
593 const auto& fma_y_output = need_reshape ? dummy_->NewDummyName() : y;
595 MakeNode(
"Add", {mul_output, b}, {fma_y_output}, def.name()));
598 const auto shape = dummy_->NewDummyName();
599 nodes.emplace_back(MakeNode(
"Shape", {x}, {shape}));
600 nodes.emplace_back(MakeNode(
"Reshape", {fma_y_output, shape}, {y}));
606 ConvertedResult OnnxExporter::CreateConvPoolNodes(
607 const caffe2::OperatorDef& def,
608 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
609 auto result = CommonCaffe2OpToOnnxNodes(def);
610 auto& nodes = result.first;
611 auto& node = nodes.back();
613 std::unordered_map<std::string, AttributeProto> attrs;
614 for (
const auto& attr : node.attribute()) {
615 attrs.emplace(attr.name(), attr);
620 if (node.op_type() ==
"MaxPool" || node.op_type() ==
"AveragePool") {
621 auto it = attrs.find(
"global_pooling");
622 if (it != attrs.end() && it->second.has_i() && it->second.i()) {
623 node.set_op_type(
"Global" + node.op_type());
629 ApplyTrans(&attrs, global,
"kernel", 2,
"kernel_shape");
630 ApplyTrans(&attrs, global,
"stride");
631 ApplyTrans(&attrs, global,
"dilation");
632 ApplyTrans(&attrs, global,
"adj");
633 ApplyTrans(&attrs, global,
"pad", 4);
636 auto it = attrs.find(
"legacy_pad");
637 if (it != attrs.end()) {
638 auto legacy_pad_attr = it->second;
641 node.op_type().size() >= 4 &&
642 (node.op_type().rfind(
"Pool") == node.op_type().size() - 4));
643 const auto& input_size = shapes.at(node.input(0));
644 const auto& output_size = shapes.at(node.output(0));
645 CAFFE_ENFORCE_EQ(output_size.dims().size(), 4);
647 legacy_pad_attr.i() !=
static_cast<int64_t
>(caffe2::LegacyPadding::NOTSET)) {
648 if (legacy_pad_attr.i() ==
649 static_cast<int64_t
>(caffe2::LegacyPadding::VALID)) {
650 CAFFE_ENFORCE(!attrs.count(
"pads"));
651 attrs.emplace(
"auto_pad", MakeAttribute(
"auto_pad",
"VALID"));
652 }
else if (legacy_pad_attr.i() ==
653 static_cast<int64_t
>(caffe2::LegacyPadding::SAME)) {
654 CAFFE_ENFORCE(!attrs.count(
"pads"));
657 attrs.emplace(
"auto_pad", MakeAttribute(
"auto_pad",
"SAME_UPPER"));
658 }
else if (legacy_pad_attr.i() ==
659 static_cast<int64_t
>(caffe2::LegacyPadding::CAFFE_LEGACY_POOLING)) {
665 LOG(WARNING) <<
"Converting legacy padding to explicit padding.";
666 auto* pads_attr = attrs.at(
"pads").mutable_ints();
667 auto& strides_attr = attrs.at(
"strides").ints();
668 auto& kernel_shape_attr = attrs.at(
"kernel_shape").ints();
669 for (
int i = 0; i < 2; ++i) {
670 int64_t tmp_pad = output_size.dims(i + 2) * strides_attr.Get(i) -
671 pads_attr->Get(i) - 1 + kernel_shape_attr.Get(i) -
672 input_size.dims(i + 2);
673 pads_attr->Set(i + 2, tmp_pad);
676 LOG(ERROR) <<
"Don't know how to handle the legacy_pad:" << legacy_pad_attr.i();
677 CAFFE_THROW(
"Failed to handle legacy padding in pool operator!");
682 node.clear_attribute();
683 for (
const auto& kv : attrs) {
684 auto* attr = node.add_attribute();
685 attr->CopyFrom(kv.second);
691 ConvertedResult OnnxExporter::CreateLrnNodes(
692 const caffe2::OperatorDef& def,
693 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
694 auto result = CommonCaffe2OpToOnnxNodes(def);
695 auto& nodes = result.first;
697 CAFFE_ENFORCE_EQ(nodes.size(), 1);
698 auto& node = nodes.back();
699 if (node.output_size() == 2) {
700 node.mutable_output()->RemoveLast();
706 ConvertedResult OnnxExporter::CreateConcatNodes(
707 const caffe2::OperatorDef& def,
708 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
709 caffe2::OperatorDef mdef(def);
712 for (
int i = 0; i < mdef.arg_size(); ++i) {
713 const auto& arg = mdef.arg(i);
714 if (arg.name() ==
"add_axis") {
716 ArgumentHelper::RemoveArgument(mdef, i);
721 auto result = CommonCaffe2OpToOnnxNodes(mdef);
722 auto& nodes = result.first;
723 nodes.reserve(nodes.size() + 3);
724 auto& const_tensors = result.second;
726 CAFFE_ENFORCE_EQ(nodes.size(), 1);
727 auto& node = nodes.back();
728 bool explicit_axis =
false;
730 if (ArgumentHelper::HasArgument(mdef,
"axis")) {
731 axis = ArgumentHelper::GetSingleArgument(mdef,
"axis", -1);
732 explicit_axis =
true;
734 if (!explicit_axis) {
735 node.add_attribute()->CopyFrom(MakeAttribute(
"axis", 1));
739 auto final_output = node.output(0);
741 CAFFE_ENFORCE_GE(axis, 0);
742 std::vector<int64_t> dims;
743 const auto& shape0 = shapes.at(mdef.input(0));
744 for (
int i = 1; i < mdef.input_size(); ++i) {
745 const auto& shape = shapes.at(mdef.input(i));
746 CAFFE_ENFORCE_EQ(shape.dims(axis), shape0.dims(axis));
748 for (
const auto d : shape0.dims()) {
751 dims.insert(dims.begin() + axis, mdef.input_size());
753 auto concat_output = dummy_->NewDummyName();
754 *node.mutable_output(0) = concat_output;
755 const_tensors.emplace_back(CreateOnnxShapeTensor(dummy_, dims));
756 nodes.emplace_back(MakeNode(
758 {concat_output, const_tensors.back().name()},
764 if (node.output_size() == 2) {
765 std::string second_output = node.output(1);
766 node.mutable_output()->RemoveLast();
767 std::vector<int32_t> split_info;
768 int adj_size = shapes.at(mdef.input(0)).dims_size() + (add_axis ? 1 : 0);
769 int canonical_axis = canonical_axis_index_(axis, adj_size);
770 CAFFE_ENFORCE_LT(canonical_axis, adj_size,
"Axis not in input ndim range.");
771 for (
int i = 0; i < mdef.input_size(); ++i) {
772 split_info.push_back(
773 add_axis ? 1 : shapes.at(mdef.input(i)).dims(canonical_axis));
775 auto split_info_tensor =
776 MakeTensor(
"split_info", split_info, TensorProto::INT32);
777 auto cnode = MakeNode(
"Constant", {}, {second_output});
778 cnode.add_attribute()->CopyFrom(MakeAttribute(
"value", split_info_tensor));
779 nodes.emplace_back(std::move(cnode));
784 ConvertedResult OnnxExporter::CreateMergeDimNodes(
785 const caffe2::OperatorDef& def,
786 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
787 const auto& x = def.input(0);
788 const auto& y = def.output(0);
790 ConvertedResult result;
791 auto& nodes = result.first;
792 auto& const_tensors = result.second;
795 const auto ndim = shapes.at(x).dims().size();
796 CAFFE_ENFORCE_GE(ndim, 2,
"No enough dims to merge.");
797 std::vector<int64_t> dims(ndim);
800 const_tensors.emplace_back(CreateOnnxShapeTensor(dummy_, dims));
803 const auto reshaped = dummy_->NewDummyName();
804 nodes.emplace_back(MakeNode(
"Reshape",
805 { x, const_tensors.back().name() },
808 nodes.emplace_back(MakeNode(
"Squeeze",
811 std::vector<AttributeProto>{
812 MakeAttribute(
"axes", std::vector<int64_t>{ 0 }),
818 ConvertedResult OnnxExporter::CreateChannelShuffleNodes(
819 const caffe2::OperatorDef& def,
820 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
821 const auto& x = def.input(0);
822 const auto& y = def.output(0);
823 const auto& x_shape = shapes.at(x);
825 x_shape.dims().size(),
827 "Input shape of ChannelShuffle needs to be in NCHW format");
828 auto n = x_shape.dims(0);
829 auto c = x_shape.dims(1);
830 auto h = x_shape.dims(2);
831 auto w = x_shape.dims(3);
833 for (
const auto& arg : def.arg()) {
834 if (arg.name() ==
"group") {
839 CAFFE_ENFORCE(g && c % g == 0);
840 ConvertedResult result;
841 auto& nodes = result.first;
842 auto& const_tensors = result.second;
844 const auto reshape_output = dummy_->NewDummyName();
845 std::vector<int64_t> dims = {n, g, c / g, h, w};
846 const_tensors.emplace_back(CreateOnnxShapeTensor(dummy_, dims));
848 MakeNode(
"Reshape", {x, const_tensors.back().name()}, {reshape_output}));
850 const auto transpose_output = dummy_->NewDummyName();
851 dims = {0, 2, 1, 3, 4};
852 nodes.emplace_back(MakeNode(
856 {MakeAttribute(
"perm", dims)}));
859 const_tensors.emplace_back(CreateOnnxShapeTensor(dummy_, dims));
860 nodes.emplace_back(MakeNode(
861 "Reshape", {transpose_output, const_tensors.back().name()}, {y}));
866 ConvertedResult OnnxExporter::CreateReduceMeanNodes(
867 const caffe2::OperatorDef& def,
868 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
869 CAFFE_ENFORCE_GE(def.input_size(), 1);
870 CAFFE_ENFORCE_LE(def.input_size(), 2);
871 CAFFE_ENFORCE_EQ(def.input_size(), 1,
"Input \"lengths\" is not supported.");
872 CAFFE_ENFORCE_GE(def.output_size(), 1);
873 const auto& x = def.input(0);
874 const auto& y = def.output(0);
875 const auto& dims = shapes.at(x).dims();
877 ConvertedResult result;
878 auto& nodes = result.first;
879 auto& const_tensors = result.second;
880 std::unordered_map<std::string, const caffe2::Argument*> args;
881 for (
const auto& a : def.arg()) {
882 args.emplace(a.name(), &a);
885 std::vector<int64_t> axes;
886 int64_t keepdims = 1;
888 if (def.type() ==
"ReduceMean") {
890 auto it = args.find(
"axes");
891 if (it == args.end()) {
892 axes.resize(dims.size());
893 std::iota(axes.begin(), axes.end(), 0);
895 axes.assign(it->second->ints().begin(), it->second->ints().end());
899 it = args.find(
"keepdims");
900 if (it != args.end()) {
901 keepdims = it->second->i();
905 auto it = args.find(
"num_reduce_dim");
906 const int64_t num_reduce_dim = it == args.end() ? 1 : it->second->i();
907 CAFFE_ENFORCE_LE(num_reduce_dim, dims.size());
908 axes.resize(num_reduce_dim);
910 int64_t start_dim = 0;
911 if (def.type() ==
"ReduceFrontMean") {
913 }
else if (def.type() ==
"ReduceBackMean") {
914 start_dim = dims.size() - axes.size();
916 std::iota(axes.begin(), axes.end(), start_dim);
921 nodes.emplace_back(MakeNode(
"ReduceMean",
925 MakeAttribute(
"axes", axes),
926 MakeAttribute(
"keepdims", keepdims),
933 ConvertedResult OnnxExporter::CreateUpsampleNodes(
934 const caffe2::OperatorDef& def,
935 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
936 ConvertedResult result;
938 auto& nodes = result.first;
939 auto resolved_scale = dummy_->NewDummyName();
940 if (def.input_size() == 1) {
941 float width_scale = 1.0;
942 float height_scale = 1.0;
943 for (
const auto& a : def.arg()) {
944 if (a.name() ==
"width_scale") {
946 }
else if (a.name() ==
"height_scale") {
947 height_scale = a.f();
950 CAFFE_ENFORCE_GT(width_scale, 0);
951 CAFFE_ENFORCE_GT(height_scale, 0);
952 std::vector<float> tmp_vector = {1, 1, height_scale, width_scale};
953 auto resolved_scale_tensor =
954 MakeTensor(
"resolved scale tensor", tmp_vector, TensorProto::FLOAT);
956 auto node = MakeNode(
"Constant", {}, {resolved_scale});
957 node.add_attribute()->CopyFrom(
958 MakeAttribute(
"value", resolved_scale_tensor));
959 nodes.emplace_back(node);
962 CAFFE_ENFORCE_EQ(def.input_size(), 2);
963 std::vector<float> tmp_vector = {1, 1};
964 auto scale_pads_tensor =
965 MakeTensor(
"scale pads", tmp_vector, TensorProto::FLOAT);
966 auto unresolved_scale_pads = dummy_->NewDummyName();
968 auto node = MakeNode(
"Constant", {}, {unresolved_scale_pads});
969 node.add_attribute()->CopyFrom(MakeAttribute(
"value", scale_pads_tensor));
970 nodes.emplace_back(node);
973 "Concat", {unresolved_scale_pads, def.input(1)}, {resolved_scale});
974 node.add_attribute()->CopyFrom(MakeAttribute(
"axis", 0));
975 nodes.emplace_back(node);
977 std::vector<std::string> inputs = {def.input(0), resolved_scale};
978 std::vector<std::string> outputs(def.output().begin(), def.output().end());
979 auto node = MakeNode(
"Upsample", inputs, outputs, def.name());
980 node.add_attribute()->CopyFrom(MakeAttribute(
"mode",
"nearest"));
981 nodes.emplace_back(node);
985 ConvertedResult OnnxExporter::CreateSliceNodes(
986 const caffe2::OperatorDef& def,
987 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
991 "ONNX Slice operator does not support dynamic slice.");
992 auto result = CommonCaffe2OpToOnnxNodes(def);
993 auto& nodes = result.first;
994 CAFFE_ENFORCE_EQ(nodes.size(), 1);
995 auto& node = nodes.back();
996 const auto& shape = shapes.at(node.input(0));
998 std::vector<int64_t> dims;
999 for (
auto& attr : *node.mutable_attribute()) {
1000 if (attr.name() ==
"starts") {
1001 auto len = attr.ints_size();
1004 std::iota(dims.begin(), dims.end(), 0);
1006 }
else if (attr.name() ==
"ends") {
1007 for (
int i = 0; i < attr.ints_size(); ++i) {
1008 auto end = attr.ints(i);
1013 end = shape.dims(i);
1017 attr.set_ints(i, end);
1021 if (!dims.empty()) {
1022 node.add_attribute()->CopyFrom(MakeAttribute(
"axes", dims));
1028 ConvertedResult OnnxExporter::CreateReshapeNodes(
1029 const caffe2::OperatorDef& def,
1030 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
1031 auto result = CommonCaffe2OpToOnnxNodes(def);
1032 auto& nodes = result.first;
1033 auto& const_tensors = result.second;
1034 CAFFE_ENFORCE_EQ(nodes.size(), 1);
1035 auto& node = nodes.back();
1038 int attr_size = node.attribute_size();
1039 for (; i < attr_size; ++i) {
1040 const auto& attr = node.attribute(i);
1041 if (attr.name() ==
"shape") {
1042 std::vector<int64_t> shape;
1043 for (
const auto k : attr.ints()) {
1046 const_tensors.emplace_back(CreateOnnxShapeTensor(dummy_, shape));
1047 node.add_input(const_tensors.back().name());
1051 if (i != attr_size) {
1052 if (i != attr_size - 1) {
1053 node.mutable_attribute()->SwapElements(i, attr_size - 1);
1055 node.mutable_attribute()->RemoveLast();
1058 if (node.output_size() == 2) {
1059 std::string shape_input = node.output(0);
1060 std::string shape_output = node.output(1);
1061 node.mutable_output()->RemoveLast();
1062 nodes.emplace_back(AddShapeNode(shape_input, shape_output));
1068 ConvertedResult OnnxExporter::CreateGemmNodes(
1069 const caffe2::OperatorDef& def,
1070 const std::unordered_map<std::string, caffe2::TensorShape>& shapes) {
1071 CAFFE_ENFORCE_EQ(def.input_size(), 3);
1072 CAFFE_ENFORCE_GE(def.output_size(), 1);
1073 auto x = def.input(0);
1074 auto w = def.input(1);
1075 const auto& b = def.input(2);
1076 const auto& y = def.output(0);
1077 const auto& x_shape = shapes.at(x);
1078 const auto& w_shape = shapes.at(w);
1079 CAFFE_ENFORCE_GE(x_shape.dims().size(), 2);
1080 CAFFE_ENFORCE_GE(w_shape.dims().size(), 2);
1082 ConvertedResult result;
1083 auto& nodes = result.first;
1084 auto& const_tensors = result.second;
1085 std::unordered_map<std::string, const caffe2::Argument*> args;
1086 for (
const auto& a : def.arg()) {
1087 args.emplace(a.name(), &a);
1090 auto it = args.find(
"axis");
1092 bool has_axis = (it != args.end());
1094 axis = it->second->i();
1097 auto gemm_x_input = x;
1098 if (x_shape.dims().size() > 2) {
1100 const auto inner = DimProd(x_shape, axis, x_shape.dims().size());
1102 gemm_x_input = dummy_->NewDummyName();
1103 const_tensors.emplace_back(CreateOnnxShapeTensor(dummy_,
1104 std::vector<int64_t>{ -1, inner }));
1105 nodes.emplace_back(MakeNode(
"Reshape",
1106 { x, const_tensors.back().name() },
1110 it = args.find(
"axis_w");
1112 if (it != args.end()) {
1113 axis_w = it->second->i();
1115 if (w_shape.dims().size() > 2) {
1117 auto outer = DimProd(w_shape, 0, axis_w);
1118 auto inner = DimProd(w_shape, axis_w, w_shape.dims().size());
1119 auto reshaped_w = dummy_->NewDummyName();
1120 const_tensors.emplace_back(CreateOnnxShapeTensor(dummy_,
1121 std::vector<int64_t>{ outer, inner }));
1122 nodes.emplace_back(MakeNode(
"Reshape",
1123 { w, const_tensors.back().name() },
1128 auto gemm_y_output = axis > 1 ? dummy_->NewDummyName() : y;
1129 nodes.emplace_back(MakeNode(
"Gemm",
1130 { gemm_x_input, w, b },
1132 { MakeAttribute(
"transB", 1L) },
1137 const auto x_shape = dummy_->NewDummyName();
1138 nodes.emplace_back(MakeNode(
"Shape", {x}, {x_shape}));
1140 const auto x_shape_outer = dummy_->NewDummyName();
1141 nodes.emplace_back(MakeNode(
"Slice",
1144 std::vector<AttributeProto>{
1145 MakeAttribute(
"starts", std::vector<int64_t>{ 0 }),
1146 MakeAttribute(
"ends", std::vector<int64_t>{ axis }),
1149 const auto y_shape = dummy_->NewDummyName();
1150 const_tensors.emplace_back(CreateOnnxShapeTensor(dummy_, { -1 }));
1151 nodes.emplace_back(MakeNode(
"Concat",
1152 { x_shape_outer, const_tensors.back().name() },
1154 std::vector<AttributeProto>{
1155 MakeAttribute(
"axis", static_cast<int64_t>(0)),
1158 nodes.emplace_back(MakeNode(
"Reshape",
1159 { gemm_y_output, y_shape },
1166 void OnnxExporter::InitOpToTensorProto(
1167 const caffe2::OperatorDef& op,
1168 TensorProto* tensor) {
1169 CAFFE_ENFORCE_EQ(op.input_size(), 0);
1170 CAFFE_ENFORCE_EQ(op.output_size(), 1);
1173 tensor->set_name(op.output(0));
1175 const Argument* values =
nullptr;
1176 const Argument* shape =
nullptr;
1177 for (
const auto& arg: op.arg()) {
1178 if (arg.name() ==
"values") {
1180 }
else if (arg.name() ==
"shape") {
1185 CAFFE_ENFORCE(values);
1186 CAFFE_ENFORCE(shape);
1189 for (
const auto i: shape->ints()) {
1190 tensor->add_dims(i);
1194 if (op.type() ==
"GivenTensorFill") {
1195 tensor->set_data_type(TensorProto::FLOAT);
1196 for (
const auto i : values->floats()) {
1197 tensor->add_float_data(i);
1199 }
else if (op.type() ==
"GivenTensorInt64Fill") {
1200 tensor->set_data_type(TensorProto::INT64);
1201 for (
const auto i : values->ints()) {
1202 tensor->add_int64_data(i);
1204 }
else if (op.type() ==
"GivenTensorIntFill") {
1205 tensor->set_data_type(TensorProto::INT32);
1206 for (
const auto i : values->ints()) {
1207 tensor->add_int32_data(i);
1209 }
else if (op.type() ==
"GivenTensorBoolFill") {
1210 tensor->set_data_type(TensorProto::INT32);
1211 for (
const auto i : values->ints()) {
1212 tensor->add_int32_data(i);
1214 }
else if (op.type() ==
"GivenTensorStringFill") {
1215 tensor->set_data_type(TensorProto::STRING);
1217 for (
const auto& s : values->strings()) {
1218 tensor->mutable_raw_data()->append(s);
1222 c10::str(
"Cannot convert C2 op ", op.type(),
"to ONNX TensorProto"));
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...