* Change the signature of FullyConnectedOp to identically handle both input parameters.
* Refactor uses of FullyConnectedOp.
Signed-off-by: Sergei Barannikov <s.barannikov@samsung.com>
void IrDotDumper::visit(ops::FullyConnectedOp& op) {
auto nodeInfo = DotIrNodeInfo().withType("FullyConnected", op.getName())
.withInShapes(getInputShapes(op))
- .withOutShapes(getOutputShapes(op))
- .withKernelShape(op.getWeights().getShape());
+ .withOutShapes(getOutputShapes(op));
dotBuilder.updateWithOp(&op, nodeInfo);
}
void FullyConnectedOp::inferOutputShapes() {
auto& input_shape = getInputShape(0);
- auto& weights_shape = getWeights().getShape();
+ auto& weights_shape = getInputShape(1);
auto input_rank = input_shape.rank();
auto weights_rank = weights_shape.rank();
class FullyConnectedOp : public Operation {
public:
- FullyConnectedOp(const IODescriptor& arg, const TensorVariant& weights)
- : Operation(Type::fullyConnected, {arg}), _weights(weights) {
+ FullyConnectedOp(const IODescriptor& arg1, const IODescriptor& arg2)
+ : Operation(Type::fullyConnected, {arg1, arg2}) {
inferOutputShapes();
}
- const TensorVariant& getWeights() const { return _weights; }
-
private:
void inferOutputShapes();
-
- TensorVariant _weights;
};
-
-
} // namespace ops
} // namespace mir
} // namespace nnc
}
void AclCppOpGenerator::visit(ops::FullyConnectedOp& op) {
- const TensorVariant ir_weights = transposeTensor<1, 0>(op.getWeights());
- const Shape& ir_weights_shape = ir_weights.getShape();
-
auto& prev_nodes = op.getPrevNodes();
- assert(prev_nodes.size() == 1);
+ assert(prev_nodes.size() == 2);
+
auto in_op = prev_nodes[0].op;
+ auto ir_weights_op = dynamic_cast<mir::ops::ConstantOp*>(prev_nodes[1].op);
+ if (ir_weights_op == nullptr)
+ throw AclCppException("Unsupported operation type");
+
+ const TensorVariant ir_weights = transposeTensor<1, 0>(ir_weights_op->getValue());
+ const Shape& ir_weights_shape = ir_weights.getShape();
// Get the input node tensor id in the DOM.
auto in = AF::id(tensorName(in_op));
// 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 result = createOp<ops::FullyConnectedOp>(reshape->getOutput(0), weights_tensor);
-
+ auto weights = createOp<ops::ConstantOp>(weights_tensor)->getOutput(0);
+ auto result = createOp<ops::FullyConnectedOp>(reshape->getOutput(0), weights);
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)};
/// @brief Helper function for creating FullyConnected operation with non-square input.
IODescriptor
CaffeOpCreator::createFullyConnected(const mir::IODescriptor& input,
- const mir::TensorVariant& weights,
+ const mir::IODescriptor& weights,
int32_t axis) {
const auto& input_shape = input.op->getOutputShape(input.index);
- const auto& weights_shape = weights.getShape();
+ const auto& weights_shape = weights.op->getOutputShape(weights.index);
assert(axis >= 0 && axis < input_shape.rank());
assert(weights_shape.rank() == 2);
CaffeOpCreator::convertInnerProduct(const LayerParameter& layer,
const std::vector<mir::IODescriptor>& inputs) {
const auto& params = layer.inner_product_param();
- auto weights = convertBlob(layer.blobs(0));
+ auto weights_tensor = convertBlob(layer.blobs(0));
if (!params.transpose())
- weights = transposeTensor<1, 0>(weights);
+ weights_tensor = transposeTensor<1, 0>(weights_tensor);
+ auto weights = createOp<ops::ConstantOp>("", weights_tensor)->getOutput(0);
auto result = createFullyConnected(inputs[0], weights, params.axis());
// Add the bias, if any.
const int32_t hidden_size = params.num_output();
// Learned parameters of the layer. Tensors are transposed to match the ModelIR.
- const auto& xw = transposeTensor<1, 0>(convertBlob(layer.blobs(0)));
- auto xb = createOp<ops::ConstantOp>("", convertBlob(layer.blobs(1)))->getOutput(0);
- const auto& hw = transposeTensor<1, 0>(convertBlob(layer.blobs(2)));
+ auto xw_tensor = transposeTensor<1, 0>(convertBlob(layer.blobs(0)));
+ auto xb_tensor = convertBlob(layer.blobs(1));
+ auto hw_tensor = transposeTensor<1, 0>(convertBlob(layer.blobs(2)));
+ auto xw = createOp<ops::ConstantOp>("", xw_tensor)->getOutput(0);
+ auto xb = createOp<ops::ConstantOp>("", xb_tensor)->getOutput(0);
+ auto hw = createOp<ops::ConstantOp>("", hw_tensor)->getOutput(0);
// Add a dummy dimension so that element-wise operations perform properly.
cont = createOp<ops::ReshapeOp>("", cont, Shape{seq_length, batch_size, 1})->getOutput(0);
mir::IODescriptor
createFullyConnected(const mir::IODescriptor& input,
- const mir::TensorVariant& weights,
+ const mir::IODescriptor& weights,
int32_t axis);
TensorVariant convertBlob(const caffe::BlobProto& blob);
}
void NNInterpreter::visit(ops::FullyConnectedOp& op) {
- auto operand = op.getPrevNodes()[0];
- TensorVariant input = var(operand.op->getId())[operand.index];
- var(op.getId()) = FullyConnected<float>(input, op)();
+ auto operand1 = op.getPrevNodes()[0];
+ auto operand2 = op.getPrevNodes()[1];
+ TensorVariant input1 = var(operand1.op->getId())[operand1.index];
+ TensorVariant input2 = var(operand2.op->getId())[operand2.index];
+ var(op.getId()) = FullyConnected(input1, input2, op)();
}
void NNInterpreter::visit(ops::GemmOp& op) {
*/
#include "FullyConnected.h"
-//Do not remove
-//Used to force compile FullyConnected.h
+
+namespace nnc {
+
+FullyConnected::FullyConnected(const mir::TensorVariant& input,
+ const mir::TensorVariant& weights,
+ const mir::ops::FullyConnectedOp& _op)
+ : _op(_op), _input(input), _weights(weights) {}
+
+std::vector<mir::TensorVariant> FullyConnected::operator()() {
+ mir::TensorVariant res = OperationImpl<float>::allocate_tensor(_op.getOutputShape(0));
+ mir::Tensor<float> accessor(res);
+
+ const mir::Shape& in_shape = _input.getShape();
+ int32_t in_rank = in_shape.rank();
+
+ const mir::Shape& w_shape = _weights.getShape();
+ int32_t w_rank = w_shape.rank();
+
+ assert(in_shape.dim(in_rank - 1) == w_shape.dim(w_rank - 2));
+ (void)in_rank;
+
+ mir::ShapeRange out_range(res.getShape());
+
+ int32_t len = w_shape.dim(w_rank - 2);
+
+ for (auto& out_index : out_range) {
+ mir::Index t_index = out_index;
+ float& output_element = accessor.at(out_index);
+ int32_t col = t_index.at(w_rank - 1);
+ int32_t row = t_index.at(w_rank - 2);
+ for (int32_t i = 0; i < len; ++i) {
+ t_index.at(w_rank - 1) = i;
+ float in = _input.at(t_index);
+ t_index.at(w_rank - 1) = col;
+ t_index.at(w_rank - 2) = i;
+ float w = _weights.at(t_index);
+ t_index.at(w_rank - 2) = row;
+ output_element += in * w;
+ }
+ }
+
+ return {res};
+}
+
+}
#include "core/modelIR/operations/FullyConnectedOp.h"
#include "OperationImpl.h"
-namespace nnc
-{
+namespace nnc {
-template<typename T>
-class FullyConnected : public OperationImpl<T>
-{
+class FullyConnected : public OperationImpl<float> {
public:
- FullyConnected(const mir::TensorVariant &_input, const mir::ops::FullyConnectedOp &_op) : _op(_op), _input(_input) {}
+ FullyConnected(const mir::TensorVariant& input,
+ const mir::TensorVariant& weights,
+ const mir::ops::FullyConnectedOp& _op);
- std::vector<mir::TensorVariant> operator()() override
- {
- mir::TensorVariant res = OperationImpl<T>::allocate_tensor(_op.getOutputShape(0));
- mir::Tensor<T> accessor(res);
-
- mir::ShapeRange outRange(res.getShape());
-
- mir::Tensor<T> weights(_op.getWeights());
- const mir::Shape &wShape = weights.getShape();
- int32_t wRank = wShape.rank();
-
- const mir::Shape &inShape = _input.getShape();
- int32_t inRank = inShape.rank();
-
- assert(inShape.dim(inRank - 1) == wShape.dim(wRank - 2));
- (void)inRank;
-
- const auto len = wShape.dim(wRank - 2);
-
- int32_t row;
- int32_t col;
- for (auto &outIdx : outRange)
- {
- mir::Index tIdx = outIdx;
- T& outputElement = accessor.at(outIdx);
- col = tIdx.at(wRank - 1);
- row = tIdx.at(wRank - 2);
- for (int32_t i = 0; i < len; ++i)
- {
- tIdx.at(wRank - 1) = i;
- const T& in = _input.at(tIdx);
- tIdx.at(wRank - 1) = col;
- tIdx.at(wRank - 2) = i;
- const T& w = weights.at(tIdx);
- tIdx.at(wRank - 2) = row;
- outputElement += w * in;
- }
- }
-
- return {res};
- }
+ std::vector<mir::TensorVariant> operator()() override;
private:
- const mir::ops::FullyConnectedOp &_op;
- const mir::Tensor<T> _input;
+ const mir::ops::FullyConnectedOp& _op;
+ const mir::Tensor<float> _input;
+ const mir::Tensor<float> _weights;
};
} // namespace nnc
void Serializer::visit(ops::FullyConnectedOp& op) {
_curOp->_paramStartOffset = _buffer.size();
- serializeTensor(op.getWeights());
serializeShape(op.getOutputShape(0));
}
genericPool(AveragePool, out, params, in);
}
-void fullConnect(Tensor &out, const char *params, const Tensor &in)
-{
- const float *input = in.getData();
- Dims<4> input_d = shapeToDims(in.getShape());
- Kernel kernel = deserializeKernel(params);
+void fullConnect(Tensor& out, const char* params, const Tensor& in, const Tensor& w) {
Shape out_s = deserializeShape(params);
-
out.reShape(out_s);
- FullyConnected(input, input_d,
- kernel.data, kernel.dims,
+ FullyConnected(in.getData(), shapeToDims(in.getShape()),
+ w.getData(), shapeToDims(w.getShape()),
out.getData(), shapeToDims(out_s));
}
// Add Reshape operation to make sure the input for FC operation has shape [1, fc_input_size]
int32_t fc_input_size = params[0].getShape().dim(0);
auto flatten = createOp<ops::ReshapeOp>(inputs[0], Shape{1, fc_input_size});
-
- auto result = createOp<ops::FullyConnectedOp>(flatten->getOutput(0), params[0]);
- auto bias = createOp<ops::ConstantOp>(params[1]);
- result = createOp<ops::BiasAddOp>(result->getOutput(0), bias->getOutput(0));
+ auto weights = createOp<ops::ConstantOp>(params[0])->getOutput(0);
+ auto result = createOp<ops::FullyConnectedOp>(flatten->getOutput(0), weights);
+ auto bias = createOp<ops::ConstantOp>(params[1])->getOutput(0);
+ result = createOp<ops::BiasAddOp>(result->getOutput(0), bias);
return {addFusedActivation(result->getOutput(0), opts->fused_activation_function())};
}
# 'axis' for CAPPED_RELU is not an error, it just denotes a numeric parameter.
OP_FORMATS = {
- 'FULLY_CONNECTED': ('kernels',),
+ 'FULLY_CONNECTED': (),
'CONV_2D': ('kernels', 'padType', 'shapes'),
'DEPTHWISE_CONV_2D': ('kernels', 'padType', 'shapes'),
'POOL_2D': ('padType', 'poolType', 'shapes'),
static Operation* createFullyConnected(std::unique_ptr<Graph>& g,
const std::vector<IODescriptor>& inputs,
const opinfo::OperatorInfo* opInfo) {
- return g->create<ops::FullyConnectedOp>("y", inputs[0], *getKernel(opInfo));
+ return g->create<ops::FullyConnectedOp>("y", inputs[0], inputs[1]);
}
static Operation* createConv2D(std::unique_ptr<Graph>& g,
# For example: [2, 3, 4]=RANDOM, [3, 4, 5]=NEAR_ZERO etc.
FULLY_CONNECTED
-[3, 3] [3, 3]
-[5, 10] [10, 3]
+[[3, 3] [3, 3]]
+[[5, 10] [10, 3]]
CONV_2D
# input shape: [height, width, in_channels]
const int32_t out_size = 7;
Shape input_shape_data{1, in_size};
Shape weights_shape{in_size, out_size};
- TensorVariant weights = createTensorVariant(weights_shape);
+ TensorVariant weights_tensor = createTensorVariant(weights_shape);
Graph g;
- OpConstructor opGenerator = [weights](Graph& g, const vector<IODescriptor>& inputs) {
- return g.create<mir::ops::FullyConnectedOp>("fc", inputs[0], weights);
+ OpConstructor opGenerator = [weights_tensor](Graph& g, const vector<IODescriptor>& inputs) {
+ auto weights = g.create<mir::ops::ConstantOp>("", weights_tensor)->getOutput(0);
+ return g.create<mir::ops::FullyConnectedOp>("fc", inputs[0], weights);
};
fillGraph(g, opGenerator, {input_shape_data});
TEST(cpp_operations_test, fully_connected) {
vector<int> input_shape_data{3, 13};
- mir::Shape weights_shape{13, 7};
- vector<unique_ptr<mir::TensorVariant>> input_ntensors(1);
- Tensor input_atensor;
- fillTensors(input_ntensors[0], input_atensor, input_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::FullyConnectedOp>("y", inputs[0], weights);
+ vector<int> weights_shape_data{13, 7};
+ 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);
+ auto op_generator = [](mir::Graph& g, const std::vector<mir::IODescriptor>& inputs) {
+ return g.create<mir::ops::FullyConnectedOp>("y", inputs[0], inputs[1]);
};
- createAndRunTestGraph(op_generator, fullConnect, input_ntensors, input_atensor);
+ createAndRunTestGraph(op_generator, fullConnect, input_ntensors, input_atensor0, input_atensor1);
}
TEST(cpp_operations_test, resize_NN_test) {