[nnc] Make BiasAddOp and ScaleOp treat the second tensor as ordinary argument (#2848)
authorСергей Баранников/AI Tools Lab /SRR/Engineer/삼성전자 <s.barannikov@samsung.com>
Tue, 15 Jan 2019 16:16:07 +0000 (19:16 +0300)
committerEfimov Alexander/AI Tools Lab/./Samsung Electronics <a.efimov@samsung.com>
Tue, 15 Jan 2019 16:16:07 +0000 (19:16 +0300)
* Change the signatures of BiasAddOp and ScaleOp to identically handle both input parameters.
* Refactor uses of BiasAddOp and ScaleOp.

Signed-off-by: Sergei Barannikov <s.barannikov@samsung.com>
21 files changed:
contrib/nnc/core/modelIR/IrDotDumper.cpp
contrib/nnc/include/core/modelIR/operations/BiasAddOp.h
contrib/nnc/include/core/modelIR/operations/ScaleOp.h
contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.cpp
contrib/nnc/passes/caffe2_frontend/caffe2_op_creator.cpp
contrib/nnc/passes/caffe_frontend/caffe_op_creator.cpp
contrib/nnc/passes/interpreter/Interpreter.cpp
contrib/nnc/passes/interpreter/ops/Bias.cpp
contrib/nnc/passes/interpreter/ops/Bias.h
contrib/nnc/passes/interpreter/ops/Scale.cpp
contrib/nnc/passes/interpreter/ops/Scale.h
contrib/nnc/passes/onnx_frontend/ONNXOpCreator.cpp
contrib/nnc/passes/soft_backend/SBSerializer.cpp
contrib/nnc/passes/soft_backend/code_snippets/cpp_operations.def
contrib/nnc/passes/soft_backend/code_snippets/cpp_scale.def
contrib/nnc/passes/tflite_frontend/tflite_op_creator.cpp
contrib/nnc/tests/interpreter/gen/gen_test_data.py
contrib/nnc/tests/interpreter/graph_creator.cpp
contrib/nnc/tests/interpreter/test_data/test_description.txt
contrib/nnc/unittests/acl_backend/MIRToDOM.cpp
contrib/nnc/unittests/soft_backend/CPPOperations.cpp

index 4ab778f..5097718 100644 (file)
@@ -72,8 +72,7 @@ static std::vector<Shape> getOutputShapes(const Operation& op) {
 void IrDotDumper::visit(ops::BiasAddOp &op) {
   auto nodeInfo = DotIrNodeInfo().withType("BiasAdd", op.getName())
                                  .withInShapes(getInputShapes(op))
-                                 .withOutShapes(getOutputShapes(op))
-                                 .withKernelShape(op.getWeights().getShape());
+                                 .withOutShapes(getOutputShapes(op));
 
   dotBuilder.updateWithOp(&op, nodeInfo);
 }
@@ -204,8 +203,7 @@ void IrDotDumper::visit(ops::BatchNormOp& op) {
 void IrDotDumper::visit(ops::ScaleOp& op) {
   auto nodeInfo = DotIrNodeInfo().withType("ScaleOp", op.getName())
                                  .withInShapes(getInputShapes(op))
-                                 .withOutShapes(getOutputShapes(op))
-                                 .withShape("Scale Tensor", op.getWeights().getShape());
+                                 .withOutShapes(getOutputShapes(op));
   dotBuilder.updateWithOp(&op, nodeInfo);
 }
 
index 7c16d34..3cbfaab 100644 (file)
@@ -26,16 +26,11 @@ namespace ops {
 
 class BiasAddOp : public Operation {
 public:
-  BiasAddOp(const IODescriptor& arg, const TensorVariant& weights)
-    : Operation(Type::biasAdd, {arg}), _weights(weights) {
+  BiasAddOp(const IODescriptor& arg1, const IODescriptor& arg2)
+    : Operation(Type::biasAdd, {arg1, arg2}) {
     // Infer output shape.
     setOutputShape(0, getInputShape(0));
   }
-
-  const TensorVariant& getWeights() const { return _weights; }
-
-private:
-  TensorVariant _weights;
 };
 
 } // namespace ops
index 0fd9ca2..c8a9743 100644 (file)
@@ -25,19 +25,11 @@ namespace ops {
 
 class ScaleOp : public Operation {
 public:
-  ScaleOp(const IODescriptor& arg, const TensorVariant& weights)
-      : Operation(Type::scale, {arg}), _weights(weights) {
+  ScaleOp(const IODescriptor& arg1, const IODescriptor& arg2)
+      : Operation(Type::scale, {arg1, arg2}) {
     // Infer output shape.
     setOutputShape(0, getInputShape(0));
   }
-
-  /**
-   * @return The input 1-dimensional scale tensor.
-   */
-  const TensorVariant& getWeights() const { return _weights; }
-
-private:
-  TensorVariant _weights;
 };
 
 } // namespace ops
index 9a3a774..5a79e93 100644 (file)
@@ -374,12 +374,15 @@ void AclCppOpGenerator::visit(ops::CappedReluOp& op) {
 }
 
 void AclCppOpGenerator::visit(ops::BiasAddOp& op) {
-  const auto& ir_biases = op.getWeights();
-  assert(ir_biases.getShape().rank() == 1);
-
   auto& prev_nodes = op.getPrevNodes();
-  assert(prev_nodes.size() == 1);
+  assert(prev_nodes.size() == 2);
   auto in_op = prev_nodes[0].op;
+  auto ir_biases_op = dynamic_cast<ops::ConstantOp*>(prev_nodes[1].op);
+  if (ir_biases_op == nullptr)
+    throw AclCppException("Unsupported operation type");
+
+  const auto& ir_biases = ir_biases_op->getValue();
+  assert(ir_biases.getShape().rank() == 1);
 
   // Get the input node tensor id in the DOM.
   shared_ptr<ArtifactId> input = AF::id(tensorName(in_op));
@@ -508,8 +511,14 @@ void AclCppOpGenerator::visit(ops::ScaleOp& op) {
   // May be not a perfect implementation, using the CLPixelWiseMultiplication ACL function taking
   // two input tensors with the same shapes.
   auto prev_nodes = op.getPrevNodes();
-  assert(prev_nodes.size() == 1);
+  assert(prev_nodes.size() == 2);
   auto in_op = prev_nodes[0].op;
+  auto ir_scales_op = dynamic_cast<ops::ConstantOp*>(prev_nodes[1].op);
+  if (ir_scales_op == nullptr)
+    throw AclCppException("Unsupported operation type");
+
+  const auto& ir_scales = ir_scales_op->getValue();
+  assert(ir_scales.getShape().rank() == 1);
 
   // Get input tensor identifier in the generated artifact.
   auto input = AF::id(tensorName(in_op));
@@ -542,8 +551,6 @@ void AclCppOpGenerator::visit(ops::ScaleOp& op) {
 
   auto operation_name = transposed_output->name() + "_scale_layer";
 
-  const auto& ir_scales = op.getWeights();
-
   // Reshape the IR scales tensor and generate the corresponding DOM tensor.
   const Shape ir_input_shape = transposeShape<0, 3, 1, 2>(op.getInputShape(0));
   Shape ir_scales_shape(ir_input_shape.rank());
@@ -908,7 +915,7 @@ shared_ptr<ArtifactId> AclCppOpGenerator::genTensor(const string& name,
 }
 
 shared_ptr<ArtifactId> AclCppOpGenerator::genTensor(Operation& op, const Shape& ir_shape) {
-  if (op.getPrevNodes().empty())
+  if (op.getType() == Operation::Type::variable)
     _inputs.insert(&op);
 
   if (op.getNextNodes().empty())
index eb54491..690d21b 100644 (file)
@@ -307,26 +307,27 @@ std::vector<IODescriptor> Caffe2OpCreator::convertConv(const std::vector<IODescr
   int num_groups = getSingleArgument(op, "group", 1);
   bool is_depthwise = (num_groups != 1) && (in_group_size == 1) && (out_channels == num_groups);
 
-  mir::Operation* conv2d;
+  mir::Operation* result;
   if (is_depthwise) {
     // TODO handle properly kernel with layer multiplier
     auto transposed_tensor = mir::transposeTensor<0, 1, 3, 2>(kernel_tensor);
-    conv2d = createOp<ops::DepthwiseConv2DOp>(convertCaffeToMIR(inputs[0]), transposed_tensor,
+    result = createOp<ops::DepthwiseConv2DOp>(convertCaffeToMIR(inputs[0]), transposed_tensor,
                                               stride_shape, pad_before, pad_after);
   } else {
     // first we need to convert kernel of grouped convolution to appropriate ordinary kernel
     if (num_groups != 1)
       kernel_tensor = fixGroupedKernel(num_groups, kernel_tensor);
 
-    conv2d = createOp<ops::Conv2DOp>(convertCaffeToMIR(inputs[0]), kernel_tensor,
+    result = createOp<ops::Conv2DOp>(convertCaffeToMIR(inputs[0]), kernel_tensor,
                                      stride_shape, pad_before, pad_after);
   }
 
   if (op.input_size() > 2) {  // Bias is optional
-    auto bias_add = createOp<ops::BiasAddOp>(conv2d->getOutput(0), mir_tensors.at(op.input(2)));
-    return {convertMIRToCaffe(bias_add->getOutput(0))};
+    auto bias = createOp<ops::ConstantOp>(mir_tensors.at(op.input(2)))->getOutput(0);
+    result = createOp<ops::BiasAddOp>(result->getOutput(0), bias);
   }
-  return {convertMIRToCaffe(conv2d->getOutput(0))};
+
+  return {convertMIRToCaffe(result->getOutput(0))};
 }
 
 std::vector<IODescriptor> Caffe2OpCreator::convertConcat(const std::vector<IODescriptor>& inputs,
@@ -357,10 +358,11 @@ Caffe2OpCreator::convertFullyConnected(const std::vector<IODescriptor>& inputs,
   // Transform input into 2-D tensor by flattening axes
   Shape shape{input_shape.dim(0), input_shape.numElements() / input_shape.dim(0)};
   auto reshape = createOp<ops::ReshapeOp>(inputs[0], shape);
-  auto fully_connected = createOp<ops::FullyConnectedOp>(reshape->getOutput(0), weights_tensor);
+  auto result = createOp<ops::FullyConnectedOp>(reshape->getOutput(0), weights_tensor);
 
-  auto bias = createOp<ops::BiasAddOp>(fully_connected->getOutput(0), mir_tensors.at(op.input(2)));
-  return {bias->getOutput(0)};
+  auto bias = createOp<ops::ConstantOp>(mir_tensors.at(op.input(2)))->getOutput(0);
+  result = createOp<ops::BiasAddOp>(result->getOutput(0), bias);
+  return {result->getOutput(0)};
 }
 
 std::vector<IODescriptor> Caffe2OpCreator::convertMaxPool(const std::vector<IODescriptor>& inputs,
@@ -386,15 +388,11 @@ Caffe2OpCreator::convertMul(const std::vector<mir::IODescriptor>& inputs,
                             const ::caffe2::OperatorDef& op,
                             const MIRTensors& mir_tensors) {
   const auto& input_shape = inputs[0].op->getOutputShape(inputs[0].index);
-  const auto& multiplier = mir_tensors.at(op.input(1));
-
-  assert(multiplier.getShape().rank() == 1 && "Only 1-rank multiplier is supported");
-  assert(multiplier.getShape().numElements() == input_shape.dim(1)
-         && "Only multiplier size equal to number of input channels is supported");
 
   // TODO: replace with elementwise op, when broadcating will be added in elementwise op
-  auto mul = createOp<ops::ScaleOp>(convertCaffeToMIR(inputs[0]), multiplier);
-  return {convertMIRToCaffe(mul->getOutput(0))};
+  auto multiplier = createOp<ops::ConstantOp>(mir_tensors.at(op.input(1)))->getOutput(0);
+  auto result = createOp<ops::ScaleOp>(convertCaffeToMIR(inputs[0]), multiplier);
+  return {convertMIRToCaffe(result->getOutput(0))};
 }
 
 std::vector<IODescriptor>
@@ -422,28 +420,31 @@ Caffe2OpCreator::convertSpatialBN(const std::vector<mir::IODescriptor>& inputs,
                                   const MIRTensors& mir_tensors) {
   // overall_res = (X - mean) / sqrt(var + epsilon) * scale + bias
 
-  const auto& scale = mir_tensors.at(op.input(1));
-  const auto& bias = mir_tensors.at(op.input(2));
-  const auto& mean = mir_tensors.at(op.input(3));
-  const auto& var = mir_tensors.at(op.input(4));
+  const auto& scale_tensor = mir_tensors.at(op.input(1));
+  const auto& bias_tensor = mir_tensors.at(op.input(2));
+  const auto& mean_tensor = mir_tensors.at(op.input(3));
+  const auto& var_tensor = mir_tensors.at(op.input(4));
   float eps = getSingleArgument(op, "epsilon", 1e-5f);
 
   // res1 = X - mean
-  Tensor<float> bias_data(mean);
+  Tensor<float> bias_data(mean_tensor);
   for (auto& idx: ShapeRange(bias_data.getShape()))
     bias_data.at(idx) *= -1;
-  auto bias_add_1 = createOp<ops::BiasAddOp>(convertCaffeToMIR(inputs[0]), mean);
+  auto mean = createOp<ops::ConstantOp>(mean_tensor)->getOutput(0);
+  auto result = createOp<ops::BiasAddOp>(convertCaffeToMIR(inputs[0]), mean);
 
   // res2 = res1 * scale / (var + epsilon)
-  Tensor<float> multiplier(scale);
-  for (auto& idx: ShapeRange(scale.getShape()))
-    multiplier.at(idx) /= std::sqrt(*(float*) var.at(idx) + eps);
-  auto scale_op = createOp<ops::ScaleOp>(bias_add_1->getOutput(0), scale);
+  Tensor<float> multiplier(scale_tensor);
+  for (auto& idx: ShapeRange(scale_tensor.getShape()))
+    multiplier.at(idx) /= std::sqrt(*(float*) var_tensor.at(idx) + eps);
+  auto scale = createOp<ops::ConstantOp>(scale_tensor)->getOutput(0);
+  result = createOp<ops::ScaleOp>(result->getOutput(0), scale);
 
   // overall_res = res2 + bias
-  auto bias_add_2 = createOp<ops::BiasAddOp>(scale_op->getOutput(0), bias);
+  auto bias = createOp<ops::ConstantOp>(bias_tensor)->getOutput(0);
+  result = createOp<ops::BiasAddOp>(result->getOutput(0), bias);
 
-  return {convertMIRToCaffe(bias_add_2->getOutput(0))};
+  return {convertMIRToCaffe(result->getOutput(0))};
 }
 
 std::vector<IODescriptor> Caffe2OpCreator::convertSum(const std::vector<IODescriptor>& inputs) {
index 9fe6aa1..d681d06 100644 (file)
@@ -267,8 +267,8 @@ CaffeOpCreator::convertConvolution(const caffe::LayerParameter& layer,
 
   // Add the bias, if any.
   if (params.bias_term()) {
-    auto bias_weights = convertBlob(layer.blobs(1));
-    result = createOp<ops::BiasAddOp>(layer.name() + ".bias", result->getOutput(0), bias_weights);
+    auto bias = createOp<ops::ConstantOp>("", convertBlob(layer.blobs(1)))->getOutput(0);
+    result = createOp<ops::BiasAddOp>(layer.name() + ".bias", result->getOutput(0), bias);
   }
 
   return {convertMIRToCaffe(result->getOutput(0))};
@@ -290,18 +290,16 @@ CaffeOpCreator::convertDeconvolution(const caffe::LayerParameter& layer,
     // first we need to convert kernel of grouped convolution to appropriate ordinary kernel
     kernel_weights = fixGroupedKernel(opts.group(), kernel_weights);
   }
-  auto deconv2d = createOp<ops::DeConv2DOp>(layer.name(), convertCaffeToMIR(inputs[0]),
+  auto result = createOp<ops::DeConv2DOp>(layer.name(), convertCaffeToMIR(inputs[0]),
                                             kernel_weights, strides, padding);
 
   // bias_term is optional (so might not be present) and defaults to true
-  if (!opts.has_bias_term() || opts.bias_term()) {
-    auto bias_weights = convertBlob(layer.blobs(1));
-    auto bias_add = createOp<ops::BiasAddOp>(layer.name() + ".bias", deconv2d->getOutput(0),
-                                             bias_weights);
-    return {convertMIRToCaffe(bias_add->getOutput(0))};
-  } else {
-    return {convertMIRToCaffe(deconv2d->getOutput(0))};
+  if (opts.bias_term()) {
+    auto bias = createOp<ops::ConstantOp>("", convertBlob(layer.blobs(1)))->getOutput(0);
+    result = createOp<ops::BiasAddOp>(layer.name() + ".bias", result->getOutput(0), bias);
   }
+
+  return {convertMIRToCaffe(result->getOutput(0))};
 }
 
 std::vector<mir::IODescriptor>
@@ -317,8 +315,8 @@ CaffeOpCreator::convertInnerProduct(const LayerParameter& layer,
 
   // Add the bias, if any.
   if (params.bias_term()) {
-    const auto& bias_weights = convertBlob(layer.blobs(1));
-    result = createOp<ops::BiasAddOp>(layer.name() + ".bias", result, bias_weights)->getOutput(0);
+    auto bias = createOp<ops::ConstantOp>("", convertBlob(layer.blobs(1)))->getOutput(0);
+    result = createOp<ops::BiasAddOp>(layer.name() + ".bias", result, bias)->getOutput(0);
   }
 
   return {result};
@@ -490,13 +488,13 @@ std::vector<mir::IODescriptor>
 CaffeOpCreator::convertScale(const caffe::LayerParameter& layer,
                              const std::vector<mir::IODescriptor>& inputs) {
   const auto& params = layer.scale_param();
-  auto weights = convertBlob(layer.blobs(0));
-  auto result = createOp<ops::ScaleOp>(layer.name(), convertCaffeToMIR(inputs[0]), weights);
+  auto scale = createOp<ops::ConstantOp>("", convertBlob(layer.blobs(0)))->getOutput(0);
+  auto result = createOp<ops::ScaleOp>(layer.name(), convertCaffeToMIR(inputs[0]), scale);
 
   // Add the bias, if any.
   if (params.bias_term()) {
-    auto bias_weights = convertBlob(layer.blobs(1));
-    result = createOp<ops::BiasAddOp>(layer.name() + ".bias", result->getOutput(0), bias_weights);
+    auto bias = createOp<ops::ConstantOp>("", convertBlob(layer.blobs(1)))->getOutput(0);
+    result = createOp<ops::BiasAddOp>(layer.name() + ".bias", result->getOutput(0), bias);
   }
 
   return {convertMIRToCaffe(result->getOutput(0))};
@@ -531,8 +529,9 @@ CaffeOpCreator::convertBatchNorm(const caffe::LayerParameter& layer,
   Tensor<float> bias_data(mean_weights);
   for (Index idx : ShapeRange(bias_data.getShape()))
     bias_data.at(idx) *= -scale_factor;
-  auto bias_add = createOp<ops::BiasAddOp>(layer.name() + ".bias", convertCaffeToMIR(inputs[0]),
-                                           mean_weights);
+  auto mean = createOp<ops::ConstantOp>("", mean_weights)->getOutput(0);
+  auto result = createOp<ops::BiasAddOp>(layer.name() + ".bias", convertCaffeToMIR(inputs[0]),
+                                         mean);
 
   // create scale argument from variance:
   // multiply elements of variance by scaleFactor and
@@ -541,9 +540,9 @@ CaffeOpCreator::convertBatchNorm(const caffe::LayerParameter& layer,
   Tensor<float> scale_data(variance_weights);
   for (Index idx : ShapeRange(scale_data.getShape()))
     scale_data.at(idx) = 1.0f / std::sqrt(scale_data.at(idx) * scale_factor + eps);
-  auto scale = createOp<ops::ScaleOp>(layer.name() + ".scale", bias_add->getOutput(0),
-                                      variance_weights);
-  return {convertMIRToCaffe(scale->getOutput(0))};
+  auto variance = createOp<ops::ConstantOp>("", variance_weights)->getOutput(0);
+  result = createOp<ops::ScaleOp>(layer.name() + ".scale", result->getOutput(0), variance);
+  return {convertMIRToCaffe(result->getOutput(0))};
 }
 
 std::vector<mir::IODescriptor>
@@ -571,8 +570,8 @@ CaffeOpCreator::convertEmbed(const caffe::LayerParameter& layer,
 
   // Add the bias, if any.
   if (params.bias_term()) {
-    auto bias_weights = convertBlob(layer.blobs(1));
-    result = createOp<ops::BiasAddOp>(layer.name() + ".bias", result->getOutput(0), bias_weights);
+    auto bias = createOp<ops::ConstantOp>("", convertBlob(layer.blobs(1)))->getOutput(0);
+    result = createOp<ops::BiasAddOp>(layer.name() + ".bias", result->getOutput(0), bias);
   }
 
   return {result->getOutput(0)};
@@ -708,7 +707,7 @@ CaffeOpCreator::convertLSTM(const caffe::LayerParameter& layer,
 
   // Learned parameters of the layer. Tensors are transposed to match the ModelIR.
   const auto& xw = transposeTensor<1, 0>(convertBlob(layer.blobs(0)));
-  const auto& xb = convertBlob(layer.blobs(1));
+  auto xb = createOp<ops::ConstantOp>("", convertBlob(layer.blobs(1)))->getOutput(0);
   const auto& hw = transposeTensor<1, 0>(convertBlob(layer.blobs(2)));
 
   // Add a dummy dimension so that element-wise operations perform properly.
index c6cd178..d4b74bc 100644 (file)
@@ -238,9 +238,11 @@ void NNInterpreter::visit(ops::DepthwiseConv2DOp& op){
 }
 
 void NNInterpreter::visit(ops::BiasAddOp& op) {
-  auto operand = op.getPrevNodes()[0];
-  auto input = var(operand.op->getId())[operand.index];
-  var(op.getId()) = BiasAdd(input, op.getWeights(), op.getOutputShape(0))();
+  auto operand1 = op.getPrevNodes()[0];
+  auto operand2 = op.getPrevNodes()[1];
+  auto input1 = var(operand1.op->getId())[operand1.index];
+  auto input2 = var(operand2.op->getId())[operand2.index];
+  var(op.getId()) = BiasAdd(input1, input2)();
   DUMP(op, false);
 }
 
@@ -253,10 +255,11 @@ void NNInterpreter::visit(ops::BatchNormOp& op) {
 }
 
 void NNInterpreter::visit(ops::ScaleOp& op) {
-  auto operand = op.getPrevNodes()[0];
-  TensorVariant input(var(operand.op->getId())[operand.index]);
-  // TODO implement this
-  var(op.getId()) = Scale(input, op)();
+  auto operand1 = op.getPrevNodes()[0];
+  auto operand2 = op.getPrevNodes()[1];
+  auto input1 = var(operand1.op->getId())[operand1.index];
+  auto input2 = var(operand2.op->getId())[operand2.index];
+  var(op.getId()) = Scale(input1, input2)();
   DUMP(op, false);
 }
 
index 159c01f..bcf4ca9 100644 (file)
  */
 
 #include "Bias.h"
+#include "Fill.h"
+
+namespace nnc {
+
+BiasAdd::BiasAdd(const mir::TensorVariant& in1, const mir::TensorVariant& in2)
+    : _input1(in1), _input2(in2) {
+  assert(_input2.getShape().rank() == 1);
+  assert(_input1.getShape().dim(-1) == _input2.getShape().dim(0));
+}
+
+std::vector<mir::TensorVariant> BiasAdd::operator()() {
+  return Fill<float>(_input1.getShape(), [this](const mir::Index& idx) {
+    return _input1.at(idx) + _input2.atOffset({idx.at(idx.rank() - 1)});
+  })();
+}
+
+}
index 408c97e..ccc5d0d 100644 (file)
 #define _NNC_BACKEND_INTERPRETER_BIAS_
 
 #include "OperationImpl.h"
-#include "Fill.h"
 
-namespace nnc
-{
+namespace nnc {
 
-class BiasAdd : public OperationImpl<float>
-{
+class BiasAdd : public OperationImpl<float> {
 public:
-  BiasAdd(const mir::TensorVariant &input, const mir::TensorVariant &weights, const mir::Shape &outputShape)
-      : _weights(weights), _input(input), _outputShape(outputShape)
-  {
-    assert(_weights.getShape().rank() == 1);
-    assert(_outputShape.rank() == _input.getShape().rank());
-    assert(_outputShape.dim(_outputShape.rank() - 1) == _weights.getShape().dim(0));
-  }
-
-  std::vector<mir::TensorVariant> operator()() override
-  {
-    return Fill<float>(_outputShape, [this](const mir::Index &idx) {
-      return _input.at(idx) + _weights.atOffset({idx.at(idx.rank() - 1)});
-    })();
-  }
+  BiasAdd(const mir::TensorVariant& in1, const mir::TensorVariant& in2);
+
+  std::vector<mir::TensorVariant> operator()() override;
 
 private:
-  const mir::Tensor<float> _weights;
-  const mir::Tensor<float> _input;
-  const mir::Shape &_outputShape;
+  const mir::Tensor<float> _input1;
+  const mir::Tensor<float> _input2;
 };
 
 } // namespace nnc
index b2fdeb6..0f05598 100644 (file)
  */
 
 #include "Scale.h"
-
 #include "Fill.h"
 
-namespace nnc
-{
+namespace nnc {
+
+Scale::Scale(const mir::TensorVariant& in1, const mir::TensorVariant& in2)
+    : _input1(in1), _input2(in2) {
+  assert(_input2.getShape().rank() == 1);
+  assert(_input1.getShape().dim(-1) == _input2.getShape().dim(0));
+}
 
-std::vector<mir::TensorVariant> Scale::operator()()
-{
-  //For now handles only most common case with scale applied by last dimension
-  mir::Tensor<float> weightsAccessor(_op.getWeights());
-  return Fill<float>(_input.getShape(), [this, weightsAccessor](const mir::Index &idx) {
-    return _input.at(idx) * weightsAccessor.atOffset({idx.at(idx.rank() - 1)});
+std::vector<mir::TensorVariant> Scale::operator()() {
+  return Fill<float>(_input1.getShape(), [this](const mir::Index& idx) {
+    return _input1.at(idx) * _input2.atOffset({idx.at(idx.rank() - 1)});
   })();
 }
 
index e2e9115..faa8d92 100644 (file)
 
 #include "OperationImpl.h"
 
-#include "core/modelIR/operations/ScaleOp.h"
+namespace nnc {
 
-namespace nnc
-{
-/**
- * @brief Implements ScaleOp for interpreter backend
- * @todo check if I need support for any datatypes other than DTYPE::FLOAT
- */
 class Scale : public OperationImpl<float> {
 public:
-  /**
-   * @param in input data
-   * @param op scale operation description
-   */
-  explicit Scale(const mir::TensorVariant& in, const mir::ops::ScaleOp& op) : _input(in), _op(op) {}
-
-  /**
-   * @brief computes operation aplication result
-   * @return vector of all outputs from this node
-   */
+  Scale(const mir::TensorVariant& in1, const mir::TensorVariant& in2);
+
   std::vector<mir::TensorVariant> operator()() override;
 
 private:
-  mir::Tensor<float> _input;
-  const mir::ops::ScaleOp& _op;
+  const mir::Tensor<float> _input1;
+  const mir::Tensor<float> _input2;
 };
 
 } // namespace nnc
index d3814cb..a76ea1e 100644 (file)
@@ -139,18 +139,12 @@ ONNXOpCreator::convertConv2D(const std::vector<mir::IODescriptor>& inputs,
   // We should transpose ONNX MCHW to HWOI
   auto transposed = transposeTensor<2, 3, 1, 0>(in_weights_tensor);
 
-  mir::ops::ConstantOp* input_bias = nullptr;
-  if (inputs.size() > 2) {
-    input_bias = dynamic_cast<mir::ops::ConstantOp*>(inputs[2].op);
-    assert(input_bias && "1D optional bias could be a constant tensor only");
-  }
-
   // Transpose ONNX NCHW to MIR NHWC
   auto t_input = convertONNXToMIR(inputs[0]);
   auto result = createOp<ops::Conv2DOp>(t_input, transposed, cdata.strides_shape,
                                         cdata.padding_before, cdata.padding_after);
-  if (input_bias)
-    result = createOp<ops::BiasAddOp>(result->getOutput(0), input_bias->getValue());
+  if (inputs.size() > 2)
+    result = createOp<ops::BiasAddOp>(result->getOutput(0), inputs[2]);
 
   return {convertMIRToONNX(result->getOutput(0))};
 }
@@ -319,30 +313,32 @@ ONNXOpCreator::convertBatchNorm(const std::vector<mir::IODescriptor>& inputs,
   std::tie(found, value) = getFloatAttribute(onnx_node, "epsilon");
   float epsilon = found ? value : 1e-05f;
 
-  const auto& scale = input_tensors.at(inputs[1].op->getName());
-  const auto& bias = input_tensors.at(inputs[2].op->getName());
-  const auto& mean = input_tensors.at(inputs[3].op->getName());
-  const auto& var = input_tensors.at(inputs[4].op->getName());
+  const auto& scale_tensor = input_tensors.at(inputs[1].op->getName());
+  const auto& bias_tensor = input_tensors.at(inputs[2].op->getName());
+  const auto& mean_tensor = input_tensors.at(inputs[3].op->getName());
+  const auto& var_tensor = input_tensors.at(inputs[4].op->getName());
 
   // res1 = X - mean
-  Tensor<float> bias_data(mean);
+  Tensor<float> bias_data(mean_tensor);
   for (auto& idx: ShapeRange(bias_data.getShape()))
     bias_data.at(idx) *= -1;
 
   auto data = convertONNXToMIR(inputs[0]);
-  auto bias_add_1 = createOp<ops::BiasAddOp>(data, mean);
+  auto mean = createOp<ops::ConstantOp>(mean_tensor)->getOutput(0);
+  auto result = createOp<ops::BiasAddOp>(data, mean);
 
   // res2 = res1 * scale / (var + epsilon)
-  Tensor<float> multiplier(scale);
-  Tensor<float> var_accessor(var);
-  for (auto& idx: ShapeRange(scale.getShape()))
+  Tensor<float> multiplier(scale_tensor);
+  Tensor<float> var_accessor(var_tensor);
+  for (auto& idx: ShapeRange(scale_tensor.getShape()))
     multiplier.at(idx) /= std::sqrt(var_accessor.at(idx) + epsilon);
-  auto scale_op = createOp<ops::ScaleOp>(bias_add_1->getOutput(0), scale);
+  result = createOp<ops::ScaleOp>(result->getOutput(0), scale_tensor);
 
   // overall_res = res2 + bias
-  auto bias_add_2 = createOp<ops::BiasAddOp>(scale_op->getOutput(0), bias);
+  auto bias = createOp<ops::ConstantOp>(bias_tensor)->getOutput(0);
+  result = createOp<ops::BiasAddOp>(result->getOutput(0), bias);
 
-  return {convertMIRToONNX(bias_add_2->getOutput(0))};
+  return {convertMIRToONNX(result->getOutput(0))};
 }
 
 std::vector<IODescriptor>
index 85601f6..98a5a5d 100644 (file)
@@ -232,7 +232,7 @@ void Serializer::visit(ops::CappedReluOp& op) {
 
 void Serializer::visit(ops::BiasAddOp& op) {
   _curOp->_paramStartOffset = _buffer.size();
-  serializeTensor(op.getWeights());
+  // no parameters to dump
 }
 
 void Serializer::visit(ops::VariableOp& op) {
@@ -263,7 +263,7 @@ void Serializer::visit(ops::BatchNormOp& op) {
 
 void Serializer::visit(ops::ScaleOp& op) {
   _curOp->_paramStartOffset = _buffer.size();
-  serializeTensor(op.getWeights());
+  // no parameters to dump
 }
 
 void Serializer::visit(mir::ops::SliceOp& op) {
index 73d73c6..527e558 100644 (file)
@@ -479,14 +479,13 @@ void cappedRelu(Tensor &out, const char *params, const Tensor &in)
   CappedRelu(input, input_d, cap, out.getData(), input_d);
 }
 
-void biasAdd(Tensor &out, const char *params, const Tensor &in)
+void biasAdd(Tensor &out, const char *params, const Tensor &in1, const Tensor& in2)
 {
-  Kernel bias = deserializeKernel(params);
+  out.reShape(in1.getShape());
+  out.fillData(in1.getData(), in1.getShape().getNumElems());
 
-  out.reShape(in.getShape());
-  out.fillData(in.getData(), in.getShape().getNumElems());
-
-  AddBiasAndEvalActivationFunction(bias.data, bias.dims, out.getData(), shapeToDims(out.getShape()));
+  AddBiasAndEvalActivationFunction(in2.getData(), shapeToDims(in2.getShape()),
+                                   out.getData(), shapeToDims(out.getShape()));
 }
 
 void slice(Tensor& out, const char* params, const Tensor& in) {
index 7a060af..b8fd400 100644 (file)
  * limitations under the License.
  */
 
-void scale(Tensor &out, const char *params, const Tensor &in)
-{
-  out.reShape(in.getShape());
-  Kernel weights = deserializeKernel(params);
-  Shape inShape = in.getShape();
-  const int wSize = weights.dims.sizes[0];
-  assert(wSize == inShape[inShape.getDims() - 1]);
-  assert(weights.dims.sizes[1] == 1);
-  assert(weights.dims.sizes[2] == 1);
-  assert(weights.dims.sizes[3] == 1);
+void scale(Tensor& out, const char* params, const Tensor& in1, const Tensor& in2) {
+  const Shape& in1_shape = in1.getShape();
+  const Shape& in2_shape = in2.getShape();
+  assert(in2_shape.getDims() == 1 && in2_shape[0] == in1_shape[in1_shape.getDims() - 1]);
+  out.reShape(in1_shape);
 
-  const float *wData = weights.data;
-  const float *inData = in.getData();
-  float *outData = out.getData();
-  int32_t dataSize = inShape.getNumElems();
+  const float* in1_data = in1.getData();
+  const float* in2_data = in2.getData();
+  float* out_data = out.getData();
+  int32_t w_size = in2_shape[0];
+  int32_t data_size = in1_shape.getNumElems();
 
-  assert(dataSize % wSize == 0);
-  for (int32_t sliceOffset = 0; sliceOffset < dataSize; sliceOffset += wSize)
-  {
-    for (int i = 0; i < wSize; i++)
-    {
-      outData[sliceOffset + i] = inData[sliceOffset + i] * wData[i];
+  assert(data_size % w_size == 0);
+  for (int32_t slice_offset = 0; slice_offset < data_size; slice_offset += w_size) {
+    for (int i = 0; i < w_size; i++) {
+      out_data[slice_offset + i] = in1_data[slice_offset + i] * in2_data[i];
     }
   }
 }
index c23d62c..77916d7 100644 (file)
@@ -98,7 +98,8 @@ TFLiteOpCreator::convertConv2D(const std::vector<mir::IODescriptor>& inputs,
 
   auto result = createOp<ops::Conv2DOp>(inputs[0], params[0],
                                         strides, padding_before, padding_after);
-  result = createOp<ops::BiasAddOp>(result->getOutput(0), params[1]);
+  auto bias = createOp<ops::ConstantOp>(params[1]);
+  result = createOp<ops::BiasAddOp>(result->getOutput(0), bias->getOutput(0));
   return {addFusedActivation(result->getOutput(0), opts->fused_activation_function())};
 }
 
@@ -122,7 +123,8 @@ TFLiteOpCreator::convertDepthwiseConv2D(const std::vector<mir::IODescriptor>& in
 
   auto result = createOp<ops::DepthwiseConv2DOp>(inputs[0], params[0],
                                                  strides, padding_before, padding_after);
-  result = createOp<ops::BiasAddOp>(result->getOutput(0), params[1]);
+  auto bias = createOp<ops::ConstantOp>(params[1]);
+  result = createOp<ops::BiasAddOp>(result->getOutput(0), bias->getOutput(0));
   return {addFusedActivation(result->getOutput(0), opts->fused_activation_function())};
 }
 
@@ -321,7 +323,8 @@ TFLiteOpCreator::convertFullyConnected(const std::vector<mir::IODescriptor>& inp
   auto flatten = createOp<ops::ReshapeOp>(inputs[0], Shape{1, fc_input_size});
 
   auto result = createOp<ops::FullyConnectedOp>(flatten->getOutput(0), params[0]);
-  result = createOp<ops::BiasAddOp>(result->getOutput(0), params[1]);
+  auto bias = createOp<ops::ConstantOp>(params[1]);
+  result = createOp<ops::BiasAddOp>(result->getOutput(0), bias->getOutput(0));
   return {addFusedActivation(result->getOutput(0), opts->fused_activation_function())};
 }
 
index ac8f36e..0c154e0 100644 (file)
@@ -25,7 +25,7 @@ OP_FORMATS = {
     'RESHAPE': ('shapes',),
     'RELU': (),
     'CAPPED_RELU': ('axis',),
-    'BIAS_ADD': ('kernels',),
+    'BIAS_ADD': (),
     'SOFTMAX': ('axis',)
 }
 
@@ -245,7 +245,7 @@ class OpInfoProvider:
         return [tf.maximum(0.0, tf.minimum(self._inputs[0], self.axis)).eval()]
 
     def get_bias_add_result(self):
-        return [tf.add(self._inputs[0], self._kernels[0]).eval()]
+        return [tf.add(self._inputs[0], self._inputs[1]).eval()]
 
     def get_softmax_result(self):
         return [tf.nn.softmax(self._inputs[0], self.axis).eval()]
index 1bf3a58..e1c9e20 100644 (file)
@@ -104,7 +104,8 @@ static Operation* createSoftmax(std::unique_ptr<Graph>& g,
 static Operation* createBiasAdd(std::unique_ptr<Graph>& g,
                                 const std::vector<IODescriptor>& inputs,
                                 const opinfo::OperatorInfo* opInfo) {
-  return g->create<ops::BiasAddOp>("y", inputs[0], *getKernel(opInfo));
+  (void)opInfo;
+  return g->create<ops::BiasAddOp>("y", inputs[0], inputs[1]);
 }
 
 static Operation* createOp(std::unique_ptr<Graph>& g,
index 4d7c26e..3a94b36 100644 (file)
@@ -233,7 +233,8 @@ TEST(acl_backend_mir_to_dom, bias) {
 
   Graph g;
   OpConstructor op_generator = [&w](Graph& g, const vector<IODescriptor>& inputs) {
-    return g.create<mir::ops::BiasAddOp>("bias", inputs[0], w);
+    auto bias = g.create<mir::ops::ConstantOp>("", w)->getOutput(0);
+    return g.create<mir::ops::BiasAddOp>("bias", inputs[0], bias);
   };
   vector<Shape> input_shapes{{1, 10, 10, channels}};
 
@@ -253,7 +254,8 @@ TEST(acl_backend_mir_to_dom, scale) {
 
   Graph g;
   OpConstructor op_generator = [&w](Graph& g, const vector<IODescriptor>& inputs) {
-      return g.create<mir::ops::ScaleOp>("scale", inputs[0], w);
+    auto scale = g.create<mir::ops::ConstantOp>("", w)->getOutput(0);
+    return g.create<mir::ops::ScaleOp>("scale", inputs[0], scale);
   };
   vector<Shape> input_shapes{{1, 10, 10, channels}};
 
index 2d78eb1..1a32faf 100644 (file)
@@ -344,32 +344,34 @@ void createAndRunTestGraph(
 
 TEST(cpp_operations_test, bias) {
   vector<int> input_shape_data{2, 3, 4, 5};
-  mir::Shape weights_shape{5};
-  vector<unique_ptr<mir::TensorVariant>> input_ntensors(1);
-  Tensor input_atensor;
-  fillTensors(input_ntensors[0], input_atensor, input_shape_data, 1.0f);
+  vector<int> weights_shape_data{5};
+  vector<unique_ptr<mir::TensorVariant>> input_ntensors(2);
+  Tensor input_atensor0;
+  Tensor input_atensor1;
+  fillTensors(input_ntensors[0], input_atensor0, input_shape_data, 1.0f);
+  fillTensors(input_ntensors[1], input_atensor1, weights_shape_data, 1.0f);
 
-  mir::TensorVariant weights = createNTensor(weights_shape, 1.0f);
-  auto op_generator = [&weights](mir::Graph& g, const std::vector<mir::IODescriptor>& inputs) {
-    return g.create<mir::ops::BiasAddOp>("y", inputs[0], weights);
+  auto op_generator = [](mir::Graph& g, const std::vector<mir::IODescriptor>& inputs) {
+    return g.create<mir::ops::BiasAddOp>("y", inputs[0], inputs[1]);
   };
 
-  createAndRunTestGraph(op_generator, biasAdd, input_ntensors, input_atensor);
+  createAndRunTestGraph(op_generator, biasAdd, input_ntensors, input_atensor0, input_atensor1);
 }
 
 TEST(cpp_operations_test, scale) {
   vector<int> input_shape_data{2, 3, 4, 5};
-  mir::Shape weights_shape{5};
-  vector<unique_ptr<mir::TensorVariant>> input_ntensors(1);
-  Tensor input_atensor;
-  fillTensors(input_ntensors[0], input_atensor, input_shape_data, 1.0f);
+  vector<int> weights_shape_data{5};
+  vector<unique_ptr<mir::TensorVariant>> input_ntensors(2);
+  Tensor input_atensor0;
+  Tensor input_atensor1;
+  fillTensors(input_ntensors[0], input_atensor0, input_shape_data, 1.0f);
+  fillTensors(input_ntensors[1], input_atensor1, weights_shape_data, 1.0f);
 
-  mir::TensorVariant weights = createNTensor(weights_shape, 1.0f);
-  auto op_generator = [&weights](mir::Graph& g, const std::vector<mir::IODescriptor>& inputs) {
-    return g.create<mir::ops::ScaleOp>("y", inputs[0], weights);
+  auto op_generator = [](mir::Graph& g, const std::vector<mir::IODescriptor>& inputs) {
+    return g.create<mir::ops::ScaleOp>("y", inputs[0], inputs[1]);
   };
 
-  createAndRunTestGraph(op_generator, scale, input_ntensors, input_atensor);
+  createAndRunTestGraph(op_generator, scale, input_ntensors, input_atensor0, input_atensor1);
 }
 
 TEST(cpp_operations_test, capped_relu) {