Add utility functions for processing Caffe layer options (#637)
authorDmitry Mozolev/AI Tools Lab /SRR/Engineer/삼성전자 <d.mozolev@samsung.com>
Sun, 15 Jul 2018 06:33:30 +0000 (09:33 +0300)
committerSergey Vostokov/AI Tools Lab /SRR/Staff Engineer/삼성전자 <s.vostokov@samsung.com>
Sun, 15 Jul 2018 06:33:30 +0000 (15:33 +0900)
Add Caffe layer to Model IR converter skeleton
Added a class for converting Caffe layers to Model IR operations.

Signed-off-by: Dmitry Mozolev <d.mozolev@samsung.com>
contrib/nnc/libs/frontend/caffe/src/caffe_op_creator.cpp

index a063b68..35f7556 100644 (file)
@@ -21,6 +21,242 @@ namespace frontend
 namespace caffe
 {
 
+namespace util
+{
+
+template <typename OptsType>
+static inline bool has2DStride(const OptsType& opts)
+{
+  if (opts.has_stride_h() != opts.has_stride_w())
+  {
+    throw PluginException("Conv or Pool layer has only 1 out of 2 2D strides, investigate");
+  }
+  // We already checked that both 2D strides are both present or both are not
+  return opts.has_stride_h();
+}
+
+static inline Shape getStrideFromOneValue(bool hasStride, unsigned int stride)
+{
+  if (hasStride)
+  {
+    return Shape{stride, stride, 1};
+  }
+  else
+  {
+    return Shape{1, 1, 1};
+  }
+}
+
+static inline Shape getStrideFromTwoValues(unsigned int stride1, unsigned int stride2)
+{
+  return Shape{stride1, stride2, 1};
+}
+
+template <typename OptsType>
+static inline Shape getStride(const OptsType& opts)
+{
+  // stride might have size 0, then it defaults to 1
+  if (opts.stride_size() == 0)
+  {
+    return Shape{1, 1, 1};
+  }
+  else if (opts.stride_size() == 1)
+  {
+    return getStrideFromOneValue(true, opts.stride(0));
+  }
+  else
+  {
+    return getStrideFromTwoValues(opts.stride(0), opts.stride(1));
+  }
+}
+
+template <typename OptsType>
+static inline bool has2DPad(const OptsType& opts)
+{
+  return opts.has_pad_h() || opts.has_pad_w();
+}
+
+static ops::PaddingType getPadTypeFromOneValue(bool hasPad, unsigned int pad)
+{
+  if (hasPad)
+  {
+    if (pad == 0)
+    {
+      return ops::PaddingType::Valid;
+    }
+    else
+    {
+      return ops::PaddingType::Same;
+    }
+  }
+  else
+  {
+    return ops::PaddingType::Valid;
+  }
+}
+
+static ops::PaddingType getPadTypeFromTwoValues(unsigned int pad1, unsigned int pad2)
+{
+  bool areBothPadsZero = pad1 == 0 && pad2 == 0;
+  bool doPadsDiffer = pad1 != pad2;
+  if (areBothPadsZero)
+  {
+    return ops::PaddingType::Valid;
+  }
+  else if (doPadsDiffer)
+  {
+    std::string pads = std::to_string(pad1) + ", " + std::to_string(pad2);
+    throw PluginException(std::string("Unsupported pads: ") + pads);
+  }
+  else
+  {
+    return ops::PaddingType::Same;
+  }
+}
+
+template <typename OptsType>
+static inline ops::PaddingType getPadTypeFromTwoValues(const OptsType &opts)
+{
+  unsigned int pad1, pad2;
+  pad1 = opts.has_pad_h() ? opts.pad_h() : 0;
+  pad2 = opts.has_pad_w() ? opts.pad_w() : 0;
+
+  return getPadTypeFromTwoValues(pad1, pad2);
+}
+
+template <typename OptsType>
+static ops::PaddingType getPadType(const OptsType &opts)
+{
+  if (opts.pad_size() == 0)
+  {
+    return ops::PaddingType::Valid;
+  }
+  else if (opts.pad_size() == 1)
+  {
+    return getPadTypeFromOneValue(true, opts.pad(0));
+  }
+  else
+  {
+    return getPadTypeFromTwoValues(opts.pad(0), opts.pad(1));
+  }
+}
+
+/**
+ * @brief Tries to determine whether padding is SAME or VALID given
+ * numeric pad values from Caffe layer options. Only 2D convolutions/pools
+ * are supported currently.
+ * @todo Currently, pad_h and pad_w options take precedence if they are present,
+ * but maybe it is not correct logic. Check how it really is done.
+ * @todo Specific calculations are required to check that padding is indeed SAME,
+ * currently we just return SAME if padding is non-zero.
+ */
+__attribute__ ((unused)) static ops::PaddingType getConvPadType(const ConvolutionParameter& opts)
+{
+  if (has2DPad(opts))
+    return getPadTypeFromTwoValues(opts);
+  else
+    return getPadType(opts);
+}
+
+__attribute__ ((unused)) static ops::PaddingType getPoolPadType(const PoolingParameter& opts)
+{
+  if (has2DPad(opts))
+    return getPadTypeFromTwoValues(opts);
+  else
+    return getPadTypeFromOneValue(opts.has_pad(), opts.pad());
+}
+
+/**
+ * @brief Determines stride values from Caffe layer options.
+ * Only 2D convolutions/pools are supported currently.
+ * @todo Currently, stride_h and stride_w options take precedence if they are present,
+ * but maybe it is not correct logic. Check how it really is done.
+ */
+__attribute__ ((unused)) static Shape getConvStride(const ConvolutionParameter& opts)
+{
+  if (has2DStride(opts))
+    return getStrideFromTwoValues(opts.stride_h(), opts.stride_w());
+  else
+    return getStride(opts);
+}
+
+__attribute__ ((unused)) static Shape getPoolStride(const PoolingParameter& opts)
+{
+  if (has2DStride(opts))
+    return getStrideFromTwoValues(opts.stride_h(), opts.stride_w());
+  else
+    return getStrideFromOneValue(opts.has_stride(), opts.stride());
+}
+
+__attribute__ ((unused)) static Shape getPoolWindowShape(const PoolingParameter &opts)
+{
+  if (opts.has_kernel_h() != opts.has_kernel_w())
+  {
+    throw PluginException("Pool layer has only 1 out of 2 kernel dimensions, investigate");
+  }
+
+  if (opts.has_kernel_h())
+  {
+    return Shape{opts.kernel_h(), opts.kernel_w(), 1};
+  }
+  else if (opts.has_kernel_size())
+  {
+    return Shape{opts.kernel_size(), opts.kernel_size(), 1};
+  }
+  else
+  {
+    throw PluginException("Pooling layer doesn't have kernel size data, investigate");
+  }
+}
+
+__attribute__ ((unused)) static ops::PoolOp::PoolingType getPoolingType(const PoolingParameter& opts)
+{
+  using PoolingType = ops::PoolOp::PoolingType;
+
+  if (opts.pool() == PoolingParameter::MAX)
+    return PoolingType::MAX;
+  else if (opts.pool() == PoolingParameter::AVE)
+    return PoolingType::AVG;
+  else
+    throw PluginException("Unsupported pooling type: " +
+                          PoolingParameter::PoolMethod_Name(opts.pool()));
+}
+
+/**
+ * @brief Determines correct value for Caffe Softmax/Concat axis parameter.
+ * @todo Change cout to a log library call.
+ * @todo Decide how to process negative axis values.
+ * @todo Decide how to process axis in general.
+ */
+template <typename OptsType>
+__attribute__ ((unused)) static int getAxisValue(const OptsType& opts)
+{
+  int axis = 2;
+  if (opts.has_axis())
+  {
+    axis = opts.axis();
+    if (axis == 0)
+    {
+      std::cout << "WARNING: axis parameter equals 0. It is normal,"
+                   "but implies that the model might not have a batch dimension,"
+                   "so make sure import works correctly." << std::endl;
+    }
+    else if (axis < 0)
+    {
+      throw PluginException("Softmax/Concat layer negative axis values are not supported yet.");
+    }
+    else if (axis != 1)
+    {
+      throw PluginException("Softmax/Concat layer axis param is not 1, which implies"
+                            "unsupported NN architecture.");
+    }
+  }
+
+  return axis;
+}
+
+} // namespace util
+
 std::vector<INode::Ref> OpCreator::createConv2D(InputOps inputs, InputParams params,
                                                 const caffe::ConvolutionParameter& opts)
 {