From 93ec0baeeace8c3c151886e4e3815266f2b157d0 Mon Sep 17 00:00:00 2001 From: "Efimov Alexander/AI Tools Lab/./Samsung Electronics" Date: Thu, 24 Jan 2019 19:47:48 +0300 Subject: [PATCH] [nnc] CPU backend sequence memory and transpose operations (#2884) - Move inference sequence entities to separate file - Refactor inference sequence uses to support several types of operations - Code style fixes in cpu soft backend Signed-off-by: Efimov Alexander --- .../project/18_NN_Compiler_and_Optimizer_DLD.rst | 8 +- .../nnc/include/passes/soft_backend/CPPGenerator.h | 8 +- contrib/nnc/passes/soft_backend/CMakeLists.txt | 2 +- contrib/nnc/passes/soft_backend/CPPGenerator.cpp | 205 +++++++++------------ contrib/nnc/passes/soft_backend/ModelAnalyzer.cpp | 107 ++++++----- contrib/nnc/passes/soft_backend/ModelAnalyzer.h | 60 ++---- contrib/nnc/passes/soft_backend/SBSerializer.cpp | 75 ++++---- contrib/nnc/passes/soft_backend/SBSerializer.h | 25 ++- contrib/nnc/passes/soft_backend/SequencedIR.cpp | 17 ++ contrib/nnc/passes/soft_backend/SequencedIR.h | 139 ++++++++++++++ .../nnc/unittests/soft_backend/CPPOperations.cpp | 10 +- .../nnc/unittests/soft_backend/ModelAnalyzer.cpp | 19 +- 12 files changed, 385 insertions(+), 290 deletions(-) create mode 100644 contrib/nnc/passes/soft_backend/SequencedIR.cpp create mode 100644 contrib/nnc/passes/soft_backend/SequencedIR.h diff --git a/contrib/nnc/doc/project/18_NN_Compiler_and_Optimizer_DLD.rst b/contrib/nnc/doc/project/18_NN_Compiler_and_Optimizer_DLD.rst index fe232e9..a1beb3e 100644 --- a/contrib/nnc/doc/project/18_NN_Compiler_and_Optimizer_DLD.rst +++ b/contrib/nnc/doc/project/18_NN_Compiler_and_Optimizer_DLD.rst @@ -586,12 +586,12 @@ Inference sequence construction ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``ModelAnalyzer`` object walks computational graph on phase 1 from general sequence in topological order and creates layout of it's operations. This sequence is represented -by list of ``ModelAnalyzer::OpDescr`` objects. +by list of ``ModelAnalyzer::Action`` objects. -``ModelAnalyzer::OpDescr`` object contains name of operation, +``ModelAnalyzer::Action`` object contains name of operation, pointer to corresponding CG Node, identifiers of input and output Tensors. -Information about artifact variables(Tensors) is stored in array of ``TensorDescription`` objects, +Information about artifact variables(Tensors) is stored in array of ``TensorDescriptor`` objects, Mentioned object holds name of ``Tensor`` and it's properties(is it input/output/temporary). Every ``VariableOp`` node emits "input" type ``Tensor`` variable. Every node with name(what is not ``VariableOp``) emits "output" type ``Tensor`` variable that holds operation output. @@ -602,7 +602,7 @@ Serialization ``Serializer`` object visits CG nodes and stores corresponding data in raw binary format in internal array of bytes. Every operation receives unique (with exception of operations that has nothing to serialize, for example relu) offset in this array -where it's data stored. This offset is stored in ``_paramStartOffset`` of ``ModelAnalyzer::OpDescr`` object. +where it's data stored. This offset is stored in ``_paramStartOffset`` of ``ModelAnalyzer::Action`` object. Shape, strides are stored as arrays of integers: first value serialized is array size, then all dimension values. Pads are stored as vectors too, but they have additional value to save: diff --git a/contrib/nnc/include/passes/soft_backend/CPPGenerator.h b/contrib/nnc/include/passes/soft_backend/CPPGenerator.h index a6ba4fc..f4205c2 100644 --- a/contrib/nnc/include/passes/soft_backend/CPPGenerator.h +++ b/contrib/nnc/include/passes/soft_backend/CPPGenerator.h @@ -22,7 +22,9 @@ namespace nnc { -struct TensorDescription; +namespace sir { +struct TensorDescriptor; +} /** * @brief CPPCodeGenerator implements interfaces that provides BaseCodeGenerator for C++ language @@ -50,7 +52,7 @@ protected: * @param varId id of variable that setter fills */ void printSetter(std::ostream& out, const std::string& className, - const std::string& setterName, const TensorDescription& td); + const std::string& setterName, const sir::TensorDescriptor& td); /** * @brief Prints getters of artifact * @param out Output stream @@ -59,7 +61,7 @@ protected: * @param varId id of variable that getter returns */ void printGetter(std::ostream& out, const std::string& className, - const std::string& setterName, const TensorDescription& td); + const std::string& setterName, const sir::TensorDescriptor& td); /** * @brief Prints inference sequence placed in doInference method of artifact * @param out Output stream diff --git a/contrib/nnc/passes/soft_backend/CMakeLists.txt b/contrib/nnc/passes/soft_backend/CMakeLists.txt index 2ecc219..6e907b3 100644 --- a/contrib/nnc/passes/soft_backend/CMakeLists.txt +++ b/contrib/nnc/passes/soft_backend/CMakeLists.txt @@ -1,4 +1,4 @@ -set(SOFT_BACKEND_COMMON_SOURCES BaseGenerator.cpp ModelAnalyzer.cpp SBSerializer.cpp) +set(SOFT_BACKEND_COMMON_SOURCES BaseGenerator.cpp ModelAnalyzer.cpp SBSerializer.cpp SequencedIR.cpp) set(SOFT_BACKEND_CPP_SOURCES CPPGenerator.cpp) set(SOFT_BACKEND_C_SOURCES CGenerator.cpp) diff --git a/contrib/nnc/passes/soft_backend/CPPGenerator.cpp b/contrib/nnc/passes/soft_backend/CPPGenerator.cpp index af905e7..fbe1754 100644 --- a/contrib/nnc/passes/soft_backend/CPPGenerator.cpp +++ b/contrib/nnc/passes/soft_backend/CPPGenerator.cpp @@ -57,39 +57,30 @@ using namespace std; #include "cpp_gather.generated.h" #include "cpp_gemm.generated.h" -namespace nnc -{ +namespace nnc { -using TensorType = TensorDescription::Type; +using namespace sir; + +using TensorType = TensorDescriptor::Type; /** * @brief Renames tensors with respect to C++ naming conventions * @param ma Intermediate artifact information */ -void CPPCodeGenerator::formatTensorNames(const ModelAnalyzer &ma) -{ +void CPPCodeGenerator::formatTensorNames(const ModelAnalyzer& ma) { int tmpTensors = 0; - for (const TensorDescription &td: ma.getTensors()) - { + for (const TensorDescriptor& td: ma.getTensors()) { string formattedName; - if(td._name.empty()) - { - assert(td._type == TensorType::TEMPORARY); + if(td.name.empty()) { + assert(td.type == TensorType::temporary); formattedName = "Tensor_" + to_string(tmpTensors++); - } - else - { - if (td._type != TensorType::TEMPORARY) - { + } else { + if (td.type != TensorType::temporary) formattedName.append("_"); - } - formattedName.append(td._name); - for (char &c: formattedName) - { + formattedName.append(td.name); + for (char& c: formattedName) { if (!isalnum(c)) - { c = '_'; - } } } _formattedTensors.push_back(move(formattedName)); @@ -103,54 +94,46 @@ void CPPCodeGenerator::formatTensorNames(const ModelAnalyzer &ma) * network constructor, setters to feed data to network, getters to get results, * and doInference method that performs actual inference. */ -void CPPCodeGenerator::materializeHeader(ostream &out, const ModelAnalyzer &ma) -{ +void CPPCodeGenerator::materializeHeader(ostream& out, const ModelAnalyzer& ma) { string className = ma.getModelName() + "Model"; out.write(cpp_header_types, sizeof(cpp_header_types)); out << "class " << className << "\n" "{\n" "public:\n" - " " << className << "(const std::string ¶metersPath);\n" + " " << className << "(const std::string& parametersPath);\n" " ~" << className << "();\n"; // generate input setters if (ma.getInputs().size() == 1) - { - out << " bool setInput(const Tensor &inputs);\n"; - } - for (const size_t inId: ma.getInputs()) - { - const string &tName = _formattedTensors[inId]; + out << " bool setInput(const Tensor& inputs);\n"; + for (const size_t inId: ma.getInputs()) { + const string& tName = _formattedTensors[inId]; out << " bool set" << tName << "(const Tensor& t);\n"; } // generate output getters - if (ma.getOutputs().size() == 1) - { + if (ma.getOutputs().size() == 1) { out << " std::shared_ptr getOutput();\n"; } - for (const size_t outId: ma.getPersistentTensors()) - { - const string &tName = _formattedTensors[outId]; + for (const size_t outId: ma.getPersistentTensors()) { + const string& tName = _formattedTensors[outId]; out << " std::shared_ptr get" << tName << "();\n"; } out << " void doInference();\n\n" "private:\n" " " << className << "() = delete;\n" - " " << className << "(const " << className << " &orig) = delete;\n" - " " << className << " &operator=(const " << className << " &orig) = delete;\n"; + " " << className << "(const " << className << "& orig) = delete;\n" + " " << className << "& operator=(const " << className << "& orig) = delete;\n"; // generate input/output tensors - for (const size_t inId: ma.getInputs()) - { - const string &tName = _formattedTensors[inId]; + for (const size_t inId: ma.getInputs()) { + const string& tName = _formattedTensors[inId]; out << " Tensor " << tName << ";\n"; } - for (const size_t outId: ma.getPersistentTensors()) - { - const string &tName = _formattedTensors[outId]; + for (const size_t outId: ma.getPersistentTensors()) { + const string& tName = _formattedTensors[outId]; out << " std::shared_ptr " << tName << ";\n"; } // pointer to NN parameters - out << " char * _parameters;\n"; + out << " char* _parameters;\n"; out << " size_t _paramSize;\n"; out << "};\n"; } @@ -162,16 +145,14 @@ void CPPCodeGenerator::materializeHeader(ostream &out, const ModelAnalyzer &ma) * @param formatted List of formatted tensor names * @param op Operation that requires variables */ -static void printTmpTensors(ostream &out, const ModelAnalyzer &ma, - const vector &formatted, const OpDescr &op) -{ - for (size_t id: op._outputs) - { - const TensorDescription &td = ma.getTensors()[id]; - assert(td._type != TensorType::INPUT && "no input nodes should be inserted into inference sequence"); - if (td._type == TensorType::PERSISTENT) +static void printTmpTensors(ostream& out, const ModelAnalyzer& ma, + const vector& formatted, const CallFunction& op) { + for (size_t id: op.outputs) { + const TensorDescriptor& td = ma.getTensors()[id]; + assert(td.type != TensorType::input&& "no input nodes should be inserted into inference sequence"); + if (td.type == TensorType::persistent) continue; - const string &tName = formatted[id]; + const string& tName = formatted[id]; out << " Tensor " << tName << ";\n"; } } @@ -181,11 +162,9 @@ static void printTmpTensors(ostream &out, const ModelAnalyzer &ma, * @param out Stream to write program text * @param args arguments to print */ -static void printOperationArgs(ostream &out, const vector &args) -{ +static void printOperationArgs(ostream& out, const vector& args) { bool insertComma = false; - for (const string &arg: args) - { + for (const string& arg: args) { if (insertComma) out << ", "; insertComma = true; @@ -193,78 +172,76 @@ static void printOperationArgs(ostream &out, const vector &args) } } -void CPPCodeGenerator::gatherOperationArguments(const ModelAnalyzer &ma, - const vector &argIds, - vector &args) -{ - for (size_t id: argIds) - { - const string &tensorName = _formattedTensors[id]; - if (ma.getTensors()[id]._type == TensorDescription::Type::PERSISTENT) - { +void CPPCodeGenerator::gatherOperationArguments(const ModelAnalyzer& ma, + const vector& argIds, + vector& args) { + + for (size_t id: argIds) { + const string& tensorName = _formattedTensors[id]; + if (ma.getTensors()[id].type == TensorDescriptor::Type::persistent) args.push_back("*" + tensorName); - } else - { args.push_back(tensorName); - } } } -void CPPCodeGenerator::printSetter(ostream &out, const string &className, const string &setterName, const TensorDescription &td) -{ - const string &varName = _formattedTensors[td._id]; +void CPPCodeGenerator::printSetter(ostream& out, + const string& className, + const string& setterName, + const TensorDescriptor& td) { + + const string& varName = _formattedTensors[td.id]; out << "bool " << className << "::set" << setterName << "(const Tensor& t)\n" "{\n"; // need to insert input correctness check - const mir::Shape expected = td._shape; + const mir::Shape expected = td.shape; int rank = expected.rank(); - if (rank != 0) - { - out << " " << "if (t.getShape().getDims() != " << td._shape.rank() << ") return false;\n"; + if (rank != 0) { + out << " " << "if (t.getShape().getDims() != " << td.shape.rank() << ") return false;\n"; for (int i = 0; i < rank; ++i) - { out << " " << "if (t.getShape()[" << i << "] != " << expected.dim(i) << ") return false;\n"; - } } out << " " << varName << " = t;\n" " return true;\n" "}\n\n"; } -void CPPCodeGenerator::printGetter(ostream &out, const string &className, const string &getterName, const TensorDescription &td) -{ - const string &varName = _formattedTensors[td._id]; +void CPPCodeGenerator::printGetter(ostream& out, + const string& className, + const string& getterName, + const TensorDescriptor& td) { + + const string& varName = _formattedTensors[td.id]; out << "shared_ptr " << className <<"::get" << getterName << "()\n" "{\n" " return " << varName << ";\n" "}\n\n"; } -void CPPCodeGenerator::materializeInferenceSequence(ostream &out, const ModelAnalyzer &ma) -{ - using OpDescr = OpDescr; +void CPPCodeGenerator::materializeInferenceSequence(ostream& out, const ModelAnalyzer& ma) { + // Allocate temporary(im2col) tensor out << " Tensor " << _formattedTensors[ma.getTempTID()] << "(Shape{" << ma.getMaxTemporarySize() << "});\n"; - for (const OpDescr &op: ma.getInferenceSequence()) - { - if (op._op->getType() == mir::Operation::Type::variable) + + for (const unique_ptr& action: ma.getInferenceSequence()) { + CallFunction& call = *static_cast(action.get()); + if (call.mirOp->getType() == mir::Operation::Type::variable) continue; // create temporary tensors - printTmpTensors(out, ma, _formattedTensors, op); + printTmpTensors(out, ma, _formattedTensors, call); // materialize call - out << " " << op._opName << "("; - const auto &prevNodes = op._op->getPrevNodes(); - const auto &outTensors = op._outputs; + out << " " << call.funcName << "("; + const auto& prevNodes = call.mirOp->getPrevNodes(); + const auto& outTensors = call.outputs; vector args; args.reserve(prevNodes.size() + outTensors.size() + 1); // gather output arguments - gatherOperationArguments(ma, op._outputs, args); + gatherOperationArguments(ma, call.outputs, args); // parameters offset - args.push_back("_parameters + " + to_string(params::HEADER_LEN + op._paramStartOffset)); + args.push_back("_parameters + " + to_string(params::HEADER_LEN + call.paramStartOffset)); // gather input arguments - gatherOperationArguments(ma, op._inputs, args); + gatherOperationArguments(ma, call.inputs, args); // put arguments into stream printOperationArgs(out, args); out << ");\n"; @@ -274,8 +251,7 @@ void CPPCodeGenerator::materializeInferenceSequence(ostream &out, const ModelAna /** * Function writes to output stream needed code snippets, and implementations of artifact class functions. */ -void CPPCodeGenerator::materializeCode(ostream &out, const ModelAnalyzer &ma, const Serializer &s) -{ +void CPPCodeGenerator::materializeCode(ostream& out, const ModelAnalyzer& ma, const Serializer& s) { string className = ma.getModelName() + "Model"; out << "#include \"" << cli::artifactName << ".h\"\n"; @@ -317,7 +293,7 @@ void CPPCodeGenerator::materializeCode(ostream &out, const ModelAnalyzer &ma, co out.write(cpp_leaky_relu, sizeof(cpp_leaky_relu)); // gen NN constructor - out << className << "::" << className << "(const string ¶metersPath)\n" + out << className << "::" << className << "(const string& parametersPath)\n" "{\n" " readParameters(_parameters, _paramSize, parametersPath, " << s.getFormatVersion() << ", " << s.getModelHash() << ");\n" @@ -329,40 +305,35 @@ void CPPCodeGenerator::materializeCode(ostream &out, const ModelAnalyzer &ma, co "}\n\n"; // generate input setters // generate main setter if network has only one - const auto &inputs = ma.getInputs(); - const auto &tensors = ma.getTensors(); - if (inputs.size() == 1) - { - const TensorDescription &td = tensors[inputs[0]]; + const auto& inputs = ma.getInputs(); + const auto& tensors = ma.getTensors(); + if (inputs.size() == 1) { + const TensorDescriptor& td = tensors[inputs[0]]; printSetter(out, className, "Input", td); } // generate setters by names - for (size_t inId: inputs) - { - const string &inName = _formattedTensors[inId]; - const TensorDescription &td = tensors[inId]; + for (size_t inId: inputs) { + const string& inName = _formattedTensors[inId]; + const TensorDescriptor& td = tensors[inId]; printSetter(out, className, inName, td); } // gen output getters // generate main getter if network has only one - const auto &outputs = ma.getOutputs(); - if (outputs.size() == 1) - { - const TensorDescription &td = tensors[outputs[0]]; + const auto& outputs = ma.getOutputs(); + if (outputs.size() == 1) { + const TensorDescriptor& td = tensors[outputs[0]]; printGetter(out, className, "Output", td); } - for (size_t outId: ma.getPersistentTensors()) - { - const string &outName = _formattedTensors[outId]; - const TensorDescription &td = tensors[outId]; + for (size_t outId: ma.getPersistentTensors()) { + const string& outName = _formattedTensors[outId]; + const TensorDescriptor& td = tensors[outId]; printGetter(out, className, outName, td); } out << "void " << className << "::doInference()\n" "{\n"; - for (size_t outId: ma.getPersistentTensors()) - { - const string &outName = _formattedTensors[outId]; + for (size_t outId: ma.getPersistentTensors()) { + const string& outName = _formattedTensors[outId]; out << " " << outName << ".reset(new Tensor());\n"; } diff --git a/contrib/nnc/passes/soft_backend/ModelAnalyzer.cpp b/contrib/nnc/passes/soft_backend/ModelAnalyzer.cpp index d7ba484..626d56f 100644 --- a/contrib/nnc/passes/soft_backend/ModelAnalyzer.cpp +++ b/contrib/nnc/passes/soft_backend/ModelAnalyzer.cpp @@ -58,13 +58,14 @@ using namespace std; -namespace nnc -{ +namespace nnc { -using namespace nnc::mir; +using namespace mir; +using namespace sir; + +void ModelAnalyzer::appendOperationToInference( + Operation* op, const string& function_name, std::vector aux_args) { -void ModelAnalyzer::addOpDescr( - Operation* op, const string& function_name, std::vector aux_args = {}) { vector node_output_tensors; const string &op_name = op->getName(); @@ -89,25 +90,22 @@ void ModelAnalyzer::addOpDescr( // process operation inputs vector node_input_tensors; - for (const IODescriptor &d: op->getPrevNodes()) { + for (const IODescriptor& d: op->getPrevNodes()) { size_t idx = d.index; - Operation *prev_op = d.op; + Operation* prev_op = d.op; assert(_opToDescr.find(prev_op) != _opToDescr.end()); - const OpDescr &descr = *_opToDescr[prev_op]; - const size_t &inTid = descr._outputs[idx]; + assert(dynamic_cast(_opToDescr[prev_op])); + const CallFunction* call = static_cast(_opToDescr[prev_op]); + const size_t &inTid = call->outputs[idx]; node_input_tensors.push_back(inTid); } - // this op uses temporary memory (e.g. im2col) - if (!aux_args.empty()) { - std::copy(aux_args.begin(), aux_args.end(), std::back_inserter(node_input_tensors)); - } - - _inferenceSequence.push_back({op, function_name, - std::move(node_input_tensors), - std::move(node_output_tensors), - 0}); - _opToDescr[op] = &_inferenceSequence.back(); + std::copy(aux_args.begin(), aux_args.end(), std::back_inserter(node_input_tensors)); + unique_ptr operation_call(new CallFunction(op, function_name, + std::move(node_input_tensors), + std::move(node_output_tensors))); + _inferenceSequence.push_back(std::move(operation_call)); + _opToDescr[op] = _inferenceSequence.back().get(); } void ModelAnalyzer::updateMaxTemporarySize(const size_t size) { @@ -117,14 +115,14 @@ void ModelAnalyzer::updateMaxTemporarySize(const size_t size) { size_t ModelAnalyzer::declareInputTensor(const std::string& name, const mir::Shape& shape) { assert(!name.empty() && "Input tensor must have name"); size_t id = _allocatedTensors++; - _tensors.push_back({id, TensorDescription::Type::INPUT, name, shape}); + _tensors.push_back({id, TensorDescriptor::Type::input, name, shape}); _inputs.push_back(id); return id; } size_t ModelAnalyzer::declarePersistentTensor(const std::string& name) { size_t id = _allocatedTensors++; - auto type = TensorDescription::Type::PERSISTENT; + auto type = TensorDescriptor::Type::persistent; if (name.empty()) { // special case for unnamed output tensors _tensors.push_back({id, type, "unnamed_output" + to_string(id), {}}); @@ -137,7 +135,7 @@ size_t ModelAnalyzer::declarePersistentTensor(const std::string& name) { size_t ModelAnalyzer::declareTemporaryTensor() { size_t id = _allocatedTensors++; - _tensors.push_back({id, TensorDescription::Type::TEMPORARY, "", {}}); + _tensors.push_back({id, TensorDescriptor::Type::temporary, "", {}}); return id; } @@ -194,13 +192,14 @@ void ModelAnalyzer::analyze(const mir::Graph* g) { } for (Operation* out_op: g->collectOutputs()) { - OpDescr* descr = _opToDescr[out_op]; - _outputs.insert(_outputs.end(), descr->_outputs.begin(), descr->_outputs.end()); + 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()); } } void ModelAnalyzer::visit(ops::ConcatOp& op) { - addOpDescr(&op, "concat"); + appendOperationToInference(&op, "concat"); } void ModelAnalyzer::visit(ops::Conv2DOp& op) { @@ -209,15 +208,15 @@ void ModelAnalyzer::visit(ops::Conv2DOp& op) { const int32_t tmp_size = kernel_shape.dim(0) * kernel_shape.dim(1) * kernel_shape.dim(2) * out_shape.dim(0) * out_shape.dim(1) * out_shape.dim(2); updateMaxTemporarySize(static_cast(tmp_size)); - addOpDescr(&op, "conv2d", {_temp_tensor_id}); + appendOperationToInference(&op, "conv2d", {_temp_tensor_id}); } void ModelAnalyzer::visit(ops::DepthwiseConv2DOp& op) { - addOpDescr(&op, "depthwiseConv2d"); + appendOperationToInference(&op, "depthwiseConv2d"); } void ModelAnalyzer::visit(ops::SoftmaxOp& op) { - addOpDescr(&op, "softmax"); + appendOperationToInference(&op, "softmax"); } /** @@ -236,28 +235,28 @@ void ModelAnalyzer::visit(ops::PoolOp& op) { default: assert(false && "unsupported pooling type"); } - addOpDescr(&op, funcName); + appendOperationToInference(&op, funcName); } void ModelAnalyzer::visit(ops::FullyConnectedOp& op) { - addOpDescr(&op, "fullConnect"); + appendOperationToInference(&op, "fullConnect"); } void ModelAnalyzer::visit(ops::GemmOp& op) { - addOpDescr(&op, "gemmOp"); + appendOperationToInference(&op, "gemmOp"); } void ModelAnalyzer::visit(ops::CappedReluOp& op) { - addOpDescr(&op, "cappedRelu"); + appendOperationToInference(&op, "cappedRelu"); } void ModelAnalyzer::visit(ops::BiasAddOp& op) { - addOpDescr(&op, "biasAdd"); + appendOperationToInference(&op, "biasAdd"); } void ModelAnalyzer::visit(ops::VariableOp& op) { assert(op.getPrevNodes().empty()); - addOpDescr(&op, "in"); + appendOperationToInference(&op, "in"); } void ModelAnalyzer::visit(ops::ConstantOp& op) { @@ -268,21 +267,21 @@ void ModelAnalyzer::visit(ops::ConstantOp& op) { if (op.getNextNodes().empty()) return; - addOpDescr(&op, "constant"); + appendOperationToInference(&op, "constant"); } void ModelAnalyzer::visit(ops::ReluOp& op) { - addOpDescr(&op, "relu"); + appendOperationToInference(&op, "relu"); } void ModelAnalyzer::visit(ops::ReshapeOp& op) { - addOpDescr(&op, "reshape"); + appendOperationToInference(&op, "reshape"); } void ModelAnalyzer::visit(mir::ops::ResizeOp& op) { switch (op.getMode()) { case mir::ops::ResizeOp::ResizeMethod::nearestNeighbor: - addOpDescr(&op, "resize"); + appendOperationToInference(&op, "resize"); break; default: assert(false && "Not Implemented!"); @@ -290,23 +289,23 @@ void ModelAnalyzer::visit(mir::ops::ResizeOp& op) { } void ModelAnalyzer::visit(ops::DropoutOp& op) { - addOpDescr(&op, "dropout"); + appendOperationToInference(&op, "dropout"); } void ModelAnalyzer::visit(ops::ScaleOp& op) { - addOpDescr(&op, "scale"); + appendOperationToInference(&op, "scale"); } void ModelAnalyzer::visit(mir::ops::SliceOp& op) { - addOpDescr(&op, "slice"); + appendOperationToInference(&op, "slice"); } void ModelAnalyzer::visit(ops::BatchNormOp& op) { - addOpDescr(&op, "batchNorm"); + appendOperationToInference(&op, "batchNorm"); } void ModelAnalyzer::visit(mir::ops::TanhOp& op) { - addOpDescr(&op, "tanhActivation"); + appendOperationToInference(&op, "tanhActivation"); } void ModelAnalyzer::visit(mir::ops::ElementwiseOp& op) { @@ -330,11 +329,11 @@ void ModelAnalyzer::visit(mir::ops::ElementwiseOp& op) { default: assert(false && "unsupported elementwise operation type"); } - addOpDescr(&op, func_name); + appendOperationToInference(&op, func_name); } void ModelAnalyzer::visit(mir::ops::EluOp& op) { - addOpDescr(&op, "elu"); + appendOperationToInference(&op, "elu"); } void ModelAnalyzer::visit(mir::ops::DeConv2DOp& op) { @@ -343,25 +342,25 @@ void ModelAnalyzer::visit(mir::ops::DeConv2DOp& op) { const int32_t tmp_size = kernel_shape.dim(0) * kernel_shape.dim(1) * kernel_shape.dim(3) * out_shape.dim(0) * out_shape.dim(1) * out_shape.dim(2); updateMaxTemporarySize(static_cast(tmp_size)); - addOpDescr(&op, "convTransposed2d", {_temp_tensor_id}); + appendOperationToInference(&op, "convTransposed2d", {_temp_tensor_id}); } void ModelAnalyzer::visit(ops::SqueezeOp& op) { - addOpDescr(&op, "reshape"); + appendOperationToInference(&op, "reshape"); } void ModelAnalyzer::visit(ops::SqrtOp& op) { - addOpDescr(&op, "sqrtFN"); + appendOperationToInference(&op, "sqrtFN"); } void ModelAnalyzer::visit(mir::ops::PadOp& op) { - addOpDescr(&op, "pad"); + appendOperationToInference(&op, "pad"); } void ModelAnalyzer::visit(mir::ops::ReduceFOp& op) { switch (op.getFuncType()) { case mir::ops::ReduceFOp::FuncType::mean: - addOpDescr(&op, "reduceMean"); + appendOperationToInference(&op, "reduceMean"); break; default: assert(false && "NOT IMPLEMENTED"); @@ -369,19 +368,19 @@ void ModelAnalyzer::visit(mir::ops::ReduceFOp& op) { } void ModelAnalyzer::visit(mir::ops::TransposeOp& op) { - addOpDescr(&op, "transpose"); + appendOperationToInference(&op, "transpose"); } void ModelAnalyzer::visit(mir::ops::GatherOp& op) { - addOpDescr(&op, "gather"); + appendOperationToInference(&op, "gather"); } void ModelAnalyzer::visit(mir::ops::SigmoidOp& op) { - addOpDescr(&op, "sigmoid"); + appendOperationToInference(&op, "sigmoid"); } void ModelAnalyzer::visit(mir::ops::LeakyReluOp& op) { - addOpDescr(&op, "leakyRelu"); + appendOperationToInference(&op, "leakyRelu"); } } // namespace nnc diff --git a/contrib/nnc/passes/soft_backend/ModelAnalyzer.h b/contrib/nnc/passes/soft_backend/ModelAnalyzer.h index f793608..ef891b5 100644 --- a/contrib/nnc/passes/soft_backend/ModelAnalyzer.h +++ b/contrib/nnc/passes/soft_backend/ModelAnalyzer.h @@ -17,6 +17,8 @@ #ifndef _NNC_SOFT_BACKEND_MODEL_ANALYZER_H_ #define _NNC_SOFT_BACKEND_MODEL_ANALYZER_H_ +#include "SequencedIR.h" + #include "core/modelIR/Visitor.h" #include "core/modelIR/Shape.h" #include "core/modelIR/TensorVariant.h" @@ -27,8 +29,6 @@ #include #include #include -#include -#include #include namespace nnc { @@ -37,45 +37,6 @@ namespace mir { class Graph; } -const size_t INVALID_TENSOR_ID = std::numeric_limits::max(); - -/** - * @brief Represents variable used in artifact. - * This variable can store inputs, outputs of network and temporary data. - */ -struct TensorDescription { - /** - * INPUT tensors of this type supposed to be set outside of artifact - * PERSISTENT tensors store data after inference process is over, this include NN outputs - * TEMPORARY tensors are not accessible outside artifact in any way, - * they are created and destructed on demand - */ - enum class Type { - INPUT, - PERSISTENT, - TEMPORARY - }; - size_t _id; - Type _type; - std::string _name; - // if _shape.rank() == 0 - assume shape is not known for this tensor on compilation - mir::Shape _shape; -}; - -/** - * @brief OpDescr represents operation call in inference sequence - */ -struct OpDescr { - mir::Operation* _op; - std::string _opName; - // list of input tensors - std::vector _inputs; - // list of output tensors - std::vector _outputs; - size_t _paramStartOffset; - std::list _temporaries; -}; - /** * @brief Constructs inference sequence for given computational graph, * gathers list of variables used in artifact. @@ -144,21 +105,21 @@ public: /** * @return vector of all network tensors */ - const std::vector& getTensors() const { + const std::vector& getTensors() const { return _tensors; } /** * @return Inference sequence */ - const std::list& getInferenceSequence() const { + const std::list>& getInferenceSequence() const { return _inferenceSequence; } /** * @return Inference sequence */ - std::list& getInferenceSequence() { + std::list>& getInferenceSequence() { return _inferenceSequence; } @@ -187,8 +148,9 @@ private: * Inserts information about CG operation into inference sequence: name of operation, * creates tensors for operation outputs, binds operation inputs with tensors from previous operations */ - void addOpDescr(mir::Operation* op, - const std::string& function_name, std::vector aux_args); + void appendOperationToInference(mir::Operation* op, + const std::string& function_name, + std::vector aux_args = {}); /** * @brief Registers a temporary buffer of size *size* used by op *op_id* @@ -218,7 +180,7 @@ private: size_t declareTemporaryTensor(); std::string _modelName = "NN"; - std::list _inferenceSequence; + std::list> _inferenceSequence; size_t _allocatedTensors = 0; std::vector _inputs; /// @brief list of persistent tensors @@ -227,8 +189,8 @@ private: std::vector _outputs; size_t _max_temp_size = 0; size_t _temp_tensor_id; - std::vector _tensors; - std::map _opToDescr; + std::vector _tensors; + std::map _opToDescr; }; } // namespace nnc diff --git a/contrib/nnc/passes/soft_backend/SBSerializer.cpp b/contrib/nnc/passes/soft_backend/SBSerializer.cpp index afdfd5e..c7f526a 100644 --- a/contrib/nnc/passes/soft_backend/SBSerializer.cpp +++ b/contrib/nnc/passes/soft_backend/SBSerializer.cpp @@ -51,6 +51,7 @@ #include "pass/PassException.h" #include +#include #define UNUSED(x) ((void)(x)) @@ -145,7 +146,7 @@ void Serializer::serializePads(const Op& op, int32_t padsRank) } void Serializer::visit(ops::ConcatOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // axis number should fit into one byte assert(op.getAxis() <= MAX_DIMS); serializeT(op.getAxis()); @@ -153,7 +154,7 @@ void Serializer::visit(ops::ConcatOp& op) { } void Serializer::visit(ops::Conv2DOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // serialize strides serializeShape(op.getStrides()); // serialize pads @@ -164,7 +165,7 @@ void Serializer::visit(ops::Conv2DOp& op) { } void Serializer::visit(ops::DepthwiseConv2DOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // serialize strides serializeShape(op.getStrides()); // serialize pads @@ -175,14 +176,14 @@ void Serializer::visit(ops::DepthwiseConv2DOp& op) { } void Serializer::visit(ops::SoftmaxOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // axis number should fit into one byte assert(op.getAxis() <= MAX_DIMS); serializeT(op.getAxis()); } void Serializer::visit(ops::PoolOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // serialize window shape serializeShape(op.getWindowShape()); // serialize strindes @@ -208,22 +209,22 @@ void Serializer::visit(ops::PoolOp& op) { } void Serializer::visit(ops::FullyConnectedOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); serializeShape(op.getOutputShape(0)); } void Serializer::visit(ops::GemmOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); serializeShape(op.getOutputShape(0)); } void Serializer::visit(ops::CappedReluOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); serializeT(op.getCap()); } void Serializer::visit(ops::BiasAddOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // no parameters to dump } @@ -232,70 +233,63 @@ void Serializer::visit(ops::VariableOp& op) { } void Serializer::visit(ops::ConstantOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); serializeTensor(op.getValue()); } void Serializer::visit(ops::ReluOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // no parameters to dump } void Serializer::visit(ops::ReshapeOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); serializeShape(op.getOutputShape(0)); } void Serializer::visit(ops::BatchNormOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); serializeT(op.getEps()); serializeT(op.getMovingAvgFraction()); serializeT(op.getSpatial()); } void Serializer::visit(ops::ScaleOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // no parameters to dump } void Serializer::visit(mir::ops::SliceOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); serializeShape(op.getStarts()); serializeShape(op.getSizes()); serializeShape(op.getOutputShape(0)); } void Serializer::visit(ops::DropoutOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); serializeT(op.getRate()); } -void Serializer::serialize(list& inferenceSequence) { - for (OpDescr& descr: inferenceSequence) { - _curOp = &descr; - descr._op->accept(this); - } -} - void Serializer::visit(mir::ops::TanhOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // no parameters to dump } void Serializer::visit(mir::ops::ElementwiseOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // Op type is known at codegen Time serializeT((int32_t)op.getBroadcast()); serializeShape(op.getOutputShape(0)); } void Serializer::visit(mir::ops::EluOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); serializeT(op.getAlpha()); } void Serializer::visit(mir::ops::DeConv2DOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // serialize strides serializeShape(op.getStrides()); // serialize pads @@ -306,12 +300,12 @@ void Serializer::visit(mir::ops::DeConv2DOp& op) { } void Serializer::visit(ops::SqueezeOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); serializeShape(op.getOutputShape(0)); } void Serializer::visit(mir::ops::PadOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // serialize paddings int num_dims = op.getNumDim(); @@ -330,25 +324,25 @@ void Serializer::visit(mir::ops::PadOp& op) { } void Serializer::visit(mir::ops::SqrtOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // no parameters to dump } void Serializer::visit(mir::ops::ResizeOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // Result shape is the same as Output shape serializeShape(op.getOutputShape(0)); } void Serializer::visit(mir::ops::ReduceFOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); serializeShape(Shape(op.getReductionDims())); // reuse shape serialization serializeT(op.getKeepDims()); serializeShape(op.getOutputShape(0)); } void Serializer::visit(mir::ops::TransposeOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // serializer parameters auto& axis_order = op.getAxisOrder(); serializeT(static_cast(axis_order.size())); @@ -360,7 +354,7 @@ void Serializer::visit(mir::ops::TransposeOp& op) { } void Serializer::visit(mir::ops::GatherOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); // serialize parameters serializeT(op.getAxis()); // serialize output shape @@ -368,13 +362,22 @@ void Serializer::visit(mir::ops::GatherOp& op) { } void Serializer::visit(mir::ops::SigmoidOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); } void Serializer::visit(mir::ops::LeakyReluOp& op) { - _curOp->_paramStartOffset = _buffer.size(); + _curOp->paramStartOffset = _buffer.size(); serializeT(op.getAlpha()); serializeShape(op.getOutputShape(0)); } +void Serializer::serialize(list>& inference_sequence) { + for (unique_ptr& action: inference_sequence) { + if (action->type != sir::Action::Type::callFunction) + continue; + _curOp = static_cast(action.get()); + _curOp->mirOp->accept(this); + } +} + } // namespace nnc diff --git a/contrib/nnc/passes/soft_backend/SBSerializer.h b/contrib/nnc/passes/soft_backend/SBSerializer.h index 6e5c7cc..5a995ce 100644 --- a/contrib/nnc/passes/soft_backend/SBSerializer.h +++ b/contrib/nnc/passes/soft_backend/SBSerializer.h @@ -72,20 +72,17 @@ public: void visit(mir::ops::TransposeOp& op) override; void visit(mir::ops::VariableOp& op) override; - void serialize(std::list &inferenceSequence); + void serialize(std::list>& inference_sequence); - const std::vector &getBuffer() const - { + const std::vector &getBuffer() const { return _buffer; } - uint32_t getFormatVersion() const - { + uint32_t getFormatVersion() const { return _formatVersion; } - uint32_t getModelHash() const - { + uint32_t getModelHash() const { return _modelHash; } private: @@ -94,25 +91,25 @@ private: * @param data Buffer containing data to serialize * @param size Size of data to serialize */ - void packData(const void *data, size_t size); - template + void packData(const void* data, size_t size); /** * @brief Serialize trivially copyable objects * @tparam T Type of object to serialize * @param obj Reference to object to serialize */ - void serializeT(const T &obj); + template + void serializeT(const T& obj); /** * @brief Serialize Tensor shape object * @param s shape to serialize */ - void serializeShape(const nnc::mir::Shape &s); + void serializeShape(const nnc::mir::Shape& s); /** * @brief Function serializes type of given tensor base data, * it's shape and raw data in 'c' format(i.e. layout of multidimensional C array) * @param t Tensor to serialize */ - void serializeTensor(const mir::TensorVariant &t); + void serializeTensor(const mir::TensorVariant& t); /** * @brief Serialize pads for operations like Conv2D * @tparam Op Operation type @@ -120,9 +117,9 @@ private: * @param padsRank Number of pads to serialize */ template - void serializePads(const Op &op, int32_t padsRank); + void serializePads(const Op& op, int32_t padsRank); - OpDescr *_curOp; + sir::CallFunction* _curOp; const uint32_t _formatVersion = 1; uint32_t _modelHash = 0; std::vector _buffer; diff --git a/contrib/nnc/passes/soft_backend/SequencedIR.cpp b/contrib/nnc/passes/soft_backend/SequencedIR.cpp new file mode 100644 index 0000000..267fe57 --- /dev/null +++ b/contrib/nnc/passes/soft_backend/SequencedIR.cpp @@ -0,0 +1,17 @@ +/* + * 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. + */ + +#include "SequencedIR.h" diff --git a/contrib/nnc/passes/soft_backend/SequencedIR.h b/contrib/nnc/passes/soft_backend/SequencedIR.h new file mode 100644 index 0000000..07123d3 --- /dev/null +++ b/contrib/nnc/passes/soft_backend/SequencedIR.h @@ -0,0 +1,139 @@ +/* + * 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_SOFT_BACKEND_SEQUENCED_IR_H_ +#define _NNC_SOFT_BACKEND_SEQUENCED_IR_H_ + +#include "core/modelIR/Shape.h" +#include "core/modelIR/Operation.h" + +#include +#include +#include +#include +#include + +namespace nnc { + +namespace sir { + +const size_t INVALID_TENSOR_ID = std::numeric_limits::max(); + +/** + * @brief Represents variable used in artifact. + * This variable can store inputs, outputs of network and temporary data. + */ +struct TensorDescriptor { + /** + * input tensors of this type supposed to be set outside of artifact + * persistent tensors store data after inference process is over, this include NN outputs + * temporary tensors are not accessible outside artifact in any way, + * they are created and destructed on demand + */ + enum class Type { + input, + persistent, + temporary + }; + + size_t id; + Type type; + std::string name; + // if _shape.rank() == 0 - assume shape is not known for this tensor on compilation + mir::Shape shape; +}; + +/** + * @brief Action represents some operation in inference sequence + */ +struct Action { + + /** + * Defines which type of action to perform + * createTmp responsible for creation of temporary tensor in inference sequence + * destroyTmp responsible for deletion of temporary tensor + * transpose + */ + enum class Type { + createTmp, + destroyTmp, + callFunction, + transposeTensor + }; + + Action(Type t) : type(t) {} + + virtual ~Action() = default; + + Type type; +}; + +struct TransposeTensor : public Action { + + TransposeTensor(size_t input, size_t output, std::vector&& perm) + : Action(Type::transposeTensor), + perm(std::move(perm)), + input(input), + output(output) { + } + + + std::vector perm; + size_t input; + size_t output; +}; + +struct CreateTmp : public Action { + + CreateTmp(size_t tid) : Action(Type::createTmp), tensorId(tid) {} + + size_t tensorId; +}; + +struct DestroyTmp : public Action { + + DestroyTmp(size_t tid) : Action(Type::destroyTmp), tensorId(tid) {} + + size_t tensorId; +}; + +struct CallFunction : public Action { + + CallFunction(mir::Operation* op, const std::string& func_name, + std::vector&& inputs, std::vector&& outputs) + : Action(Type::callFunction), + mirOp(op), + funcName(func_name), + inputs(inputs), + outputs(outputs), + paramStartOffset(0) {} + + CallFunction() : Action(Type::callFunction) {} + + mir::Operation* mirOp; + std::string funcName; + // list of input tensors + std::vector inputs; + // list of output tensors + std::vector outputs; + size_t paramStartOffset; +}; + +} // namespace sir + +} // namespace nnc + +#endif // _NNC_SOFT_BACKEND_SEQUENCED_IR_H_ diff --git a/contrib/nnc/unittests/soft_backend/CPPOperations.cpp b/contrib/nnc/unittests/soft_backend/CPPOperations.cpp index 0d21ea4..ca42d3b 100644 --- a/contrib/nnc/unittests/soft_backend/CPPOperations.cpp +++ b/contrib/nnc/unittests/soft_backend/CPPOperations.cpp @@ -307,13 +307,13 @@ void createAndRunTestGraph( mir::Operation *actual_operation = fillGraph(g, op_generator, input_ntensors); // serialize data for soft backend operation - list inference_sequence; - OpDescr op_descr; - op_descr._op = actual_operation; - inference_sequence.push_back(op_descr); + list> inference_sequence; + unique_ptr op_call(new sir::CallFunction); + op_call->mirOp = actual_operation; + inference_sequence.push_back(std::move(op_call)); Serializer serializer; serializer.serialize(inference_sequence); - assert(inference_sequence.front()._paramStartOffset == 0); + 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); diff --git a/contrib/nnc/unittests/soft_backend/ModelAnalyzer.cpp b/contrib/nnc/unittests/soft_backend/ModelAnalyzer.cpp index 9033c38..4367d76 100644 --- a/contrib/nnc/unittests/soft_backend/ModelAnalyzer.cpp +++ b/contrib/nnc/unittests/soft_backend/ModelAnalyzer.cpp @@ -24,6 +24,11 @@ using namespace std; using namespace nnc; using namespace mir; +using namespace sir; + +static const CallFunction* getCall(const unique_ptr& ptr) { + return dynamic_cast(ptr.get()); +} /* * This test designed to check basic layout properties of Model analyzer @@ -56,13 +61,13 @@ TEST(ModelAnalyzer, linearization) { // Check that layout is desired ModelAnalyzer ma; ma.analyze(&g); - auto seq = ma.getInferenceSequence(); + const auto& seq = ma.getInferenceSequence(); ASSERT_EQ(seq.size(), 6u); auto it = seq.begin(); - ASSERT_EQ((it++)->_op, input); - ASSERT_EQ((it++)->_op, head2); - ASSERT_EQ((it++)->_op, tail2); - ASSERT_EQ((it++)->_op, head1); - ASSERT_EQ((it++)->_op, tail1); - ASSERT_EQ((it++)->_op, join); + ASSERT_EQ(getCall(*(it++))->mirOp, input); + ASSERT_EQ(getCall(*(it++))->mirOp, head2); + ASSERT_EQ(getCall(*(it++))->mirOp, tail2); + ASSERT_EQ(getCall(*(it++))->mirOp, head1); + ASSERT_EQ(getCall(*(it++))->mirOp, tail1); + ASSERT_EQ(getCall(*(it++))->mirOp, join); } -- 2.7.4