From c730e0b1c8a6f89e3d1eca7d9ea6369f12725050 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=91=D0=B0=D1=80?= =?utf8?q?=D0=B0=D0=BD=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2/AI=20Tools=20Lab=20/S?= =?utf8?q?RR/Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Mon, 28 Jan 2019 18:52:01 +0300 Subject: [PATCH] [nnc] Add OutputOp class to represent graph outputs (#2930) * Add OutputOp class to represent graph outputs. * Add generation of instances of the class in importers. * Modify backends to account for the new graph structure. Signed-off-by: Sergei Barannikov --- contrib/nnc/core/modelIR/Graph.cpp | 94 +++++----------------- contrib/nnc/core/modelIR/IrDotDumper.cpp | 13 ++- contrib/nnc/core/modelIR/Operation.cpp | 1 + contrib/nnc/include/core/modelIR/Graph.h | 59 ++++---------- contrib/nnc/include/core/modelIR/IrDotDumper.h | 1 + .../nnc/include/core/modelIR/operations/OutputOp.h | 35 ++++++++ .../core/modelIR/operations/operations.lst.h | 1 + .../nnc/include/passes/interpreter/Interpreter.h | 3 +- .../passes/acl_soft_backend/AclCppOpGenerator.cpp | 11 ++- .../passes/acl_soft_backend/AclCppOpGenerator.h | 1 + .../nnc/passes/caffe2_frontend/caffe2_importer.cpp | 4 +- .../passes/caffe2_frontend/caffe2_op_creator.cpp | 1 + .../nnc/passes/caffe_frontend/caffe_importer.cpp | 5 +- .../nnc/passes/caffe_frontend/caffe_op_creator.cpp | 1 + contrib/nnc/passes/interpreter/Interpreter.cpp | 13 +-- .../nnc/passes/interpreter/interpreter_pass.cpp | 35 ++------ .../nnc/passes/onnx_frontend/ONNXImporterImpl.cpp | 7 +- contrib/nnc/passes/soft_backend/ModelAnalyzer.cpp | 23 ++++-- contrib/nnc/passes/soft_backend/ModelAnalyzer.h | 3 +- contrib/nnc/passes/soft_backend/SBSerializer.cpp | 4 + contrib/nnc/passes/soft_backend/SBSerializer.h | 5 +- .../soft_backend/code_snippets/cpp_operations.def | 4 + .../nnc/passes/tflite_frontend/tflite_importer.cpp | 13 ++- .../passes/tflite_frontend/tflite_op_creator.cpp | 1 + contrib/nnc/tests/soft_backend/CompileCPP.cpp | 12 +-- contrib/nnc/unittests/acl_backend/MIRToDOM.cpp | 17 ++-- contrib/nnc/unittests/core/Graph.cpp | 31 ++----- .../nnc/unittests/soft_backend/CPPOperations.cpp | 25 +++--- 28 files changed, 193 insertions(+), 230 deletions(-) create mode 100644 contrib/nnc/include/core/modelIR/operations/OutputOp.h diff --git a/contrib/nnc/core/modelIR/Graph.cpp b/contrib/nnc/core/modelIR/Graph.cpp index 237d56a..c82ff64 100644 --- a/contrib/nnc/core/modelIR/Graph.cpp +++ b/contrib/nnc/core/modelIR/Graph.cpp @@ -52,34 +52,15 @@ static void replaceUsages(const Operation* op, Operation* with) { with->getMutablePrevNodes() = op->getPrevNodes(); } -Operation* Graph::getInput(const std::string& name) { - auto it = _inputs.find(name); - if (it == _inputs.end()) - return nullptr; - else - return it->second; -} - -Operation* Graph::getOutput(const std::string& name) { - auto it = _outputs.find(name); - if (it == _outputs.end()) - return nullptr; - else - return it->second; -} - void Graph::accept(IVisitor* visitor) { std::deque q; std::set known_ops; - for (const auto& e : _inputs) { - q.push_back(e.second); - known_ops.insert(e.second); //Consider all input _ops resolved by default - } - - for (const auto& e : _constants) { - q.push_back(e); - known_ops.insert(e); //Consider all input _ops resolved by default + for (auto* op : _ops) { + if (op->getNumInputs() == 0) { + q.emplace_back(op); + known_ops.insert(op); + } } //BFS @@ -111,53 +92,28 @@ Graph::~Graph() { } } -void Graph::markOutput(Operation* op) { - auto it = _outputs.find(op->getName()); - if (it != _outputs.end()) { - throw std::runtime_error("Output node with same name already exists"); - } +void Graph::registerOp(Operation* op) { + _ops.push_back(op); - _outputs[op->getName()] = op; -} + if (auto* input_op = dynamic_cast(op)) + _inputs.emplace_back(input_op); -std::vector Graph::collectInputs() const { - std::vector res; - for (auto& e : _inputs) { - res.emplace_back(e.second); - } - return res; -} - -std::vector Graph::collectConstants() const { - std::vector res; - for (auto& e : _constants) { - res.emplace_back(e); - } - return res; -} - -std::vector Graph::collectOutputs() const { - std::vector res; - for (auto& e : _outputs) { - res.emplace_back(e.second); - } - return res; + if (auto* output_op = dynamic_cast(op)) + _outputs.emplace_back(output_op); } void Graph::replaceNode(const Operation* op, Operation* with) { - auto in = _inputs.find(op->getName()); - if (in != _inputs.end()) { - (*in).second = with; - } + replaceUsages(op, with); - auto out_it = _outputs.find(op->getName()); - if (out_it != _outputs.end()) { - (*out_it).second = with; - } + _inputs.erase(std::remove_if(_inputs.begin(), _inputs.end(), [op](ops::InputOp* n) { + return n == op; + }), _inputs.end()); - replaceUsages(op, with); + _outputs.erase(std::remove_if(_outputs.begin(), _outputs.end(), [op](ops::OutputOp* n) { + return n == op; + }), _outputs.end()); - _ops.erase(std::remove_if(_ops.begin(), _ops.end(), [op] (Operation* n) { + _ops.erase(std::remove_if(_ops.begin(), _ops.end(), [op](Operation* n) { return n == op; }), _ops.end()); } @@ -198,17 +154,5 @@ void Graph::replaceInputNodes(const std::vector& new_inputs) { } } -void Graph::replaceOutputNodes(const std::vector& new_outputs) { - _outputs.clear(); - - std::set new_outputs_set(new_outputs.begin(), new_outputs.end()); - - for (auto& op : _ops) { - if (new_outputs_set.count(op->getName()) != 0) { - markOutput(op); - } - } -} - } // namespace mir } // namespace nnc diff --git a/contrib/nnc/core/modelIR/IrDotDumper.cpp b/contrib/nnc/core/modelIR/IrDotDumper.cpp index 62c400e..96d704e 100644 --- a/contrib/nnc/core/modelIR/IrDotDumper.cpp +++ b/contrib/nnc/core/modelIR/IrDotDumper.cpp @@ -14,9 +14,8 @@ * limitations under the License. */ -#include - #include "core/modelIR/IrDotDumper.h" + #include "core/modelIR/operations/BatchNormOp.h" #include "core/modelIR/operations/BiasAddOp.h" #include "core/modelIR/operations/CappedReluOp.h" @@ -33,6 +32,7 @@ #include "core/modelIR/operations/GemmOp.h" #include "core/modelIR/operations/InputOp.h" #include "core/modelIR/operations/LeakyReluOp.h" +#include "core/modelIR/operations/OutputOp.h" #include "core/modelIR/operations/PadOp.h" #include "core/modelIR/operations/PoolOp.h" #include "core/modelIR/operations/ReduceFOp.h" @@ -48,6 +48,8 @@ #include "core/modelIR/operations/TanhOp.h" #include "core/modelIR/operations/TransposeOp.h" +#include + namespace nnc { namespace mir { @@ -353,6 +355,13 @@ void IrDotDumper::visit(mir::ops::LeakyReluOp& op) { dotBuilder.updateWithOp(&op, node_info); } +void IrDotDumper::visit(ops::OutputOp& op) { + auto node_info = DotIrNodeInfo().withType("OutputOp", op.getName()) + .withInShapes(getInputShapes(op)); + + dotBuilder.updateWithOp(&op, node_info); +} + } // namespace mir } // namespace nnc diff --git a/contrib/nnc/core/modelIR/Operation.cpp b/contrib/nnc/core/modelIR/Operation.cpp index a44c5a3..20a1ee6 100644 --- a/contrib/nnc/core/modelIR/Operation.cpp +++ b/contrib/nnc/core/modelIR/Operation.cpp @@ -31,6 +31,7 @@ #include "core/modelIR/operations/GemmOp.h" #include "core/modelIR/operations/InputOp.h" #include "core/modelIR/operations/LeakyReluOp.h" +#include "core/modelIR/operations/OutputOp.h" #include "core/modelIR/operations/PadOp.h" #include "core/modelIR/operations/PoolOp.h" #include "core/modelIR/operations/ReduceFOp.h" diff --git a/contrib/nnc/include/core/modelIR/Graph.h b/contrib/nnc/include/core/modelIR/Graph.h index 6df4ea0..2f82fb2 100644 --- a/contrib/nnc/include/core/modelIR/Graph.h +++ b/contrib/nnc/include/core/modelIR/Graph.h @@ -24,8 +24,8 @@ #include #include "core/modelIR/Operation.h" -#include "core/modelIR/operations/ConstantOp.h" #include "core/modelIR/operations/InputOp.h" +#include "core/modelIR/operations/OutputOp.h" namespace nnc { namespace mir { @@ -51,28 +51,23 @@ class Graph { void accept(IVisitor* visitor); - void markOutput(Operation* op); - Operation* getInput(const std::string& name); - Operation* getOutput(const std::string& name); - /** - * @brief Returns all inputs from graph - * @returns vector containing all graph input nodes + * @brief Returns all graph nodes + * @return vector containing all graph nodes */ - std::vector collectInputs() const; + const std::vector& getNodes() const { return _ops; } /** - * @brief Returns all constants from graph - * @returns vector containing all graph constant nodes + * @brief Returns all graph input nodes + * @returns vector containing all graph input nodes */ - std::vector collectConstants() const; + const std::vector& getInputs() const { return _inputs; } /** - * @brief Returns all outputs from graph - * @returns vector containing all graph outputs nodes + * @brief Returns all graph output nodes + * @returns vector containing all graph output nodes */ - std::vector collectOutputs() const; - + const std::vector& getOutputs() const { return _outputs; } /** * @brief Subsitude node in graph with another keeping all edges @@ -96,39 +91,13 @@ class Graph { */ void replaceInputNodes(const std::vector& new_inputs); - /** - * @brief Change graph outputs to nodes with names in newOutputs - * @param new_outputs names of nodes to be marked as output nodes - * @warning Output node order is not preserved and may differ from newOutputs vector - * @note Does essentially the same as markOutput() does, but takes node names - */ - void replaceOutputNodes(const std::vector& new_outputs); - - private: - void registerOp(Operation* op) { - _ops.push_back(op); - } - - //TODO: maybe make user to mark input _ops in a more obvious way - void registerOp(ops::InputOp* op) { - auto it = _inputs.find(op->getName()); - if( it != _inputs.end()) { - throw std::runtime_error("Input name collision"); - } - _inputs.insert(it, {op->getName(), op}); - _ops.push_back(op); - } - - void registerOp(ops::ConstantOp* op) { - _constants.insert(op); - _ops.push_back(op); - } +private: + void registerOp(Operation* op); std::vector _ops; size_t _lastNodeId = 0; - std::unordered_map _inputs; - std::unordered_map _outputs; - std::set _constants; + std::vector _inputs; + std::vector _outputs; }; } // namespace mir diff --git a/contrib/nnc/include/core/modelIR/IrDotDumper.h b/contrib/nnc/include/core/modelIR/IrDotDumper.h index b20903d..6604801 100644 --- a/contrib/nnc/include/core/modelIR/IrDotDumper.h +++ b/contrib/nnc/include/core/modelIR/IrDotDumper.h @@ -47,6 +47,7 @@ public: void visit(ops::GemmOp& op) override; void visit(ops::InputOp& op) override; void visit(ops::LeakyReluOp& op) override; + void visit(ops::OutputOp& op) override; void visit(ops::PadOp& op) override; void visit(ops::PoolOp& op) override; void visit(ops::ReduceFOp& op) override; diff --git a/contrib/nnc/include/core/modelIR/operations/OutputOp.h b/contrib/nnc/include/core/modelIR/operations/OutputOp.h new file mode 100644 index 0000000..c497835 --- /dev/null +++ b/contrib/nnc/include/core/modelIR/operations/OutputOp.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 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 _NNC_CORE_IR_MODEL_OUTPUT_H_ +#define _NNC_CORE_IR_MODEL_OUTPUT_H_ + +#include "core/modelIR/Operation.h" + +namespace nnc { +namespace mir { +namespace ops { + +class OutputOp : public Operation { +public: + explicit OutputOp(IODescriptor input) : Operation(Type::output, {input}) {} +}; + +} // namespace ops +} // namespace mir +} // namespace nnc + +#endif //_NNC_CORE_IR_MODEL_OUTPUT_H_ diff --git a/contrib/nnc/include/core/modelIR/operations/operations.lst.h b/contrib/nnc/include/core/modelIR/operations/operations.lst.h index 51afdd2..dfc0fb8 100644 --- a/contrib/nnc/include/core/modelIR/operations/operations.lst.h +++ b/contrib/nnc/include/core/modelIR/operations/operations.lst.h @@ -34,6 +34,7 @@ HANDLE_OP(gather, GatherOp) HANDLE_OP(gemmOp, GemmOp) HANDLE_OP(input, InputOp) HANDLE_OP(leakyReLU, LeakyReluOp) +HANDLE_OP(output, OutputOp) HANDLE_OP(pad, PadOp) HANDLE_OP(pool, PoolOp) HANDLE_OP(reduceF, ReduceFOp) diff --git a/contrib/nnc/include/passes/interpreter/Interpreter.h b/contrib/nnc/include/passes/interpreter/Interpreter.h index 1d9fd26..dcc499c 100644 --- a/contrib/nnc/include/passes/interpreter/Interpreter.h +++ b/contrib/nnc/include/passes/interpreter/Interpreter.h @@ -53,6 +53,7 @@ public: void visit(ops::GemmOp& op) override; void visit(ops::InputOp& op) override; void visit(ops::LeakyReluOp& op) override; + void visit(ops::OutputOp& op) override; void visit(ops::PadOp& op) override; void visit(ops::PoolOp& op) override; void visit(ops::ReduceFOp& op) override; @@ -69,7 +70,7 @@ public: void visit(ops::TransposeOp& op) override; void setInput(const std::string &name, const TensorVariant& data); - std::vector &getResult(Operation* op); + TensorVariant getResult(IODescriptor tensor); void dump(Operation& op, bool all = false); ~NNInterpreter() override = default; diff --git a/contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.cpp b/contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.cpp index 9b745f3..ff32854 100644 --- a/contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.cpp +++ b/contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.cpp @@ -36,6 +36,7 @@ #include "core/modelIR/operations/GemmOp.h" #include "core/modelIR/operations/InputOp.h" #include "core/modelIR/operations/LeakyReluOp.h" +#include "core/modelIR/operations/OutputOp.h" #include "core/modelIR/operations/PadOp.h" #include "core/modelIR/operations/PoolOp.h" #include "core/modelIR/operations/ReduceFOp.h" @@ -989,7 +990,7 @@ shared_ptr AclCppOpGenerator::genTensor(IODescriptor ir_tensor) { } void AclCppOpGenerator::genNamed(Graph* graph) { - const auto& inputs = graph->collectInputs(); + const auto& inputs = graph->getInputs(); if (inputs.size() == 1) { auto f = _artifactClass->func(true, "arm_compute::CLTensor&", "getInput"); auto b = f->getBlock(); @@ -997,11 +998,11 @@ void AclCppOpGenerator::genNamed(Graph* graph) { b->ret(id); } - const auto& outputs = graph->collectOutputs(); + const auto& outputs = graph->getOutputs(); if (outputs.size() == 1) { auto f = _artifactClass->func(true, "arm_compute::CLTensor&", "getOutput"); auto b = f->getBlock(); - auto id = AF::id(tensorName(outputs[0]->getOutput(0))); + auto id = AF::id(tensorName(outputs[0]->getInput(0))); b->ret(id); } } @@ -1162,5 +1163,9 @@ void AclCppOpGenerator::visit(mir::ops::LeakyReluOp& op) { genActivation(op, "LEAKY_RELU", op.getAlpha()); } +void AclCppOpGenerator::visit(mir::ops::OutputOp&) { + // No-op. +} + } // namespace nnc diff --git a/contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.h b/contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.h index 6b37ff6..c4de819 100644 --- a/contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.h +++ b/contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.h @@ -63,6 +63,7 @@ public: void visit(mir::ops::GemmOp& op) override; void visit(mir::ops::InputOp& op) override; void visit(mir::ops::LeakyReluOp& op) override; + void visit(mir::ops::OutputOp& op) override; void visit(mir::ops::PadOp& op) override; void visit(mir::ops::PoolOp& op) override; void visit(mir::ops::ReduceFOp& op) override; diff --git a/contrib/nnc/passes/caffe2_frontend/caffe2_importer.cpp b/contrib/nnc/passes/caffe2_frontend/caffe2_importer.cpp index c606fd1..46a64e0 100644 --- a/contrib/nnc/passes/caffe2_frontend/caffe2_importer.cpp +++ b/contrib/nnc/passes/caffe2_frontend/caffe2_importer.cpp @@ -22,6 +22,7 @@ #include "caffe2_op_types.h" #include "caffe2_op_creator.h" +#include "core/modelIR/operations/OutputOp.h" #include "core/modelIR/Shape.h" #include "pass/PassException.h" @@ -279,8 +280,7 @@ void Caffe2Importer::setGraphOutputs() { // For now, we assume that: // - there is exactly one output; // - the output is from the last layer. - _lastMIROp->setName("out"); - _graph->markOutput(_lastMIROp); + _graph->create("out", _lastMIROp->getOutput(0)); } const std::map Caffe2Importer::_operatorTypes = { diff --git a/contrib/nnc/passes/caffe2_frontend/caffe2_op_creator.cpp b/contrib/nnc/passes/caffe2_frontend/caffe2_op_creator.cpp index 0de8459..e9da1e2 100644 --- a/contrib/nnc/passes/caffe2_frontend/caffe2_op_creator.cpp +++ b/contrib/nnc/passes/caffe2_frontend/caffe2_op_creator.cpp @@ -18,6 +18,7 @@ #include "core/modelIR/operations/BiasAddOp.h" #include "core/modelIR/operations/CappedReluOp.h" #include "core/modelIR/operations/ConcatOp.h" +#include "core/modelIR/operations/ConstantOp.h" #include "core/modelIR/operations/Conv2DOp.h" #include "core/modelIR/operations/DepthwiseConv2DOp.h" #include "core/modelIR/operations/DropoutOp.h" diff --git a/contrib/nnc/passes/caffe_frontend/caffe_importer.cpp b/contrib/nnc/passes/caffe_frontend/caffe_importer.cpp index 24b7563..9f95b13 100644 --- a/contrib/nnc/passes/caffe_frontend/caffe_importer.cpp +++ b/contrib/nnc/passes/caffe_frontend/caffe_importer.cpp @@ -23,6 +23,7 @@ #include "caffe_op_creator.h" #include "caffe_op_types.h" +#include "core/modelIR/operations/OutputOp.h" #include "core/modelIR/Shape.h" #include "core/modelIR/TensorUtil.h" #include "pass/PassException.h" @@ -220,7 +221,9 @@ void CaffeImporter::setGraphOutputs() { // For now, we assume that: // - there is exactly one output; // - the output is from the last layer. - _graph->markOutput(_blobNameToIODescriptor[last_layer.top(0)].op); + auto output = _blobNameToIODescriptor[last_layer.top(0)]; + _graph->create(output.op->getName(), output); + output.op->setName(""); } void CaffeImporter::cleanup() { diff --git a/contrib/nnc/passes/caffe_frontend/caffe_op_creator.cpp b/contrib/nnc/passes/caffe_frontend/caffe_op_creator.cpp index 43b0fa2..aef1a05 100644 --- a/contrib/nnc/passes/caffe_frontend/caffe_op_creator.cpp +++ b/contrib/nnc/passes/caffe_frontend/caffe_op_creator.cpp @@ -18,6 +18,7 @@ #include "core/modelIR/operations/BiasAddOp.h" #include "core/modelIR/operations/CappedReluOp.h" #include "core/modelIR/operations/ConcatOp.h" +#include "core/modelIR/operations/ConstantOp.h" #include "core/modelIR/operations/Conv2DOp.h" #include "core/modelIR/operations/Deconv2DOp.h" #include "core/modelIR/operations/DepthwiseConv2DOp.h" diff --git a/contrib/nnc/passes/interpreter/Interpreter.cpp b/contrib/nnc/passes/interpreter/Interpreter.cpp index b68550c..fe54c50 100644 --- a/contrib/nnc/passes/interpreter/Interpreter.cpp +++ b/contrib/nnc/passes/interpreter/Interpreter.cpp @@ -31,6 +31,7 @@ #include "core/modelIR/operations/GatherOp.h" #include "core/modelIR/operations/GemmOp.h" #include "core/modelIR/operations/InputOp.h" +#include "core/modelIR/operations/OutputOp.h" #include "core/modelIR/operations/LeakyReluOp.h" #include "core/modelIR/operations/PadOp.h" #include "core/modelIR/operations/PoolOp.h" @@ -145,12 +146,8 @@ void NNInterpreter::visit(ops::ConstantOp& op) { var(op.getId()) = {op.getValue()}; } -std::vector &NNInterpreter::getResult(Operation* op) { - auto res = vars.find(op->getId()); - if (res != vars.end()) - return res->second; - else - throw std::runtime_error("No such value: " + std::to_string(op->getId())); +TensorVariant NNInterpreter::getResult(IODescriptor tensor) { + return vars.at(tensor.op->getId()).at(tensor.index); } void NNInterpreter::visit(ops::ConcatOp& op) { @@ -466,4 +463,8 @@ void NNInterpreter::visit(ops::LeakyReluOp& op) { DUMP(op, false); } +void NNInterpreter::visit(ops::OutputOp&) { + // No-op. +} + } // namespace nnc diff --git a/contrib/nnc/passes/interpreter/interpreter_pass.cpp b/contrib/nnc/passes/interpreter/interpreter_pass.cpp index 6acf095..12a395d 100644 --- a/contrib/nnc/passes/interpreter/interpreter_pass.cpp +++ b/contrib/nnc/passes/interpreter/interpreter_pass.cpp @@ -54,12 +54,12 @@ using namespace mir; * @param tensor_name - name, by wich tensor will be saved * @param destination - path to file, in which tensor will be saved */ -static void writeTensorToHDF5File(TensorVariant* tensor, +static void writeTensorToHDF5File(const TensorVariant& tensor, std::string tensor_name, const std::string& destination) { // Prepare shape, rank, dims, numElems - auto& shape = tensor->getShape(); + auto& shape = tensor.getShape(); const int32_t rank = shape.rank(); hsize_t dims[rank]; for (int32_t axis = 0; axis < rank; ++axis) { @@ -70,7 +70,7 @@ static void writeTensorToHDF5File(TensorVariant* tensor, std::vector values; values.reserve(shape.numElements()); ShapeRange out_range(shape); - Tensor tensor_accessor(*tensor); + Tensor tensor_accessor(tensor); for (auto& out_idx : out_range) values.push_back(tensor_accessor.at(out_idx)); @@ -95,7 +95,7 @@ PassData InterpreterPass::run(PassData data) { NNInterpreter interpreter; // Check ops - const auto& inputs = g->collectInputs(); + const auto& inputs = g->getInputs(); assert(inputs.size() == 1 && "Interpreter doesn't support networks with multiple input nodes"); auto input_node = inputs[0]; @@ -103,38 +103,19 @@ PassData InterpreterPass::run(PassData data) { interpreter.setInput(input_node->getName(), input_data); g->accept(&interpreter); - // Check nodes - const auto& outputs = g->collectOutputs(); -#if 0 - interpreter.dump(*outputs[0], true); -#endif - - for (auto& out : outputs) { - auto outputNode = interpreter.getResult(out); - if (outputNode.empty()) - throw PassException("No value for output node <" + out->getName() + ">"); - } - - bool is_several_outs = (outputs.size() > 1); - - nnc::mir::TensorVariant* out_data = nullptr; - for (auto& out_node : outputs) { - out_data = new TensorVariant(interpreter.getResult(out_node)[0]); + for (auto out_node : g->getOutputs()) { + const auto& tensor = interpreter.getResult(out_node->getInput(0)); #ifdef NNC_HDF5_SUPPORTED - writeTensorToHDF5File(out_data, out_node->getName(), cli::artifactDir); + writeTensorToHDF5File(tensor, out_node->getName(), cli::artifactDir); #else std::cout << "Result <" << out_node->getName() << "> wasn't saved, due to lack of HDF5" << std::endl; #endif // NNC_HDF5_SUPPORTED - if (is_several_outs) - delete out_data; } - _out = is_several_outs ? nullptr : out_data; - - return _out; + return nullptr; } TensorVariant InterpreterPass::loadInput(const Shape& shape) { diff --git a/contrib/nnc/passes/onnx_frontend/ONNXImporterImpl.cpp b/contrib/nnc/passes/onnx_frontend/ONNXImporterImpl.cpp index ab2a875..ce9de6b 100644 --- a/contrib/nnc/passes/onnx_frontend/ONNXImporterImpl.cpp +++ b/contrib/nnc/passes/onnx_frontend/ONNXImporterImpl.cpp @@ -24,6 +24,7 @@ #include "core/modelIR/operations/DepthwiseConv2DOp.h" #include "core/modelIR/operations/ElementwiseOp.h" #include "core/modelIR/operations/InputOp.h" +#include "core/modelIR/operations/OutputOp.h" #include "core/modelIR/operations/TransposeOp.h" #include "core/modelIR/Operation.h" #include "core/modelIR/Shape.h" @@ -336,8 +337,10 @@ mir::Graph *ONNXImporterImpl::createIR() { } // set graph outputs // TODO: it should be done with onnx graph outputs - for (auto& output_idx : _graphOutputs) - _graph->markOutput(output_idx.op); + for (auto output : _graphOutputs) { + _graph->create(output.op->getName(), output); + output.op->setName(""); + } return _graph; } diff --git a/contrib/nnc/passes/soft_backend/ModelAnalyzer.cpp b/contrib/nnc/passes/soft_backend/ModelAnalyzer.cpp index 7f8f3b4..be4231a 100644 --- a/contrib/nnc/passes/soft_backend/ModelAnalyzer.cpp +++ b/contrib/nnc/passes/soft_backend/ModelAnalyzer.cpp @@ -36,6 +36,7 @@ #include "core/modelIR/operations/GemmOp.h" #include "core/modelIR/operations/InputOp.h" #include "core/modelIR/operations/LeakyReluOp.h" +#include "core/modelIR/operations/OutputOp.h" #include "core/modelIR/operations/PadOp.h" #include "core/modelIR/operations/PoolOp.h" #include "core/modelIR/operations/ReduceFOp.h" @@ -78,9 +79,12 @@ void ModelAnalyzer::appendOperationToInference( // register constant tensor // it's data is deserialized to described tensor by O(1) at runtime node_output_tensor_id = declareTemporaryTensor(); - } else if (!op_name.empty() || op->getNextNodes().empty()) { + } else if (op->getType() == Operation::Type::output) { // process output op node_output_tensor_id = declarePersistentTensor(op_name); + } else if (!op_name.empty()) { + // process a named operation + node_output_tensor_id = declarePersistentTensor(op_name); } else { // process ordinary unnamed operation node_output_tensor_id = declareTemporaryTensor(); @@ -226,7 +230,7 @@ void ModelAnalyzer::constructInferenceSequence(const vector& post_or } void ModelAnalyzer::collectOutputs(const mir::Graph* g) { - for (Operation* out_op: g->collectOutputs()) { + for (ops::OutputOp* out_op: g->getOutputs()) { assert(dynamic_cast(_opToDescr[out_op])); auto op_call = static_cast(_opToDescr[out_op]); _outputs.insert(_outputs.end(), op_call->outputs.begin(), op_call->outputs.end()); @@ -241,17 +245,18 @@ void ModelAnalyzer::analyze(const mir::Graph* g) { // Set contains pointer to node if it is visited by DFS set visited; - // Collect all inputs and constants - vector init_ops(g->collectInputs()); - auto constants = g->collectConstants(); - init_ops.insert(init_ops.end(), constants.begin(), constants.end()); + vector init_ops; + for (Operation* op : g->getNodes()) { + if (op->getNumInputs() == 0) { + init_ops.emplace_back(op); + } + } // Register temporary tensor for im2col buffer _temp_tensor_id = declareTemporaryTensor(); // Walk all network inputs for (Operation* in : init_ops) { - assert(dynamic_cast(in) || dynamic_cast(in)); if (!visited.count(in)) { visited.insert(in); s.push({in, 0}); @@ -469,4 +474,8 @@ void ModelAnalyzer::visit(mir::ops::LeakyReluOp& op) { appendOperationToInference(&op, "leakyRelu"); } +void ModelAnalyzer::visit(mir::ops::OutputOp& op) { + appendOperationToInference(&op, "out"); +} + } // namespace nnc diff --git a/contrib/nnc/passes/soft_backend/ModelAnalyzer.h b/contrib/nnc/passes/soft_backend/ModelAnalyzer.h index 062e54a..6ae1be0 100644 --- a/contrib/nnc/passes/soft_backend/ModelAnalyzer.h +++ b/contrib/nnc/passes/soft_backend/ModelAnalyzer.h @@ -62,9 +62,10 @@ public: void visit(mir::ops::EluOp& op) override; void visit(mir::ops::FullyConnectedOp& op) override; void visit(mir::ops::GatherOp& op) override; + void visit(mir::ops::GemmOp& op) override; void visit(mir::ops::InputOp& op) override; void visit(mir::ops::LeakyReluOp& op) override; - void visit(mir::ops::GemmOp& op) override; + void visit(mir::ops::OutputOp& op) override; void visit(mir::ops::PadOp& op) override; void visit(mir::ops::PoolOp& op) override; void visit(mir::ops::ReduceFOp& op) override; diff --git a/contrib/nnc/passes/soft_backend/SBSerializer.cpp b/contrib/nnc/passes/soft_backend/SBSerializer.cpp index 821797e..a2900b6 100644 --- a/contrib/nnc/passes/soft_backend/SBSerializer.cpp +++ b/contrib/nnc/passes/soft_backend/SBSerializer.cpp @@ -380,4 +380,8 @@ void Serializer::serialize(vector>& inference_sequence) } } +void Serializer::visit(mir::ops::OutputOp& op) { + // no parameters to dump +} + } // namespace nnc diff --git a/contrib/nnc/passes/soft_backend/SBSerializer.h b/contrib/nnc/passes/soft_backend/SBSerializer.h index bd32fde..f6ac1f9 100644 --- a/contrib/nnc/passes/soft_backend/SBSerializer.h +++ b/contrib/nnc/passes/soft_backend/SBSerializer.h @@ -53,10 +53,11 @@ public: void visit(mir::ops::ElementwiseOp& op) override; void visit(mir::ops::EluOp& op) override; void visit(mir::ops::FullyConnectedOp& op) override; - void visit(mir::ops::InputOp& op) override; - void visit(mir::ops::LeakyReluOp& op) override; void visit(mir::ops::GatherOp& op) override; void visit(mir::ops::GemmOp& op) override; + void visit(mir::ops::InputOp& op) override; + void visit(mir::ops::LeakyReluOp& op) override; + void visit(mir::ops::OutputOp& op) override; void visit(mir::ops::PadOp& op) override; void visit(mir::ops::PoolOp& op) override; void visit(mir::ops::ReduceFOp& op) override; diff --git a/contrib/nnc/passes/soft_backend/code_snippets/cpp_operations.def b/contrib/nnc/passes/soft_backend/code_snippets/cpp_operations.def index 794f964..5c4649f 100644 --- a/contrib/nnc/passes/soft_backend/code_snippets/cpp_operations.def +++ b/contrib/nnc/passes/soft_backend/code_snippets/cpp_operations.def @@ -667,3 +667,7 @@ void gather(Tensor &out, const char *params, const Tensor &data, const Tensor &i void constant(Tensor& out, const char* params) { out = deserializeTensor(params); } + +void out(Tensor& out, const char* params, const Tensor& in) { + out = in; +} diff --git a/contrib/nnc/passes/tflite_frontend/tflite_importer.cpp b/contrib/nnc/passes/tflite_frontend/tflite_importer.cpp index d5ee59c..a83dbed 100644 --- a/contrib/nnc/passes/tflite_frontend/tflite_importer.cpp +++ b/contrib/nnc/passes/tflite_frontend/tflite_importer.cpp @@ -16,7 +16,9 @@ #include "schema_generated.h" #include "tflite_importer.h" +#include "core/modelIR/operations/ConstantOp.h" #include "core/modelIR/operations/ElementwiseOp.h" +#include "core/modelIR/operations/OutputOp.h" #include "tflite_op_creator.h" #include "passes/common_frontend/op_creator_helper.h" @@ -329,9 +331,11 @@ mir::TensorVariant TfliteImporter::createTensor(const Tensor* t, const Buffer* b } void TfliteImporter::setGraphOutputs() { - // Marking nodes as output nodes. - for (auto output_idx : _graphOutputs) - _graph->markOutput(_tensorMap[output_idx].op); + for (auto output_idx : _graphOutputs) { + auto output = _tensorMap[output_idx]; + _graph->create(output.op->getName(), output); + output.op->setName(""); + } } void TfliteImporter::setIrNodeNames() { @@ -339,7 +343,8 @@ void TfliteImporter::setIrNodeNames() { // Note: we change the computation graph, (for example, TFLite Conv2D // turns into IR Conv2D->BiasAdd->ReLU), so not all of the nodes will have names. for (auto iter : _tensorMap) { - iter.second.op->setName((*_tensors)[iter.first]->name()->c_str()); + const Tensor* tensor = (*_tensors)[iter.first]; + iter.second.op->setName(tensor->name()->c_str()); } } diff --git a/contrib/nnc/passes/tflite_frontend/tflite_op_creator.cpp b/contrib/nnc/passes/tflite_frontend/tflite_op_creator.cpp index 3a5e53d..34150ac 100644 --- a/contrib/nnc/passes/tflite_frontend/tflite_op_creator.cpp +++ b/contrib/nnc/passes/tflite_frontend/tflite_op_creator.cpp @@ -20,6 +20,7 @@ #include "core/modelIR/operations/BiasAddOp.h" #include "core/modelIR/operations/CappedReluOp.h" #include "core/modelIR/operations/ConcatOp.h" +#include "core/modelIR/operations/ConstantOp.h" #include "core/modelIR/operations/Conv2DOp.h" #include "core/modelIR/operations/Deconv2DOp.h" #include "core/modelIR/operations/DepthwiseConv2DOp.h" diff --git a/contrib/nnc/tests/soft_backend/CompileCPP.cpp b/contrib/nnc/tests/soft_backend/CompileCPP.cpp index dc0ffd1..6350489 100644 --- a/contrib/nnc/tests/soft_backend/CompileCPP.cpp +++ b/contrib/nnc/tests/soft_backend/CompileCPP.cpp @@ -31,6 +31,7 @@ #include "core/modelIR/Graph.h" #include "core/modelIR/Shape.h" #include "core/modelIR/operations/InputOp.h" +#include "core/modelIR/operations/OutputOp.h" #include "core/modelIR/operations/ReluOp.h" #include "passes/soft_backend/CPPGenerator.h" @@ -45,12 +46,11 @@ using namespace nnc::mir; // Creates simple graph with input and output -void fillGraph(Graph &g) -{ - Shape inputShape{1, 2, 3}; - Operation* inputOp = g.create("in", inputShape); - Operation* outputOp = g.create("out", inputOp->getOutput(0)); - g.markOutput(outputOp); +static void fillGraph(Graph& g) { + Shape input_shape{1, 2, 3}; + Operation* input_op = g.create("in", input_shape); + Operation* relu_op = g.create("relu", input_op->getOutput(0)); + Operation* output_op = g.create("out", relu_op->getOutput(0)); } static void checkFileExists(const string &path) diff --git a/contrib/nnc/unittests/acl_backend/MIRToDOM.cpp b/contrib/nnc/unittests/acl_backend/MIRToDOM.cpp index 6df381a..70333bd 100644 --- a/contrib/nnc/unittests/acl_backend/MIRToDOM.cpp +++ b/contrib/nnc/unittests/acl_backend/MIRToDOM.cpp @@ -40,6 +40,7 @@ #include "core/modelIR/operations/EluOp.h" #include "core/modelIR/operations/FullyConnectedOp.h" #include "core/modelIR/operations/InputOp.h" +#include "core/modelIR/operations/OutputOp.h" #include "core/modelIR/operations/PadOp.h" #include "core/modelIR/operations/PoolOp.h" #include "core/modelIR/operations/ReduceFOp.h" @@ -70,19 +71,19 @@ const char* artifactName = "nnmodel"; * @param input_shapes vector of network input shapes * */ void fillGraph(Graph& g, const OpConstructor& op_constr, const vector& input_shapes) { - // Create inputs + // Create graph inputs. vector inputs; - int num_inputs = input_shapes.size(); - for (int i = 0; i < num_inputs; ++i) { - auto inputOp = g.create("x" + to_string(i), input_shapes[i]); - inputs.push_back(inputOp->getOutput(0)); + for (std::size_t i = 0; i < input_shapes.size(); ++i) { + auto input_op = g.create("x" + to_string(i), input_shapes[i]); + inputs.push_back(input_op->getOutput(0)); } - // Create operation + // Create the operation. Operation* op = op_constr(g, inputs); - // Mark outputs - g.markOutput(op); + // Create graph outputs. + for (std::size_t i = 0; i < op->getNumOutputs(); ++i) + g.create("y" + to_string(i), op->getOutput(i)); } /** diff --git a/contrib/nnc/unittests/core/Graph.cpp b/contrib/nnc/unittests/core/Graph.cpp index 9a4ad13..19beffc 100644 --- a/contrib/nnc/unittests/core/Graph.cpp +++ b/contrib/nnc/unittests/core/Graph.cpp @@ -3,6 +3,7 @@ #include "core/modelIR/Graph.h" #include "core/modelIR/operations/ConcatOp.h" #include "core/modelIR/operations/InputOp.h" +#include "core/modelIR/operations/OutputOp.h" #include "core/modelIR/operations/ReluOp.h" namespace { @@ -47,28 +48,7 @@ TEST(Graph, ReplaceInputs) { g->accept(&d); auto str = ss.str(); - ASSERT_EQ(str, "iop4iop1rop2rop3cop5"); - delete g; -}; - -TEST(Graph, ReplaceOutputs) { - //There is not much to test here as Graph::replaceOutputNodes simply calls Graph::markOutput - // multiple times ( Graph::markOutput just places passed node into Graph::_outputs map ) - - auto g = new Graph; - - auto n1 = g->create("op1", Shape{1}); - auto n2 = g->create("op2", n1->getOutput(0)); - auto n3 = g->create("op3", n2->getOutput(0)); - auto n4 = g->create("op4", n2->getOutput(0)); - auto n5 = g->create("op5", - std::vector{n3->getOutput(0), n4->getOutput(0)}, - 0); - - g->replaceOutputNodes({"op3"}); - - std::vector expectedOutputs{n3}; - ASSERT_EQ(g->collectOutputs(), expectedOutputs); + ASSERT_EQ(str, "iop1iop4rop2rop3cop5"); delete g; }; @@ -77,13 +57,12 @@ TEST(Graph, ReplaceOutputNodeWithInput) { auto n1 = g->create("op1", Shape{}); auto n2 = g->create("op2", n1->getOutput(0)); - - g->markOutput(n2); + auto n3 = g->create("op3", n2->getOutput(0)); auto in2 = g->replaceWithInputNode(n2); - std::vector expectedInputs{in2, n1}; - ASSERT_EQ(g->collectInputs(), expectedInputs); + std::vector expectedInputs{dynamic_cast(n1), in2}; + ASSERT_EQ(g->getInputs(), expectedInputs); delete g; } diff --git a/contrib/nnc/unittests/soft_backend/CPPOperations.cpp b/contrib/nnc/unittests/soft_backend/CPPOperations.cpp index 5c9d788..b88b830 100644 --- a/contrib/nnc/unittests/soft_backend/CPPOperations.cpp +++ b/contrib/nnc/unittests/soft_backend/CPPOperations.cpp @@ -70,6 +70,7 @@ #include "core/modelIR/operations/FullyConnectedOp.h" #include "core/modelIR/operations/InputOp.h" #include "core/modelIR/operations/LeakyReluOp.h" +#include "core/modelIR/operations/OutputOp.h" #include "core/modelIR/operations/PadOp.h" #include "core/modelIR/operations/PoolOp.h" #include "core/modelIR/operations/ReduceFOp.h" @@ -120,20 +121,21 @@ mir::Operation* fillGraph(mir::Graph& g, const function& inputs)>& op_gen, const vector>& input_ntensors) { - // Create inputs + // Create graph inputs. std::vector inputs; - int num_inputs = input_ntensors.size(); - for (int i = 0; i < num_inputs; ++i) { + for (std::size_t i = 0; i < input_ntensors.size(); ++i) { auto input_op = g.create("x" + std::to_string(i), input_ntensors[i]->getShape()); inputs.push_back(input_op->getOutput(0)); } - // Create operation + // Create the operation. mir::Operation* op = op_gen(g, inputs); - // Mark outputs - g.markOutput(op); + // Create graph outputs. + assert(op->getNumOutputs() == 1); + g.create(op->getName(), op->getOutput(0)); + op->setName(""); return op; } @@ -212,14 +214,14 @@ void fillTensors(unique_ptr &ntensor, /** * @brief Run interpreter to get reference output data */ -mir::TensorVariant getReferenceTensor(mir::Graph &g, - const vector> &input_ntensors, - const string& output_name) { +mir::TensorVariant +getReferenceTensor(mir::Graph& g, + const vector>& input_ntensors) { mir::NNInterpreter interpreter; for (int i = 0; i < static_cast(input_ntensors.size()); ++i) interpreter.setInput("x" + to_string(i), *input_ntensors[i]); g.accept(&interpreter); - return interpreter.getResult(g.getOutput(output_name))[0]; + return interpreter.getResult(g.getOutputs()[0]->getInput(0)); }; /** @@ -314,8 +316,7 @@ void createAndRunTestGraph( serializer.serialize(inference_sequence); assert(static_cast(inference_sequence.front().get())->paramStartOffset == 0); - const string& output_name = actual_operation->getName(); - mir::TensorVariant reference_output = getReferenceTensor(g, input_ntensors, output_name); + mir::TensorVariant reference_output = getReferenceTensor(g, input_ntensors); Tensor test_output; artifactOperation(test_output, serializer.getBuffer().data(), input_atensors...); -- 2.7.4