if ( cli::caffeFrontend )
{
#ifdef NNC_FRONTEND_CAFFE_ENABLED
- pass = &caffe::CaffeFrontend::getInstance();
+ pass = &CaffeFrontend::getInstance();
#endif // NNC_FRONTEND_CAFFE_ENABLED
}
else if ( cli::tflFrontend )
using namespace nnc::mir;
using namespace nnc::cli;
-enum Format {FormatDot, FormatDump};
-
-static Option<bool> isDumpFormat(optname("--dump"),
- overview("if not setted DOT format will be used"),
- false,
- optional(true));
-
-int main(int argc, const char **argv)
-{
+int main(int argc, const char **argv) {
cli::CommandLine::getParser()->parseCommandLine(argc, argv, false);
std::string model = cli::inputFile;
- nnc::caffe::CaffeImporter importer{model};
+ nnc::CaffeImporter importer{model};
- if (!importer.import())
- {
- std::cout << "Could not load model \"" << model << "\"" << std::endl;
- return -1;
+ try {
+ importer.import();
+ IrDotDumper dotDumper;
+ ShapeInference inf;
+ auto g = static_cast<Graph *>(importer.createIR());
+ g->accept(&inf);
+ g->accept(&dotDumper);
+ dotDumper.writeDot(std::cout);
}
-
- Format format = isDumpFormat ? FormatDump : FormatDot;
-
- switch (format)
- {
- case FormatDump:
- importer.dump();
- break;
- case FormatDot:
- try
- {
- IrDotDumper dotDumper;
- ShapeInference inf;
- auto g = static_cast<Graph *>(importer.createIR());
- g->accept(&inf);
- g->accept(&dotDumper);
-
- dotDumper.writeDot(std::cout);
- } catch (PassException &e) {
- std::cout << "Error: " << e.what() << std::endl;
- return -1;
- }
- break;
- default:
- std::cout << "Error: Unsuported format" << std::endl;
+ catch (PassException &e) {
+ std::cout << "Error: " << e.reason() << std::endl;
return -1;
}
enum Format {FormatDot, FormatDump};
-static Option<bool> isDumpFormat(optname("--dump"),
- overview("if not setted DOT format will be used"),
- false,
- optional(true));
-
-int main(int argc, const char **argv)
-{
+int main(int argc, const char **argv) {
cli::CommandLine::getParser()->parseCommandLine(argc, argv, false);
std::string model = cli::inputFile;
nnc::tflite::v3::TfliteImporter importer{model};
- if (!importer.import())
- {
- std::cout << "Could not load model \"" << model << "\"" << std::endl;
- return -1;
+ try {
+ importer.import();
+ IrDotDumper dotDumper;
+ mir::ShapeInference inf;
+ auto g = static_cast<mir::Graph *>(importer.createIR());
+ g->accept(&inf);
+ g->accept(&dotDumper);
+ dotDumper.writeDot(std::cout);
}
-
- Format format = isDumpFormat ? FormatDump : FormatDot;
-
- switch (format)
- {
- case FormatDump:
- importer.dump();
- break;
- case FormatDot:
- try
- {
- IrDotDumper dotDumper;
- mir::ShapeInference inf;
- auto g = static_cast<mir::Graph *>(importer.createIR());
- g->accept(&inf);
- g->accept(&dotDumper);
-
- dotDumper.writeDot(std::cout);
- } catch (PassException &e) {
- std::cout << "Error: " << e.what() << std::endl;
- return -1;
- }
- break;
- default:
- std::cout << "Error: Unsuported format" << std::endl;
+ catch (PassException &e) {
+ std::cout << "Error: " << e.what() << std::endl;
return -1;
}
#include "pass/PassData.h"
-namespace nnc
-{
-namespace caffe
-{
+namespace nnc {
/**
* @brief class represent frontend of caffe NN framework
*/
-class CaffeFrontend : public Pass
-{
+class CaffeFrontend : public Pass {
public:
CaffeFrontend &operator=(const CaffeFrontend &) = delete;
CaffeFrontend(const CaffeFrontend &) = delete;
PassData run(PassData data) override;
};
-} // namespace caffe
} // namespace nnc
#endif //NNCC_CAFFEFRONTEND_H
#ifndef FRONTEND_COMMON_INCLUDE_NN_IMPORTER_
#define FRONTEND_COMMON_INCLUDE_NN_IMPORTER_
-namespace nnc
-{
+#include "core/modelIR/graph.h"
-class NNImporter
-{
+namespace nnc {
+
+class NNImporter {
public:
virtual ~NNImporter() = default;
- virtual bool import() = 0;
- virtual void *createIR() = 0;
- virtual void dump() = 0;
+ virtual void import() = 0;
+ virtual mir::Graph *createIR() = 0;
};
} // namespace nnc
+++ /dev/null
-/*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <iostream>
-
-#include "caffe_dump_visitor.h"
-
-namespace nnc
-{
-namespace caffe
-{
-
-using std::cout;
-using std::endl;
-
-std::ostream& operator<<(std::ostream& os, const BlobShape& bs)
-{
- cout << "[";
- for (int i = 0; i < bs.dim_size(); ++i)
- {
- if (i != 0)
- cout << ", ";
- cout << bs.dim(i);
- }
- cout << "]";
-
- return os;
-}
-
-void DumpVisitor::visit(const NetParameter &np)
-{
- cout << "[Network name]: " << np.name() << endl;
- cout << "[Network layer_size]: " << np.layer_size() << endl;
-
- if (np.layers_size() != 0)
- cout << "[DEPRECATED, Network layers_size]: " << np.layers_size() << endl;
- if (np.input_size() != 0)
- cout << "[DEPRECATED, Network input_size]: " << np.input_size() << endl;
- if (np.input_shape_size() != 0)
- cout << "[DEPRECATED, Network input_shapes_size]: " << np.input_shape_size() << endl;
- if (np.input_dim_size() != 0)
- cout << "[DEPRECATED, Network input_dim_size]: " << np.input_dim_size() << endl;
-}
-
-void DumpVisitor::visit(const LayerParameter &lp)
-{
- cout << "[" << lp.type() << "]: " << lp.name() << endl;
-
- for (int i = 0; i < lp.bottom_size(); ++i)
- cout << " [Input]: " << lp.bottom(i) << endl;
-
- for (int i = 0; i < lp.top_size(); ++i)
- cout << " [Output]: " << lp.top(i) << endl;;
-}
-
-void DumpVisitor::visit(const BlobProto &bp)
-{
- if (bp.has_shape())
- {
- cout << " [Blob shape]: ";
- cout << bp.shape() << endl;
- }
-}
-
-void DumpVisitor::visit(const BlobShape& bs)
-{
- cout << " [Blob shape]: ";
- cout << bs << endl;
-}
-
-} // namespace caffe
-} // namespace nnc
+++ /dev/null
-/*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef NNCC_CAFFE_DUMP_VISITOR_H
-#define NNCC_CAFFE_DUMP_VISITOR_H
-
-#include "caffe_visitor.h"
-
-namespace nnc
-{
-namespace caffe
-{
-
-class DumpVisitor : public Visitor
-{
-public:
- void visit(const NetParameter ¶meter) override;
- void visit(const LayerParameter ¶meter) override;
- void visit(const BlobProto &proto) override;
- void visit(const BlobShape &shape) override;
-};
-
-} // namespace caffe
-} // namespace nnc
-
-#endif //NNCC_CAFFE_DUMP_VISITOR_H
#include "caffe_importer.h"
-namespace nnc
-{
-namespace caffe
-{
+namespace nnc {
-Pass &CaffeFrontend::getInstance()
-{
+Pass &CaffeFrontend::getInstance() {
static CaffeFrontend instance;
return instance;
}
-PassData CaffeFrontend::run(PassData data)
-{
+PassData CaffeFrontend::run(PassData data) {
(void)data;
- nnc::caffe::CaffeImporter importer{cli::inputFile};
+ nnc::CaffeImporter importer{cli::inputFile};
- bool success = importer.import();
-
- if (!success)
- {
- throw PassException("Could not load model: " + cli::inputFile + "\n");
- }
+ importer.import();
return reinterpret_cast<mir::Graph *>(importer.createIR());
}
-} // namespace caffe
} // namespace nnc
* limitations under the License.
*/
+#include <vector>
#include <fstream>
#include <sstream>
+#include <cassert>
-#include "caffe_walker.h"
-#include "caffe_model_visitor.h"
-#include "caffe_dump_visitor.h"
#include "caffe_importer.h"
#include "proto_reader.h"
-namespace nnc
-{
-namespace caffe
-{
+#include "core/modelIR/Shape.h"
+#include "core/modelIR/operations/variable_op.h"
+#include "core/modelIR/TensorUtil.h"
+#include "pass/PassException.h"
-bool CaffeImporter::import()
-{
- GOOGLE_PROTOBUF_VERIFY_VERSION;
+#include "passes/common_frontend/shape_helper.h"
+
+namespace nnc {
- net.reset(new NetParameter());
+using VariableOp = nnc::mir::ops::VariableOp;
+using nnc::mir::Shape;
+using nnc::mir::transposeTensor;
- // import success flag is returned
- return readProtoFromBinaryFile(modelFilename.c_str(), net.get());
+void CaffeImporter::import() {
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+ _net.reset(new NetParameter());
+ if (!readProtoFromBinaryFile(_modelFilename.c_str(), _net.get()))
+ throw PassException("Could not load model: " + _modelFilename + "\n");
+
+ collectUnsupportedLayers();
}
-void *CaffeImporter::createIR()
-{
- ModelVisitor irCreator;
- ModelWalker caffeWalker(&irCreator);
+Graph *CaffeImporter::createIR() {
+
+ for (int i = 0; i < _net->layer_size(); ++i)
+ createMIRNodesFromLayer(_net->layer(i));
+
+ setIrNodeNames();
+ setGraphOutputs();
+
+ return _graph;
+}
+
+void CaffeImporter::collectUnsupportedLayers() {
+ processDeprecatedInput();
+
+ for (int i = 0; i < _net->layer_size(); ++i)
+ collectUnsupportedOp(_net->layer(i));
+
+ if (!_problemsOpSet.empty()) {
+ std::string msg("Detected problems:\n");
+ for (const auto& problemStr : _problemsOpSet)
+ msg.append(problemStr + "\n");
+ throw PassException(msg);
+ }
+}
+
+void CaffeImporter::createMIRNodesFromLayer(const LayerParameter& lp) {
+ auto inputs = createOpInputs(lp);
+ auto params = createOpParams(lp);
+
+ std::vector<INode::Ref> outputs;
+ INode *prev;
+ CaffeOpType opType = _operatorTypes.at(lp.type());
+
+ switch (opType) {
+ case CaffeOpType::input:
+ processInputLayer(lp);
+ break;
+ case CaffeOpType::convolution:
+ outputs = _opCreator.createConv2D(inputs, params, lp.convolution_param());
+ break;
+ case CaffeOpType::innerProduct:
+ outputs = _opCreator.createFullyConnected(inputs, params, lp.inner_product_param());
+ break;
+ case CaffeOpType::pooling:
+ outputs = _opCreator.createPool(inputs, params, lp.pooling_param());
+ break;
+ case CaffeOpType::concat:
+ outputs = _opCreator.createConcat(inputs, params, lp.concat_param());
+ break;
+ case CaffeOpType::reshape:
+ outputs = _opCreator.createReshape(inputs, params, lp.reshape_param());
+ break;
+ case CaffeOpType::ReLU:
+ outputs = _opCreator.createRelu(inputs, params, lp.relu_param());
+ break;
+ case CaffeOpType::softmax:
+ outputs = _opCreator.createSoftmax(inputs, params, lp.softmax_param());
+ break;
+ case CaffeOpType::scale:
+ outputs = _opCreator.createScale(inputs, params, lp.scale_param());
+ break;
+ case CaffeOpType::batchNorm:
+ outputs = _opCreator.createBatchNorm(inputs, params, lp.batch_norm_param());
+ break;
+ case CaffeOpType::dropout:
+ outputs = _opCreator.createDropout(inputs, params, lp.dropout_param());
+ break;
+ case CaffeOpType::split:
+ prev = _opsForBlobsTheyOutput[lp.bottom(0)];
+ for (int i = 0; i < lp.top_size(); ++i)
+ _opsForBlobsTheyOutput[lp.top(i)] = prev;
+ break;
+ default:
+ assert(false && "All unsupported types should have been found before this pass.");
+ }
+
+ for (auto item : outputs)
+ _opsForBlobsTheyOutput[lp.top(0)] = item;
+ _graphOutputs.assign(outputs.begin(), outputs.end());
+}
+
+void CaffeImporter::collectUnsupportedOp(const LayerParameter& lp) {
- caffeWalker.walkNetParameter(*net);
- irCreator.setIrNodeNames();
- irCreator.setGraphOutputs();
+ auto it = _operatorTypes.find(lp.type());
+ if (it == _operatorTypes.end()) {
+ _problemsOpSet.insert(lp.type() + ": unknown layer");
+ return;
+ }
- return irCreator.getGraph();
+ CaffeOpType opType = it->second;
+ std::vector<std::shared_ptr<IrTensor>> params;
+
+ switch (opType) {
+ case CaffeOpType::input:
+ case CaffeOpType::softmax:
+ case CaffeOpType::scale:
+ case CaffeOpType::dropout:
+ case CaffeOpType::split:
+ case CaffeOpType::concat:
+ // No checks
+ break;
+ case CaffeOpType::convolution:
+ OpCreator::checkConv2D(lp.convolution_param(), _problemsOpSet);
+ break;
+ case CaffeOpType::innerProduct:
+ OpCreator::checkFullyConnected(lp.inner_product_param(), _problemsOpSet);
+ break;
+ case CaffeOpType::pooling:
+ OpCreator::checkPool(lp.pooling_param(), _problemsOpSet);
+ break;
+ case CaffeOpType::reshape:
+ OpCreator::checkReshape(lp.reshape_param(), _problemsOpSet);
+ break;
+ case CaffeOpType::ReLU:
+ OpCreator::checkRelu(lp.relu_param(), _problemsOpSet);
+ break;
+ case CaffeOpType::batchNorm:
+ params = createOpParams(lp);
+ OpCreator::checkBatchNorm(lp.batch_norm_param(), params, _problemsOpSet);
+ break;
+ default:
+ _problemsOpSet.insert(lp.type() + ": unsupported layer");
+ break;
+ }
}
-void CaffeImporter::dump()
+void CaffeImporter::createGraphInputs(const std::vector<std::string> &names,
+ const std::vector<Shape> &shapes) {
+ assert(names.size() == shapes.size());
+
+ for (size_t i = 0; i < names.size(); ++i) {
+ auto node = _graph->create<VariableOp>(names[i]);
+ _opsForBlobsTheyOutput[names[i]] = node;
+
+ Shape inputShape = shapes[i];
+ // WARNING! Temporary solution! Assuming that every 4D input will be used for a convolution,
+ // so we change every 4D input from Caffe NCHW to Model IR HWC (batch is cut off earlier).
+ // TODO: Implement a more consistent way of handling shapes within the model.
+ if (shapes[i].rank() == 3) {
+ const Shape &sh = shapes[i];
+ inputShape = Shape{sh.dim(1), sh.dim(2), sh.dim(0)};
+ }
+ // WARNING! Temporary solution!
+
+ node->getOperation()->setOutputShape(0, inputShape);
+ }
+}
+
+void CaffeImporter::processDeprecatedInput() {
+ if (_net->input_dim_size() != 0 || _net->input_shape_size() != 0)
+ throw PassException("Deprecated Caffe input types are not supported");
+}
+
+void CaffeImporter::processInputLayer(const LayerParameter& lp) {
+ std::vector<std::string> inputNames;
+ for (const auto &name : lp.top())
+ inputNames.push_back(name);
+
+ for (const auto &shape : lp.input_param().shape()) {
+ Shape sh = ShapeHelper::createShape(shape.dim(), shape.dim_size());
+ _inputShapes.push_back(ShapeHelper::cutOffBatchDim(sh));
+ }
+
+ if (!_inputShapes.empty())
+ createGraphInputs(inputNames, _inputShapes);
+}
+
+std::shared_ptr<IrTensor> CaffeImporter::createTensor(const BlobProto &bp) {
+ auto type = IrTensor::DTYPE::FLOAT;
+ size_t elementSize;
+
+ const char *srcData;
+ size_t bufferSize;
+
+ if (bp.data_size() != 0) {
+ assert(bp.double_data_size() == 0);
+ elementSize = sizeof(float);
+ bufferSize = bp.data_size() * elementSize;
+ srcData = reinterpret_cast<const char *>(bp.data().data());
+ }
+ else if (bp.double_data_size() != 0) {
+ elementSize = sizeof(double);
+ bufferSize = bp.double_data_size() * elementSize;
+ srcData = reinterpret_cast<const char *>(bp.double_data().data());
+ }
+ else {
+ throw PassException("No data in Caffe BlobProto, investigate");
+ }
+
+ // Create untyped tensor. Note, tensor contents will be *copied* here.
+ std::shared_ptr<char> tensorBufferCopy(new char[bufferSize],
+ std::default_delete<char[]>());
+
+ char *dstData = tensorBufferCopy.get();
+ memcpy(dstData, srcData, bufferSize);
+
+ Shape tensorShape = ShapeHelper::createShape(
+ bp.shape().dim(), static_cast<size_t>(bp.shape().dim_size()));
+
+ auto tensor = std::make_shared<IrTensor>(tensorShape, tensorBufferCopy, type, elementSize);
+
+ return tensor;
+}
+
+std::vector<INode::Ref> CaffeImporter::createOpInputs(const LayerParameter &lp)
{
- DumpVisitor dumper;
- ModelWalker caffeWalker(&dumper);
+ std::vector<INode::Ref> inputs;
- caffeWalker.walkNetParameter(*net);
+ for (const auto &inputBlobName : lp.bottom())
+ inputs.push_back(_opsForBlobsTheyOutput[inputBlobName]);
+
+ return inputs;
}
-} // namespace caffe
+/**
+ * @brief Prepares Caffe layer parameters for Model IR operation creator.
+ */
+std::vector<std::shared_ptr<IrTensor>> CaffeImporter::createOpParams(const LayerParameter &lp) {
+ std::vector<std::shared_ptr<IrTensor>> params;
+
+ for (const auto &blob : lp.blobs()) {
+
+ std::shared_ptr<IrTensor> tensor = createTensor(blob);
+
+ if (lp.has_convolution_param() && blob.shape().dim_size() == 4) {
+ // TODO support non default channel axis
+ assert(lp.convolution_param().axis() == 1 && "assuming channel axis number set to default");
+ params.emplace_back(transposeTensor<2, 3, 1, 0>(tensor));
+ }
+ else if (lp.has_inner_product_param() && blob.shape().dim_size() == 2) {
+ params.emplace_back(transposeTensor<1, 0>(tensor));
+ }
+ else {
+ params.push_back(tensor);
+ }
+ }
+
+ return params;
+}
+
+void CaffeImporter::setGraphOutputs() {
+ // Marking nodes as output nodes.
+ for (auto &outputIdx : _graphOutputs)
+ _graph->markOutput(outputIdx);
+}
+
+void CaffeImporter::setIrNodeNames() {
+ for (auto &item : _opsForBlobsTheyOutput)
+ item.second->setName(item.first);
+}
+
+const std::map<std::string, CaffeOpType> CaffeImporter::_operatorTypes = {
+ {"AbsVal", CaffeOpType::absVal},
+ {"Accuracy", CaffeOpType::accuracy},
+ {"ArgMax", CaffeOpType::argMax},
+ {"BatchNorm", CaffeOpType::batchNorm},
+ {"BatchReindex", CaffeOpType::batchReindex},
+ {"Bias", CaffeOpType::bias},
+ {"BNLL", CaffeOpType::BNLL},
+ {"Clip", CaffeOpType::clip},
+ {"Concat", CaffeOpType::concat},
+ {"ContrastiveLoss", CaffeOpType::contrastiveLoss},
+ {"Convolution", CaffeOpType::convolution},
+ {"Crop", CaffeOpType::crop},
+ {"Data", CaffeOpType::data},
+ {"Deconvolution", CaffeOpType::deconvolution},
+ {"Dropout", CaffeOpType::dropout},
+ {"DummyData", CaffeOpType::dummyData},
+ {"Eltwise", CaffeOpType::eltwise},
+ {"ELU", CaffeOpType::ELU},
+ {"Embed", CaffeOpType::embed},
+ {"EuclidianLoss", CaffeOpType::euclidianLoss},
+ {"Exp", CaffeOpType::exp},
+ {"Filter", CaffeOpType::filter},
+ {"Flatten", CaffeOpType::flatten},
+ {"HDF5Data", CaffeOpType::HDF5Data},
+ {"HDF5Output", CaffeOpType::HDF5Output},
+ {"HingeLoss", CaffeOpType::hingeLoss},
+ {"Im2Col", CaffeOpType::im2Col},
+ {"ImageData", CaffeOpType::imageData},
+ {"InfogainLoss", CaffeOpType::infogainLoss},
+ {"InnerProduct", CaffeOpType::innerProduct},
+ {"Input", CaffeOpType::input},
+ {"Log", CaffeOpType::log},
+ {"LRN", CaffeOpType::LRN},
+ {"LSTM", CaffeOpType::LSTM},
+ {"MemoryData", CaffeOpType::memoryData},
+ {"MultinomialLogisticLoss", CaffeOpType::multinomialLogisticLoss},
+ {"MVN", CaffeOpType::MVN},
+ {"Parameter", CaffeOpType::parameter},
+ {"Pooling", CaffeOpType::pooling},
+ {"Power", CaffeOpType::power},
+ {"PReLU", CaffeOpType::PReLU},
+ {"Python", CaffeOpType::python},
+ {"Recurrent", CaffeOpType::recurrent},
+ {"Reduction", CaffeOpType::reduction},
+ {"ReLU", CaffeOpType::ReLU},
+ {"Reshape", CaffeOpType::reshape},
+ {"RNN", CaffeOpType::RNN},
+ {"Scale", CaffeOpType::scale},
+ {"SigmoidCrossEntropyLoss", CaffeOpType::sigmoidCrossEntropyLoss},
+ {"Sigmoid", CaffeOpType::sigmoid},
+ {"Silence", CaffeOpType::silence},
+ {"Softmax", CaffeOpType::softmax},
+ {"SoftmaxLoss", CaffeOpType::softmaxLoss},
+ {"SPP", CaffeOpType::SPP},
+ {"Split", CaffeOpType::split},
+ {"Slice", CaffeOpType::slice},
+ {"Tanh", CaffeOpType::tanh},
+ {"Threshold", CaffeOpType::threshold},
+ {"Tile", CaffeOpType::tile},
+ {"WindowData", CaffeOpType::windowData}
+};
+
} // namespace nnc
+
/*
* Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
*
#ifndef NNCC_CAFFE_IMPORTER_H
#define NNCC_CAFFE_IMPORTER_H
+#include <set>
#include <string>
#include <memory>
#include "caffe/proto/caffe.pb.h"
#include "passes/common_frontend/nn_importer.h"
+#include "caffe_op_creator.h"
+#include "caffe_op_types.h"
-namespace nnc
-{
-namespace caffe
-{
+namespace nnc {
using namespace ::caffe;
-class CaffeImporter : public NNImporter
-{
+class CaffeImporter : public NNImporter {
public:
- explicit CaffeImporter(std::string filename) : modelFilename(std::move(filename)) {};
+ explicit CaffeImporter(std::string filename) : _modelFilename(std::move(filename)),
+ _graph(new mir::Graph()),
+ _opCreator(_graph) {};
- bool import() override;
- void *createIR() override;
- void dump() override;
+ void import() override;
+ Graph *createIR() override;
private:
- std::string modelFilename;
- std::unique_ptr<NetParameter> net;
+ std::string _modelFilename;
+ std::unique_ptr<NetParameter> _net;
+ mir::Graph* _graph;
+ OpCreator _opCreator;
+
+ std::vector<mir::Shape> _inputShapes;
+ std::map<std::string, mir::INode::Ref> _opsForBlobsTheyOutput;
+ std::vector<mir::INode::Ref> _graphOutputs;
+
+ static const std::map<std::string, CaffeOpType> _operatorTypes;
+ std::set<std::string> _problemsOpSet{};
+
+ void setGraphOutputs();
+ void setIrNodeNames();
+
+ void collectUnsupportedLayers();
+ void createMIRNodesFromLayer(const LayerParameter& lp);
+ void collectUnsupportedOp(const LayerParameter& lp);
+ std::shared_ptr<mir::TensorVariant> createTensor(const ::caffe::BlobProto&);
+ std::vector<mir::INode::Ref> createOpInputs(const ::caffe::LayerParameter&);
+ std::vector<std::shared_ptr<mir::TensorVariant>> createOpParams(const ::caffe::LayerParameter&);
+
+ void createGraphInputs(const std::vector<std::string> &names,
+ const std::vector<mir::Shape> &shapes);
+ void processInputLayer(const ::caffe::LayerParameter&);
+ void processDeprecatedInput();
};
-} // namespace caffe
} // namespace nnc
#endif // NNCC_CAFFE_IMPORTER_H
+++ /dev/null
-/*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <vector>
-#include <cassert>
-
-#include "core/modelIR/Shape.h"
-#include "core/modelIR/operations/variable_op.h"
-#include "core/modelIR/TensorUtil.h"
-#include "pass/PassException.h"
-
-#include "passes/common_frontend/shape_helper.h"
-#include "caffe_model_visitor.h"
-
-
-namespace nnc
-{
-namespace caffe
-{
-
-using VariableOp = nnc::mir::ops::VariableOp;
-using nnc::mir::Shape;
-using nnc::mir::transposeTensor;
-
-void ModelVisitor::visit(const NetParameter& np)
-{
- processDeprecatedInput(np);
-}
-
-void ModelVisitor::visit(const LayerParameter& lp)
-{
- auto inputs = createOpInputs(lp);
- auto params = createOpParams(lp);
-
- std::vector<INode::Ref> outputs;
-
- // TODO: support other layer types
-
- // This is the Input layer
- if (lp.has_input_param())
- {
- processInputLayer(lp);
- }
- else if (lp.type() == "Convolution")
- {
- outputs = opCreator.createConv2D(inputs, params, lp.convolution_param());
- }
- else if (lp.has_inner_product_param())
- {
- outputs = opCreator.createFullyConnected(inputs, params, lp.inner_product_param());
- }
- else if (lp.has_pooling_param())
- {
- outputs = opCreator.createPool(inputs, params, lp.pooling_param());
- }
- else if (lp.has_concat_param() || lp.type() == "Concat")
- {
- outputs = opCreator.createConcat(inputs, params, lp.concat_param());
- }
- else if (lp.has_reshape_param())
- {
- outputs = opCreator.createReshape(inputs, params, lp.reshape_param());
- }
- else if (lp.has_relu_param() || lp.type() == "ReLU")
- {
- outputs = opCreator.createRelu(inputs, params, lp.relu_param());
- }
- else if (lp.has_softmax_param() || lp.type() == "Softmax")
- {
- outputs = opCreator.createSoftmax(inputs, params, lp.softmax_param());
- }
- else if (lp.has_scale_param())
- {
- outputs = opCreator.createScale(inputs, params, lp.scale_param());
- }
- else if (lp.has_batch_norm_param())
- {
- outputs = opCreator.createBatchNorm(inputs, params, lp.batch_norm_param());
- }
- else if (lp.has_dropout_param())
- {
- outputs = opCreator.createDropout(inputs, params, lp.dropout_param());
- }
- else if (lp.type() == "Split")
- {
- INode *prev = opsForBlobsTheyOutput[lp.bottom(0)];
- for (int i = 0; i < lp.top_size(); ++i)
- {
- opsForBlobsTheyOutput[lp.top(i)] = prev;
- }
- }
- else
- {
- throw PassException("Encountered unsupported Caffe layer type");
- }
-
- for (auto item : outputs)
- {
- opsForBlobsTheyOutput[lp.top(0)] = item;
- }
- graphOutputs.assign(outputs.begin(), outputs.end());
-}
-
-void ModelVisitor::visit(const BlobProto&) {}
-void ModelVisitor::visit(const BlobShape&) {}
-
-Graph *ModelVisitor::getGraph()
-{
- return graph;
-}
-
-void ModelVisitor::createGraphInputs(const std::vector<std::string> &names,
- const std::vector<Shape> &shapes)
-{
- assert(names.size() == shapes.size());
-
- for (size_t i = 0; i < names.size(); ++i)
- {
- auto node = graph->create<VariableOp>(names[i]);
- opsForBlobsTheyOutput[names[i]] = node;
-
- Shape inputShape = shapes[i];
- // WARNING! Temporary solution! Assuming that every 4D input will be used for a convolution,
- // so we change every 4D input from Caffe NCHW to Model IR HWC (batch is cut off earlier).
- // TODO: Implement a more consistent way of handling shapes within the model.
- if (shapes[i].rank() == 3)
- {
- const Shape &sh = shapes[i];
- inputShape = Shape{sh.dim(1), sh.dim(2), sh.dim(0)};
- }
- // WARNING! Temporary solution!
-
- node->getOperation()->setOutputShape(0, inputShape);
- }
-}
-
-void ModelVisitor::processDeprecatedInput(const NetParameter& np)
-{
- if (np.input_dim_size() != 0 || np.input_shape_size() != 0)
- {
- throw PassException("Deprecated Caffe input types are not supported");
- }
-}
-
-void ModelVisitor::processInputLayer(const LayerParameter& lp)
-{
- if (!inputShapes.empty())
- {
- throw std::runtime_error("Model contains both Input layer and deprecated input methods");
- }
-
- std::vector<std::string> inputNames;
- for (const auto &name : lp.top())
- {
- inputNames.push_back(name);
- }
-
- for (const auto &shape : lp.input_param().shape())
- {
- Shape sh = ShapeHelper::createShape(shape.dim(), shape.dim_size());
- inputShapes.push_back(ShapeHelper::cutOffBatchDim(sh));
- }
-
- if (!inputShapes.empty())
- {
- createGraphInputs(inputNames, inputShapes);
- }
-}
-
-std::shared_ptr<IrTensor> ModelVisitor::createTensor(const BlobProto &bp)
-{
- IrTensor::DTYPE type = IrTensor::DTYPE::FLOAT;
- size_t elementSize;
-
- const char *srcData;
- size_t bufferSize;
-
- if (bp.data_size() != 0)
- {
- assert(bp.double_data_size() == 0);
- elementSize = sizeof(float);
- bufferSize = bp.data_size() * elementSize;
- srcData = reinterpret_cast<const char *>(bp.data().data());
- }
- else if (bp.double_data_size() != 0)
- {
- elementSize = sizeof(double);
- bufferSize = bp.double_data_size() * elementSize;
- srcData = reinterpret_cast<const char *>(bp.double_data().data());
- }
- else
- {
- throw PassException("No data in Caffe BlobProto, investigate");
- }
-
- // Create untyped tensor. Note, tensor contents will be *copied* here.
- std::shared_ptr<char> tensorBufferCopy(new char[bufferSize],
- std::default_delete<char[]>());
-
- char *dstData = tensorBufferCopy.get();
- memcpy(dstData, srcData, bufferSize);
-
- Shape tensorShape = ShapeHelper::createShape(
- bp.shape().dim(), static_cast<size_t>(bp.shape().dim_size()));
-
- auto tensor = std::make_shared<IrTensor>(tensorShape, tensorBufferCopy, type, elementSize);
-
- return tensor;
-}
-
-std::vector<INode::Ref> ModelVisitor::createOpInputs(const LayerParameter &lp)
-{
- std::vector<INode::Ref> inputs;
-
- for (const auto &inputBlobName : lp.bottom())
- {
- inputs.push_back(opsForBlobsTheyOutput[inputBlobName]);
- }
-
- return inputs;
-}
-
-/**
- * @brief Prepares Caffe layer parameters for Model IR operation creator.
- */
-std::vector<std::shared_ptr<IrTensor>> ModelVisitor::createOpParams(const LayerParameter &lp)
-{
- std::vector<std::shared_ptr<IrTensor>> params;
-
- for (const auto &blob : lp.blobs())
- {
-
- std::shared_ptr<IrTensor> tensor = createTensor(blob);
-
- if (lp.has_convolution_param() && blob.shape().dim_size() == 4)
- {
- // TODO support non default channel axis
- assert(lp.convolution_param().axis() == 1 && "assuming channel axis number set to default");
- params.emplace_back(transposeTensor<2, 3, 1, 0>(tensor));
- }
- else if (lp.has_inner_product_param() && blob.shape().dim_size() == 2)
- {
- params.emplace_back(transposeTensor<1, 0>(tensor));
- }
- else
- {
- params.push_back(tensor);
- }
- }
-
- return params;
-}
-
-void ModelVisitor::setGraphOutputs() {
- // Marking nodes as output nodes.
- for (auto &outputIdx : graphOutputs)
- {
- graph->markOutput(outputIdx);
- }
-}
-
-void ModelVisitor::setIrNodeNames() {
- for (auto &item : opsForBlobsTheyOutput)
- {
- item.second->setName(item.first);
- }
-}
-
-} // namespace caffe
-} // namespace nnc
+++ /dev/null
-/*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef NNCC_CAFFE_IR_VISITOR_H
-#define NNCC_CAFFE_IR_VISITOR_H
-
-#include <map>
-#include <vector>
-
-#include "core/modelIR/graph.h"
-#include "core/modelIR/ir_node.h"
-#include "core/modelIR/TensorVariant.h"
-
-#include "caffe_visitor.h"
-#include "caffe_op_creator.h"
-
-namespace nnc
-{
-namespace caffe
-{
-
-class ModelVisitor : public Visitor
-{
-public:
- ModelVisitor() : graph(new mir::Graph()), opCreator(graph) {};
-
- void visit(const ::caffe::NetParameter&) override;
- void visit(const ::caffe::LayerParameter&) override;
- void visit(const ::caffe::BlobProto&) override;
- void visit(const ::caffe::BlobShape&) override;
-
- mir::Graph* getGraph();
- void setGraphOutputs();
- void setIrNodeNames();
-
-private:
- mir::Graph* graph = nullptr;
- OpCreator opCreator;
-
- std::vector<mir::Shape> inputShapes;
- std::map<std::string, mir::INode::Ref> opsForBlobsTheyOutput;
- std::vector<mir::INode::Ref> graphOutputs;
-
- std::shared_ptr<mir::TensorVariant> createTensor(const ::caffe::BlobProto&);
- std::vector<mir::INode::Ref> createOpInputs(const ::caffe::LayerParameter&);
- std::vector<std::shared_ptr<mir::TensorVariant>> createOpParams(const ::caffe::LayerParameter&);
-
- void createGraphInputs(const std::vector<std::string> &names,
- const std::vector<mir::Shape> &shapes);
- void processInputLayer(const ::caffe::LayerParameter&);
- void processDeprecatedInput(const ::caffe::NetParameter&);
-};
-
-} // namespace caffe
-} // namespace nnc
-
-#endif //NNCC_CAFFE_IR_VISITOR_H
#include "pass/PassException.h"
#include "caffe_op_creator.h"
+#include <set>
#include <cmath>
-namespace nnc
-{
-namespace caffe
-{
+namespace nnc {
using namespace mir;
-
template <typename OptsType>
-static inline bool has2DStride(const OptsType& opts)
-{
+static inline bool has2DStride(const OptsType& opts) {
if (opts.has_stride_h() != opts.has_stride_w())
- {
throw PassException("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, uint32_t stride)
-{
- if (hasStride)
- {
+static inline Shape getStrideFromOneValue(bool hasStride, uint32_t stride) {
+ if (hasStride) {
return Shape{static_cast<int32_t>(stride), static_cast<int32_t>(stride), 1};
}
else
- {
return Shape{1, 1, 1};
- }
}
-static inline Shape getStrideFromTwoValues(uint32_t stride1, uint32_t stride2)
-{
+static inline Shape getStrideFromTwoValues(uint32_t stride1, uint32_t stride2) {
return Shape{static_cast<int32_t>(stride1), static_cast<int32_t>(stride2), 1};
}
template <typename OptsType>
-static inline Shape getStride(const OptsType& opts)
-{
+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));
- }
}
/**
* @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)
-{
+__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)
-{
+__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)
-{
+__attribute__ ((unused)) static Shape getPoolWindowShape(const PoolingParameter &opts) {
if (opts.has_kernel_h() != opts.has_kernel_w())
- {
throw PassException("Pool layer has only 1 out of 2 kernel dimensions, investigate");
- }
- if (opts.has_kernel_h())
- {
+ if (opts.has_kernel_h()) {
return Shape{static_cast<int32_t>(opts.kernel_h()), static_cast<int32_t>(opts.kernel_w()), 1};
}
- else if (opts.has_kernel_size())
- {
+ else if (opts.has_kernel_size()) {
return Shape{static_cast<int32_t>(opts.kernel_size()), static_cast<int32_t>(opts.kernel_size()), 1};
}
else
- {
throw PassException("Pooling layer doesn't have kernel size data, investigate");
- }
}
-__attribute__ ((unused)) static ops::PoolOp::PoolingType getPoolingType(const PoolingParameter& opts)
-{
+__attribute__ ((unused)) static ops::PoolOp::PoolingType getPoolingType(const PoolingParameter& opts) {
using PoolingType = ops::PoolOp::PoolingType;
if (opts.pool() == PoolingParameter::MAX)
* @todo Decide how to process axis in general.
*/
template <typename OptsType>
-__attribute__ ((unused)) static int getAxisValue(const OptsType& opts)
-{
+__attribute__ ((unused)) static int getAxisValue(const OptsType& opts) {
// -1 represents last one dimension
int axis = -1;
- if (opts.has_axis())
- {
+ 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 != 1 && axis != -1)
- {
throw PassException("Softmax/Concat layer axis param is not 1 or -1, which implies"
"unsupported NN architecture.");
- }
}
// axis 1 represents channels in caffe, in Model ir it is second dimension for now
* @param foldedKernel original grouped kernel
* @return unfolded kernel, compatible with ordinary conv2D operation
*/
-static std::shared_ptr<IrTensor> fixGroupedKernel(int groups, std::shared_ptr<IrTensor> foldedKernel)
-{
+static std::shared_ptr<IrTensor> fixGroupedKernel(int groups, std::shared_ptr<IrTensor> foldedKernel) {
const int kernelInChanNum = 2;
const int kernelOutChanNum = 3;
assert(kernelOutChannels % groups == 0);
// Iterate over "unfolded" kernel Shape and insert appropriate values into result kernel
- for (const mir::Index &idx: mir::ShapeRange(unfoldKernelShape))
- {
+ for (const mir::Index &idx: mir::ShapeRange(unfoldKernelShape)) {
auto inGroupNo = idx.at(kernelInChanNum) / inGroupSize;
auto outGroupNo = idx.at(kernelOutChanNum) / outGroupSize;
// check that input channel group fits output channel group
- if (inGroupNo == outGroupNo)
- {
+ if (inGroupNo == outGroupNo) {
// compute index in original kernel that corresponds output index
mir::Index foldedIdx(idx);
foldedIdx.at(kernelInChanNum) %= inGroupSize;
std::copy(foldedKernel->at(foldedIdx), foldedKernel->at(foldedIdx) + dataSize, unfoldKernel->at(idx));
}
- else
- {
+ else {
// fill element of output kernel with zero element
assert(foldedKernel->getDataType() == IrTensor::DTYPE::FLOAT && "unsupported data type, add appropriate zero element creation");
float *elem = reinterpret_cast<float *>(unfoldKernel->at(idx));
return unfoldKernel;
}
-
-std::vector<INode::Ref> OpCreator::createConv2D(InputOps inputs, InputParams params,
- const caffe::ConvolutionParameter& opts)
-{
+void OpCreator::checkConv2D(const caffe::ConvolutionParameter& opts,
+ std::set<std::string> &problemsOpSet) {
assert(opts.stride_size() <= 2);
+ if (opts.pad_size() != 0 && (opts.has_pad_h() || opts.has_pad_w()))
+ problemsOpSet.insert("Conv2D: Conflicting padding properties");
+
+ if (opts.pad_size() > 2)
+ problemsOpSet.insert("Conv2D: Unsupported number of pads");
+}
+
+std::vector<INode::Ref> OpCreator::createConv2D(InputOps inputs, InputParams params,
+ const caffe::ConvolutionParameter& opts) {
ops::PaddingType padType = ops::PaddingType::Custom;
Shape strideShape = getConvStride(opts);
std::shared_ptr<IrTensor> unfoldedTensor = params[0];
- if (opts.group() != 1)
- {
+ if (opts.group() != 1) {
// first we need to convert kernel of grouped convolution to appropriate ordinary kernel
unfoldedTensor = fixGroupedKernel(opts.group(), params[0]);
}
// Set pads
auto *op = static_cast<ops::Conv2DOp *>(outputs[0]->getOperation());
- if (opts.pad_size() != 0 && (opts.has_pad_h() || opts.has_pad_w()))
- throw PassException("Conflicting padding properties in convolution");
-
int pad_h = opts.has_pad_h() ? opts.pad_h() : 0;
int pad_w = opts.has_pad_w() ? opts.pad_w() : 0;
- switch (opts.pad_size())
- {
- case 0:
- // no common padding property set
- break;
- case 1:
+ if (opts.pad_size() == 1)
pad_h = pad_w = opts.pad(0);
- break;
- default:
- throw PassException("Unsupported number of pads");
- }
+
op->setPadding(0, pad_h);
op->setPadding(1, pad_w);
op->setPadding(2, 0);
return outputs;
}
+void OpCreator::checkFullyConnected(const caffe::InnerProductParameter &opts,
+ std::set<std::string> &problemsOpSet) {
+ if (opts.has_axis() && opts.axis() != 1)
+ problemsOpSet.insert("Fully Connected: layer axis param is not supported yet");
+
+ if (opts.has_transpose() && opts.transpose())
+ problemsOpSet.insert("Fully Connected: layer transpose param is not supported yet");
+}
+
/**
* @brief Converts Caffe InnerProduct layer to Model IR FullyConnected operation.
* @todo InnerProduct layer take NCHW input and flattens the CHW part. We insert the
* @todo Support axis and transpose parameters as needed.
*/
std::vector<INode::Ref> OpCreator::createFullyConnected(InputOps &inputs, InputParams ¶ms,
- const caffe::InnerProductParameter &opts)
-{
- if (opts.has_axis() && opts.axis() != 1)
- {
- throw PassException("InnerProduct layer axis param is not supported yet");
- }
-
- if (opts.has_transpose() && opts.transpose())
- {
- throw PassException("InnerProduct layer transpose param is not supported yet");
- }
-
+ const caffe::InnerProductParameter &opts) {
// Add Reshape operation to make sure the input for FC operation has shape [1, fcInputSize]
// It is needed because Caffe InnerProduct layer takes NCHW input and flattens the CHW part.
auto outputs = createOp<ops::ReshapeOp>(inputs);
}
std::vector<INode::Ref> OpCreator::createConcat(InputOps inputs, InputParams params,
- const caffe::ConcatParameter& opts)
-{
+ const caffe::ConcatParameter& opts) {
(void)params;
return createOp<ops::ConcatOp>(inputs, inputs.size(), getAxisValue(opts));
}
+void OpCreator::checkPool(const caffe::PoolingParameter &opts,
+ std::set<std::string> &problemsOpSet) {
+ if (opts.has_global_pooling() && opts.global_pooling())
+ problemsOpSet.insert("Pooling: pooling layer global_pooling param is not supported yet");
+
+ ops::PoolOp::PoolingType poolType = getPoolingType(opts);
+ if (poolType != ops::PoolOp::PoolingType::AVG && poolType != ops::PoolOp::PoolingType::MAX)
+ problemsOpSet.insert("Pooling: unsupported pooling type");
+
+ if (opts.has_pad() && (opts.has_pad_h() || opts.has_pad_w()))
+ problemsOpSet.insert("Pooling: conflicting padding properties in pooling");
+}
+
std::vector<INode::Ref> OpCreator::createPool(InputOps inputs, InputParams params,
- const caffe::PoolingParameter& opts)
-{
+ const caffe::PoolingParameter& opts) {
(void)params;
- if (opts.has_global_pooling() && opts.global_pooling())
- {
- throw PassException("Pooling layer global_pooling param is not supported yet");
- }
-
Shape windowShape = getPoolWindowShape(opts);
ops::PoolOp::PoolingType poolType = getPoolingType(opts);
ops::PaddingType padType = ops::PaddingType::Custom;
Shape stride = getPoolStride(opts);
ops::PoolOp::BorderType borderType;
- switch (poolType)
- {
+ switch (poolType) {
case ops::PoolOp::PoolingType::AVG:
borderType = ops::PoolOp::BorderType::ZEROFILLED;
break;
borderType = ops::PoolOp::BorderType::EMPTY;
break;
default:
- throw PassException("Unsupported pooling type");
+ // This check performed in checkPool()
+ assert(false);
}
auto pooling = createOp<ops::PoolOp>(inputs, windowShape, stride, poolType, padType, borderType);
// Set pads
auto op = static_cast<ops::PoolOp *>(pooling[0]->getOperation());
- if (opts.has_pad() && (opts.has_pad_h() || opts.has_pad_w()))
- throw PassException("Conflicting padding properties in pooling");
-
int pad_h = opts.has_pad_h() ? opts.pad_h() : 0;
int pad_w = opts.has_pad_w() ? opts.pad_w() : 0;
if (opts.has_pad())
- {
pad_h = pad_w = opts.pad();
- }
op->setPadding(0, pad_h);
op->setPadding(1, pad_w);
op->setPadding(2, 0);
}
std::vector<INode::Ref> OpCreator::createSoftmax(InputOps inputs, InputParams params,
- const caffe::SoftmaxParameter& opts)
-{
+ const caffe::SoftmaxParameter& opts) {
(void)params;
return createOp<ops::SoftmaxOp>(inputs, getAxisValue(opts));
}
+void OpCreator::checkReshape(const caffe::ReshapeParameter &opts,
+ std::set<std::string> &problemsOpSet) {
+ if (opts.has_axis() || opts.has_num_axes())
+ problemsOpSet.insert("Reshape layer axis and num_axes params are not supported yet");
+
+ if (!opts.has_shape())
+ problemsOpSet.insert("Reshape layer doesn't have shape parameter");
+
+ Shape newShape = ShapeHelper::createShape(opts.shape().dim(), opts.shape().dim_size());
+
+ for (int32_t i = 0; i < newShape.rank(); ++i)
+ if (newShape.dim(i) == 0)
+ problemsOpSet.insert("Reshape layer zero shape values are not supported yet");
+}
+
/**
* @brief Converts Caffe Reshape layer to Model IR Reshape operation.
* @todo Support "axis" and "num_axes" parameters as needed.
* @todo Support zero values in "shape" parameter.
*/
std::vector<INode::Ref> OpCreator::createReshape(InputOps inputs, InputParams params,
- const caffe::ReshapeParameter& opts)
-{
+ const caffe::ReshapeParameter& opts) {
(void)params;
auto outputs = createOp<ops::ReshapeOp>(inputs);
- if (opts.has_axis() || opts.has_num_axes())
- {
- throw PassException("Reshape layer axis and num_axes params are not supported yet");
- }
-
- if (!opts.has_shape())
- {
- throw PassException("Reshape layer doesn't have shape parameter");
- }
-
Shape newShape = ShapeHelper::createShape(opts.shape().dim(), opts.shape().dim_size());
- for (int32_t i = 0; i < newShape.rank(); ++i)
- {
- if (newShape.dim(i) == 0)
- throw PassException("Reshape layer zero shape values are not supported yet");
- }
-
outputs[0]->getOperation()->setOutputShape(0, newShape);
return outputs;
}
+void OpCreator::checkRelu(const caffe::ReLUParameter &opts,
+ std::set<std::string> &problemsOpSet) {
+ if (opts.has_negative_slope())
+ problemsOpSet.insert("ReLU layer negative_slope param is not supported yet.");
+}
+
std::vector<INode::Ref> OpCreator::createRelu(InputOps inputs, InputParams params,
- const caffe::ReLUParameter& opts)
-{
+ const caffe::ReLUParameter& opts) {
(void)params;
- if (opts.has_negative_slope())
- {
- throw PassException("ReLU layer negative_slope param is not supported yet.");
- }
-
return createOp<ops::ReluOp>(inputs);
}
-std::vector<INode::Ref> OpCreator::createScale(InputOps inputs, InputParams params, const ScaleParameter& opts)
-{
+std::vector<INode::Ref> OpCreator::createScale(InputOps inputs, InputParams params, const ScaleParameter& opts) {
auto outputs = createOp<ops::ScaleOp>(inputs, std::move(*params[0]));
// bias_term is optional (so might not be present) and defaults to true
if (!opts.has_bias_term() || opts.bias_term())
return outputs;
}
-std::vector<INode::Ref> OpCreator::createBatchNorm(InputOps inputs, InputParams params, const BatchNormParameter& opts)
-{
+void OpCreator::checkBatchNorm(const caffe::BatchNormParameter &opts, InputParams params,
+ std::set<std::string> &problemsOpSet) {
+ // Check that last blob(with scaleFactor) containing only one number
+ if (params[2]->getShape().rank() != 1 && params[2]->getShape().dim(0) != 1)
+ problemsOpSet.insert("Unexpected shape of scale parameter in batch norm");
+}
+
+std::vector<INode::Ref> OpCreator::createBatchNorm(InputOps inputs, InputParams params, const BatchNormParameter& opts) {
const float MAFRAC_DEF = 0.999f;
const float EPS_DEF = 1e-5f;
// optional params may be left out, so we fill them with defalt values (lifted from caffe docs)
(void)moving_average_fraction;
float eps = (opts.has_eps()) ? opts.eps() : EPS_DEF;
- // Check that last blob(with scaleFactor) containing only one number
- if (params[2]->getShape().rank() != 1 && params[2]->getShape().dim(0) != 1)
- throw PassException("Unexpected shape of scale parameter in batch norm");
-
float scaleFactor = *reinterpret_cast<float *>(params[2]->at(mir::Index{0}));
// Code below is taken from cpu caffe implementation:
// https://github.com/BVLC/caffe/blob/master/src/caffe/layers/batch_norm_layer.cpp#L100
Tensor<float> biasData(*params[0]);
for (Index idx: ShapeRange(biasData.getShape()))
- {
biasData.at(idx) *= -scaleFactor;
- }
auto meanOutputs = createOp<ops::BiasAddOp>(inputs, std::move(*params[0]));
// create scale argument from variance:
// normalize biased input using scale operation
Tensor<float> scaleData(*params[1]);
for (Index idx: ShapeRange(scaleData.getShape()))
- {
scaleData.at(idx) = 1.0f/std::sqrt(scaleData.at(idx)*scaleFactor + eps);
- }
auto varianceOutputs = createOp<ops::ScaleOp>(meanOutputs, std::move(*params[1]));
return varianceOutputs;
}
-std::vector<INode::Ref> OpCreator::createDropout(InputOps inputs, InputParams params, const DropoutParameter& opts)
-{
+std::vector<INode::Ref> OpCreator::createDropout(InputOps inputs, InputParams params, const DropoutParameter& opts) {
(void)params;
const float DROPOUT_RATIO_DEF = 0.5f;
// optional params may be left out, so we fill them with defalt values (lifted from caffe docs)
return createOp<ops::DropoutOp>(inputs, dropot_ratio);
}
-void OpCreator::connectInputs(INode::Ref op, InputOps inputs)
-{
+void OpCreator::connectInputs(INode::Ref op, InputOps inputs) {
// TODO: this part doesn't support the situation where an operator takes as input
// some tensor that is not the 0th output of some other operator
for (int i = 0; i < static_cast<int>(inputs.size()); ++i)
- {
op->connectInputTo(i, inputs[i]->getOutput(0));
- }
}
-} // namespace caffe
} // namespace nnc
#ifndef NNCC_CAFFE_OP_CREATOR_H
#define NNCC_CAFFE_OP_CREATOR_H
+#include <set>
#include <map>
#include <vector>
#include <memory>
#include "caffe/proto/caffe.pb.h"
-namespace nnc
-{
-namespace caffe
-{
+namespace nnc {
using namespace ::caffe;
-namespace ops = nnc::mir::ops;
using nnc::mir::Graph;
using nnc::mir::INode;
using IrTensor = nnc::mir::TensorVariant;
using nnc::mir::Shape;
-class OpCreator
-{
+class OpCreator {
public:
using InputOps = std::vector<INode::Ref>&;
using InputParams = std::vector<std::shared_ptr<IrTensor>>&;
std::vector<INode::Ref> createBatchNorm(InputOps inputs, InputParams params, const BatchNormParameter& opts);
std::vector<INode::Ref> createDropout(InputOps inputs, InputParams params, const DropoutParameter& opts);
+ static void checkConv2D(const caffe::ConvolutionParameter& opts, std::set<std::string> &unsupportedOpSet);
+ static void checkFullyConnected(const caffe::InnerProductParameter &opts, std::set<std::string> &problemsOpSet);
+ static void checkPool(const caffe::PoolingParameter &opts, std::set<std::string> &problemsOpSet);
+ static void checkReshape(const caffe::ReshapeParameter &opts, std::set<std::string> &problemsOpSet);
+ static void checkRelu(const caffe::ReLUParameter &opts, std::set<std::string> &problemsOpSet);
+ static void checkBatchNorm(const caffe::BatchNormParameter &opts, InputParams params,
+ std::set<std::string> &problemsOpSet);
+
private:
Graph* graph = nullptr;
};
template<typename OpType, typename ...Types>
-std::vector<INode::Ref> OpCreator::createOp(std::vector<INode::Ref>& inputs, Types&&... args)
-{
+std::vector<INode::Ref> OpCreator::createOp(std::vector<INode::Ref>& inputs, Types&&... args) {
std::vector<INode::Ref> outputs;
// TODO: set operation names
return outputs;
}
-} // namespace caffe
} // namespace nnc
#endif //NNCC_CAFFE_OP_CREATOR_H
--- /dev/null
+/*
+ * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NNCC_CAFFE_OP_TYPES_H
+#define NNCC_CAFFE_OP_TYPES_H
+
+enum class CaffeOpType {
+ absVal,
+ accuracy,
+ argMax,
+ batchNorm,
+ batchReindex,
+ bias,
+ BNLL,
+ clip,
+ concat,
+ contrastiveLoss,
+ convolution,
+ crop,
+ data,
+ deconvolution,
+ dropout,
+ dummyData,
+ eltwise,
+ ELU,
+ embed,
+ euclidianLoss,
+ exp,
+ filter,
+ flatten,
+ HDF5Data,
+ HDF5Output,
+ hingeLoss,
+ im2Col,
+ imageData,
+ infogainLoss,
+ innerProduct,
+ input,
+ log,
+ LRN,
+ LSTM,
+ memoryData,
+ multinomialLogisticLoss,
+ MVN,
+ parameter,
+ pooling,
+ power,
+ PReLU,
+ python,
+ recurrent,
+ reduction,
+ ReLU,
+ reshape,
+ RNN,
+ scale,
+ sigmoidCrossEntropyLoss,
+ sigmoid,
+ silence,
+ slice,
+ softmax,
+ softmaxLoss,
+ split,
+ SPP,
+ tanh,
+ threshold,
+ tile,
+ windowData
+};
+
+#endif //NNCC_CAFFE_OP_TYPES_H
+++ /dev/null
-/*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef NNCC_CAFFE_VISITOR_H
-#define NNCC_CAFFE_VISITOR_H
-
-#include "caffe/proto/caffe.pb.h"
-
-namespace nnc
-{
-namespace caffe
-{
-
-using namespace ::caffe;
-
-class Visitor
-{
-public:
- virtual ~Visitor() = default;
-
- virtual void visit(const NetParameter&) = 0;
- virtual void visit(const LayerParameter&) = 0;
- virtual void visit(const BlobProto&) = 0;
- virtual void visit(const BlobShape&) = 0;
-};
-
-} // namespace caffe
-} // namespace nnc
-
-#endif //NNCC_CAFFE_VISITOR_H
+++ /dev/null
-/*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "caffe_walker.h"
-
-namespace nnc
-{
-namespace caffe
-{
-
-void ModelWalker::walkNetParameter(const NetParameter& np)
-{
- visitor->visit(np);
-
- for (int i = 0; i < np.layer_size(); ++i)
- {
- walkLayerParameter(np.layer(i));
- }
-
- for (int i = 0; i < np.input_shape_size(); ++i)
- {
- visitor->visit(np.input_shape(i));
- }
-}
-
-void ModelWalker::walkLayerParameter(const LayerParameter& lp)
-{
- visitor->visit(lp);
-
- for (int i = 0; i < lp.blobs_size(); ++i)
- {
- visitor->visit(lp.blobs(i));
- }
-}
-
-} // namespace caffe
-} // namespace nnc
+++ /dev/null
-/*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef NNCC_CAFFE_WALKER_H
-#define NNCC_CAFFE_WALKER_H
-
-#include "caffe/proto/caffe.pb.h"
-
-#include "caffe_visitor.h"
-
-namespace nnc
-{
-namespace caffe
-{
-
-using namespace ::caffe;
-
-class ModelWalker
-{
-public:
- ModelWalker(Visitor* visitor) : visitor(visitor) {};
-
- void walkNetParameter(const NetParameter&);
- void walkLayerParameter(const LayerParameter&);
-
-private:
- Visitor* visitor;
-};
-
-} // namespace caffe
-} // namespace nnc
-
-#endif // NNCC_CAFFE_WALKER_H
#include "proto_reader.h"
-namespace nnc
-{
-namespace caffe
-{
+namespace nnc {
const int protoBytesLimit = INT_MAX;
const int protoBytesWarningLimit = 1024 * 1024 * 512;
-bool readProtoFromTextFile(const char* filename, ::caffe::NetParameter* proto)
-{
+bool readProtoFromTextFile(const char* filename, ::caffe::NetParameter* proto) {
int fd = open(filename, O_RDONLY);
- if (fd == -1)
- {
+ if (fd == -1) {
std::cout << "File not found: " << filename << std::endl;
return false;
}
return success;
}
-bool readProtoFromBinaryFile(const char* filename, ::caffe::NetParameter* proto)
-{
+bool readProtoFromBinaryFile(const char* filename, ::caffe::NetParameter* proto) {
int fd = open(filename, O_RDONLY);
- if (fd == -1)
- {
+ if (fd == -1) {
std::cout << "File not found: " << filename << std::endl;
return false;
}
return success;
}
-} // namespace caffe
} // namespace nnc
#include "google/protobuf/io/zero_copy_stream_impl.h"
#include "google/protobuf/text_format.h"
-namespace nnc
-{
-namespace caffe
-{
+namespace nnc {
using google::protobuf::io::FileInputStream;
using google::protobuf::io::ZeroCopyInputStream;
bool readProtoFromTextFile(const char* filename, ::caffe::NetParameter* proto);
bool readProtoFromBinaryFile(const char* filename, ::caffe::NetParameter* proto);
-} // namespace caffe
} // namespace nnc
#endif // NNCC_PROTO_READER_H
{
nnc::tflite::v3::TfliteImporter importer{cli::inputFile};
- bool success = importer.import();
-
- if (!success)
- {
- throw PassException("Could not load model: " + cli::inputFile + "\n");
- }
+ importer.import();
return reinterpret_cast<mir::Graph *>(importer.createIR());
}
TfliteImporter::TfliteImporter(std::string filename)
{
+ _modelFilename = filename;
modelRaw.reset(new ModelAllocation{std::move(filename)});
}
-bool TfliteImporter::importUnpacked()
+void TfliteImporter::importUnpacked()
{
- bool importSuccess = import();
+ import();
- if (importSuccess)
- {
- model.reset(modelPacked->UnPack());
- }
-
- return importSuccess;
+ model.reset(modelPacked->UnPack());
}
-bool TfliteImporter::import()
+void TfliteImporter::import()
{
const void *modelBuffer = modelRaw->getDataPnt();
if (!modelBuffer)
- {
- return false;
- }
+ throw PassException("Could not load model: " + _modelFilename+ "\n");
auto verifier = flatbuffers::Verifier(reinterpret_cast<const uint8_t *>(modelBuffer),
modelRaw->getNumBytes());
- bool importSuccess = VerifyModelBuffer(verifier);
- if (importSuccess)
- {
- modelPacked = GetModel(modelRaw->getDataPnt());
- }
+ if (!VerifyModelBuffer(verifier))
+ throw PassException("Could not load model: " + _modelFilename + "\n");
- return importSuccess;
+ modelPacked = GetModel(modelRaw->getDataPnt());
}
-void *TfliteImporter::createIR()
+Graph *TfliteImporter::createIR()
{
IrVisitor irCreator{};
ModelWalker walker{std::vector<Visitor *>{&irCreator}};
return irCreator.getGraph();
}
-void TfliteImporter::dump()
-{
- DumpVisitor dumper{};
- ModelWalker walker{std::vector<Visitor *>{&dumper}};
-
- walker.walk(modelPacked);
-}
-
* limitations under the License.
*/
-
-class TfliteImporter : NNImporter
-{
+class TfliteImporter : NNImporter {
public:
explicit TfliteImporter(std::string filename);
- bool import() override;
- void *createIR() override;
- void dump() override;
+ void import() override;
+ mir::Graph *createIR() override;
- bool importUnpacked();
+ void importUnpacked();
protected:
+ std::string _modelFilename;
std::unique_ptr<ModelAllocation> modelRaw;
std::unique_ptr<ModelT> model;
const Model *modelPacked = nullptr;
#include "schema_v3.h"
#include "passes/common_frontend/nn_importer.h"
#include "passes/common_frontend/model_allocation.h"
+#include "pass/PassException.h"
namespace nnc
{
using namespace nnc;
-int main(int argc, const char **argv)
-{
+int main(int argc, const char **argv) {
if (argc != 2)
- {
return 1;
- }
cli::CommandLine::getParser()->parseCommandLine(argc, argv);
std::string modelName = cli::inputFile;
- nnc::caffe::CaffeImporter importer{modelName};
-
- bool success = importer.import();
+ nnc::CaffeImporter importer{modelName};
- if (!success)
- {
- std::cout << "Could not load model \"" << modelName << "\"" << std::endl;
- return 1;
- }
+ importer.import();
- try
- {
+ try {
importer.createIR();
}
- catch (...)
- {
+ catch (...) {
std::cout << "Could not create IR for model \"" << modelName << "\"" << std::endl;
return 1;
}
nnc::tflite::v3::TfliteImporter importer{modelName};
- bool success = importer.import();
-
- if (!success)
- {
- std::cout << "Could not load model \"" << modelName << "\"" << std::endl;
- return 1;
- }
+ importer.import();
try
{
add_subdirectory(core)
add_subdirectory(soft_backend)
add_subdirectory(support)
+add_subdirectory(caffe_frontend)
--- /dev/null
+file(GLOB_RECURSE TESTS "*.cpp")
+
+add_definitions(-DTEST_DIR="${CMAKE_CURRENT_SOURCE_DIR}/test_data/")
+
+add_nnc_unit_test(nnc_caffe_frontend_test ${TESTS} ${OPTIONS_SRC})
+if (TARGET nnc_caffe_frontend_test)
+ nncc_target_link_libraries(nnc_caffe_frontend_test caffe_importer nnc_support nnc_core)
+ target_include_directories(nnc_caffe_frontend_test PRIVATE ${NNC_CAFFE_FRONTEND_DIR})
+endif()
--- /dev/null
+#include "caffe_importer.h"
+#include "gtest/gtest.h"
+#include "pass/PassException.h"
+#include <string>
+#include <iostream>
+
+const char *ErrorMsg = "Detected problems:\n"
+ "DummyData: unsupported layer\n"
+ "LSTM: unsupported layer\n"
+ "Sasha: unknown layer\n";
+
+// When adding support for new layers, change the model, not the test
+TEST(CAFFE_IMPORT_UNSUPPORTED, ImportAModelWithUnsupportedLayers) {
+ std::string filename = std::string(TEST_DIR) + "unsupported.caffemodel";
+
+ nnc::CaffeImporter importer{filename};
+ try {
+ importer.import();
+ importer.createIR();
+ }
+ catch (nnc::PassException &e) {
+ ASSERT_EQ(ErrorMsg, e.reason());
+ return;
+ }
+
+ FAIL();
+}