[Snippets][CPU] Eltwise subgraphs code generation (#7936)
This commit is contained in:
1 parent
c10a27f601
commit
fa9434494f
53 files changed
+3265
-605
No files matched your search
@@ -44,7 +44,8 @@ target_link_libraries(${TARGET_NAME} PRIVATE mkldnn
|
||||
inference_engine
|
||||
inference_engine_transformations
|
||||
inference_engine_lp_transformations
|
||||
ov_shape_inference)
|
||||
ov_shape_inference
|
||||
inference_engine_snippets)
|
||||
|
||||
target_compile_definitions(${TARGET_NAME} PRIVATE IMPLEMENT_INFERENCE_EXTENSION_API)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
@@ -75,6 +76,7 @@ target_include_directories(${TARGET_NAME}_obj PRIVATE $<TARGET_PROPERTY:inferenc
|
||||
$<TARGET_PROPERTY:openvino::itt,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
$<TARGET_PROPERTY:inference_engine_lp_transformations,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
$<TARGET_PROPERTY:ov_shape_inference,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
$<TARGET_PROPERTY:inference_engine_snippets,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
$<TARGET_PROPERTY:openvino::conditional_compilation,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
|
||||
@@ -115,6 +115,8 @@ void Config::readProperties(const std::map<std::string, std::string> &prop) {
|
||||
IE_THROW() << "Wrong value for property key " << PluginConfigParams::KEY_ENFORCE_BF16
|
||||
<< ". Expected only YES/NO";
|
||||
}
|
||||
} else if (key == PluginConfigParams::KEY_CACHE_DIR) {
|
||||
cache_dir = val;
|
||||
} else {
|
||||
IE_THROW(NotFound) << "Unsupported property " << key << " by CPU plugin";
|
||||
}
|
||||
@@ -167,6 +169,7 @@ void Config::updateProperties() {
|
||||
_config.insert({ PluginConfigParams::KEY_PERFORMANCE_HINT, perfHintsConfig.ovPerfHint });
|
||||
_config.insert({ PluginConfigParams::KEY_PERFORMANCE_HINT_NUM_REQUESTS,
|
||||
std::to_string(perfHintsConfig.ovPerfHintNumRequests) });
|
||||
_config.insert({PluginConfigParams::KEY_CACHE_DIR, cache_dir});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ struct Config {
|
||||
#ifdef CPU_DEBUG_CAPS
|
||||
DebugCaps::Config debugCaps;
|
||||
#endif
|
||||
std::string cache_dir{};
|
||||
|
||||
void readProperties(const std::map<std::string, std::string> &config);
|
||||
void updateProperties();
|
||||
|
||||
@@ -183,6 +183,7 @@ const InferenceEngine::details::caseless_unordered_map<std::string, Type> type_t
|
||||
{ "MatrixNms", MatrixNms},
|
||||
{ "MulticlassNms", MulticlassNms},
|
||||
{ "Reference", Reference},
|
||||
{ "Subgraph", Subgraph}
|
||||
};
|
||||
|
||||
Type TypeFromName(const std::string& type) {
|
||||
@@ -364,6 +365,8 @@ std::string NameFromType(const Type type) {
|
||||
return "MulticlassNms";
|
||||
case Reference:
|
||||
return "Reference";
|
||||
case Subgraph:
|
||||
return "Subgraph";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
@@ -99,7 +99,8 @@ enum Type {
|
||||
ExtractImagePatches,
|
||||
NonMaxSuppression,
|
||||
MatrixNms,
|
||||
MulticlassNms
|
||||
MulticlassNms,
|
||||
Subgraph
|
||||
};
|
||||
|
||||
enum Algorithm {
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright (C) 2020-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "snippets/snippets_isa.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <array>
|
||||
|
||||
#include "cpu_generator.hpp"
|
||||
#include "jit_snippets_emitters.hpp"
|
||||
#include "jit_eltwise_emitters.hpp"
|
||||
#include "jit_mkldnn_emitters.hpp"
|
||||
#include "jit_mkldnn_ext_emitters.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace ngraph::snippets;
|
||||
|
||||
#define CREATE_EMITTER(e_type) [this](const std::shared_ptr<ngraph::Node>& n) \
|
||||
-> std::shared_ptr<ngraph::snippets::Emitter> {return std::make_shared<e_type>(h.get(), isa, n);};
|
||||
|
||||
class jit_snippet : public dnnl::impl::cpu::x64::jit_generator {
|
||||
public:
|
||||
DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_snippet)
|
||||
|
||||
~jit_snippet() = default;
|
||||
|
||||
jit_snippet() : jit_generator() {
|
||||
}
|
||||
|
||||
void generate() override {
|
||||
}
|
||||
};
|
||||
|
||||
MKLDNNPlugin::CPUTargetMachine::CPUTargetMachine(dnnl::impl::cpu::x64::cpu_isa_t host_isa)
|
||||
: TargetMachine(), h(new jit_snippet()), isa(host_isa) {
|
||||
// data movement
|
||||
jitters[ngraph::opset1::Parameter::get_type_info_static()] = CREATE_EMITTER(NopEmitter);
|
||||
jitters[ngraph::snippets::op::BlockedParameter::get_type_info_static()] = CREATE_EMITTER(NopEmitter);
|
||||
jitters[ngraph::opset1::Result::get_type_info_static()] = CREATE_EMITTER(NopEmitter);
|
||||
// jitters[ngraph::opset1::Constant::get_type_info_static()] = CREATE_EMITTER(); // Not supported
|
||||
|
||||
jitters[ngraph::snippets::op::Load::get_type_info_static()] = CREATE_EMITTER(LoadEmitter);
|
||||
jitters[ngraph::snippets::op::VectorLoad::get_type_info_static()] = CREATE_EMITTER(LoadEmitter);
|
||||
jitters[ngraph::snippets::op::ScalarLoad::get_type_info_static()] = CREATE_EMITTER(ScalarLoadEmitter);
|
||||
jitters[ngraph::snippets::op::BroadcastLoad::get_type_info_static()] = CREATE_EMITTER(BroadcastLoadEmitter);
|
||||
|
||||
jitters[ngraph::snippets::op::Store::get_type_info_static()] = CREATE_EMITTER(StoreEmitter);
|
||||
jitters[ngraph::snippets::op::VectorStore::get_type_info_static()] = CREATE_EMITTER(StoreEmitter);
|
||||
jitters[ngraph::snippets::op::ScalarStore::get_type_info_static()] = CREATE_EMITTER(ScalarStoreEmitter);
|
||||
|
||||
jitters[ngraph::snippets::op::Scalar::get_type_info_static()] = CREATE_EMITTER(ScalarEmitter);
|
||||
jitters[ngraph::snippets::op::BroadcastMove::get_type_info_static()] = CREATE_EMITTER(FakeBroadcastEmitter);
|
||||
// jitters[ngraph::snippets::op::Nop::get_type_info_static()] = CREATE_EMITTER(NopEmitter); // Not supported
|
||||
// jitters[ngraph::opset1::Broadcast::get_type_info_static()] = CREATE_EMITTER(); // Not supported
|
||||
|
||||
// jitters[ngraph::opset1::Convert::get_type_info_static()] = CREATE_EMITTER(); // Not supported
|
||||
// jitters[ngraph::opset1::FakeQuantize::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
|
||||
// binary
|
||||
jitters[ngraph::opset1::Add::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_add_emitter);
|
||||
jitters[ngraph::opset1::Divide::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_divide_emitter);
|
||||
jitters[ngraph::opset1::Equal::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_equal_emitter);
|
||||
jitters[ngraph::opset1::FloorMod::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_floor_mod_emitter);
|
||||
jitters[ngraph::opset1::Greater::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_greater_emitter);
|
||||
jitters[ngraph::opset1::GreaterEqual::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_greater_equal_emitter);
|
||||
jitters[ngraph::opset1::Less::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_less_emitter);
|
||||
jitters[ngraph::opset1::LessEqual::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_less_equal_emitter);
|
||||
jitters[ngraph::opset1::LogicalAnd::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_logical_and_emitter);
|
||||
jitters[ngraph::opset1::LogicalOr::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_logical_or_emitter);
|
||||
jitters[ngraph::opset1::LogicalXor::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_logical_xor_emitter);
|
||||
jitters[ngraph::opset1::Maximum::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_maximum_emitter);
|
||||
jitters[ngraph::opset1::Minimum::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_minimum_emitter);
|
||||
jitters[ngraph::opset1::Mod::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_mod_emitter);
|
||||
jitters[ngraph::opset1::Multiply::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_multiply_emitter);
|
||||
jitters[ngraph::opset1::NotEqual::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_not_equal_emitter);
|
||||
jitters[ngraph::snippets::op::PowerStatic::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_power_static_emitter);
|
||||
jitters[ngraph::opset1::Power::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_power_dynamic_emitter);
|
||||
jitters[ngraph::opset1::PRelu::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_prelu_emitter);
|
||||
jitters[ngraph::opset1::SquaredDifference::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_squared_difference_emitter);
|
||||
jitters[ngraph::opset1::Subtract::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_subtract_emitter);
|
||||
jitters[ngraph::opset1::Xor::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_logical_xor_emitter);
|
||||
|
||||
// unary
|
||||
jitters[ngraph::opset1::Abs::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_abs_emitter);
|
||||
// jitters[ngraph::opset1::Acos::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
// jitters[ngraph::opset1::Asin::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
// jitters[ngraph::opset1::Atan::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
// jitters[ngraph::opset1::Ceiling::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
jitters[ngraph::opset1::Clamp::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_clamp_emitter);
|
||||
// jitters[ngraph::opset1::Cos::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
// jitters[ngraph::opset1::Cosh::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
jitters[ngraph::opset1::Elu::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_elu_emitter);
|
||||
jitters[ngraph::opset1::Erf::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_erf_emitter);
|
||||
jitters[ngraph::opset1::Exp::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_exp_emitter);
|
||||
// jitters[ngraph::opset1::Floor::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
// jitters[ngraph::opset1::Log::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
jitters[ngraph::opset1::LogicalNot::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_logical_not_emitter);
|
||||
jitters[ngraph::opset1::Negative::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_negative_emitter);
|
||||
jitters[ngraph::opset1::Relu::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_relu_emitter);
|
||||
// jitters[ngraph::opset1::Sign::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
jitters[ngraph::opset1::Sigmoid::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_sigmoid_emitter);
|
||||
// jitters[ngraph::opset1::Sin::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
// jitters[ngraph::opset1::Sinh::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
jitters[ngraph::opset1::Sqrt::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_sqrt_emitter);
|
||||
// jitters[ngraph::opset1::Tan::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
jitters[ngraph::opset1::Tanh::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_tanh_emitter);
|
||||
|
||||
jitters[ngraph::op::v4::HSwish::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_hswish_emitter);
|
||||
// jitters[ngraph::opset1::HardSigmoid::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
// jitters[ngraph::opset1::Selu::get_type_info_static()] = CREATE_EMITTER(); // not supported
|
||||
jitters[ngraph::op::v0::Gelu::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_gelu_v0_emitter);
|
||||
jitters[ngraph::op::v7::Gelu::get_type_info_static()] = CREATE_EMITTER(MKLDNNPlugin::jit_gelu_v7_emitter);
|
||||
|
||||
jitters[ngraph::snippets::op::Kernel::get_type_info_static()] = CREATE_EMITTER(KernelEmitter);
|
||||
jitters[ngraph::snippets::op::Tile::get_type_info_static()] = CREATE_EMITTER(TileEmitter);
|
||||
}
|
||||
|
||||
size_t MKLDNNPlugin::CPUTargetMachine::get_lanes() const {
|
||||
switch (isa) {
|
||||
case dnnl::impl::cpu::x64::avx2 : return dnnl::impl::cpu::x64::cpu_isa_traits<dnnl::impl::cpu::x64::avx2>::vlen / sizeof(float);
|
||||
case dnnl::impl::cpu::x64::sse41 : return dnnl::impl::cpu::x64::cpu_isa_traits<dnnl::impl::cpu::x64::sse41>::vlen / sizeof(float);
|
||||
case dnnl::impl::cpu::x64::avx512_common : return dnnl::impl::cpu::x64::cpu_isa_traits<dnnl::impl::cpu::x64::avx512_common>::vlen / sizeof(float);
|
||||
default : IE_THROW() << "unknown isa " << isa;
|
||||
}
|
||||
}
|
||||
|
||||
bool MKLDNNPlugin::CPUTargetMachine::is_supported() const {
|
||||
return dnnl::impl::cpu::x64::mayiuse(isa);
|
||||
}
|
||||
|
||||
code MKLDNNPlugin::CPUTargetMachine::get_snippet() const {
|
||||
h->create_kernel();
|
||||
return h->jit_ker();
|
||||
}
|
||||
|
||||
MKLDNNPlugin::CPUGenerator::CPUGenerator(dnnl::impl::cpu::x64::cpu_isa_t isa_) : Generator(std::make_shared<CPUTargetMachine>(isa_)) {
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (C) 2020-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <transformations_visibility.hpp>
|
||||
#include <cpu/x64/jit_generator.hpp>
|
||||
|
||||
#include "snippets/generator.hpp"
|
||||
|
||||
namespace MKLDNNPlugin {
|
||||
|
||||
class CPUTargetMachine : public ngraph::snippets::TargetMachine {
|
||||
public:
|
||||
CPUTargetMachine(dnnl::impl::cpu::x64::cpu_isa_t host_isa);
|
||||
|
||||
bool is_supported() const override;
|
||||
ngraph::snippets::code get_snippet() const override;
|
||||
size_t get_lanes() const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<dnnl::impl::cpu::x64::jit_generator> h;
|
||||
dnnl::impl::cpu::x64::cpu_isa_t isa;
|
||||
};
|
||||
|
||||
class CPUGenerator : public ngraph::snippets::Generator {
|
||||
public:
|
||||
CPUGenerator(dnnl::impl::cpu::x64::cpu_isa_t isa);
|
||||
~CPUGenerator() = default;
|
||||
};
|
||||
|
||||
} // namespace MKLDNNPlugin
|
||||
@@ -1286,21 +1286,14 @@ size_t jit_logical_not_emitter::aux_vecs_count() const {
|
||||
/// POWER_STATIC ///
|
||||
jit_power_static_emitter::jit_power_static_emitter(jit_generator *host, cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& node, Precision exec_prc)
|
||||
: jit_emitter(host, host_isa, node, exec_prc) {
|
||||
auto parent = node->input(1).get_source_output().get_node_shared_ptr();
|
||||
if (!std::dynamic_pointer_cast<ngraph::op::Constant>(parent)) {
|
||||
throw ngraph::ngraph_error("unsupported non constant power");
|
||||
auto powerStaticNode = ov::as_type_ptr<ngraph::snippets::op::PowerStatic>(node);
|
||||
if (powerStaticNode == nullptr) {
|
||||
IE_THROW() << "Can't cast to snippets::op::PowerStatic";
|
||||
}
|
||||
|
||||
if (!(node->input(1).get_shape() == ngraph::Shape() || ngraph::shape_size(node->input(1).get_shape()) == 1)) {
|
||||
throw ngraph::ngraph_error("unsupported non scalar power");
|
||||
}
|
||||
power = ngraph::as_type_ptr<ngraph::op::Constant>(parent)->get_data_ptr<float>()[0];
|
||||
power = powerStaticNode->get_power();
|
||||
scale = 1.f;
|
||||
shift = 0.f;
|
||||
push_arg_entry_of("power", cpu::x64::float2int(power), true);
|
||||
push_arg_entry_of("scale", 0x3f800000, true);
|
||||
push_arg_entry_of("shift", 0x00000000, true);
|
||||
push_arg_entry_of("one", 0x3f800000, true);
|
||||
|
||||
prepare_table();
|
||||
}
|
||||
@@ -1611,6 +1604,11 @@ jit_erf_emitter::jit_erf_emitter(jit_generator *host, cpu_isa_t host_isa, const
|
||||
prepare_table();
|
||||
}
|
||||
|
||||
jit_erf_emitter::jit_erf_emitter(jit_generator *host, cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& node, Precision exec_prc)
|
||||
: jit_emitter(host, host_isa, node, exec_prc) {
|
||||
prepare_table();
|
||||
}
|
||||
|
||||
size_t jit_erf_emitter::get_inputs_num() const { return 1; }
|
||||
|
||||
void jit_erf_emitter::emit_impl(
|
||||
|
||||
@@ -531,6 +531,9 @@ public:
|
||||
jit_erf_emitter(mkldnn::impl::cpu::x64::jit_generator *host, mkldnn::impl::cpu::x64::cpu_isa_t host_isa, const MKLDNNNode* node,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32);
|
||||
|
||||
jit_erf_emitter(mkldnn::impl::cpu::x64::jit_generator *host, mkldnn::impl::cpu::x64::cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& n,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32);
|
||||
|
||||
size_t get_inputs_num() const override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -101,7 +101,8 @@ void jit_emitter::emitter_preamble(const std::vector<size_t> &in_idxs, const std
|
||||
aux_vec_idxs.push_back(idx);
|
||||
preserved_vec_idxs.push_back(idx);
|
||||
}
|
||||
assert(aux_vec_idxs.size() >= aux_vecs_count());
|
||||
if (aux_vec_idxs.size() < aux_vecs_count())
|
||||
IE_THROW() << "Failed to allocate required number of vector registers";
|
||||
|
||||
// Same logic but to allocate gprs
|
||||
for (auto idx : pool_gpr_idxs)
|
||||
@@ -124,7 +125,8 @@ void jit_emitter::emitter_preamble(const std::vector<size_t> &in_idxs, const std
|
||||
aux_gpr_idxs.push_back(_idx);
|
||||
preserved_gpr_idxs.push_back(_idx);
|
||||
}
|
||||
assert(aux_gpr_idxs.size() == aux_gprs_count());
|
||||
if (aux_gpr_idxs.size() < aux_gprs_count())
|
||||
IE_THROW() << "Failed to allocate required number of general-purpose registers";
|
||||
|
||||
if (!entry_map_.empty()) {
|
||||
// last aux_gpr_idx is for p_table, we can use aux_gpr_idxs from idx 0 for other purpose
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <ie_common.h>
|
||||
#include <cpu/x64/jit_generator.hpp>
|
||||
|
||||
#include "snippets/snippets_isa.hpp"
|
||||
#include "snippets/generator.hpp"
|
||||
#include "mkldnn_node.h"
|
||||
|
||||
#include <set>
|
||||
@@ -24,23 +26,23 @@ struct emitter_context {
|
||||
virtual ~emitter_context() = default;
|
||||
};
|
||||
|
||||
class jit_emitter {
|
||||
class jit_emitter : public ngraph::snippets::Emitter {
|
||||
public:
|
||||
jit_emitter(dnnl::impl::cpu::x64::jit_generator* host, dnnl::impl::cpu::x64::cpu_isa_t host_isa, const MKLDNNNode* node,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32, emitter_in_out_map in_out_type = emitter_in_out_map::vec_to_vec)
|
||||
: h(host), host_isa_(host_isa), exec_prc_(exec_prc), in_out_type_(in_out_type), l_table (new Xbyak::Label()) {
|
||||
: Emitter(nullptr), h(host), host_isa_(host_isa), exec_prc_(exec_prc), in_out_type_(in_out_type), l_table (new Xbyak::Label()) {
|
||||
k_mask = Xbyak::Opmask(1); // FIXME: in general case we need preserve k_mask state as well
|
||||
}
|
||||
|
||||
jit_emitter(dnnl::impl::cpu::x64::jit_generator* host, dnnl::impl::cpu::x64::cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& n,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32, emitter_in_out_map in_out_type = emitter_in_out_map::vec_to_vec)
|
||||
: h(host), host_isa_(host_isa), exec_prc_(exec_prc), in_out_type_(in_out_type), l_table (new Xbyak::Label()) {
|
||||
: Emitter(n), h(host), host_isa_(host_isa), exec_prc_(exec_prc), in_out_type_(in_out_type), l_table (new Xbyak::Label()) {
|
||||
k_mask = Xbyak::Opmask(1); // FIXME: in general case we need preserve k_mask state as well
|
||||
}
|
||||
|
||||
virtual void emit_code(const std::vector<size_t> &in_idxs, const std::vector<size_t> &out_idxs,
|
||||
const std::vector<size_t> &pool_vec_idxs = {}, const std::vector<size_t> &pool_gpr_idxs = {}) const;
|
||||
virtual void emit_data() const;
|
||||
void emit_code(const std::vector<size_t> &in_idxs, const std::vector<size_t> &out_idxs,
|
||||
const std::vector<size_t> &pool_vec_idxs = {}, const std::vector<size_t> &pool_gpr_idxs = {}) const override;
|
||||
void emit_data() const override;
|
||||
|
||||
virtual void emit_code(const std::vector<size_t> &in_idxs, const std::vector<size_t> &out_idxs,
|
||||
const std::shared_ptr<const emitter_context> &emit_context,
|
||||
@@ -147,6 +149,8 @@ private:
|
||||
const auto scale = te.bcast ? get_vec_length() : sizeof(table_entry_val_t);
|
||||
return te.off + key_off_val_shift * scale;
|
||||
}
|
||||
virtual void validate_arguments(const std::vector<size_t>&, const std::vector<size_t>&,
|
||||
const std::vector<size_t>&, const std::vector<size_t> &) const {}
|
||||
};
|
||||
|
||||
} // namespace MKLDNNPlugin
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright (C) 2020-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "jit_mkldnn_emitters.hpp"
|
||||
|
||||
namespace MKLDNNPlugin {
|
||||
|
||||
class jit_relu_emitter : public jit_mkldnn_emitter {
|
||||
public:
|
||||
jit_relu_emitter(dnnl::impl::cpu::x64::jit_generator *host, dnnl::impl::cpu::x64::cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& n,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32)
|
||||
: jit_mkldnn_emitter(host, host_isa, n, exec_prc) {
|
||||
kind = mkldnn_eltwise_relu;
|
||||
alpha = 0.f;
|
||||
beta = 0.f;
|
||||
|
||||
set_injector();
|
||||
}
|
||||
};
|
||||
|
||||
class jit_sigmoid_emitter : public jit_mkldnn_emitter {
|
||||
public:
|
||||
jit_sigmoid_emitter(mkldnn::impl::cpu::x64::jit_generator *host, mkldnn::impl::cpu::x64::cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& n,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32)
|
||||
: jit_mkldnn_emitter(host, host_isa, n, exec_prc) {
|
||||
kind = mkldnn_eltwise_logistic;
|
||||
alpha = 0.f;
|
||||
beta = 0.f;
|
||||
|
||||
set_injector();
|
||||
}
|
||||
};
|
||||
|
||||
class jit_tanh_emitter : public jit_mkldnn_emitter {
|
||||
public:
|
||||
jit_tanh_emitter(mkldnn::impl::cpu::x64::jit_generator *host, mkldnn::impl::cpu::x64::cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& n,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32)
|
||||
: jit_mkldnn_emitter(host, host_isa, n, exec_prc) {
|
||||
kind = mkldnn_eltwise_tanh;
|
||||
alpha = 0.f;
|
||||
beta = 0.f;
|
||||
|
||||
set_injector();
|
||||
}
|
||||
};
|
||||
|
||||
class jit_elu_emitter : public jit_mkldnn_emitter {
|
||||
public:
|
||||
jit_elu_emitter(mkldnn::impl::cpu::x64::jit_generator *host, mkldnn::impl::cpu::x64::cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& n,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32)
|
||||
: jit_mkldnn_emitter(host, host_isa, n, exec_prc) {
|
||||
kind = mkldnn_eltwise_elu;
|
||||
alpha = ngraph::as_type_ptr<ngraph::opset1::Elu>(n)->get_alpha();
|
||||
beta = 0.f;
|
||||
|
||||
set_injector();
|
||||
}
|
||||
};
|
||||
|
||||
class jit_exp_emitter : public jit_mkldnn_emitter {
|
||||
public:
|
||||
jit_exp_emitter(mkldnn::impl::cpu::x64::jit_generator *host, mkldnn::impl::cpu::x64::cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& n,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32)
|
||||
: jit_mkldnn_emitter(host, host_isa, n, exec_prc) {
|
||||
kind = mkldnn_eltwise_exp;
|
||||
alpha = 0.f;
|
||||
beta = 0.f;
|
||||
|
||||
set_injector();
|
||||
}
|
||||
};
|
||||
|
||||
class jit_abs_emitter : public jit_mkldnn_emitter {
|
||||
public:
|
||||
jit_abs_emitter(mkldnn::impl::cpu::x64::jit_generator *host, mkldnn::impl::cpu::x64::cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& n,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32)
|
||||
: jit_mkldnn_emitter(host, host_isa, n, exec_prc) {
|
||||
kind = mkldnn_eltwise_abs;
|
||||
alpha = 0.f;
|
||||
beta = 0.f;
|
||||
|
||||
set_injector();
|
||||
}
|
||||
};
|
||||
|
||||
class jit_clamp_emitter : public jit_mkldnn_emitter {
|
||||
public:
|
||||
jit_clamp_emitter(mkldnn::impl::cpu::x64::jit_generator *host, mkldnn::impl::cpu::x64::cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& n,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32)
|
||||
: jit_mkldnn_emitter(host, host_isa, n, exec_prc) {
|
||||
kind = dnnl_eltwise_clip;
|
||||
auto op = ngraph::as_type_ptr<ngraph::opset1::Clamp>(n);
|
||||
alpha = op->get_min();
|
||||
beta = op->get_max();
|
||||
|
||||
set_injector();
|
||||
}
|
||||
};
|
||||
|
||||
class jit_hswish_emitter : public jit_mkldnn_emitter {
|
||||
public:
|
||||
jit_hswish_emitter(mkldnn::impl::cpu::x64::jit_generator *host, mkldnn::impl::cpu::x64::cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& n,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32)
|
||||
: jit_mkldnn_emitter(host, host_isa, n, exec_prc) {
|
||||
kind = dnnl_eltwise_hardswish;
|
||||
alpha = 0.f;
|
||||
beta = 0.f;
|
||||
|
||||
set_injector();
|
||||
}
|
||||
};
|
||||
class jit_gelu_v0_emitter : public jit_mkldnn_emitter {
|
||||
public:
|
||||
jit_gelu_v0_emitter(mkldnn::impl::cpu::x64::jit_generator *host, mkldnn::impl::cpu::x64::cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& n,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32)
|
||||
: jit_mkldnn_emitter(host, host_isa, n, exec_prc) {
|
||||
kind = dnnl_eltwise_gelu_erf;
|
||||
|
||||
set_injector();
|
||||
}
|
||||
};
|
||||
|
||||
class jit_gelu_v7_emitter : public jit_mkldnn_emitter {
|
||||
public:
|
||||
jit_gelu_v7_emitter(mkldnn::impl::cpu::x64::jit_generator *host, mkldnn::impl::cpu::x64::cpu_isa_t host_isa, const std::shared_ptr<ngraph::Node>& n,
|
||||
InferenceEngine::Precision exec_prc = InferenceEngine::Precision::FP32)
|
||||
: jit_mkldnn_emitter(host, host_isa, n, exec_prc) {
|
||||
auto gelu = getNgraphOpAs<ngraph::op::v7::Gelu>(n);
|
||||
ngraph::op::GeluApproximationMode approximationMode = gelu->get_approximation_mode();
|
||||
if (approximationMode == ngraph::op::GeluApproximationMode::ERF)
|
||||
kind = dnnl_eltwise_gelu_erf;
|
||||
else if (approximationMode == ngraph::op::GeluApproximationMode::TANH)
|
||||
kind = dnnl_eltwise_gelu_tanh;
|
||||
else
|
||||
IE_THROW(NotImplemented) << "Subgraph node doesn't support ngraph operation Gelu with approximation mode: " << approximationMode;
|
||||
|
||||
set_injector();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace MKLDNNPlugin
|
||||
@@ -0,0 +1,641 @@
|
||||
// Copyright (C) 2020-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ngraph/rt_info.hpp>
|
||||
#include <ngraph/variant.hpp>
|
||||
|
||||
#include "jit_emitter.hpp"
|
||||
using namespace Xbyak;
|
||||
namespace MKLDNNPlugin {
|
||||
|
||||
#define SNIPPETS_MAX_SNIPPETS_DIMS 7
|
||||
#define SNIPPETS_MAX_HARNESS_DIMS 5
|
||||
#define SNIPPETS_MAX_TILE_RANK 2
|
||||
#define GET_OFF(field) offsetof(jit_snippets_call_args, field)
|
||||
struct jit_snippets_call_args {
|
||||
const void *src_ptrs[SNIPPETS_MAX_SNIPPETS_DIMS] = {};
|
||||
void *dst_ptrs[SNIPPETS_MAX_SNIPPETS_DIMS] = {};
|
||||
};
|
||||
|
||||
struct jit_snippets_compile_args {
|
||||
int64_t scheduler_dims[SNIPPETS_MAX_TILE_RANK] = {};
|
||||
int64_t scheduler_offsets[SNIPPETS_MAX_SNIPPETS_DIMS] = {};
|
||||
int64_t data_offsets[SNIPPETS_MAX_SNIPPETS_DIMS * SNIPPETS_MAX_HARNESS_DIMS] = {};
|
||||
std::vector<int64_t> output_dims = {};
|
||||
};
|
||||
///
|
||||
/// \brief Kernel is the only entry point to Codogen Jit compilation. Kernel calculates appropriate data offsets,
|
||||
/// and invokes enclosed outer Tiles. Only 2d Tiles are currently supported, so the emitters should
|
||||
/// be organized in the following way:
|
||||
/// KernelEmitter { /* entry point */
|
||||
/// TileEmitter { /* outer tile */
|
||||
/// TileEmitter { /* inner vector tile */
|
||||
/// ... /* All the necessary Load/Strore/elementwise emitters */
|
||||
/// }
|
||||
/// TileEmitter { /* inner scalar tile for tail processing */
|
||||
/// ... /* All the necessary Load/Strore/elementwise emitters */
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// Note that Kernel params are passed directly to the emit_code(). The vector of inputs should contain 2 arguments, the
|
||||
/// output vector should be empty. Input parameters
|
||||
///
|
||||
/// \param in[0] The number of the node inputs
|
||||
/// \param in[1] The number of the node outputs
|
||||
///
|
||||
// Todo: Scheduler dims and offsets are currently calculated in MKLDNN Subgraph node and passed to the KernelEmitter.
|
||||
// However, it seems more natural to calculate all the offsets right in the Kernel op, because the calculation is
|
||||
// not device-specific. It is based only on input/output dims (which we already know) and harness num dims
|
||||
// (which we should pass from the plugin). It seems also better to wrap the enclosed emitters in tiles in the Kernel op
|
||||
// and avoid creating empty tiles.
|
||||
class KernelEmitter : public jit_emitter {
|
||||
public:
|
||||
KernelEmitter(mkldnn::impl::cpu::x64::jit_generator* h, mkldnn::impl::cpu::x64::cpu_isa_t isa,
|
||||
const std::shared_ptr<ov::Node>& n)
|
||||
: jit_emitter(h, isa, n) {
|
||||
const auto kernel = ov::as_type_ptr<ngraph::snippets::op::Kernel>(n);
|
||||
if (!kernel)
|
||||
IE_THROW() << "KernelEmitter invoked with invalid op argument";
|
||||
if (!kernel->compile_params)
|
||||
IE_THROW() << "KernelEmitter invoked without compile_params";
|
||||
code = kernel->region;
|
||||
jcp = *reinterpret_cast<const jit_snippets_compile_args*>(kernel->compile_params);
|
||||
}
|
||||
|
||||
size_t get_inputs_num() const override {return 0;}
|
||||
|
||||
void emit_code(const std::vector<size_t> &in, const std::vector<size_t> &out,
|
||||
const std::vector<size_t> &pool = {}, const std::vector<size_t> &gpr = {}) const override {
|
||||
validate_arguments(in, out, pool, gpr);
|
||||
emit_impl(in, out, pool, gpr, nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
void validate_arguments(const std::vector<size_t> &in, const std::vector<size_t> &out,
|
||||
const std::vector<size_t> &pool = {}, const std::vector<size_t> &gpr = {}) const override {
|
||||
if (in.size() != 2)
|
||||
IE_THROW() << "KernelEmitter got invalid number of inputs. Expected 2, got " << in.size();
|
||||
if (out.size() != 0)
|
||||
IE_THROW() << "KernelEmitter got unexpected output arguments.";
|
||||
const size_t num_params = in[0] + in[1];
|
||||
if (num_params > SNIPPETS_MAX_SNIPPETS_DIMS)
|
||||
IE_THROW() << "KernelEmitter supports only up to " << SNIPPETS_MAX_SNIPPETS_DIMS <<
|
||||
" parameters, got " << num_params;
|
||||
const int64_t harness_num_dims = jcp.output_dims.size() - 1;
|
||||
if (harness_num_dims > SNIPPETS_MAX_HARNESS_DIMS)
|
||||
IE_THROW() << "KernelEmitter supports harness with up to " << SNIPPETS_MAX_HARNESS_DIMS <<
|
||||
" dims, got " << harness_num_dims;
|
||||
}
|
||||
|
||||
void emit_impl(const std::vector<size_t>& in,
|
||||
const std::vector<size_t>& out,
|
||||
const std::vector<size_t>& pool,
|
||||
const std::vector<size_t>& gpr,
|
||||
const MKLDNNPlugin::emitter_context *emit_context) const override {
|
||||
const size_t num_inputs = in[0];
|
||||
const size_t num_outputs = in[1];
|
||||
const size_t num_params = num_inputs + num_outputs;
|
||||
int reg64_tmp_start { 8 }; // R8, R9, R10, R11, R12, R13, R14, R15 inputs+outputs+1
|
||||
const int64_t harness_num_dims = jcp.output_dims.size() - 1;
|
||||
|
||||
Reg64 reg_indexes { dnnl::impl::cpu::x64::abi_param1 };
|
||||
Reg64 reg_const_params { dnnl::impl::cpu::x64::abi_param2 };
|
||||
Xbyak::Reg64 reg_tmp_64 { dnnl::impl::cpu::x64::abi_not_param1};
|
||||
|
||||
h->preamble();
|
||||
|
||||
std::vector<Reg64> regs(num_params);
|
||||
auto init_ptrs_with_offsets = [&](Reg64 pointer, const int64_t *offsets) {
|
||||
for (int j = 0; j < harness_num_dims; j++) {
|
||||
if (jcp.output_dims[j] != 1 && offsets[j] != 0) {
|
||||
h->mov(reg_tmp_64, offsets[j]);
|
||||
h->imul(reg_tmp_64, h->ptr[reg_indexes + j * sizeof(size_t)]);
|
||||
h->add(pointer, reg_tmp_64);
|
||||
}
|
||||
}
|
||||
};
|
||||
for (auto i = 0; i < num_params; i++) {
|
||||
regs[i] = Reg64(reg64_tmp_start + i);
|
||||
if (i < num_inputs)
|
||||
h->mov(regs[i], h->ptr[reg_const_params + GET_OFF(src_ptrs) + i * sizeof(void*)]);
|
||||
else
|
||||
h->mov(regs[i], h->ptr[reg_const_params + GET_OFF(dst_ptrs) + (i - num_inputs) * sizeof(void*)]);
|
||||
init_ptrs_with_offsets(regs[i], &jcp.data_offsets[i * harness_num_dims]);
|
||||
}
|
||||
|
||||
for (auto& c : code) {
|
||||
c.first->emit_code(c.second.first, c.second.second, pool, gpr);
|
||||
}
|
||||
|
||||
h->postamble();
|
||||
}
|
||||
|
||||
jit_snippets_compile_args jcp;
|
||||
std::vector<std::pair<std::shared_ptr<Emitter>, ngraph::snippets::RegInfo>> code;
|
||||
};
|
||||
///
|
||||
/// \brief Tile is designed to organize loop over the input and output data. It is essentially a for(...) loop:
|
||||
/// it calculates the total number of iterations, performs operations specified by enclosed emitters, advances iteration counters
|
||||
/// and breaks when necessary.
|
||||
///
|
||||
/// \param in[0] The number of input entities (or scheduler counts) processed during one iteration of the tile.
|
||||
/// It is expected to be 1 for outer or scalar tiles and vlen for vector tiles.
|
||||
/// \param in[1] Increment of the previous Tile in current dimension. Must be 0 if this is the first Tile.
|
||||
/// So previous_inc is zero for outer and vector tiles (the are the first in dim) and vlen for scalar tiles (they usually go after vector Tiles).
|
||||
/// \param in[2] sum number inputs and number of outputs of the node.
|
||||
/// \param in[3] dimension of the tile. Note that only 2d Tile are currently supported, so dim is 0 for outer tiles, 1 for inner tiles.
|
||||
///
|
||||
// Todo: Inner and outer tiles have different semantics. For example, outer tile always has the increment == 1, and it can contain only
|
||||
// tile emitters (one outer or two inner). So it seems better to create different classes for inner and outer tiles.
|
||||
// Todo: Currently data pointers incremented after each read/write in Load/Store emitters, so we have to decrement them here
|
||||
// if the same data needs to be read twice. Better to move all the pointer increments to TileEmitter and avoid the increments if necessary.
|
||||
class TileEmitter : public jit_emitter {
|
||||
public:
|
||||
TileEmitter(mkldnn::impl::cpu::x64::jit_generator* h, mkldnn::impl::cpu::x64::cpu_isa_t isa,
|
||||
const std::shared_ptr<ov::Node>& n)
|
||||
: jit_emitter(h, isa, n) {
|
||||
const auto tile = ov::as_type_ptr<ngraph::snippets::op::Tile>(n);
|
||||
if (!tile)
|
||||
IE_THROW() << "TileEmitter invoked with invalid op argument";
|
||||
if (!tile->compile_params)
|
||||
IE_THROW() << "TileEmitter invoked without compile_params";
|
||||
code = tile->region;
|
||||
jcp = *reinterpret_cast<const jit_snippets_compile_args*>(tile->compile_params);
|
||||
}
|
||||
|
||||
size_t get_inputs_num() const override {return 0;}
|
||||
|
||||
void emit_code(const std::vector<size_t> &in, const std::vector<size_t> &out,
|
||||
const std::vector<size_t> &pool = {}, const std::vector<size_t> &gpr = {}) const override {
|
||||
validate_arguments(in, out, pool, gpr);
|
||||
emit_impl(in, out, pool, gpr, nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
void validate_arguments(const std::vector<size_t> &in, const std::vector<size_t> &out,
|
||||
const std::vector<size_t> &pool = {}, const std::vector<size_t> &gpr = {}) const override {
|
||||
if (in.size() != 4)
|
||||
IE_THROW() << "TileEmitter got invalid number of inputs. Expected 4, got " << in.size();
|
||||
if (out.size() != 0)
|
||||
IE_THROW() << "TileEmitter got unexpected output arguments.";
|
||||
const size_t num_params = in[2];
|
||||
if (num_params > SNIPPETS_MAX_SNIPPETS_DIMS)
|
||||
IE_THROW() << "TileEmitter supports only up to " << SNIPPETS_MAX_SNIPPETS_DIMS <<
|
||||
" parameters, got " << num_params;
|
||||
const size_t dim = in[3];
|
||||
if (dim >= SNIPPETS_MAX_TILE_RANK)
|
||||
IE_THROW() << "TileEmitter supports tile ranks up to " << SNIPPETS_MAX_TILE_RANK <<
|
||||
" got " << dim;
|
||||
}
|
||||
|
||||
void emit_impl(const std::vector<size_t>& in,
|
||||
const std::vector<size_t>& out,
|
||||
const std::vector<size_t>& pool,
|
||||
const std::vector<size_t>& gpr,
|
||||
const MKLDNNPlugin::emitter_context *emit_context) const override {
|
||||
const size_t inc = in[0];
|
||||
const size_t previous_inc = in[1]; // increment of a previous tile in the same dim (0 if the first tile in the dim)
|
||||
const size_t num_params = in[2];
|
||||
const size_t dim = in[3]; // tile dimension: 0 - outer, 1 - inner
|
||||
const int reg64_tmp_start { 8 }; // R8, R9, R10, R11, R12, R13, R14, R15 inputs+outputs+1
|
||||
Reg64 amount = Reg64(reg64_tmp_start + num_params); // amount
|
||||
std::array<Label, 2> for_body;
|
||||
|
||||
// If R15 is not used, reserve it for use in scalar to avoid redundant push-pop's.
|
||||
// todo: Do we need explicitly check that code contains ScalarEmitter?
|
||||
std::vector<size_t> local_gpr = reg64_tmp_start + num_params < 15 ? std::vector<size_t>{15} : std::vector<size_t>{};
|
||||
std::vector<Reg64> regs(num_params);
|
||||
for (auto i = 0; dim == 0 && i < num_params; i++)
|
||||
regs[i] = Reg64(reg64_tmp_start + i);
|
||||
// Loop processing could be simplified in some cases
|
||||
if (inc > jcp.scheduler_dims[dim]) {
|
||||
return;
|
||||
} else if (inc == jcp.scheduler_dims[dim]) {
|
||||
for (auto& c : code) {
|
||||
c.first->emit_code(c.second.first, c.second.second, pool, local_gpr);
|
||||
}
|
||||
} else {
|
||||
// The previous tile has done nothing, all the work is ours
|
||||
if (previous_inc == 0 || previous_inc > jcp.scheduler_dims[dim]) {
|
||||
h->mov(amount, jcp.scheduler_dims[dim]);
|
||||
// The previous tile has done all the work
|
||||
} else if (jcp.scheduler_dims[dim] % previous_inc == 0) {
|
||||
return;
|
||||
}// else: the previous tile has already set a proper work amount
|
||||
h->cmp(amount, inc);
|
||||
h->jl(for_body[0], CodeGenerator::T_NEAR);
|
||||
|
||||
h->L(for_body[1]);
|
||||
{
|
||||
h->push(amount);
|
||||
for (auto& c : code) {
|
||||
c.first->emit_code(c.second.first, c.second.second, pool, local_gpr);
|
||||
}
|
||||
h->pop(amount);
|
||||
// Todo: Load and Store emitters are currently implemented so they ALWAYS increment appropriate pointers
|
||||
// after reading/writing. This might be a problem if we need to read the same data multiple times (broadcasting shapes).
|
||||
// To overcome this limitation, we add appropriate negative offsets if necessary.
|
||||
for (auto i = 0; dim == 0 && i < num_params; i++) {
|
||||
if (jcp.scheduler_offsets[i] != 0) {
|
||||
h->add(regs[i], jcp.scheduler_offsets[i]);
|
||||
}
|
||||
}
|
||||
h->sub(amount, inc);
|
||||
h->cmp(amount, inc);
|
||||
h->jge(for_body[1], CodeGenerator::T_NEAR);
|
||||
}
|
||||
|
||||
h->L(for_body[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// A = <42, 17>
|
||||
// B = < 1, 17>
|
||||
// for (auto k = 0; k < dom_0; k++) { // 42
|
||||
// for (auto n = 0; n < dom_1; n++) { // 17
|
||||
// auto a = *ptr0; ptr0 += vlan; // vector/scalar load
|
||||
// auto b = *ptr1; ptr1 += vlan; // vector/scalar load
|
||||
// }
|
||||
// ptr0 -= 0*dom_1;
|
||||
// ptr1 -= 1*dom_1;
|
||||
// }
|
||||
|
||||
// broadcast by MVD is extra case
|
||||
// A = <42, 17>
|
||||
// B = <42, 1>
|
||||
// for (auto k = 0; k < dom_0; k++) { // 42
|
||||
// for (auto n = 0; n < dom_1; n++) { // 17
|
||||
// auto a = *ptr0; ptr0 += vlan; // vector/scalar load
|
||||
// auto b = *ptr1; // broadcast load
|
||||
// }
|
||||
// ptr0 -= 0*dom_1;
|
||||
// ptr1 += sizeof(ptr1[0]); //ptr1 -= -sizeof(ptr1[0]);
|
||||
// }
|
||||
|
||||
// A = <42, 17, 31>
|
||||
// B = < 1, 17, 31>
|
||||
// for (auto k = 0; k < dom_0; k++) { // 42
|
||||
// for (auto n = 0; n < dom_1; n++) { // 17
|
||||
// for (auto m = 0; m < dom_2; m++) { // 31
|
||||
// auto a = *ptr0; ptr0 += vlan; // vector/scalar load
|
||||
// auto b = *ptr1; ptr1 += vlan; // vector/scalar load
|
||||
// }
|
||||
// }
|
||||
// ptr0 -= 0*dom_1*dom2;
|
||||
// ptr1 -= 1*dom_1*dom2;
|
||||
// }
|
||||
jit_snippets_compile_args jcp;
|
||||
std::vector<std::pair<std::shared_ptr<Emitter>, ngraph::snippets::RegInfo>> code;
|
||||
};
|
||||
|
||||
class NopEmitter : public jit_emitter {
|
||||
public:
|
||||
NopEmitter(mkldnn::impl::cpu::x64::jit_generator* h, mkldnn::impl::cpu::x64::cpu_isa_t isa, const std::shared_ptr<ov::Node>& n)
|
||||
: jit_emitter(h, isa, n) {
|
||||
}
|
||||
|
||||
size_t get_inputs_num() const override {return 0;}
|
||||
|
||||
private:
|
||||
void emit_impl(const std::vector<size_t>& in,
|
||||
const std::vector<size_t>& out,
|
||||
const std::vector<size_t>& pool,
|
||||
const std::vector<size_t>& gpr,
|
||||
const MKLDNNPlugin::emitter_context *emit_context) const override {
|
||||
}
|
||||
};
|
||||
|
||||
class FakeBroadcastEmitter : public jit_emitter {
|
||||
public:
|
||||
FakeBroadcastEmitter(mkldnn::impl::cpu::x64::jit_generator* h, mkldnn::impl::cpu::x64::cpu_isa_t isa, const std::shared_ptr<ov::Node>& n)
|
||||
: jit_emitter(h, isa, n) {
|
||||
if (n->get_input_shape(0).empty())
|
||||
use_broadcast = true;
|
||||
else if (*n->get_input_shape(0).rbegin() != *n->get_output_shape(0).rbegin())
|
||||
use_broadcast = true;
|
||||
else
|
||||
use_broadcast = false;
|
||||
}
|
||||
size_t get_inputs_num() const override {return 1;}
|
||||
|
||||
private:
|
||||
void emit_impl(const std::vector<size_t>& in,
|
||||
const std::vector<size_t>& out,
|
||||
const std::vector<size_t>& pool,
|
||||
const std::vector<size_t>& gpr,
|
||||
const MKLDNNPlugin::emitter_context *emit_context) const override {
|
||||
if (host_isa_ == dnnl::impl::cpu::x64::sse41) {
|
||||
emit_isa<dnnl::impl::cpu::x64::sse41>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx2) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx2>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx512_common) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx512_common>(in, out);
|
||||
} else {
|
||||
IE_THROW() << host_isa_;
|
||||
assert(!"unsupported isa");
|
||||
}
|
||||
}
|
||||
|
||||
template <dnnl::impl::cpu::x64::cpu_isa_t isa>
|
||||
void emit_isa(const std::vector<size_t> &in, const std::vector<size_t> &out) const {
|
||||
using Vmm = typename dnnl::impl::utils::conditional3<isa == dnnl::impl::cpu::x64::sse41,
|
||||
Xmm, isa == dnnl::impl::cpu::x64::avx2, Ymm, Zmm>::type;
|
||||
Vmm vmm_src0 = Vmm(in[0]);
|
||||
Vmm vmm_dst = Vmm(out[0]);
|
||||
|
||||
if (use_broadcast) {
|
||||
h->uni_vbroadcastss(vmm_dst, Xmm(in[0]));
|
||||
} else {
|
||||
h->uni_vmovups(vmm_dst, vmm_src0);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool use_broadcast;
|
||||
};
|
||||
|
||||
class ScalarEmitter : public jit_emitter {
|
||||
public:
|
||||
ScalarEmitter(mkldnn::impl::cpu::x64::jit_generator* h, mkldnn::impl::cpu::x64::cpu_isa_t isa, const std::shared_ptr<ov::Node>& n)
|
||||
: jit_emitter(h, isa, n) {
|
||||
auto out_pshape = n->output(0).get_tensor().get_partial_shape();
|
||||
if (out_pshape.is_dynamic())
|
||||
IE_THROW() << "ScalarEmitter supports only static input shapes";
|
||||
if ( out_pshape.get_shape() != ov::Shape() && ov::shape_size(out_pshape.get_shape()) != 1)
|
||||
IE_THROW() << "ScalarEmitter got invalid shape";
|
||||
value = mkldnn::impl::cpu::x64::float2int(ov::as_type_ptr<ngraph::snippets::op::Scalar>(n)->cast_vector<float>()[0]);
|
||||
|
||||
push_arg_entry_of("scalar", value, true);
|
||||
prepare_table();
|
||||
}
|
||||
|
||||
size_t get_inputs_num() const override {return 0;}
|
||||
|
||||
protected:
|
||||
size_t aux_gprs_count() const override {return 1;}
|
||||
|
||||
private:
|
||||
void emit_impl(const std::vector<size_t>& in,
|
||||
const std::vector<size_t>& out,
|
||||
const std::vector<size_t>& pool,
|
||||
const std::vector<size_t>& gpr,
|
||||
const MKLDNNPlugin::emitter_context *emit_context) const override {
|
||||
if (host_isa_ == dnnl::impl::cpu::x64::sse41) {
|
||||
emit_isa<dnnl::impl::cpu::x64::sse41>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx2) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx2>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx512_common) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx512_common>(in, out);
|
||||
} else {
|
||||
IE_THROW() << host_isa_;
|
||||
assert(!"unsupported isa");
|
||||
}
|
||||
}
|
||||
|
||||
template <dnnl::impl::cpu::x64::cpu_isa_t isa>
|
||||
void emit_isa(const std::vector<size_t> &in, const std::vector<size_t> &out) const {
|
||||
using Vmm = typename dnnl::impl::utils::conditional3<isa == dnnl::impl::cpu::x64::sse41,
|
||||
Xmm, isa == dnnl::impl::cpu::x64::avx2, Ymm, Zmm>::type;
|
||||
Vmm vmm_dst = Vmm(out[0]);
|
||||
h->uni_vbroadcastss(vmm_dst, table_val("scalar"));
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t value;
|
||||
};
|
||||
|
||||
///
|
||||
/// Memory emitters:
|
||||
///
|
||||
/// *Note*: post increment is embedded into Load/Store operation which means that
|
||||
/// it's illigal to load/store to the same address multiple times
|
||||
/// Typical application can be if Load and BroadcastLoad are performed from the same pointer.
|
||||
/// If Load goes before BroadcastLoad topologicaly the resilt will be incorrect
|
||||
/// For scalar loads we can use different tiles. Tiling indeed can be arbitrary and post increment should be somehow coded into ISA.
|
||||
/// Blocked parameter to tell if input is actually blocked. Broadcast means broadcast by W in other cases no need to substitute load.
|
||||
class MemoryEmitter : public jit_emitter {
|
||||
public:
|
||||
MemoryEmitter(mkldnn::impl::cpu::x64::jit_generator* h, mkldnn::impl::cpu::x64::cpu_isa_t isa, const std::shared_ptr<ov::Node>& n)
|
||||
: jit_emitter(h, isa, n), ea(getEA(n)) {
|
||||
}
|
||||
|
||||
size_t get_inputs_num() const override {return 1;}
|
||||
|
||||
protected:
|
||||
static auto getEA(const std::shared_ptr<ov::Node>& n) -> size_t {
|
||||
auto& rt = n->get_rt_info();
|
||||
size_t ea = 0;
|
||||
auto it = rt.find("effectiveAddress");
|
||||
if (it != rt.end()) {
|
||||
ea = it->second.as<int64_t>();
|
||||
} else {
|
||||
throw ov::Exception("effective address for Load generation cannot be determined");
|
||||
}
|
||||
return ea;
|
||||
}
|
||||
|
||||
size_t ea;
|
||||
};
|
||||
|
||||
class StoreEmitter : public MemoryEmitter {
|
||||
public:
|
||||
StoreEmitter(mkldnn::impl::cpu::x64::jit_generator* h, mkldnn::impl::cpu::x64::cpu_isa_t isa, const std::shared_ptr<ov::Node>& n)
|
||||
: MemoryEmitter(h, isa, n) {
|
||||
}
|
||||
|
||||
size_t get_inputs_num() const override {return 1;}
|
||||
|
||||
private:
|
||||
void emit_impl(const std::vector<size_t>& in,
|
||||
const std::vector<size_t>& out,
|
||||
const std::vector<size_t>& pool,
|
||||
const std::vector<size_t>& gpr,
|
||||
const MKLDNNPlugin::emitter_context *emit_context) const override {
|
||||
if (host_isa_ == dnnl::impl::cpu::x64::sse41) {
|
||||
emit_isa<dnnl::impl::cpu::x64::sse41>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx2) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx2>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx512_common) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx512_common>(in, out);
|
||||
} else {
|
||||
IE_THROW() << host_isa_;
|
||||
assert(!"unsupported isa");
|
||||
}
|
||||
}
|
||||
|
||||
template <dnnl::impl::cpu::x64::cpu_isa_t isa>
|
||||
void emit_isa(const std::vector<size_t> &in, const std::vector<size_t> &out) const {
|
||||
using Vmm = typename dnnl::impl::utils::conditional3<isa == dnnl::impl::cpu::x64::sse41,
|
||||
Xmm, isa == dnnl::impl::cpu::x64::avx2, Ymm, Zmm>::type;
|
||||
Reg64 out_reg(ea);
|
||||
Vmm vmm_src0 = Vmm(in[0]);
|
||||
h->uni_vmovups(h->ptr[out_reg], vmm_src0);
|
||||
h->add(out_reg, mkldnn::impl::cpu::x64::cpu_isa_traits<isa>::vlen);
|
||||
}
|
||||
};
|
||||
|
||||
class ScalarStoreEmitter : public MemoryEmitter {
|
||||
public:
|
||||
ScalarStoreEmitter(mkldnn::impl::cpu::x64::jit_generator* h, mkldnn::impl::cpu::x64::cpu_isa_t isa, const std::shared_ptr<ov::Node>& n)
|
||||
: MemoryEmitter(h, isa, n) {
|
||||
}
|
||||
|
||||
size_t get_inputs_num() const override {return 1;}
|
||||
|
||||
private:
|
||||
void emit_impl(const std::vector<size_t>& in,
|
||||
const std::vector<size_t>& out,
|
||||
const std::vector<size_t>& pool,
|
||||
const std::vector<size_t>& gpr,
|
||||
const MKLDNNPlugin::emitter_context *emit_context) const override {
|
||||
if (host_isa_ == dnnl::impl::cpu::x64::sse41) {
|
||||
emit_isa<dnnl::impl::cpu::x64::sse41>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx2) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx2>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx512_common) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx512_common>(in, out);
|
||||
} else {
|
||||
IE_THROW() << host_isa_;
|
||||
assert(!"unsupported isa");
|
||||
}
|
||||
}
|
||||
|
||||
template <dnnl::impl::cpu::x64::cpu_isa_t isa>
|
||||
void emit_isa(const std::vector<size_t> &in, const std::vector<size_t> &out) const {
|
||||
using Vmm = typename dnnl::impl::utils::conditional3<isa == dnnl::impl::cpu::x64::sse41,
|
||||
Xmm, isa == dnnl::impl::cpu::x64::avx2, Ymm, Zmm>::type;
|
||||
Reg64 out_reg(ea);
|
||||
Xmm vmm_src0 = Xmm(in[0]);
|
||||
h->uni_vmovss(h->ptr[out_reg], vmm_src0);
|
||||
h->add(out_reg, sizeof(float));
|
||||
}
|
||||
};
|
||||
|
||||
class LoadEmitter : public MemoryEmitter {
|
||||
public:
|
||||
LoadEmitter(mkldnn::impl::cpu::x64::jit_generator* h, mkldnn::impl::cpu::x64::cpu_isa_t isa, const std::shared_ptr<ov::Node>& n)
|
||||
: MemoryEmitter(h, isa, n), shouldPostIncrement(*n->get_input_shape(0).rbegin() != 1) {
|
||||
}
|
||||
|
||||
size_t get_inputs_num() const override {return 0;}
|
||||
|
||||
private:
|
||||
void emit_impl(const std::vector<size_t>& in,
|
||||
const std::vector<size_t>& out,
|
||||
const std::vector<size_t>& pool,
|
||||
const std::vector<size_t>& gpr,
|
||||
const MKLDNNPlugin::emitter_context *emit_context) const override {
|
||||
if (host_isa_ == dnnl::impl::cpu::x64::sse41) {
|
||||
emit_isa<dnnl::impl::cpu::x64::sse41>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx2) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx2>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx512_common) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx512_common>(in, out);
|
||||
} else {
|
||||
IE_THROW() << host_isa_;
|
||||
assert(!"unsupported isa");
|
||||
}
|
||||
}
|
||||
|
||||
template <dnnl::impl::cpu::x64::cpu_isa_t isa>
|
||||
void emit_isa(const std::vector<size_t> &in, const std::vector<size_t> &out) const {
|
||||
using Vmm = typename dnnl::impl::utils::conditional3<isa == dnnl::impl::cpu::x64::sse41,
|
||||
Xmm, isa == dnnl::impl::cpu::x64::avx2, Ymm, Zmm>::type;
|
||||
Reg64 in_reg(ea);
|
||||
Vmm vmm_src0 = Vmm(out[0]);
|
||||
h->uni_vmovups(vmm_src0, h->ptr[in_reg]);
|
||||
|
||||
if (shouldPostIncrement) {
|
||||
h->add(in_reg, mkldnn::impl::cpu::x64::cpu_isa_traits<isa>::vlen);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool shouldPostIncrement;
|
||||
};
|
||||
|
||||
class BroadcastLoadEmitter : public MemoryEmitter {
|
||||
public:
|
||||
BroadcastLoadEmitter(mkldnn::impl::cpu::x64::jit_generator* h, mkldnn::impl::cpu::x64::cpu_isa_t isa, const std::shared_ptr<ov::Node>& n)
|
||||
: MemoryEmitter(h, isa, n) {
|
||||
}
|
||||
size_t get_inputs_num() const override {return 0;}
|
||||
|
||||
private:
|
||||
void emit_impl(const std::vector<size_t>& in,
|
||||
const std::vector<size_t>& out,
|
||||
const std::vector<size_t>& pool,
|
||||
const std::vector<size_t>& gpr,
|
||||
const MKLDNNPlugin::emitter_context *emit_context) const override {
|
||||
if (host_isa_ == dnnl::impl::cpu::x64::sse41) {
|
||||
emit_isa<dnnl::impl::cpu::x64::sse41>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx2) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx2>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx512_common) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx512_common>(in, out);
|
||||
} else {
|
||||
IE_THROW() << host_isa_;
|
||||
assert(!"unsupported isa");
|
||||
}
|
||||
}
|
||||
|
||||
template <dnnl::impl::cpu::x64::cpu_isa_t isa>
|
||||
void emit_isa(const std::vector<size_t> &in, const std::vector<size_t> &out) const {
|
||||
using Vmm = typename dnnl::impl::utils::conditional3<isa == dnnl::impl::cpu::x64::sse41,
|
||||
Xmm, isa == dnnl::impl::cpu::x64::avx2, Ymm, Zmm>::type;
|
||||
Reg64 in_reg(ea);
|
||||
Vmm vmm_src0 = Vmm(out[0]);
|
||||
|
||||
// In doesn't really matter if we broadcast or `movss` for vector tails so keep only one version for `BroadcastLoad`,
|
||||
// key point here is not to add post-increment, it might be fixed by some other approach in future
|
||||
h->uni_vbroadcastss(vmm_src0, h->ptr[in_reg]);
|
||||
}
|
||||
};
|
||||
|
||||
class ScalarLoadEmitter : public MemoryEmitter {
|
||||
public:
|
||||
ScalarLoadEmitter(mkldnn::impl::cpu::x64::jit_generator* h, mkldnn::impl::cpu::x64::cpu_isa_t isa, const std::shared_ptr<ov::Node>& n)
|
||||
: MemoryEmitter(h, isa, n), shouldPostIncrement(*n->get_input_shape(0).rbegin() != 1) {
|
||||
}
|
||||
size_t get_inputs_num() const override {return 0;}
|
||||
|
||||
private:
|
||||
void emit_impl(const std::vector<size_t>& in,
|
||||
const std::vector<size_t>& out,
|
||||
const std::vector<size_t>& pool,
|
||||
const std::vector<size_t>& gpr,
|
||||
const MKLDNNPlugin::emitter_context *emit_context) const override {
|
||||
if (host_isa_ == dnnl::impl::cpu::x64::sse41) {
|
||||
emit_isa<dnnl::impl::cpu::x64::sse41>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx2) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx2>(in, out);
|
||||
} else if (host_isa_ == dnnl::impl::cpu::x64::avx512_common) {
|
||||
emit_isa<dnnl::impl::cpu::x64::avx512_common>(in, out);
|
||||
} else {
|
||||
IE_THROW() << host_isa_;
|
||||
assert(!"unsupported isa");
|
||||
}
|
||||
}
|
||||
|
||||
template <dnnl::impl::cpu::x64::cpu_isa_t isa>
|
||||
void emit_isa(const std::vector<size_t> &in, const std::vector<size_t> &out) const {
|
||||
using Vmm = typename dnnl::impl::utils::conditional3<isa == dnnl::impl::cpu::x64::sse41,
|
||||
Xmm, isa == dnnl::impl::cpu::x64::avx2, Ymm, Zmm>::type;
|
||||
Reg64 in_reg(ea);
|
||||
Xmm vmm_src0 = Xmm(out[0]);
|
||||
h->uni_vmovss(vmm_src0, h->ptr[in_reg]);
|
||||
|
||||
// Doesn't work if the same pointer comes with multiple load operations
|
||||
if (shouldPostIncrement) {
|
||||
h->add(in_reg, sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool shouldPostIncrement;
|
||||
};
|
||||
|
||||
} // namespace MKLDNNPlugin
|
||||
@@ -81,6 +81,7 @@
|
||||
#include "nodes/mkldnn_if_node.h"
|
||||
#include "nodes/mkldnn_ctc_greedy_decoder_node.h"
|
||||
#include "nodes/mkldnn_non_zero.h"
|
||||
#include "nodes/subgraph.h"
|
||||
|
||||
#define MKLDNN_NODE(__prim, __type) \
|
||||
registerNodeIfRequired(MKLDNNPlugin, __prim, __type, MKLDNNNodeImpl<__prim>)
|
||||
@@ -170,4 +171,5 @@ MKLDNNPlugin::MKLDNNNode::NodesFactory::NodesFactory()
|
||||
MKLDNN_NODE(MKLDNNStridedSliceNode, StridedSlice);
|
||||
MKLDNN_NODE(MKLDNNGRNNode, GRN);
|
||||
MKLDNN_NODE(MKLDNNNonZeroNode, NonZero);
|
||||
MKLDNN_NODE(MKLDNNSnippetNode, Subgraph);
|
||||
}
|
||||
@@ -80,6 +80,8 @@
|
||||
#include <transformations/rt_info/fused_names_attribute.hpp>
|
||||
#include <transformations/op_conversions/fq_decomposition.hpp>
|
||||
#include <transformations/utils/utils.hpp>
|
||||
#include <snippets/pass/collapse_subgraph.hpp>
|
||||
#include "ngraph_transformations/snippets_mark_skipped.hpp"
|
||||
|
||||
#include <ngraph/opsets/opset1.hpp>
|
||||
#include <ngraph/opsets/opset2.hpp>
|
||||
@@ -125,6 +127,7 @@
|
||||
using namespace MKLDNNPlugin;
|
||||
using namespace InferenceEngine;
|
||||
|
||||
|
||||
Engine::Engine() {
|
||||
_pluginName = "CPU";
|
||||
extensionManager->AddExtension(std::make_shared<MKLDNNPlugin::MKLDNNExtension>());
|
||||
@@ -136,7 +139,8 @@ Engine::~Engine() {
|
||||
ExecutorManager::getInstance()->clear("CPUCallbackExecutor");
|
||||
}
|
||||
|
||||
static void TransformationUpToCPUSpecificOpSet(std::shared_ptr<ngraph::Function> nGraphFunc, const bool _enableLPT) {
|
||||
static void TransformationUpToCPUSpecificOpSet(std::shared_ptr<ngraph::Function> nGraphFunc, const bool _enableLPT,
|
||||
const bool _enableSnippets) {
|
||||
ngraph::pass::Manager manager;
|
||||
manager.set_per_pass_validation(false);
|
||||
manager.register_pass<ngraph::pass::InitNodeInfo>();
|
||||
@@ -468,6 +472,7 @@ static void TransformationUpToCPUSpecificOpSet(std::shared_ptr<ngraph::Function>
|
||||
ngraph::pass::Manager postLPTPassManager;
|
||||
postLPTPassManager.register_pass<ngraph::pass::FakeQuantizeDecomposition>();
|
||||
postLPTPassManager.register_pass<ngraph::pass::UnrollTensorIterator>();
|
||||
postLPTPassManager.register_pass<ReshapePRelu>();
|
||||
|
||||
postLPTPassManager.get_pass_config()->set_callback<ngraph::pass::FakeQuantizeDecomposition>([](const_node_ptr &node) -> bool {
|
||||
std::string errMsg;
|
||||
@@ -478,6 +483,7 @@ static void TransformationUpToCPUSpecificOpSet(std::shared_ptr<ngraph::Function>
|
||||
return node->get_rt_info().count("UNROLL_TI") == 0;
|
||||
});
|
||||
|
||||
|
||||
postLPTPassManager.register_pass<MoveEltwiseUpThroughDataMov>();
|
||||
postLPTPassManager.get_pass_config()->set_callback<MoveEltwiseUpThroughDataMov>([](const std::shared_ptr<const ngraph::Node>& node) -> bool {
|
||||
if (node->get_input_size() >= 2) {
|
||||
@@ -486,12 +492,41 @@ static void TransformationUpToCPUSpecificOpSet(std::shared_ptr<ngraph::Function>
|
||||
return false;
|
||||
});
|
||||
|
||||
postLPTPassManager.register_pass<ngraph::pass::ConstantFolding>();
|
||||
postLPTPassManager.run_passes(nGraphFunc);
|
||||
|
||||
if (!useLpt && _enableSnippets && with_cpu_x86_avx2()) {
|
||||
ngraph::pass::Manager tokenization_manager;
|
||||
tokenization_manager.register_pass<SnippetsMarkSkipped>();
|
||||
tokenization_manager.register_pass<ngraph::snippets::pass::EnumerateNodes>();
|
||||
tokenization_manager.register_pass<ngraph::snippets::pass::TokenizeSnippets>();
|
||||
tokenization_manager.get_pass_config()->set_callback<ngraph::snippets::pass::TokenizeSnippets>(
|
||||
[](const std::shared_ptr<const ov::Node>& n) -> bool {
|
||||
const auto& inputs = n->inputs();
|
||||
// todo: clarify whether we can evaluate snippets on const paths
|
||||
const bool has_only_const_inputs = std::all_of(inputs.begin(), inputs.end(),
|
||||
[](const ov::Input<const ov::Node> &in) {
|
||||
return ov::is_type<ov::op::v0::Constant>(in.get_source_output().get_node_shared_ptr());
|
||||
});
|
||||
// todo: clarify whether we can evaluate snippets on inputs with larger ranks
|
||||
auto rank_is_too_large = [](const ov::descriptor::Tensor& t ) {
|
||||
// callback is called has_supported_in_out(), so it's safe to assume that the shapes are static
|
||||
return t.get_partial_shape().rank().get_length() > 6;
|
||||
};
|
||||
const bool bad_input_rank = std::any_of(inputs.begin(), inputs.end(),
|
||||
[&](const ov::Input<const ov::Node>& in) {return rank_is_too_large(in.get_tensor());});
|
||||
const auto& outputs = n->outputs();
|
||||
const bool bad_output_rank = std::any_of(outputs.begin(), outputs.end(),
|
||||
[&](const ov::Output<const ov::Node>& out) {return rank_is_too_large(out.get_tensor());});
|
||||
return has_only_const_inputs || bad_input_rank || bad_output_rank;
|
||||
});
|
||||
tokenization_manager.run_passes(nGraphFunc);
|
||||
}
|
||||
}
|
||||
|
||||
static void Transformation(CNNNetwork& clonedNetwork, const bool _enableLPT) {
|
||||
static void Transformation(CNNNetwork& clonedNetwork, const bool _enableLPT, const bool _enableSnippets) {
|
||||
auto nGraphFunc = clonedNetwork.getFunction();
|
||||
TransformationUpToCPUSpecificOpSet(nGraphFunc, _enableLPT);
|
||||
TransformationUpToCPUSpecificOpSet(nGraphFunc, _enableLPT, _enableSnippets);
|
||||
ConvertToCPUSpecificOpset(nGraphFunc);
|
||||
}
|
||||
|
||||
@@ -526,8 +561,18 @@ Engine::LoadExeNetworkImpl(const InferenceEngine::CNNNetwork &network, const std
|
||||
const auto& lptProp = config.find(InferenceEngine::PluginConfigInternalParams::KEY_LP_TRANSFORMS_MODE);
|
||||
const bool enableLPT = (lptProp != config.end() && lptProp->second == PluginConfigParams::YES) /* enabled in the orig_config*/
|
||||
|| Config::LPTransformsMode::On == engConfig.lpTransformsMode /* or already enabled for the plugin */;
|
||||
const auto& BF16Prop = config.find(InferenceEngine::PluginConfigParams::KEY_ENFORCE_BF16);
|
||||
const bool enableBF16 = ((BF16Prop != config.end() && BF16Prop->second == PluginConfigParams::YES)
|
||||
|| engConfig.enforceBF16) && with_cpu_x86_avx512_core();
|
||||
const auto& modelCacheProp = config.find(InferenceEngine::PluginConfigParams::KEY_CACHE_DIR);
|
||||
const bool enableModelCache = (modelCacheProp != config.end() && !modelCacheProp->second.empty())
|
||||
|| !engConfig.cache_dir.empty();
|
||||
const auto& dynamicBatchProp = config.find(InferenceEngine::PluginConfigParams::KEY_DYN_BATCH_ENABLED);
|
||||
const bool enableDynamicBatch = (dynamicBatchProp != config.end() && dynamicBatchProp->second == PluginConfigParams::YES)
|
||||
|| engConfig.enableDynamicBatch;
|
||||
const bool enableSnippets = !(enableModelCache || enableDynamicBatch || enableBF16);
|
||||
auto nGraphFunc = clonedNetwork.getFunction();
|
||||
TransformationUpToCPUSpecificOpSet(nGraphFunc, enableLPT);
|
||||
TransformationUpToCPUSpecificOpSet(nGraphFunc, enableLPT, enableSnippets);
|
||||
|
||||
// Here the OV perf modes are turned into specific settings (as we need the network for better params selection)
|
||||
const auto& mode = config.find(PluginConfigParams::KEY_PERFORMANCE_HINT);
|
||||
@@ -736,7 +781,8 @@ QueryNetworkResult Engine::QueryNetwork(const CNNNetwork& network, const std::ma
|
||||
const auto& lptProp = config.find(InferenceEngine::PluginConfigInternalParams::KEY_LP_TRANSFORMS_MODE);
|
||||
const bool enableLPT = (lptProp != config.end() && lptProp->second == PluginConfigParams::YES) /* enabled in the orig_config*/
|
||||
|| Config::LPTransformsMode::On == engConfig.lpTransformsMode /* or already enabled */;
|
||||
Transformation(clonedNetwork, enableLPT);
|
||||
const bool enableSnippets = !(conf.cache_dir.empty() || conf.enableDynamicBatch || (conf.enforceBF16 && with_cpu_x86_avx512_core()));
|
||||
Transformation(clonedNetwork, enableLPT, enableSnippets);
|
||||
auto ops = clonedNetwork.getFunction()->get_ordered_ops();
|
||||
std::unordered_set<std::string> supported;
|
||||
std::unordered_set<std::string> unsupported;
|
||||
|
||||
-2
@@ -23,14 +23,12 @@ namespace MKLDNNPlugin {
|
||||
|
||||
inline void ConvertToCPUSpecificOpset(std::shared_ptr<ngraph::Function> &nGraphFunc) {
|
||||
ngraph::pass::Manager manager;
|
||||
manager.register_pass<ngraph::pass::ConstantFolding>();
|
||||
manager.register_pass<ConvertMatMulToFC>();
|
||||
manager.register_pass<AlignMatMulInputRanks>();
|
||||
manager.register_pass<ConvertTileToSeqTiles>();
|
||||
manager.register_pass<FullyConnectedBiasFusion>();
|
||||
manager.register_pass<ConvertToPowerStatic>();
|
||||
manager.register_pass<ConvertToLeakyRelu>();
|
||||
manager.register_pass<ReshapePRelu>();
|
||||
manager.register_pass<ConvertToSwishCPU>();
|
||||
manager.register_pass<OptimizeSequenceTransposes>();
|
||||
if (!ngraph::op::util::has_op_with_type<ngraph::op::FakeQuantize>(nGraphFunc)) {
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
#include "snippets_mark_skipped.hpp"
|
||||
#include <snippets/pass/collapse_subgraph.hpp>
|
||||
#include <ngraph/opsets/opset1.hpp>
|
||||
#include <utils/general_utils.h>
|
||||
#include <utils/cpu_utils.hpp>
|
||||
|
||||
NGRAPH_RTTI_DEFINITION(MKLDNNPlugin::SnippetsMarkSkipped, "SnippetsMarkSkipped", 0);
|
||||
|
||||
using namespace ngraph;
|
||||
namespace MKLDNNPlugin {
|
||||
namespace {
|
||||
NodeFusingType GetNodeFusingType(const std::shared_ptr<const Node> &node) {
|
||||
auto &rt = node->get_rt_info();
|
||||
const auto rinfo = rt.find("MayBeFusedInPlugin");
|
||||
if (rinfo == rt.end())
|
||||
return NodeFusingType::NotSet;
|
||||
return rinfo->second.as<NodeFusingType>();
|
||||
}
|
||||
void SetNodeFusingType(const std::shared_ptr<Node> &node, NodeFusingType nodeType) {
|
||||
auto &rt = node->get_rt_info();
|
||||
rt["MayBeFusedInPlugin"] = nodeType;
|
||||
}
|
||||
std::vector<NodeFusingType> getContinuableChains(const std::shared_ptr<const Node> &node) {
|
||||
std::vector<NodeFusingType> result;
|
||||
for (const auto& input : node->inputs()) {
|
||||
const auto parent = input.get_source_output().get_node_shared_ptr();
|
||||
const auto snt = GetNodeFusingType(parent);
|
||||
if (snt > NodeFusingType::FusedTerminator) {
|
||||
result.push_back(snt);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
int getNumNonConstInputs(const std::shared_ptr<const Node> &node) {
|
||||
int num_non_const_inputs = 0;
|
||||
for (const auto &parent_out : node->input_values()) {
|
||||
const auto parent = parent_out.get_node_shared_ptr();
|
||||
if (ov::is_type<ngraph::op::v1::Reshape>(parent)) {
|
||||
for (const auto &grandparent_out : parent->input_values()) {
|
||||
const auto grandparent = grandparent_out.get_node_shared_ptr();
|
||||
if (!ngraph::op::is_constant(grandparent))
|
||||
num_non_const_inputs++;
|
||||
}
|
||||
} else if (!ngraph::op::is_constant(parent)) {
|
||||
num_non_const_inputs++;
|
||||
}
|
||||
}
|
||||
return num_non_const_inputs;
|
||||
}
|
||||
bool SupportsFusingWithConvolution_SumActivation(const std::shared_ptr<const Node> &node) {
|
||||
// todo: Do all PReLUs are fused? Not sure about round and softRelu
|
||||
// EltwiseRoundHalfToEven, EltwiseRoundHalfAwayFromZero, EltwiseSoftRelu
|
||||
return ov::is_type<ngraph::op::Relu>(node) ||
|
||||
ov::is_type<ngraph::op::PRelu>(node) ||
|
||||
ov::is_type<ngraph::op::Elu>(node) ||
|
||||
ov::is_type<ngraph::op::Sigmoid>(node) ||
|
||||
ov::is_type<ngraph::op::v5::HSigmoid>(node) ||
|
||||
ov::is_type<ngraph::op::Clamp>(node) ||
|
||||
ov::is_type<ngraph::op::v4::Swish>(node) ||
|
||||
ov::is_type<ngraph::op::v4::HSwish>(node) ||
|
||||
ov::is_type<ngraph::op::v4::Mish>(node) ||
|
||||
ov::is_type<ngraph::op::v5::Round>(node);
|
||||
}
|
||||
|
||||
bool canBePerformedAsScaleShift(const std::shared_ptr<const Node> &node, const size_t channelAxis) {
|
||||
size_t fusingPort = 0;
|
||||
size_t numNonConstInputs = 0;
|
||||
ov::PartialShape dataShape;
|
||||
for (size_t i = 0; i < node->get_input_size(); i++) {
|
||||
const auto parent = node->get_input_node_shared_ptr(i);
|
||||
if (!ngraph::op::is_constant(parent)) {
|
||||
fusingPort = i;
|
||||
dataShape = node->get_input_partial_shape(i);
|
||||
// only one non-const parent is allowed
|
||||
if (dataShape.is_dynamic() || ++numNonConstInputs != 1)
|
||||
return false;
|
||||
} else {
|
||||
// every const parent must have exactly one child
|
||||
const auto out = parent->outputs();
|
||||
const bool has_only_child = (out.size() == 1) && (out[0].get_target_inputs().size() == 1);
|
||||
if (!has_only_child)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const auto isBroadcastableToDataInput = [&]() {
|
||||
for (size_t i = 0; i < node->get_input_size(); i++) {
|
||||
if (i == fusingPort)
|
||||
continue;
|
||||
const ov::PartialShape weightShape = node->get_input_partial_shape(i);
|
||||
if (weightShape.is_dynamic() ||
|
||||
!isPerTensorOrPerChannelBroadcastable(dataShape.get_shape(), weightShape.get_shape(), channelAxis, true))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Prelu and MulAdd are still ignored
|
||||
// isConvertablePowerStatic() is ignored
|
||||
return (ov::is_type<ngraph::opset1::Add>(node) ||
|
||||
ov::is_type<ngraph::opset1::Multiply>(node) ||
|
||||
ov::is_type<ngraph::opset1::Subtract>(node) ||
|
||||
ov::is_type<ngraph::opset1::Divide>(node)) &&
|
||||
isBroadcastableToDataInput();
|
||||
}
|
||||
|
||||
bool SupportsFusingWithConvolution_Simple(const std::shared_ptr<const Node> &node, const size_t channelAxis = 1) {
|
||||
return SupportsFusingWithConvolution_SumActivation(node) ||
|
||||
ov::is_type<ngraph::op::Tanh>(node) ||
|
||||
ov::is_type<ngraph::op::v0::Gelu>(node) ||
|
||||
ov::is_type<ngraph::op::v7::Gelu>(node) ||
|
||||
ov::is_type<ngraph::op::Abs>(node) ||
|
||||
ov::is_type<ngraph::op::Sqrt>(node) ||
|
||||
canBePerformedAsScaleShift(node, channelAxis);
|
||||
}
|
||||
// Convolution is a special case, since it supports peculiar fusings
|
||||
bool isSuitableConvolutionParent(const std::shared_ptr<const Node> &node) {
|
||||
const bool is_suitable_node = ov::is_type<ngraph::op::v1::Convolution>(node) ||
|
||||
ov::is_type<ngraph::op::v1::GroupConvolution>(node);
|
||||
// has a single output, connected to a single child
|
||||
const auto out = node->outputs();
|
||||
const bool has_only_child = (out.size() == 1) && (out[0].get_target_inputs().size() == 1);
|
||||
return is_suitable_node && has_only_child;
|
||||
}
|
||||
bool isSuitableBinaryConvolutionParent(const std::shared_ptr<const Node> &node) {
|
||||
const bool is_suitable_node = ov::is_type<ngraph::op::v1::BinaryConvolution>(node);
|
||||
// has a single output, connected to a single child
|
||||
const auto out = node->outputs();
|
||||
const bool has_only_child = (out.size() == 1) && (out[0].get_target_inputs().size() == 1);
|
||||
return is_suitable_node && has_only_child;
|
||||
}
|
||||
bool isSuitableMiscParent(const std::shared_ptr<const Node> &node) {
|
||||
const bool is_suitable_node = ov::is_type<ngraph::op::v0::MVN>(node) ||
|
||||
ov::is_type<ngraph::op::v6::MVN>(node) ||
|
||||
ov::is_type<ngraph::op::v0::NormalizeL2>(node) ||
|
||||
ov::is_type<ngraph::op::v0::Interpolate>(node) ||
|
||||
ov::is_type<ngraph::op::v4::Interpolate>(node) ||
|
||||
ov::is_type<ngraph::op::v0::LSTMCell>(node) ||
|
||||
ov::is_type<ngraph::op::v4::LSTMCell>(node) ||
|
||||
ov::is_type<ngraph::opset1::ConvolutionBackpropData>(node) ||
|
||||
ov::is_type<ngraph::op::util::ArithmeticReductionKeepDims>(node) ||
|
||||
ov::is_type<ngraph::op::util::LogicalReductionKeepDims>(node) ||
|
||||
ov::is_type<ngraph::opset1::GroupConvolutionBackpropData>(node);
|
||||
// has a single output, connected to a single child
|
||||
const auto out = node->outputs();
|
||||
const bool has_only_child = (out.size() == 1) && (out[0].get_target_inputs().size() == 1);
|
||||
return is_suitable_node && has_only_child;
|
||||
}
|
||||
// Matmul is a special case, since it supports simple + bias fusings
|
||||
bool isSuitableMatMulParent(const std::shared_ptr<const Node> &node) {
|
||||
const bool is_suitable_node = ov::is_type<ngraph::op::MatMul>(node);
|
||||
// has a single output, connected to a single child
|
||||
const auto out = node->outputs();
|
||||
const bool has_only_child = (out.size() == 1) && (out[0].get_target_inputs().size() == 1);
|
||||
return is_suitable_node && has_only_child;
|
||||
}
|
||||
bool isSuitablePoolChild(const std::shared_ptr<const Node> &node) {
|
||||
const bool is_suitable_node = ov::is_type<ngraph::op::v1::MaxPool>(node);
|
||||
// has a single output, connected to a single child
|
||||
const auto out = node->outputs();
|
||||
const bool has_only_child = (out.size() == 1) && (out[0].get_target_inputs().size() == 1);
|
||||
return is_suitable_node && has_only_child;
|
||||
}
|
||||
bool isSuitableChildForFusingSimple(const std::shared_ptr<const Node> &node, const size_t channelAxis = 1) {
|
||||
// Note: Fusing child is allowed to have several users, but that must be the end of the chain
|
||||
return SupportsFusingWithConvolution_Simple(node, channelAxis) && getNumNonConstInputs(node) == 1;
|
||||
}
|
||||
bool isSuitableChildForFusingMatMul(const std::shared_ptr<const Node> &node, NodeFusingType &updatedChainType) {
|
||||
int num_non_const_inputs = 0;
|
||||
bool can_be_converted_to_FC = false;
|
||||
ov::Shape bias_shape;
|
||||
ov::Shape matmul_shape;
|
||||
for (const auto &parent_out : node->input_values()) {
|
||||
const auto parent = parent_out.get_node_shared_ptr();
|
||||
if (ngraph::op::is_constant(parent)) {
|
||||
bias_shape = parent_out.get_shape();
|
||||
num_non_const_inputs++;
|
||||
} else {
|
||||
const auto pshape = parent_out.get_partial_shape();
|
||||
if (pshape.is_dynamic() || pshape.get_shape().empty())
|
||||
return false;
|
||||
matmul_shape = pshape.get_shape();
|
||||
const auto& grandparents = parent->input_values();
|
||||
// first check that weights are constant and both activations and weights have static shape
|
||||
if (grandparents.size() == 2 &&
|
||||
grandparents[0].get_partial_shape().is_static() &&
|
||||
grandparents[1].get_partial_shape().is_static() &&
|
||||
ov::is_type<ov::op::v0::Constant>(grandparents[1].get_node_shared_ptr())) {
|
||||
auto rank_a = grandparents[0].get_partial_shape().rank().get_length();
|
||||
auto rank_w = grandparents[1].get_partial_shape().rank().get_length();
|
||||
if (rank_a != 1 && rank_w != 1 && rank_a <= 3 && rank_w <= 3)
|
||||
can_be_converted_to_FC = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (num_non_const_inputs != 1)
|
||||
return false;
|
||||
|
||||
// FuseMatMulAndSimpleOperation or FuseFullyConnectedAndSimpleOperation
|
||||
// Invoke SupportsFusingWithConvolution_Simple directly instead of isSuitableChildForFusingSimple to
|
||||
// eliminate getNumNonConstInputs() check
|
||||
size_t fusingAxis;
|
||||
if (can_be_converted_to_FC)
|
||||
fusingAxis = matmul_shape.size() == 3 ? 2 : 1;
|
||||
else
|
||||
fusingAxis = matmul_shape.size() - 1;
|
||||
if (SupportsFusingWithConvolution_Simple(node, fusingAxis)) {
|
||||
updatedChainType = NodeFusingType::FusedWithMisc;
|
||||
return true;
|
||||
}
|
||||
// FullyConnectedBiasFusion
|
||||
if (!(can_be_converted_to_FC && ov::is_type<ngraph::opset1::Add>(node) &&
|
||||
bias_shape.back() == matmul_shape.back() &&
|
||||
bias_shape.back() == shape_size(bias_shape))) {
|
||||
return false;
|
||||
}
|
||||
// Fusing chain must be interrupted after the node, since reshape will be inserted
|
||||
if (bias_shape.size() >= 2)
|
||||
updatedChainType = NodeFusingType::FusedTerminator;
|
||||
return true;
|
||||
}
|
||||
bool isSuitableParentForFusingSumActivation(const std::shared_ptr<const Node> &node) {
|
||||
if (!ov::is_type<ngraph::op::v1::Add>(node))
|
||||
return false;
|
||||
auto isFusedBiasNode = [](std::shared_ptr<Node> n){
|
||||
if (!(ov::is_type<ngraph::op::v1::Add>(n) &&
|
||||
GetNodeFusingType(n) == NodeFusingType::FusedWithConvolution))
|
||||
return false;
|
||||
const auto conv = n->get_input_source_output(0);
|
||||
const auto bias = n->get_input_source_output(1);
|
||||
if (!(ngraph::op::is_constant(bias.get_node_shared_ptr()) && isSuitableConvolutionParent(conv.get_node_shared_ptr())))
|
||||
return false;
|
||||
const auto conv_shape = conv.get_partial_shape();
|
||||
const auto bias_shape = bias.get_partial_shape();
|
||||
if (bias_shape.is_dynamic() || conv_shape.is_dynamic() || bias_shape.size() > conv_shape.size())
|
||||
return false;
|
||||
auto getNormalizedDims = [](const ov::Shape &dims, size_t ndims) -> std::vector<size_t>{
|
||||
std::vector<size_t> normalizedDims = dims;
|
||||
for (size_t i = 0; i < (ndims - dims.size()); i++) {
|
||||
normalizedDims.insert(normalizedDims.begin(), 1);
|
||||
}
|
||||
return normalizedDims;
|
||||
};
|
||||
const auto bias_norm_dims = getNormalizedDims(bias_shape.get_shape(), conv_shape.size());
|
||||
if (bias_norm_dims.size() < 2 || bias_norm_dims[0] != 1 || conv_shape[1] != bias_norm_dims[1])
|
||||
return false;
|
||||
for (size_t i = 2; i < bias_norm_dims.size(); i++) {
|
||||
if (bias_norm_dims[i] != 1)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
int num_conv_parents = 0;
|
||||
for (size_t i = 0; i < node->get_input_size(); i++) {
|
||||
const auto n = node->get_input_node_shared_ptr(i);
|
||||
//BinaryConvolution allows other ops to be fused before the Add, while Convolution doesn't
|
||||
num_conv_parents += (isSuitableConvolutionParent(n) || isFusedBiasNode(n) ||
|
||||
GetNodeFusingType(n) == NodeFusingType::FusedWithBinaryConvolution);
|
||||
}
|
||||
return getNumNonConstInputs(node) == 2 && num_conv_parents >=1;
|
||||
}
|
||||
bool isSuitableChildForFusingSumActivation(const std::shared_ptr<const Node> &node) {
|
||||
return SupportsFusingWithConvolution_SumActivation(node);
|
||||
}
|
||||
// Continue fusing chain of the passed type if the node has one child
|
||||
// Otherwise mark node as FusedTerminator (Fused, but fusing chain is interrupted)
|
||||
void PropagateIfHasOnlyChild(const std::shared_ptr<Node> &node, NodeFusingType nodeType) {
|
||||
const auto out = node->outputs();
|
||||
const bool has_only_child = out.size() == 1 && out[0].get_target_inputs().size() == 1;
|
||||
SetNodeFusingType(node, has_only_child ? nodeType : NodeFusingType::FusedTerminator);
|
||||
}
|
||||
// todo: Skipping MultiSubGraphOp such as TensorIterator, Loop and If. Snippets might tokenize their bodies in the future.
|
||||
// Note that the function is recurrent, since there might be multi-level MultiSubGraphOp, if(){if(){}}else{} for example.
|
||||
void MarkSubgraphOpAsSkipped(const std::shared_ptr<Node> &node) {
|
||||
if (ov::is_type<ov::op::util::MultiSubGraphOp>(node)) {
|
||||
std::vector<std::shared_ptr<ov::Model>> models{};
|
||||
// Covers TensorIterator and Loop
|
||||
if (auto s = ov::as_type_ptr<ov::op::util::SubGraphOp>(node)) {
|
||||
models.push_back(s->get_function());
|
||||
// Add new multi-body subgraph op here
|
||||
} else if (auto if_op = ov::as_type_ptr<ov::op::v8::If>(node)) {
|
||||
models.push_back(if_op->get_then_body());
|
||||
models.push_back(if_op->get_else_body());
|
||||
}
|
||||
for (auto& m : models) {
|
||||
for (auto& n : m->get_ops()) {
|
||||
snippets::pass::SetSnippetsNodeType(n, snippets::pass::SnippetsNodeType::SkippedByPlugin);
|
||||
MarkSubgraphOpAsSkipped(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool SnippetsMarkSkipped::run_on_model(const std::shared_ptr<ov::Model> &m) {
|
||||
for (auto &node : m->get_ordered_ops()) {
|
||||
if (ngraph::op::is_constant(node))
|
||||
continue;
|
||||
if (ngraph::op::is_parameter(node)) {
|
||||
SetNodeFusingType(node, NodeFusingType::IgnoredAfterInputs);
|
||||
continue;
|
||||
} else if (isSuitableConvolutionParent(node)) {
|
||||
// Initiate fusing chain
|
||||
SetNodeFusingType(node, NodeFusingType::FusedWithConvolution);
|
||||
continue;
|
||||
} else if (isSuitableBinaryConvolutionParent(node)) {
|
||||
SetNodeFusingType(node, NodeFusingType::FusedWithBinaryConvolution);
|
||||
continue;
|
||||
} else if (isSuitableMiscParent(node)) {
|
||||
SetNodeFusingType(node, NodeFusingType::FusedWithMisc);
|
||||
continue;
|
||||
} else if (isSuitableMatMulParent(node)) {
|
||||
SetNodeFusingType(node, NodeFusingType::FusedWithMatMul);
|
||||
continue;
|
||||
}
|
||||
for (const auto fusingChainType : getContinuableChains(node)) {
|
||||
if (isSuitableChildForFusingSimple(node)) {
|
||||
PropagateIfHasOnlyChild(node, fusingChainType);
|
||||
} else if (fusingChainType == NodeFusingType::FusedWithConvolution ||
|
||||
fusingChainType == NodeFusingType::FusedWithBinaryConvolution) {
|
||||
if (isSuitableParentForFusingSumActivation(node)) {
|
||||
PropagateIfHasOnlyChild(node, NodeFusingType::FusedWithConvolutionSumActivation);
|
||||
// Mimic FuseConvolutionAndSimpleOperationThroughMaxPool
|
||||
} else if (isSuitablePoolChild(node)) {
|
||||
PropagateIfHasOnlyChild(node, fusingChainType);
|
||||
}
|
||||
} else if (fusingChainType == NodeFusingType::FusedWithConvolutionSumActivation &&
|
||||
isSuitableChildForFusingSumActivation(node)) {
|
||||
// Todo: Chain could be converted from FusedWithBinaryConvolution to FusedWithConvolution at this point
|
||||
// Set FusedWithConvolution, so the fusing chain could be propagated
|
||||
PropagateIfHasOnlyChild(node, NodeFusingType::FusedWithConvolution);
|
||||
} else if (fusingChainType == NodeFusingType::FusedWithMatMul) {
|
||||
// Handle fusings for both MatMul and FullyConnected
|
||||
NodeFusingType updatedChainType = fusingChainType;
|
||||
if (isSuitableChildForFusingMatMul(node, updatedChainType))
|
||||
PropagateIfHasOnlyChild(node, updatedChainType);
|
||||
} else if (fusingChainType == NodeFusingType::IgnoredAfterInputs && snippets::pass::AppropriateForSubgraph(node)) {
|
||||
SetNodeFusingType(node, NodeFusingType::IgnoredAfterInputs);
|
||||
}
|
||||
}
|
||||
if (GetNodeFusingType(node) != NodeFusingType::NotSet) {
|
||||
SetSnippetsNodeType(node, snippets::pass::SnippetsNodeType::SkippedByPlugin);
|
||||
} else {
|
||||
MarkSubgraphOpAsSkipped(node);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace MKLDNNPlugin
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ngraph/pass/graph_rewrite.hpp>
|
||||
|
||||
namespace MKLDNNPlugin {
|
||||
/**
|
||||
* @interface SnippetsMarkSkipped
|
||||
* @brief Mark operations that should be ignored by snippets on tokenization stage. A typical example is eltwise operations
|
||||
* that will be fused into convolutions on plugin side.
|
||||
*/
|
||||
class SnippetsMarkSkipped : public ov::pass::ModelPass {
|
||||
public:
|
||||
NGRAPH_RTTI_DECLARATION;
|
||||
SnippetsMarkSkipped() : ModelPass() {}
|
||||
bool run_on_model(const std::shared_ptr<ov::Model> &) override;
|
||||
};
|
||||
|
||||
/*
|
||||
NotSet - not part of a fusing chain
|
||||
FusedTerminator - the node is fused, but the chain can't be continued
|
||||
FusedWithConvolution, FusedWithConvolutionSumActivation, FusedWithMisc - fusing chains with different continuation rules
|
||||
IgnoredAfterInputs - node must be skipped, since can't be handled properly at this time. Also a continuable fusing chain.
|
||||
Order of SnippetsNodeType is important!:
|
||||
* SnippetsNodeType >= FusedTerminator is a Fused chain
|
||||
* SnippetsNodeType > FusedTerminator is a Fused chain that may be continued
|
||||
*/
|
||||
// Todo: Snippets currently support only FP32 precision, however network inputs could be converted to another precision by plugin
|
||||
// (in other words after tokenization). To handle this behavior, eltwise chains that start at input are marked as IgnoredAfterInputs.
|
||||
// Tis is not a real plugin-side fusing, but rather a workaround to guarantee that snippets always executed in FP32.
|
||||
enum class NodeFusingType : int64_t {
|
||||
NotSet,
|
||||
FusedTerminator,
|
||||
FusedWithConvolution, FusedWithBinaryConvolution, FusedWithConvolutionSumActivation,
|
||||
FusedWithMatMul, FusedWithMisc, IgnoredAfterInputs};
|
||||
} // namespace MKLDNNPlugin
|
||||
@@ -0,0 +1,548 @@
|
||||
// Copyright (C) 2018-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "subgraph.h"
|
||||
|
||||
#include <ie_parallel.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
|
||||
#include <mkldnn.hpp>
|
||||
#include <mkldnn_debug.h>
|
||||
#include <mkldnn_types.h>
|
||||
#include <mkldnn_extension_utils.h>
|
||||
|
||||
#include <ngraph/opsets/opset1.hpp>
|
||||
#include <ngraph/pass/visualize_tree.hpp>
|
||||
#include <ngraph/rt_info.hpp>
|
||||
#include <ie_ngraph_utils.hpp>
|
||||
|
||||
#include <snippets/op/subgraph.hpp>
|
||||
#include "emitters/cpu_generator.hpp"
|
||||
|
||||
using namespace MKLDNNPlugin;
|
||||
using namespace InferenceEngine;
|
||||
using namespace mkldnn::impl::utils;
|
||||
using namespace mkldnn::impl::cpu;
|
||||
using namespace mkldnn::impl::cpu::x64;
|
||||
using namespace Xbyak;
|
||||
|
||||
MKLDNNSnippetNode::MKLDNNSnippetNode(const std::shared_ptr<ngraph::Node>& op, const dnnl::engine& eng, MKLDNNWeightsSharing::Ptr &cache)
|
||||
: MKLDNNNode(op, eng, cache) {
|
||||
host_isa = dnnl::impl::cpu::x64::mayiuse(dnnl::impl::cpu::x64::avx512_common) ?
|
||||
dnnl::impl::cpu::x64::avx512_common : dnnl::impl::cpu::x64::avx2;
|
||||
|
||||
// Create a deep local copy of the input snippet to perform canonicalization & code generation
|
||||
// Todo: Probably better to implement a proper copy constructor
|
||||
if (const auto tmp_snippet = ov::as_type_ptr<ngraph::snippets::op::Subgraph>(op)) {
|
||||
ngraph::OutputVector subgraph_node_inputs;
|
||||
for (const auto &input : tmp_snippet->input_values()) {
|
||||
auto new_input = std::make_shared<ngraph::opset1::Parameter>(input.get_element_type(), input.get_partial_shape());
|
||||
subgraph_node_inputs.push_back(new_input);
|
||||
}
|
||||
auto new_body = ov::clone_model(*tmp_snippet->get_body().get());
|
||||
snippet = std::make_shared<ngraph::snippets::op::Subgraph>(subgraph_node_inputs, new_body);
|
||||
ngraph::copy_runtime_info(tmp_snippet, snippet);
|
||||
snippet->set_friendly_name(tmp_snippet->get_friendly_name());
|
||||
snippet->set_generator(std::make_shared<CPUGenerator>(host_isa));
|
||||
} else {
|
||||
IE_THROW(NotImplemented) << "Node is not an instance of snippets::op::Subgraph";
|
||||
}
|
||||
}
|
||||
|
||||
void MKLDNNSnippetNode::initSupportedPrimitiveDescriptors() {
|
||||
if (!supportedPrimitiveDescriptors.empty())
|
||||
return;
|
||||
|
||||
auto hasBroadcastByC = [this]() -> bool {
|
||||
for (auto op : ngraph::as_type_ptr<ngraph::snippets::op::Subgraph>(snippet)->get_body()->get_ops()) {
|
||||
if (ngraph::op::supports_auto_broadcast(op)) {
|
||||
auto shape = op->get_input_shape(0);
|
||||
// Filter out scalar empty shape Shape{}
|
||||
if (ngraph::shape_size(shape) != 1) {
|
||||
for (const auto& input : op->inputs()) {
|
||||
if (input.get_shape().size() > 1 && shape[1] != input.get_shape()[1] && ngraph::shape_size(input.get_shape()) != 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const Precision supportedPrecision = Precision::FP32;
|
||||
|
||||
bool dimRanksAreEqual = true;
|
||||
for (size_t i = 0; dimRanksAreEqual && i < inputShapes.size(); i++) {
|
||||
for (size_t j = 0; dimRanksAreEqual && j < outputShapes.size(); j++) {
|
||||
if (inputShapes[i].getRank() != outputShapes[j].getRank())
|
||||
dimRanksAreEqual = false;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t ndims = outputShapes[0].getRank();
|
||||
const bool isChannelsFirstApplicable = dnnl::impl::utils::one_of(ndims, 1, 2, 4, 5) && dimRanksAreEqual;
|
||||
// Todo: Snippets currently don't support per-channel broadcasting of Blocked descriptors because
|
||||
// canonicalization can't distinguish between <N, C, H, W, c> and <N, C, D, H, W> cases. So we need to pass an
|
||||
// additional parameter to canonicalization, see snippets::op::Subgraph::canonicalize for details.
|
||||
const bool isBlockedApplicable = dnnl::impl::utils::one_of(ndims, 4, 5) && dimRanksAreEqual && !hasBroadcastByC();
|
||||
enum LayoutType {
|
||||
Planar,
|
||||
ChannelsFirst,
|
||||
Blocked
|
||||
};
|
||||
auto initDesc = [&] (LayoutType lt) -> NodeDesc {
|
||||
auto createMemoryDesc = [lt](const Shape &shape, Precision prc, size_t offset) -> std::shared_ptr<CpuBlockedMemoryDesc> {
|
||||
const auto &dims = shape.getDims();
|
||||
if (lt == ChannelsFirst && shape.getRank() != 1) {
|
||||
auto ndims = shape.getRank();
|
||||
VectorDims order(ndims);
|
||||
std::iota(order.begin(), order.end(), 0);
|
||||
if (ndims > 1) {
|
||||
order.erase(order.begin() + 1);
|
||||
order.push_back(1);
|
||||
}
|
||||
|
||||
VectorDims blocks(ndims);
|
||||
for (size_t i = 0; i < order.size(); i++) {
|
||||
blocks[i] = dims[order[i]];
|
||||
}
|
||||
|
||||
return std::make_shared<CpuBlockedMemoryDesc>(prc, shape, blocks, order, offset);
|
||||
} else if (lt == Blocked && shape.getRank() != 1 && (shape.getMinDims()[1] != Shape::UNDEFINED_DIM && shape.getMinDims()[1] > 1)) {
|
||||
size_t blockSize = mayiuse(dnnl::impl::cpu::x64::avx512_common) ? 16 : 8;
|
||||
|
||||
VectorDims blocks = dims;
|
||||
VectorDims order(blocks.size());
|
||||
std::iota(order.begin(), order.end(), 0);
|
||||
|
||||
blocks[1] = dims[1] != Shape::UNDEFINED_DIM ? div_up(blocks[1], blockSize) : Shape::UNDEFINED_DIM;
|
||||
blocks.push_back(blockSize);
|
||||
order.push_back(1);
|
||||
|
||||
return std::make_shared<CpuBlockedMemoryDesc>(prc, shape, blocks, order, offset);
|
||||
} else {
|
||||
VectorDims blocks = dims;
|
||||
VectorDims order(blocks.size());
|
||||
std::iota(order.begin(), order.end(), 0);
|
||||
|
||||
return std::make_shared<CpuBlockedMemoryDesc>(prc, shape, blocks, order, offset);
|
||||
}
|
||||
};
|
||||
|
||||
size_t offset = std::numeric_limits<size_t>::max();
|
||||
NodeConfig config;
|
||||
config.dynBatchSupport = false;
|
||||
config.inConfs.resize(inputShapes.size());
|
||||
for (size_t i = 0; i < inputShapes.size(); i++) {
|
||||
PortConfig portConfig;
|
||||
portConfig.inPlace = (!i && canBeInPlace()) ? 0 : -1;
|
||||
portConfig.constant = false;
|
||||
portConfig.desc = createMemoryDesc(inputShapes[i], supportedPrecision, offset);
|
||||
if (inputShapes[i].getDims()[0] == 1) {
|
||||
const auto denseDesc = portConfig.desc->as<BlockedMemoryDesc>();
|
||||
auto strides = denseDesc->getStrides();
|
||||
strides[0] = Shape::UNDEFINED_DIM;
|
||||
portConfig.desc = std::make_shared<CpuBlockedMemoryDesc>(denseDesc->getPrecision(),
|
||||
denseDesc->getShape(),
|
||||
denseDesc->getBlockDims(),
|
||||
denseDesc->getOrder(),
|
||||
denseDesc->getOffsetPadding(),
|
||||
denseDesc->getOffsetPaddingToData(),
|
||||
strides);
|
||||
}
|
||||
config.inConfs[i] = portConfig;
|
||||
}
|
||||
config.outConfs.resize(outputShapes.size());
|
||||
for (size_t i = 0; i < outputShapes.size(); i++) {
|
||||
PortConfig portConfig;
|
||||
portConfig.inPlace = -1;
|
||||
portConfig.constant = false;
|
||||
portConfig.desc = createMemoryDesc(outputShapes[i], supportedPrecision, offset);
|
||||
if (outputShapes[i].getDims()[0] == 1) {
|
||||
const auto denseDesc = portConfig.desc->as<BlockedMemoryDesc>();
|
||||
auto strides = denseDesc->getStrides();
|
||||
strides[0] = Shape::UNDEFINED_DIM;
|
||||
portConfig.desc = std::make_shared<CpuBlockedMemoryDesc>(denseDesc->getPrecision(),
|
||||
denseDesc->getShape(),
|
||||
denseDesc->getBlockDims(),
|
||||
denseDesc->getOrder(),
|
||||
denseDesc->getOffsetPadding(),
|
||||
denseDesc->getOffsetPaddingToData(),
|
||||
strides);
|
||||
}
|
||||
config.outConfs[i] = portConfig;
|
||||
}
|
||||
|
||||
impl_desc_type impl_type = impl_desc_type::unknown;
|
||||
if (mayiuse(x64::avx512_common)) {
|
||||
impl_type = impl_desc_type::jit_avx512;
|
||||
} else if (mayiuse(x64::avx2)) {
|
||||
impl_type = impl_desc_type::jit_avx2;
|
||||
}
|
||||
return {config, impl_type};
|
||||
};
|
||||
|
||||
if (isChannelsFirstApplicable)
|
||||
supportedPrimitiveDescriptors.emplace_back(initDesc(ChannelsFirst));
|
||||
if (isBlockedApplicable)
|
||||
supportedPrimitiveDescriptors.emplace_back(initDesc(Blocked));
|
||||
supportedPrimitiveDescriptors.emplace_back(initDesc(Planar));
|
||||
}
|
||||
|
||||
void MKLDNNSnippetNode::selectOptimalPrimitiveDescriptor() {
|
||||
selectPreferPrimitiveDescriptor(getPrimitivesPriority(), true);
|
||||
}
|
||||
|
||||
void MKLDNNSnippetNode::createPrimitive() {
|
||||
// schedule definition part
|
||||
// it defines offsets, strides and sizes for snippet kernel scheduling
|
||||
define_schedule();
|
||||
|
||||
// code generation part
|
||||
// it might be worth to generate explicitly for scheduler work amount for now,
|
||||
// but in future some interface should be defined in order to communicate schedule for a kernel
|
||||
// or generate schedule for a kernel.
|
||||
// Here kernel is generated for most warying dimension by default.
|
||||
generate();
|
||||
}
|
||||
|
||||
void MKLDNNSnippetNode::execute(dnnl::stream strm) {
|
||||
if (schedule.ptr == nullptr || !canUseOptimizedImpl) {
|
||||
IE_THROW() << "MKLDNNSnippetNode can't use Optimized implementation and can't fallback to reference";
|
||||
}
|
||||
jit_snippets_call_args call_args;
|
||||
for (size_t i = 0; i < srcMemPtrs.size(); i++)
|
||||
call_args.src_ptrs[i] = reinterpret_cast<const uint8_t*>(srcMemPtrs[i]->GetData()) + start_offset_in[i];
|
||||
|
||||
for (size_t i = 0; i < dstMemPtrs.size(); i++)
|
||||
call_args.dst_ptrs[i] = reinterpret_cast<uint8_t*>(dstMemPtrs[i]->GetData()) + start_offset_out[i];
|
||||
|
||||
if (tensorRank == rank6D) {
|
||||
schedule_6d(call_args);
|
||||
} else {
|
||||
schedule_nt(call_args);
|
||||
}
|
||||
}
|
||||
|
||||
bool MKLDNNSnippetNode::created() const {
|
||||
return getType() == Subgraph;
|
||||
}
|
||||
|
||||
// internal interface for subgraph execution
|
||||
|
||||
static size_t argmax_rank(const std::vector<MKLDNNEdgeWeakPtr> &childEdges) {
|
||||
auto getOutBlockedDims = [childEdges](int i) {
|
||||
if ( auto childEdge = childEdges[i].lock() )
|
||||
return childEdge->getMemory().GetDescWithType<BlockedMemoryDesc>()->getBlockDims();
|
||||
else
|
||||
IE_THROW() << "Unable to lock childEdge weak_ptr";
|
||||
return VectorDims{};
|
||||
};
|
||||
auto getOutRank = [getOutBlockedDims](int i) {
|
||||
return getOutBlockedDims(i).size();
|
||||
};
|
||||
size_t max_rank_idx = 0;
|
||||
size_t max_rank_val = getOutRank(0);
|
||||
for (size_t i = 1; i < childEdges.size(); i++) {
|
||||
const auto i_rank_val = getOutRank(i);
|
||||
if (max_rank_val < i_rank_val) {
|
||||
max_rank_idx = i;
|
||||
max_rank_val = i_rank_val;
|
||||
} else if (max_rank_val == i_rank_val) {
|
||||
const auto max_rank_dims = getOutBlockedDims(max_rank_idx);
|
||||
const auto i_dims = getOutBlockedDims(i);
|
||||
for (size_t j = 0; j < max_rank_val; j++) {
|
||||
if (i_dims[j] > max_rank_dims[j]) {
|
||||
max_rank_idx = i;
|
||||
max_rank_val = i_rank_val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return max_rank_idx;
|
||||
}
|
||||
|
||||
static auto offset_calculation(std::vector<int64_t>& offset, const std::vector<int64_t>& dims_in, const std::vector<int64_t>& dims_out) -> void {
|
||||
int k = 1;
|
||||
for (int i = offset.size() - 1; i >= 0; i--) {
|
||||
offset[i] = (dims_in[i] == dims_out[i]) ? k : 0;
|
||||
k *= dims_in[i];
|
||||
}
|
||||
}
|
||||
|
||||
static auto collapseLastDims(std::vector<int64_t>& dims, int dimsToCollapse) -> void {
|
||||
for (int i = dims.size() - 2; i > dims.size() - dimsToCollapse - 2; i--) {
|
||||
dims[dims.size() - 1] *= dims[i];
|
||||
}
|
||||
|
||||
for (int i = dims.size() - 2; i >= dimsToCollapse; i--) {
|
||||
dims[i] = dims[i - dimsToCollapse];
|
||||
}
|
||||
|
||||
for (int i = dimsToCollapse - 1; i >= 0; i--) {
|
||||
dims[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void MKLDNNSnippetNode::define_schedule() {
|
||||
const auto config = getSelectedPrimitiveDescriptor()->getConfig();
|
||||
const auto dataSize = config.inConfs[0].desc->getPrecision().size();
|
||||
// store to use as an execution domain
|
||||
max_rank_out_desc_idx = argmax_rank(getChildEdges());
|
||||
const auto outBlockingDesc_maxRank = getChildEdgeAt(max_rank_out_desc_idx)->getMemory().GetDescWithType<BlockedMemoryDesc>();
|
||||
// initialize by maximum output dimension. Dimensions of outputs should be broadcastable
|
||||
tensorRank = std::max(static_cast<size_t>(rank6D), outBlockingDesc_maxRank->getBlockDims().size());
|
||||
|
||||
auto initDims = [this, config, &outBlockingDesc_maxRank](size_t tensorRank) {
|
||||
// assume all input sizes are even
|
||||
const size_t inputNum = getParentEdges().size();
|
||||
|
||||
dims_in.resize(inputNum);
|
||||
for (size_t i = 0; i < inputNum; i++) {
|
||||
dims_in[i].resize(tensorRank, 1);
|
||||
}
|
||||
|
||||
const auto outOrder = outBlockingDesc_maxRank->getOrder();
|
||||
for (size_t i = 0; i < inputNum; i++) {
|
||||
auto inBlockingDesc = getParentEdgeAt(i)->getMemory().GetDescWithType<BlockedMemoryDesc>();
|
||||
size_t rank = inBlockingDesc->getBlockDims().size();
|
||||
|
||||
// WA to normalize blocked and planar layouts
|
||||
// not actual thought, since [§] doesn't support mixed layouts yet
|
||||
auto inOrder = inBlockingDesc->getOrder();
|
||||
size_t startOff = outOrder.size() != outBlockingDesc_maxRank->getShape().getRank() &&
|
||||
outOrder.back() != inOrder.back() ? 1 : 0;
|
||||
for (size_t j = 0; j < rank; j++) {
|
||||
dims_in[i][dims_in[i].size() - 1 - j - startOff] = inBlockingDesc->getBlockDims()[rank - 1 - j];
|
||||
}
|
||||
}
|
||||
|
||||
// assume all output sizes are even
|
||||
const size_t outputNum = config.outConfs.size();
|
||||
|
||||
dims_out.resize(outputNum);
|
||||
for (size_t i = 0; i < outputNum; i++) {
|
||||
dims_out[i].resize(tensorRank, 1);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < outputNum; i++) {
|
||||
auto outBlockingDesc = getChildEdgeAt(i)->getMemory().GetDescWithType<BlockedMemoryDesc>();
|
||||
size_t rank = outBlockingDesc->getBlockDims().size();
|
||||
|
||||
for (size_t j = 0; j < rank; j++) {
|
||||
dims_out[i][dims_out[i].size() - 1 - j] = outBlockingDesc->getBlockDims()[rank - 1 - j];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto initOffsets = [this, config, dataSize](size_t tensorRank) {
|
||||
// find max rank input among all outputs
|
||||
const size_t inputNum = getParentEdges().size();
|
||||
offsets_in.resize(inputNum);
|
||||
for (size_t i = 0; i < inputNum; i++) {
|
||||
offsets_in[i].resize(tensorRank, 1);
|
||||
offset_calculation(offsets_in[i], dims_in[i], dims_out[max_rank_out_desc_idx]);
|
||||
for (size_t j = 0; j < tensorRank; j++) {
|
||||
offsets_in[i][j] *= dataSize;
|
||||
}
|
||||
}
|
||||
|
||||
start_offset_in.resize(inputNum);
|
||||
srcMemPtrs.resize(inputNum);
|
||||
for (size_t i = 0; i < inputNum; i++) {
|
||||
const auto memPtr = getParentEdgeAt(i)->getMemoryPtr();
|
||||
srcMemPtrs[i] = memPtr;
|
||||
start_offset_in[i] = memPtr->GetDescWithType<BlockedMemoryDesc>()->getOffsetPadding() * dataSize;
|
||||
}
|
||||
|
||||
const size_t outputNum = config.outConfs.size();
|
||||
offsets_out.resize(outputNum);
|
||||
for (size_t i = 0; i < outputNum; i++) {
|
||||
offsets_out[i].resize(tensorRank, 1);
|
||||
offset_calculation(offsets_out[i], dims_out[i], dims_out[max_rank_out_desc_idx]);
|
||||
for (size_t j = 0; j < tensorRank; j++) {
|
||||
offsets_out[i][j] *= dataSize;
|
||||
}
|
||||
}
|
||||
|
||||
start_offset_out.resize(outputNum);
|
||||
dstMemPtrs.resize(outputNum);
|
||||
for (size_t i = 0; i < outputNum; i++) {
|
||||
const auto memPtr = getChildEdgeAt(i)->getMemoryPtr();
|
||||
dstMemPtrs[i] = memPtr;
|
||||
start_offset_out[i] = memPtr->GetDescWithType<BlockedMemoryDesc>()->getOffsetPadding() * dataSize;
|
||||
}
|
||||
};
|
||||
|
||||
auto find_dims_to_collapse = [this, config, &outBlockingDesc_maxRank]() -> int {
|
||||
int collapsedDims = 0;
|
||||
size_t minimalConcurrency = parallel_get_max_threads();
|
||||
size_t minimalJitWorkAmount = 256;
|
||||
size_t currentJitWorkAmount = dims_out[max_rank_out_desc_idx].back();
|
||||
while (currentJitWorkAmount < minimalJitWorkAmount && currentJitWorkAmount < fullWorkAmount) {
|
||||
if (dims_out[max_rank_out_desc_idx].size() - collapsedDims - 2 < 0)
|
||||
break;
|
||||
|
||||
bool canCollapse = true;
|
||||
for (size_t i = 0; i < dims_in.size(); i++) {
|
||||
if ((dims_in[i][dims_in[i].size() - 2] != 1 && dims_in[i][dims_in[i].size() - 1] == 1) ||
|
||||
(dims_in[i][dims_in[i].size() - 2] == 1 && dims_in[i][dims_in[i].size() - 1] != 1)) {
|
||||
canCollapse = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t nextJitWorkAmount = currentJitWorkAmount * dims_out[max_rank_out_desc_idx][dims_out[max_rank_out_desc_idx].size() - 2];
|
||||
if (fullWorkAmount / nextJitWorkAmount >= minimalConcurrency) {
|
||||
currentJitWorkAmount = nextJitWorkAmount;
|
||||
// if we cannot use dim collapsing we should use tile2D
|
||||
if (!canCollapse) {
|
||||
if (tileRank < maxTileRank) {
|
||||
tileRank++;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
collapsedDims++;
|
||||
for (size_t i = 0; i < dims_in.size(); i++) {
|
||||
collapseLastDims(dims_in[i], 1);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < dims_out.size(); i++) {
|
||||
collapseLastDims(dims_out[i], 1);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return collapsedDims;
|
||||
};
|
||||
|
||||
auto initSchedulingInfo = [this, dataSize](const size_t tensorRank) -> void {
|
||||
// initialize scheduling information
|
||||
sch_offsets_in.resize(offsets_in.size(), 0);
|
||||
sch_offsets_out.resize(offsets_out.size(), 0);
|
||||
sch_dims.resize(maxTileRank, 1);
|
||||
sch_dims[maxTileRank-1] = dims_out[max_rank_out_desc_idx].back();
|
||||
schedulerWorkAmount = fullWorkAmount / dims_out[max_rank_out_desc_idx].back();
|
||||
if (tileRank > 1) {
|
||||
sch_dims[maxTileRank - tileRank] = dims_out[max_rank_out_desc_idx][tensorRank - 2];
|
||||
schedulerWorkAmount /= dims_out[max_rank_out_desc_idx][tensorRank - 2];
|
||||
dims_out[max_rank_out_desc_idx][tensorRank - 2] = 1;
|
||||
|
||||
// update offsets for tile 2D because loaders have ptr shifts in some cases and stores have always ptrs shifts
|
||||
for (size_t i = 0; i < offsets_in.size(); i++) {
|
||||
int64_t offset = offsets_in[i][tensorRank - 2];
|
||||
if ((offset > dataSize) || (offset == 0 && dims_in[i].back() != 1)) {
|
||||
sch_offsets_in[i] = offset - dims_out[max_rank_out_desc_idx].back() * dataSize;
|
||||
} else if (offset == dataSize) {
|
||||
sch_offsets_in[i] = offset;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < offsets_out.size(); i++) {
|
||||
int64_t offset = offsets_out[i][tensorRank - 2];
|
||||
sch_offsets_out[i] = offset - dims_out[max_rank_out_desc_idx].back() * dataSize;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
initDims(tensorRank);
|
||||
|
||||
fullWorkAmount = 1;
|
||||
for (size_t i = 0; i < dims_out[max_rank_out_desc_idx].size(); i++) {
|
||||
fullWorkAmount *= dims_out[max_rank_out_desc_idx][i];
|
||||
}
|
||||
|
||||
const int collapsedDims = find_dims_to_collapse();
|
||||
batchDimIdx = tensorRank - outBlockingDesc_maxRank->getBlockDims().size() + collapsedDims;
|
||||
|
||||
initOffsets(tensorRank);
|
||||
initSchedulingInfo(tensorRank);
|
||||
}
|
||||
|
||||
void MKLDNNSnippetNode::generate() {
|
||||
std::vector<MKLDNNEdgePtr> input_first_row;
|
||||
for (size_t i = 0; i < inputShapes.size(); i++)
|
||||
input_first_row.push_back(getParentEdgesAtPort(i)[0]);
|
||||
|
||||
auto edgeToBlockedShape = [](const MKLDNNEdgePtr& edge) -> ngraph::snippets::op::Subgraph::BlockedShape {
|
||||
const auto blockedDesc = edge->getMemory().GetDescWithType<BlockedMemoryDesc>();
|
||||
ngraph::Shape shape(blockedDesc->getBlockDims());
|
||||
ngraph::AxisVector blocking(blockedDesc->getOrder());
|
||||
ngraph::element::Type precision = InferenceEngine::details::convertPrecision(blockedDesc->getPrecision());
|
||||
return std::make_tuple(shape, blocking, precision);
|
||||
};
|
||||
ngraph::snippets::op::Subgraph::BlockedShapeVector input_blocked_shapes;
|
||||
std::transform(input_first_row.begin(), input_first_row.end(), std::back_inserter(input_blocked_shapes), edgeToBlockedShape);
|
||||
|
||||
std::vector<MKLDNNEdgePtr> output_first_row;
|
||||
for (size_t i = 0; i < outputShapes.size(); i++)
|
||||
// Can it go with difference shape or precision to different edges? I assume no.
|
||||
output_first_row.push_back(getChildEdgesAtPort(i)[0]);
|
||||
|
||||
ngraph::snippets::op::Subgraph::BlockedShapeVector output_blocked_shapes;
|
||||
std::transform(output_first_row.begin(), output_first_row.end(), std::back_inserter(output_blocked_shapes), edgeToBlockedShape);
|
||||
jit_snippets_compile_args jcp;
|
||||
jcp.output_dims = dims_out[max_rank_out_desc_idx];
|
||||
std::copy(sch_dims.begin(), sch_dims.end(), jcp.scheduler_dims);
|
||||
std::copy(sch_offsets_in.begin(), sch_offsets_in.end(), jcp.scheduler_offsets);
|
||||
std::copy(sch_offsets_out.begin(), sch_offsets_out.end(), &jcp.scheduler_offsets[sch_offsets_in.size()]);
|
||||
size_t harness_num_dims = jcp.output_dims.size() - 1;
|
||||
if (harness_num_dims > SNIPPETS_MAX_HARNESS_DIMS) {
|
||||
canUseOptimizedImpl = false;
|
||||
harness_num_dims = SNIPPETS_MAX_HARNESS_DIMS;
|
||||
}
|
||||
for (size_t i = 0; i < inputShapes.size(); i++) {
|
||||
auto b = offsets_in[i].begin();
|
||||
std::copy(b, b + harness_num_dims, &jcp.data_offsets[i * harness_num_dims]);
|
||||
}
|
||||
for (size_t i = 0; i < outputShapes.size(); i++) {
|
||||
auto b = offsets_out[i].begin();
|
||||
std::copy(b, b + harness_num_dims, &jcp.data_offsets[(inputShapes.size() + i) * harness_num_dims]);
|
||||
}
|
||||
schedule = snippet->generate(output_blocked_shapes, input_blocked_shapes, reinterpret_cast<void*>(&jcp));
|
||||
}
|
||||
|
||||
void MKLDNNSnippetNode::schedule_6d(const jit_snippets_call_args& call_args) const {
|
||||
const auto& dom = dims_out[max_rank_out_desc_idx];
|
||||
// < N, C, H, W > < 1, 1, N, C*H*W>
|
||||
parallel_for5d(dom[0], dom[1], dom[2], dom[3], dom[4],
|
||||
[&](int64_t d0, int64_t d1, int64_t d2, int64_t d3, int64_t d4) {
|
||||
int64_t indexes[] = {d0, d1, d2, d3, d4};
|
||||
schedule.get_callable<kernel>()(indexes, &call_args);
|
||||
});
|
||||
}
|
||||
|
||||
void MKLDNNSnippetNode::schedule_nt(const jit_snippets_call_args& call_args) const {
|
||||
const auto& work_size = dims_out[max_rank_out_desc_idx];
|
||||
parallel_nt(0, [&](const int ithr, const int nthr) {
|
||||
size_t start = 0, end = 0;
|
||||
splitter(schedulerWorkAmount, nthr, ithr, start, end);
|
||||
|
||||
std::vector<int64_t> indexes(work_size.size() - 1, 0);
|
||||
for (size_t iwork = start; iwork < end; ++iwork) {
|
||||
size_t tmp = iwork;
|
||||
for (ptrdiff_t j = work_size.size() - 2; j >= 0; j--) {
|
||||
indexes[j] = tmp % work_size[j];
|
||||
tmp /= work_size[j];
|
||||
}
|
||||
|
||||
schedule.get_callable<kernel>()(indexes.data(), &call_args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
REG_MKLDNN_PRIM_FOR(MKLDNNSnippetNode, Subgraph);
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (C) 2020-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ie_common.h>
|
||||
|
||||
#include <mkldnn.hpp>
|
||||
#include <cpu/x64/jit_generator.hpp>
|
||||
#include "emitters/jit_snippets_emitters.hpp"
|
||||
|
||||
#include "mkldnn_node.h"
|
||||
#include "snippets/op/subgraph.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace MKLDNNPlugin {
|
||||
|
||||
/// MKLDNNSnippetNode represents subgraph node in MKLDNN plugin
|
||||
/// potentially, snippet can be placed as a postop to any support operation while it doesn't support postops itself
|
||||
/// precision: fp32
|
||||
class MKLDNNSnippetNode : public MKLDNNNode {
|
||||
public:
|
||||
MKLDNNSnippetNode(const std::shared_ptr<ngraph::Node>& op, const mkldnn::engine& eng, MKLDNNWeightsSharing::Ptr &cache);
|
||||
~MKLDNNSnippetNode() override = default;
|
||||
|
||||
void getSupportedDescriptors() override {};
|
||||
void initSupportedPrimitiveDescriptors() override;
|
||||
void selectOptimalPrimitiveDescriptor() override;
|
||||
|
||||
// Here we convert to canonical for & jit everything
|
||||
void createPrimitive() override;
|
||||
|
||||
bool created() const override;
|
||||
|
||||
// if generator is set, it would execute generated code otherwise it would fallback to nGraph reference
|
||||
void execute(mkldnn::stream strm) override;
|
||||
|
||||
private:
|
||||
static const size_t rank6D {6};
|
||||
|
||||
typedef void (*kernel)(const void *, const void *);
|
||||
|
||||
void define_schedule();
|
||||
|
||||
void generate();
|
||||
|
||||
// Evaluates generated snippet using parallel backend
|
||||
void schedule_6d(const jit_snippets_call_args& const_args) const;
|
||||
void schedule_nt(const jit_snippets_call_args& const_args) const;
|
||||
|
||||
// Local copy of subgraph node for canonization & code generation
|
||||
std::shared_ptr<ngraph::snippets::op::Subgraph> snippet;
|
||||
|
||||
// Holds generated snippet with information about how to schedule it
|
||||
ngraph::snippets::Schedule schedule;
|
||||
|
||||
// Holds ISA version used is codeGeneration target
|
||||
dnnl::impl::cpu::x64::cpu_isa_t host_isa;
|
||||
|
||||
// Holds index of output used as in execution domain
|
||||
// it should be compatible with a schedule's work size
|
||||
size_t max_rank_out_desc_idx = 0;
|
||||
|
||||
/// scheduling info
|
||||
size_t batchDimIdx = 0;
|
||||
size_t tensorRank = 0;
|
||||
size_t tileRank = 1;
|
||||
size_t fullWorkAmount = 0;
|
||||
size_t schedulerWorkAmount = 0;
|
||||
const size_t maxTileRank = 2;
|
||||
|
||||
std::vector<MKLDNNMemoryPtr> srcMemPtrs = {};
|
||||
std::vector<MKLDNNMemoryPtr> dstMemPtrs = {};
|
||||
|
||||
std::vector<std::vector<int64_t>> dims_in = {};
|
||||
std::vector<std::vector<int64_t>> offsets_in = {};
|
||||
std::vector<ptrdiff_t> start_offset_in = {};
|
||||
std::vector<ptrdiff_t> start_offset_out = {};
|
||||
|
||||
std::vector<std::vector<int64_t>> dims_out = {};
|
||||
std::vector<std::vector<int64_t>> offsets_out = {};
|
||||
|
||||
std::vector<int64_t> sch_dims = {};
|
||||
std::vector<int64_t> sch_offsets_in = {};
|
||||
std::vector<int64_t> sch_offsets_out = {};
|
||||
bool canUseOptimizedImpl = true;
|
||||
};
|
||||
|
||||
} // namespace MKLDNNPlugin
|
||||
@@ -54,6 +54,7 @@ tolerance_map = {
|
||||
"fp16_inception_v1": {"atol": 0.001, "rtol": 0.001},
|
||||
"mobilenet_opset7": {"atol": 0.001, "rtol": 0.001},
|
||||
"resnet50_v2_opset7": {"atol": 0.001, "rtol": 0.001},
|
||||
"resnet50-v2-7": {"atol": 0.001, "rtol": 0.001},
|
||||
"test_mobilenetv2-1.0": {"atol": 0.001, "rtol": 0.001},
|
||||
"test_resnet101v2": {"atol": 0.001, "rtol": 0.001},
|
||||
"test_resnet18v2": {"atol": 0.001, "rtol": 0.001},
|
||||
@@ -97,7 +98,7 @@ tolerance_map = {
|
||||
"tiny_yolov2": {"atol": 1e-05, "rtol": 0.001},
|
||||
"mobilenetv2-1": {"atol": 1e-04, "rtol": 0.001},
|
||||
"resnet101v1": {"atol": 1e-04, "rtol": 0.001},
|
||||
"resnet101v2": {"atol": 1e-06, "rtol": 0.001},
|
||||
"resnet101v2": {"atol": 1e-5, "rtol": 0.001},
|
||||
"resnet152v2": {"atol": 1e-05, "rtol": 0.001},
|
||||
"resnet18v2": {"atol": 1e-05, "rtol": 0.001},
|
||||
"resnet34v2": {"atol": 1e-05, "rtol": 0.001},
|
||||
|
||||
@@ -25,10 +25,10 @@ ie_faster_build(${TARGET_NAME}
|
||||
UNITY
|
||||
)
|
||||
|
||||
target_link_libraries(${TARGET_NAME} PUBLIC ngraph
|
||||
target_link_libraries(${TARGET_NAME} PUBLIC ngraph inference_engine_transformations
|
||||
PRIVATE ngraph_reference)
|
||||
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${PUBLIC_HEADERS_DIR}
|
||||
target_include_directories(${TARGET_NAME} PUBLIC $<BUILD_INTERFACE:${PUBLIC_HEADERS_DIR}>
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
add_cpplint_target(${TARGET_NAME}_cpplint FOR_TARGETS ${TARGET_NAME})
|
||||
@@ -36,10 +36,8 @@ add_cpplint_target(${TARGET_NAME}_cpplint FOR_TARGETS ${TARGET_NAME})
|
||||
ie_mark_target_as_cc(${TARGET_NAME})
|
||||
|
||||
# LTO
|
||||
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES INTERPROCEDURAL_OPTIMIZATION_RELEASE ${ENABLE_LTO})
|
||||
|
||||
# install
|
||||
|
||||
# TODO: uncomment once snippets are integrated into CPU plugin
|
||||
# ov_install_static_lib(${TARGET_NAME} core)
|
||||
# install
|
||||
ov_install_static_lib(${TARGET_NAME} core)
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
|
||||
/**
|
||||
* @brief A file contains public interface for target indepenent code generator.
|
||||
* @brief A file contains public interface for target independent code generator.
|
||||
* @file generator.hpp
|
||||
*/
|
||||
#pragma once
|
||||
@@ -112,10 +112,10 @@ public:
|
||||
virtual ~Generator() = default;
|
||||
/**
|
||||
* @brief virtual method any specific implementation should implement
|
||||
* @param f runction in canonical for for table-based code generation
|
||||
* @param m model in canonical for for table-based code generation
|
||||
* @return pointer to generated code
|
||||
*/
|
||||
code generate(std::shared_ptr<Function>& f) const;
|
||||
code generate(std::shared_ptr<ov::Model>& m, const void* compile_params = nullptr) const;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<TargetMachine> target;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace ngraph {
|
||||
namespace pass {
|
||||
namespace itt {
|
||||
namespace domains {
|
||||
OV_ITT_DOMAIN(IETransform);
|
||||
OV_ITT_DOMAIN(SnippetsTransform);
|
||||
} // namespace domains
|
||||
} // namespace itt
|
||||
} // namespace pass
|
||||
@@ -13,7 +13,7 @@ namespace op {
|
||||
|
||||
/**
|
||||
* @interface Kernel
|
||||
* @brief Generated by Canonicalization and represents compute kernel legal for sheduling
|
||||
* @brief Generated by Canonicalization and represents compute kernel legal for scheduling
|
||||
* @ingroup snippets
|
||||
*/
|
||||
class Kernel : public ngraph::op::Op {
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
std::shared_ptr<Node> clone_with_new_inputs(const OutputVector& inputs) const override {
|
||||
return std::make_shared<Kernel>(region);
|
||||
}
|
||||
const void *compile_params = nullptr;
|
||||
};
|
||||
|
||||
} // namespace op
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (C) 2018-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ngraph/op/op.hpp>
|
||||
#include <ngraph/op/power.hpp>
|
||||
#include <snippets/snippets_isa.hpp>
|
||||
|
||||
namespace ngraph {
|
||||
namespace snippets {
|
||||
namespace op {
|
||||
|
||||
/**
|
||||
* @interface PowerStatic
|
||||
* @brief Generated by Canonicalization for a spasical case of power innstruction which has constant power value
|
||||
* @ingroup snippets
|
||||
*/
|
||||
class PowerStatic : public ov::op::util::UnaryElementwiseArithmetic {
|
||||
public:
|
||||
OPENVINO_OP("PowerStatic", "SnippetsOpset", ov::op::util::UnaryElementwiseArithmetic);
|
||||
|
||||
PowerStatic() = default;
|
||||
PowerStatic(const Output <Node> &arg, float power) : UnaryElementwiseArithmetic(arg), power(power) {
|
||||
constructor_validate_and_infer_types();
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> clone_with_new_inputs(const OutputVector &new_args) const override {
|
||||
check_new_args_count(this, new_args);
|
||||
return std::make_shared<PowerStatic>(new_args.at(0), power);
|
||||
}
|
||||
bool visit_attributes(AttributeVisitor &visitor) override {
|
||||
visitor.on_attribute("power", power);
|
||||
return true;
|
||||
}
|
||||
float get_power() const { return power; }
|
||||
|
||||
private:
|
||||
float power = 0;
|
||||
};
|
||||
} // namespace op
|
||||
} // namespace snippets
|
||||
} // namespace ngraph
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright (C) 2018-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ngraph/op/op.hpp>
|
||||
#include <ngraph/op/power.hpp>
|
||||
#include <snippets/snippets_isa.hpp>
|
||||
|
||||
namespace ngraph {
|
||||
namespace snippets {
|
||||
namespace op {
|
||||
|
||||
/**
|
||||
* @interface PowerStatic
|
||||
* @brief Generated by Canonicalization for a spasical case of power innstruction which has constant power value
|
||||
* @ingroup snippets
|
||||
*/
|
||||
class PowerStatic : public ngraph::op::v1::Power {
|
||||
public:
|
||||
OPENVINO_OP("PowerStatic", "SnippetsOpset", ngraph::op::v1::Power);
|
||||
|
||||
PowerStatic() : Power() {
|
||||
}
|
||||
|
||||
PowerStatic(const Output<Node>& arg0,
|
||||
const Output<Node>& arg1,
|
||||
const ngraph::op::AutoBroadcastSpec& auto_broadcast =
|
||||
ngraph::op::AutoBroadcastSpec(ngraph::op::AutoBroadcastType::NUMPY)) : Power(arg0, arg1, auto_broadcast) {
|
||||
NGRAPH_CHECK(!!std::dynamic_pointer_cast<ngraph::snippets::op::Scalar>(arg1.get_node_shared_ptr()), "second argument must be scalar constant.");
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> clone_with_new_inputs(const OutputVector& new_args) const override {
|
||||
check_new_args_count(this, new_args);
|
||||
return std::make_shared<PowerStatic>(new_args.at(0), new_args.at(1), this->get_autob());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace op
|
||||
} // namespace snippets
|
||||
} // namespace ngraph
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <ngraph/function.hpp>
|
||||
#include <openvino/core/model.hpp>
|
||||
#include <ngraph/op/op.hpp>
|
||||
#include <ngraph/rt_info.hpp>
|
||||
#include <ngraph/pass/manager.hpp>
|
||||
@@ -19,7 +19,7 @@ namespace op {
|
||||
|
||||
/**
|
||||
* @interface Subgraph
|
||||
* @brief An operation that is implemented by a function
|
||||
* @brief An operation that is implemented by a model
|
||||
* @ingroup snippets
|
||||
*/
|
||||
class Subgraph : public ngraph::op::Op {
|
||||
@@ -70,9 +70,9 @@ public:
|
||||
using BlockedShape = std::tuple<ngraph::Shape, ngraph::AxisVector, ngraph::element::Type>;
|
||||
using BlockedShapeVector = std::vector<BlockedShape>;
|
||||
|
||||
Subgraph(const OutputVector& args, std::shared_ptr<Function> body);
|
||||
Subgraph(const OutputVector& args, std::shared_ptr<ov::Model> body);
|
||||
|
||||
Subgraph(const NodeVector& args, std::shared_ptr<Function> body);
|
||||
Subgraph(const NodeVector& args, std::shared_ptr<ov::Model> body);
|
||||
|
||||
bool visit_attributes(AttributeVisitor& visitor) override;
|
||||
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
|
||||
std::shared_ptr<Node> clone_with_new_inputs(const OutputVector& inputs) const override;
|
||||
|
||||
std::shared_ptr<Function> get_body() const {
|
||||
std::shared_ptr<ov::Model> get_body() const {
|
||||
return m_body;
|
||||
}
|
||||
|
||||
@@ -91,13 +91,11 @@ public:
|
||||
std::shared_ptr<Subgraph> make_canonical_from_this();
|
||||
|
||||
snippets::Schedule generate(const BlockedShapeVector& output_shapes, const BlockedShapeVector& input_shapes,
|
||||
ngraph::pass::Manager opt = ngraph::pass::Manager());
|
||||
OPENVINO_SUPPRESS_DEPRECATED_START
|
||||
bool evaluate(const HostTensorVector& output_values, const HostTensorVector& input_values) const override;
|
||||
OPENVINO_SUPPRESS_DEPRECATED_END
|
||||
|
||||
ngraph::pass::Manager opt = ngraph::pass::Manager(), const void* compile_params = nullptr);
|
||||
snippets::Schedule generate(const BlockedShapeVector& output_shapes, const BlockedShapeVector& input_shapes,
|
||||
const void* compile_params = nullptr);
|
||||
/// Set a new body for the op; body needs to satisfy requirements on inputs/outputs
|
||||
void set_body(std::shared_ptr<Function> body);
|
||||
void set_body(std::shared_ptr<ov::Model> body);
|
||||
|
||||
// plugin sets generator for a snippet to some specific generator.
|
||||
// it's going to be replaced with Jitters table later
|
||||
@@ -114,7 +112,7 @@ private:
|
||||
void canonicalize(const BlockedShapeVector& output_shapes, const BlockedShapeVector& input_shapes);
|
||||
void convert_to_snippet_dialect();
|
||||
|
||||
std::shared_ptr<Function> m_body;
|
||||
std::shared_ptr<ov::Model> m_body;
|
||||
std::shared_ptr<ngraph::snippets::Generator> m_generator;
|
||||
};
|
||||
|
||||
@@ -124,21 +122,21 @@ static inline std::ostream& operator<<(std::ostream& os, const op::Subgraph::Blo
|
||||
}
|
||||
|
||||
static inline auto is_scalar_constant(const std::shared_ptr<ngraph::Node>& source_output_node) -> bool {
|
||||
return !!ngraph::as_type_ptr<ngraph::opset1::Constant>(source_output_node) &&
|
||||
(source_output_node->get_shape() == ngraph::Shape() || ngraph::shape_size(source_output_node->get_shape()) == 1);
|
||||
return ngraph::is_type<ngraph::opset1::Constant>(source_output_node) && ngraph::shape_size(source_output_node->get_shape()) == 1;
|
||||
};
|
||||
|
||||
static inline auto create_body(std::string name, const ngraph::ResultVector& results, const ngraph::ParameterVector& parameters) ->
|
||||
std::shared_ptr<ngraph::Function> {
|
||||
auto body = std::make_shared<ngraph::Function>(results, parameters, name);
|
||||
std::shared_ptr<ov::Model> {
|
||||
auto body = std::make_shared<ov::Model>(results, parameters, name);
|
||||
return body;
|
||||
};
|
||||
|
||||
static inline auto build_subgraph(const std::shared_ptr<ngraph::Node>& node, const ngraph::OutputVector& inputs, const std::shared_ptr<ngraph::Function>& body)
|
||||
static inline auto build_subgraph(const std::shared_ptr<ngraph::Node>& node, const ngraph::OutputVector& inputs,
|
||||
const std::shared_ptr<ov::Model>& body, const std::string name = "")
|
||||
-> std::shared_ptr<Subgraph>{
|
||||
auto subgraph = std::make_shared<Subgraph>(inputs, body);
|
||||
copy_runtime_info(node, subgraph);
|
||||
subgraph->set_friendly_name(node->get_friendly_name());
|
||||
subgraph->set_friendly_name(name.empty() ? node->get_friendly_name() : name);
|
||||
return subgraph;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
std::shared_ptr<Node> clone_with_new_inputs(const OutputVector& inputs) const override {
|
||||
return std::make_shared<Tile>(region);
|
||||
}
|
||||
const void *compile_params;
|
||||
};
|
||||
|
||||
} // namespace op
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
AssignRegisters() {
|
||||
set_property(ngraph::pass::PassProperty::REQUIRE_STATIC_SHAPE, true);
|
||||
}
|
||||
bool run_on_model(const std::shared_ptr<ngraph::Function>& m) override;
|
||||
bool run_on_model(const std::shared_ptr<ov::Model>& m) override;
|
||||
};
|
||||
|
||||
} // namespace pass
|
||||
|
||||
@@ -12,32 +12,32 @@
|
||||
namespace ngraph {
|
||||
namespace snippets {
|
||||
namespace pass {
|
||||
/*
|
||||
NotSet - default value returned by GetSnippetsNodeType(...) if the node wasn't marked
|
||||
SkippedByPlugin - indicate that snippets can't include this node in subgraph. Can be set by Plugin via SetSnippetsNodeType(...).
|
||||
*/
|
||||
enum class SnippetsNodeType : int64_t {NotSet, SkippedByPlugin};
|
||||
void SetSnippetsNodeType(const std::shared_ptr<Node>&, SnippetsNodeType);
|
||||
SnippetsNodeType GetSnippetsNodeType(const std::shared_ptr<const Node>&);
|
||||
void SetTopologicalOrder(const std::shared_ptr<Node>&, int64_t);
|
||||
int64_t GetTopologicalOrder(const std::shared_ptr<const Node>&);
|
||||
bool AppropriateForSubgraph(const std::shared_ptr<const Node>&);
|
||||
|
||||
/**
|
||||
* @interface StartSubgraph
|
||||
* @brief Matches multiple output loyout-oblivious operations to start a new subgraph
|
||||
* @interface EnumerateNodes
|
||||
* @brief Snippets rely on topological order to avoid creating cyclic dependencies. This transformation sets the order.
|
||||
* @ingroup snippets
|
||||
*/
|
||||
class StartSubgraph: public ngraph::pass::MatcherPass {
|
||||
class EnumerateNodes : public ov::pass::ModelPass {
|
||||
public:
|
||||
NGRAPH_RTTI_DECLARATION;
|
||||
explicit StartSubgraph(bool tokenize_by_node = false);
|
||||
};
|
||||
|
||||
/**
|
||||
* @interface AttachToSubgraph
|
||||
* @brief Matches loyout-oblivious operations with subgraph operation as an input to attech this node into it
|
||||
* @ingroup snippets
|
||||
*/
|
||||
class AttachToSubgraph: public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
NGRAPH_RTTI_DECLARATION;
|
||||
explicit AttachToSubgraph(bool tokenize_by_node = false);
|
||||
EnumerateNodes() : ModelPass() {}
|
||||
bool run_on_model(const std::shared_ptr<ov::Model>&) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @interface TokenizeSnippets
|
||||
* @brief Splits function to subgraphs if possible using rules above
|
||||
* @brief Splits model to subgraphs if possible using rules above
|
||||
* This pass tokenizes topology graph into subgraphs.
|
||||
* Those subgraphs consists of unary or binary layout-oblivious (LO) opetations found in subset 1.
|
||||
* Non-layout-oblivious (NLO) operations operations (called also support in this context) are ignored and become a fullstop in tokenization routine
|
||||
@@ -51,20 +51,16 @@ public:
|
||||
* 1. outputs are collected subgraph (outputs consumed by some other node & subgraph outputs consumed by the node to be merged)
|
||||
* 1. finally current node is replaced with the new subgraph. We cannot use replace_node because multiple nodes are replaced so
|
||||
* make the replacement manually by redirecting ports
|
||||
* Input subgraph is prefented from visiting twice if more than one output of it consumed by currently considered node
|
||||
* New subgraph is introduced, if there is a loop introduced
|
||||
* New subgraph is introduced, if number of inputs and outputs exceeds 7 due to scheduling limitation
|
||||
* New subgraph is introduced, if multiple outputs of merged nodes are not broadcastable to each other (equality of all outputs is too much on the other hand)
|
||||
* Scalar constants are placed as is into subgraph due to optimization purpose
|
||||
* @ingroup snippets
|
||||
*/
|
||||
class TokenizeSnippets: public ngraph::pass::GraphRewrite {
|
||||
class TokenizeSnippets: public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
NGRAPH_RTTI_DECLARATION;
|
||||
TokenizeSnippets(bool tokenize_by_node = false) {
|
||||
add_matcher<ngraph::snippets::pass::StartSubgraph>(tokenize_by_node);
|
||||
add_matcher<ngraph::snippets::pass::AttachToSubgraph>(tokenize_by_node);
|
||||
}
|
||||
explicit TokenizeSnippets();
|
||||
};
|
||||
|
||||
} // namespace pass
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace pass {
|
||||
/**
|
||||
* @interface InsertLoad
|
||||
* @brief Inserts explicit load instruction after each parameter.
|
||||
* The pass is used to convert function to a canonical form for code generation
|
||||
* The pass is used to convert model to a canonical form for code generation
|
||||
* @ingroup snippets
|
||||
*/
|
||||
class InsertLoad: public ngraph::pass::MatcherPass {
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
/**
|
||||
* @interface InsertStore
|
||||
* @brief Inserts explicit store instruction before each result.
|
||||
* The pass is used to convert function to a canonical form for code generation
|
||||
* The pass is used to convert model to a canonical form for code generation
|
||||
* @ingroup snippets
|
||||
*/
|
||||
class InsertStore: public ngraph::pass::MatcherPass {
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace pass {
|
||||
/**
|
||||
* @interface InsertMoveBroadcast
|
||||
* @brief Inserts explicit MoveBroadcast instruction if broadcasting by most warying dimension is needed.
|
||||
* The pass is used to convert function to a canonical form for code generation
|
||||
* The pass is used to convert model to a canonical form for code generation
|
||||
* @ingroup snippets
|
||||
*/
|
||||
class InsertMoveBroadcast: public ngraph::pass::MatcherPass {
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace pass {
|
||||
/**
|
||||
* @interface LoadMoveBroadcastToBroadcastLoad
|
||||
* @brief Fuses consecutive Load and MoveBroadcast into a single load insctruction.
|
||||
* The pass is used to convert function to a canonical form for code generation
|
||||
* The pass is used to convert model to a canonical form for code generation
|
||||
* @ingroup snippets
|
||||
*/
|
||||
class LoadMoveBroadcastToBroadcastLoad: public ngraph::pass::MatcherPass {
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace pass {
|
||||
|
||||
/**
|
||||
* @interface ReplaceLoadsWithScalarLoads
|
||||
* @brief Replases vector loads with scalar versions.
|
||||
* The pass is used to cange alement type of function in a canonical form vector to scalar.
|
||||
* @brief Replaces vector loads with scalar versions.
|
||||
* The pass is used to cange element type of function in a canonical form vector to scalar.
|
||||
* Used for tail generation
|
||||
* @ingroup snippets
|
||||
*/
|
||||
@@ -25,8 +25,8 @@ public:
|
||||
|
||||
/**
|
||||
* @interface ReplaceStoresWithScalarStores
|
||||
* @brief Replases vector stores with scalar versions.
|
||||
* The pass is used to cange alement type of function in a canonical form vector to scalar.
|
||||
* @brief Replaces vector stores with scalar versions.
|
||||
* The pass is used to cange element type of model in a canonical form vector to scalar.
|
||||
* Used for tail generation
|
||||
* @ingroup snippets
|
||||
*/
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
#include "op/blockedparameter.hpp"
|
||||
#include "op/broadcastload.hpp"
|
||||
#include "op/broadcastmove.hpp"
|
||||
#include "op/kernel.hpp"
|
||||
#include "op/load.hpp"
|
||||
#include "op/nop.hpp"
|
||||
#include "op/scalar.hpp"
|
||||
#include "op/scalarload.hpp"
|
||||
#include "op/scalarstore.hpp"
|
||||
#include "op/staticpower.hpp"
|
||||
#include "op/powerstatic.hpp"
|
||||
#include "op/store.hpp"
|
||||
#include "op/tile.hpp"
|
||||
#include "op/vectorload.hpp"
|
||||
#include "op/vectorstore.hpp"
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
#include "snippets/pass/insert_load_store.hpp"
|
||||
#include "snippets/op/tile.hpp"
|
||||
#include "snippets/op/kernel.hpp"
|
||||
#include <snippets/itt.hpp>
|
||||
|
||||
#include <ngraph/pass/manager.hpp>
|
||||
|
||||
auto ngraph::snippets::getRegisters(std::shared_ptr<ngraph::Node>& n) -> ngraph::snippets::RegInfo {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::getRegisters")
|
||||
auto rt = n->get_rt_info();
|
||||
|
||||
// ToDo: change to reg_t
|
||||
@@ -27,7 +29,7 @@ auto ngraph::snippets::getRegisters(std::shared_ptr<ngraph::Node>& n) -> ngraph:
|
||||
for (auto input : n->inputs()) {
|
||||
auto rt = input.get_source_output().get_node_shared_ptr()->get_rt_info();
|
||||
auto it_rt = rt.find("reginfo");
|
||||
if (it_rt == rt.end()) {
|
||||
if (it_rt != rt.end()) {
|
||||
for (auto reg : it_rt->second.as<std::vector<size_t>>()) {
|
||||
rin.push_back(reg);
|
||||
}
|
||||
@@ -36,53 +38,69 @@ auto ngraph::snippets::getRegisters(std::shared_ptr<ngraph::Node>& n) -> ngraph:
|
||||
return std::make_pair(rin, rout);
|
||||
}
|
||||
|
||||
ngraph::snippets::code ngraph::snippets::Generator::generate(std::shared_ptr<ngraph::Function>& f) const {
|
||||
ngraph::snippets::code ngraph::snippets::Generator::generate(std::shared_ptr<ov::Model>& m,
|
||||
const void* compile_params) const {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::Generator::generate")
|
||||
if (!target->is_supported())
|
||||
throw ngraph_error("unsupported architecture for code genration");
|
||||
|
||||
auto params = f->get_parameters();
|
||||
auto results = f->get_results();
|
||||
auto nptrs = results.size() + params.size();
|
||||
|
||||
if (nptrs > 7) {
|
||||
throw ngraph_error("snippet signature should not exceed 7 arguments. got " + std::to_string(nptrs));
|
||||
}
|
||||
auto params = m->get_parameters();
|
||||
auto results = m->get_results();
|
||||
auto in = params.size();
|
||||
auto out = results.size();
|
||||
auto nptrs = in + out;
|
||||
|
||||
OV_ITT_TASK_CHAIN(GENERATE, ngraph::pass::itt::domains::SnippetsTransform, "Snippets::Generator", "::VectorTile")
|
||||
// vector tile
|
||||
std::vector<std::pair<std::shared_ptr<ngraph::snippets::Emitter>, ngraph::snippets::RegInfo>> lowered;
|
||||
for (auto n : f->get_ordered_ops()) {
|
||||
for (auto n : m->get_ordered_ops()) {
|
||||
lowered.push_back(std::make_pair(target->get(n->get_type_info())(n), ngraph::snippets::getRegisters(n)));
|
||||
}
|
||||
OV_ITT_TASK_NEXT(GENERATE, "::ScalarTile")
|
||||
|
||||
// scalar tile
|
||||
auto f_scalar = ngraph::clone_function(*f.get());
|
||||
ngraph::pass::Manager m;
|
||||
m.register_pass<ngraph::snippets::pass::ReplaceLoadsWithScalarLoads>();
|
||||
m.register_pass<ngraph::snippets::pass::ReplaceStoresWithScalarStores>();
|
||||
m.run_passes(f_scalar);
|
||||
|
||||
auto m_scalar = ov::clone_model(*m.get());
|
||||
ngraph::pass::Manager mng;
|
||||
mng.register_pass<ngraph::snippets::pass::ReplaceLoadsWithScalarLoads>();
|
||||
mng.register_pass<ngraph::snippets::pass::ReplaceStoresWithScalarStores>();
|
||||
mng.run_passes(m_scalar);
|
||||
OV_ITT_TASK_NEXT(GENERATE, "::ScalarTile_get")
|
||||
std::vector<std::pair<std::shared_ptr<Emitter>, RegInfo>> scalar_lowered;
|
||||
for (auto n : f_scalar->get_ordered_ops()) {
|
||||
for (auto n : m_scalar->get_ordered_ops()) {
|
||||
scalar_lowered.push_back(std::make_pair(target->get(n->get_type_info())(n), ngraph::snippets::getRegisters(n)));
|
||||
}
|
||||
OV_ITT_TASK_NEXT(GENERATE, "::Tiles1D")
|
||||
|
||||
// wrapping into tiles
|
||||
std::vector<std::pair<std::shared_ptr<Emitter>, RegInfo>> tiles;
|
||||
tiles.push_back(std::make_pair(target->get(ngraph::snippets::op::Tile::get_type_info_static())(
|
||||
std::make_shared<ngraph::snippets::op::Tile>(lowered)),
|
||||
std::make_pair(std::vector<size_t>({target->get_lanes(), nptrs}), std::vector<size_t>{})));
|
||||
tiles.push_back(std::make_pair(target->get(ngraph::snippets::op::Tile::get_type_info_static())(
|
||||
std::make_shared<ngraph::snippets::op::Tile>(scalar_lowered)),
|
||||
std::make_pair(std::vector<size_t>{{1, nptrs}}, std::vector<size_t>{})));
|
||||
// wrapping into tiles1D
|
||||
std::vector<std::pair<std::shared_ptr<Emitter>, RegInfo>> tiles1D;
|
||||
auto tile = std::make_shared<ngraph::snippets::op::Tile>(lowered);
|
||||
tile->compile_params = compile_params;
|
||||
tiles1D.push_back(std::make_pair(target->get(ngraph::snippets::op::Tile::get_type_info_static())(tile),
|
||||
std::make_pair(std::vector<size_t>({target->get_lanes(), 0, nptrs, 1}), std::vector<size_t>{})));
|
||||
tile = std::make_shared<ngraph::snippets::op::Tile>(scalar_lowered);
|
||||
tile->compile_params = compile_params;
|
||||
tiles1D.push_back(std::make_pair(target->get(ngraph::snippets::op::Tile::get_type_info_static())(tile),
|
||||
std::make_pair(std::vector<size_t>{{1, target->get_lanes(), nptrs, 1}}, std::vector<size_t>{})));
|
||||
|
||||
OV_ITT_TASK_NEXT(GENERATE, "::Tiles2D")
|
||||
// wrapping into tiles2D
|
||||
std::vector<std::pair<std::shared_ptr<Emitter>, RegInfo>> tiles2D;
|
||||
tile = std::make_shared<ngraph::snippets::op::Tile>(tiles1D);
|
||||
tile->compile_params = compile_params;
|
||||
tiles2D.push_back(std::make_pair(target->get(ngraph::snippets::op::Tile::get_type_info_static())(tile),
|
||||
std::make_pair(std::vector<size_t>({1, 0, nptrs, 0}), std::vector<size_t>{})));
|
||||
|
||||
OV_ITT_TASK_NEXT(GENERATE, "::EmitCode")
|
||||
// emission
|
||||
std::shared_ptr<Emitter> kernel = target->get(ngraph::snippets::op::Kernel::get_type_info_static())(std::make_shared<ngraph::snippets::op::Kernel>(tiles));
|
||||
kernel->emit_code({params.size(), results.size()}, {});
|
||||
|
||||
auto tiles2DKernel = std::make_shared<ngraph::snippets::op::Kernel>(tiles2D);
|
||||
tiles2DKernel->compile_params = compile_params;
|
||||
std::shared_ptr<Emitter> kernel = target->get(ngraph::snippets::op::Kernel::get_type_info_static())(tiles2DKernel);
|
||||
kernel->emit_code({in, out}, {});
|
||||
OV_ITT_TASK_NEXT(GENERATE, "::EmitData")
|
||||
lowered.insert(lowered.end(), scalar_lowered.begin(), scalar_lowered.end());
|
||||
for (auto& op : lowered) {
|
||||
op.first->emit_data();
|
||||
}
|
||||
|
||||
OV_ITT_TASK_NEXT(GENERATE, "::GetSnippet")
|
||||
return target->get_snippet();
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "itt.hpp"
|
||||
#include <snippets/itt.hpp>
|
||||
|
||||
#include "snippets/op/broadcastload.hpp"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "itt.hpp"
|
||||
#include <snippets/itt.hpp>
|
||||
|
||||
#include "snippets/op/broadcastmove.hpp"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "itt.hpp"
|
||||
#include <snippets/itt.hpp>
|
||||
|
||||
#include "snippets/op/load.hpp"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "itt.hpp"
|
||||
#include <snippets/itt.hpp>
|
||||
|
||||
#include "snippets/op/scalarstore.hpp"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "itt.hpp"
|
||||
#include <snippets/itt.hpp>
|
||||
#include "remarks.hpp"
|
||||
|
||||
#include "snippets/op/subgraph.hpp"
|
||||
@@ -25,21 +25,22 @@ void snippets::op::Subgraph::set_generator(std::shared_ptr<ngraph::snippets::Gen
|
||||
m_generator = generator;
|
||||
}
|
||||
|
||||
snippets::op::Subgraph::Subgraph(const OutputVector& args, std::shared_ptr<Function> body)
|
||||
snippets::op::Subgraph::Subgraph(const OutputVector& args, std::shared_ptr<ov::Model> body)
|
||||
: Op(args), m_body(body), m_generator(nullptr) {
|
||||
constructor_validate_and_infer_types();
|
||||
}
|
||||
|
||||
snippets::op::Subgraph::Subgraph(const NodeVector& args, std::shared_ptr<Function> body)
|
||||
snippets::op::Subgraph::Subgraph(const NodeVector& args, std::shared_ptr<ov::Model> body)
|
||||
: Subgraph(as_output_vector(args), body) {}
|
||||
|
||||
std::shared_ptr<Node> snippets::op::Subgraph::clone_with_new_inputs(const OutputVector& inputs) const {
|
||||
INTERNAL_OP_SCOPE(Subgraph);
|
||||
return make_shared<Subgraph>(inputs, ngraph::clone_function(*m_body.get()));
|
||||
return make_shared<Subgraph>(inputs, ov::clone_model(*m_body.get()));
|
||||
}
|
||||
|
||||
void snippets::op::Subgraph::validate_and_infer_types() {
|
||||
INTERNAL_OP_SCOPE(Subgraph);
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::validate_and_infer_types")
|
||||
ngraph::ParameterVector old_parameters;
|
||||
for (auto op : m_body->get_parameters()) {
|
||||
old_parameters.push_back(op);
|
||||
@@ -65,24 +66,24 @@ bool snippets::op::Subgraph::visit_attributes(AttributeVisitor& visitor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto snippets::op::Subgraph::wrap_node_as_subgraph(const std::shared_ptr<ngraph::Node>& node) -> std::shared_ptr<op::Subgraph> {
|
||||
auto snippets::op::Subgraph::wrap_node_as_subgraph(const std::shared_ptr<ov::Node>& node) -> std::shared_ptr<op::Subgraph> {
|
||||
INTERNAL_OP_SCOPE(Subgraph);
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::wrap_node_as_subgraph")
|
||||
ngraph::ParameterVector body_parameters;
|
||||
ngraph::OutputVector body_inputs;
|
||||
|
||||
ngraph::OutputVector subgraph_inputs;
|
||||
|
||||
for (auto input : node->inputs()) {
|
||||
auto source_output = input.get_source_output();
|
||||
if (is_scalar_constant(source_output.get_node_shared_ptr())) {
|
||||
body_inputs.push_back(source_output);
|
||||
for (const auto& input : node->input_values()) {
|
||||
if (is_scalar_constant(input.get_node_shared_ptr())) {
|
||||
body_inputs.push_back(input);
|
||||
} else {
|
||||
auto parameter = std::make_shared<ngraph::opset1::Parameter>(input.get_element_type(), input.get_partial_shape());
|
||||
body_parameters.push_back(parameter);
|
||||
body_parameters.back()->set_friendly_name(source_output.get_node()->get_friendly_name());
|
||||
body_parameters.back()->set_friendly_name(input.get_node()->get_friendly_name());
|
||||
body_inputs.push_back(parameter->output(0));
|
||||
|
||||
subgraph_inputs.push_back(source_output);
|
||||
subgraph_inputs.push_back(input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +94,9 @@ auto snippets::op::Subgraph::wrap_node_as_subgraph(const std::shared_ptr<ngraph:
|
||||
throw ngraph::ngraph_error("original node outputs size and extracted subgraph node outputs size doesn't much");
|
||||
}
|
||||
|
||||
// Clear the node dependencies so graph::topological_sort will not find any extra ops in get_ordered_ops()
|
||||
// This is needed so the model body will be created correctly
|
||||
body_node->clear_control_dependencies();
|
||||
ngraph::ResultVector body_results;
|
||||
for (auto output : node->outputs()) {
|
||||
body_results.push_back(std::make_shared<ngraph::opset1::Result>(body_node->output(output.get_index())));
|
||||
@@ -118,7 +122,7 @@ std::shared_ptr<snippets::op::Subgraph> snippets::op::Subgraph::make_canonical_f
|
||||
for (auto input : this->input_values()) {
|
||||
subgraph_node_inputs.push_back(input);
|
||||
}
|
||||
auto new_body = ngraph::clone_function(*this->get_body().get());
|
||||
auto new_body = ov::clone_model(*this->get_body().get());
|
||||
auto snippet = std::make_shared<op::Subgraph>(subgraph_node_inputs, new_body);
|
||||
ngraph::copy_runtime_info(this->shared_from_this(), snippet);
|
||||
snippet->set_friendly_name(this->get_friendly_name());
|
||||
@@ -129,15 +133,16 @@ std::shared_ptr<snippets::op::Subgraph> snippets::op::Subgraph::make_canonical_f
|
||||
|
||||
// We also can think of canonization as of pass to copy original subgraph and transforming it to canonical form suitable for code generation
|
||||
// pass actual parameters and results shapes to generate for as well as channel mapping,
|
||||
// we need to distinguish between 5d tensors that represents <N, C, H, W, c> and <N, C, D, H, W> somehow like locked dimensions
|
||||
// ngraph::AxisVector to code
|
||||
// Todo: we need to distinguish between 5d tensors that represents <N, C, H, W, c> and <N, C, D, H, W> somehow like locked dimensions
|
||||
// ngraph::AxisVector to code
|
||||
void snippets::op::Subgraph::canonicalize(const BlockedShapeVector& output_shapes, const BlockedShapeVector& input_shapes) {
|
||||
INTERNAL_OP_SCOPE(Subgraph);
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::canonicalize")
|
||||
NODE_VALIDATION_CHECK(this, input_shapes.size() == m_body->get_parameters().size(),
|
||||
"Number of parameters for snippet doesn't much passed to generate method: ", input_shapes.size(), " vs ", m_body->get_parameters().size(), ".");
|
||||
"Number of parameters for snippet doesn't match passed to generate method: ", input_shapes.size(), " vs ", m_body->get_parameters().size(), ".");
|
||||
|
||||
NODE_VALIDATION_CHECK(this, output_shapes.size() == m_body->get_results().size(),
|
||||
"number of results for snippet doesn't much passed to generate method: ", output_shapes.size(), " vs ", m_body->get_results().size(), ".");
|
||||
"number of results for snippet doesn't match passed to generate method: ", output_shapes.size(), " vs ", m_body->get_results().size(), ".");
|
||||
|
||||
// replace only constants which are actually should be represented as scalars during code generation and probably move this step a bit later
|
||||
for (auto op : m_body->get_ordered_ops()) {
|
||||
@@ -149,18 +154,6 @@ void snippets::op::Subgraph::canonicalize(const BlockedShapeVector& output_shape
|
||||
}
|
||||
}
|
||||
|
||||
// repalace power with power static
|
||||
for (auto op : m_body->get_ordered_ops()) {
|
||||
if (auto power = ngraph::as_type_ptr<opset1::Power>(op)) {
|
||||
if (ngraph::as_type_ptr<snippets::op::Scalar>(power->input(1).get_node()->shared_from_this())) {
|
||||
auto power_static = std::make_shared<snippets::op::PowerStatic>(
|
||||
power->input(0).get_source_output(), power->input(1).get_source_output(), power->get_autob());
|
||||
power_static->set_friendly_name(power->get_friendly_name());
|
||||
ngraph::copy_runtime_info(power, power_static);
|
||||
ngraph::replace_node(power, power_static);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// it should be in subgraph node to be aligned with internal and external parameter list, but adding this for testing
|
||||
@@ -193,6 +186,7 @@ void snippets::op::Subgraph::canonicalize(const BlockedShapeVector& output_shape
|
||||
|
||||
void snippets::op::Subgraph::convert_to_snippet_dialect() {
|
||||
INTERNAL_OP_SCOPE(Subgraph);
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::convert_to_snippet_dialect")
|
||||
ngraph::pass::Manager manager;
|
||||
manager.register_pass<snippets::pass::InsertLoad>();
|
||||
manager.register_pass<snippets::pass::InsertStore>();
|
||||
@@ -201,24 +195,54 @@ void snippets::op::Subgraph::convert_to_snippet_dialect() {
|
||||
manager.run_passes(m_body);
|
||||
}
|
||||
|
||||
snippets::Schedule snippets::op::Subgraph::generate(const BlockedShapeVector& output_shapes, const BlockedShapeVector& input_shapes,
|
||||
ngraph::pass::Manager opt) {
|
||||
snippets::Schedule snippets::op::Subgraph::generate(const BlockedShapeVector& output_shapes,
|
||||
const BlockedShapeVector& input_shapes,
|
||||
const void* compile_params) {
|
||||
return generate(output_shapes, input_shapes, ngraph::pass::Manager(), compile_params);
|
||||
}
|
||||
|
||||
snippets::Schedule snippets::op::Subgraph::generate(const BlockedShapeVector& output_shapes,
|
||||
const BlockedShapeVector& input_shapes,
|
||||
ngraph::pass::Manager opt,
|
||||
const void* compile_params) {
|
||||
INTERNAL_OP_SCOPE(Subgraph);
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::op::generate")
|
||||
NGRAPH_CHECK(m_generator != nullptr, "generate is called while generator is not set");
|
||||
|
||||
canonicalize(output_shapes, input_shapes);
|
||||
|
||||
// Todo: ngraph::pass::Manager introduces appreciable overheads, especially while used on small graphs.
|
||||
// So don't wrap this transformation as a MatcherPass, but rewrite convert_to_snippet_dialect() as a
|
||||
// for loop to improve first-inference time.
|
||||
// replace power with power static
|
||||
|
||||
for (auto op : m_body->get_ordered_ops()) {
|
||||
if (ov::is_type<opset1::Power>(op) &&
|
||||
ov::is_type<snippets::op::Scalar>(op->get_input_node_shared_ptr(1)) &&
|
||||
ov::shape_size(op->get_input_shape(1)) == 1) {
|
||||
auto power = ov::as_type_ptr<opset1::Power>(op);
|
||||
auto scalar = ov::as_type_ptr<snippets::op::Scalar>(op->get_input_node_shared_ptr(1));
|
||||
auto value = scalar->cast_vector<float>()[0];;
|
||||
auto power_static = std::make_shared<snippets::op::PowerStatic>(power->input(0).get_source_output(), value);
|
||||
power_static->set_friendly_name(power->get_friendly_name());
|
||||
ngraph::copy_runtime_info(power, power_static);
|
||||
ngraph::replace_node(power, power_static);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
convert_to_snippet_dialect();
|
||||
opt.run_passes(m_body);
|
||||
|
||||
// generation flow
|
||||
snippets::pass::AssignRegisters().run_on_model(m_body);
|
||||
|
||||
// shedule generation should go here and be target agnostic
|
||||
// schedule generation should go here and be target agnostic
|
||||
|
||||
// actual code emission
|
||||
ngraph::snippets::code ptr = m_generator->generate(m_body);
|
||||
ngraph::snippets::code ptr = m_generator->generate(m_body, compile_params);
|
||||
|
||||
// chack that body doesnt have constants for scheduling
|
||||
// check that body doesn't have constants for scheduling
|
||||
std::vector<std::shared_ptr<opset1::Constant>> constants;
|
||||
for (auto op : m_body->get_ordered_ops()) {
|
||||
if (auto constant = ov::as_type_ptr<opset1::Constant>(op)) {
|
||||
@@ -227,7 +251,7 @@ snippets::Schedule snippets::op::Subgraph::generate(const BlockedShapeVector& ou
|
||||
}
|
||||
}
|
||||
}
|
||||
NGRAPH_CHECK(!constants.size(), "External constants detected. Snippet is illigal for sheduling");
|
||||
NGRAPH_CHECK(!constants.size(), "External constants detected. Snippet is illigal for scheduling");
|
||||
|
||||
// check resulting shapes are broadcastable to each other so can be scheduled
|
||||
Shape work_size = m_body->output(0).get_shape();
|
||||
@@ -252,13 +276,6 @@ snippets::Schedule snippets::op::Subgraph::generate(const BlockedShapeVector& ou
|
||||
return {work_size, false /*canBeLinearized*/, ptr};
|
||||
}
|
||||
|
||||
bool snippets::op::Subgraph::evaluate(const HostTensorVector& outputs, const HostTensorVector& inputs) const {
|
||||
INTERNAL_OP_SCOPE(Subgraph);
|
||||
OPENVINO_SUPPRESS_DEPRECATED_START
|
||||
return m_body->evaluate(outputs, inputs);
|
||||
OPENVINO_SUPPRESS_DEPRECATED_END
|
||||
}
|
||||
|
||||
void snippets::op::Subgraph::print() const {
|
||||
INTERNAL_OP_SCOPE(Subgraph);
|
||||
remark(13) << "subgraph " << this->get_friendly_name() << " "
|
||||
@@ -285,7 +302,7 @@ void snippets::op::Subgraph::print() const {
|
||||
|
||||
void snippets::op::Subgraph::print_statistics(bool verbose) {
|
||||
INTERNAL_OP_SCOPE(Subgraph);
|
||||
auto getNodeInventory = [](std::shared_ptr<ngraph::Node> n) -> size_t {
|
||||
auto getNodeInventory = [](std::shared_ptr<ov::Node> n) -> size_t {
|
||||
size_t total = 0;
|
||||
|
||||
for (auto input : n->inputs()) {
|
||||
@@ -307,7 +324,7 @@ void snippets::op::Subgraph::print_statistics(bool verbose) {
|
||||
return total;
|
||||
};
|
||||
|
||||
auto getFunctionInventory = [getNodeInventory](std::shared_ptr<ngraph::Function> f) -> size_t {
|
||||
auto getModelInventory = [getNodeInventory](std::shared_ptr<ov::Model> f) -> size_t {
|
||||
size_t total = 0;
|
||||
for (auto op : f->get_ordered_ops()) {
|
||||
// Results and parameters are artificially introduced,
|
||||
@@ -322,7 +339,7 @@ void snippets::op::Subgraph::print_statistics(bool verbose) {
|
||||
return total;
|
||||
};
|
||||
|
||||
auto countConstants = [](std::shared_ptr<ngraph::Function> f) -> size_t {
|
||||
auto countConstants = [](std::shared_ptr<ov::Model> f) -> size_t {
|
||||
size_t count = 0;
|
||||
for (auto op : f->get_ordered_ops()) {
|
||||
count += !!ngraph::as_type_ptr<ngraph::opset1::Constant>(op) ? 1 : 0;
|
||||
@@ -338,7 +355,7 @@ void snippets::op::Subgraph::print_statistics(bool verbose) {
|
||||
<< ";" << body->get_parameters().size()
|
||||
<< ";" << body->get_results().size()
|
||||
<< ";" << countConstants(body)
|
||||
<< ";" << getFunctionInventory(body)
|
||||
<< ";" << getModelInventory(body)
|
||||
<< ";" << getNodeInventory(this->shared_from_this()) << std::endl;
|
||||
|
||||
if (verbose) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
|
||||
// #include <openvino/cc/selective_build.h>
|
||||
#include "itt.hpp"
|
||||
#include <snippets/itt.hpp>
|
||||
#include "remarks.hpp"
|
||||
|
||||
#include "snippets/pass/assign_registers.hpp"
|
||||
@@ -13,8 +13,9 @@
|
||||
|
||||
#include <iterator>
|
||||
|
||||
bool ngraph::snippets::pass::AssignRegisters::run_on_model(const std::shared_ptr<Function>& f) {
|
||||
bool ngraph::snippets::pass::AssignRegisters::run_on_model(const std::shared_ptr<ov::Model>& f) {
|
||||
RUN_ON_FUNCTION_SCOPE(AssignRegisters);
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::op::AssignRegisters")
|
||||
int reg64_tmp_start { 8 }; // R8, R9, R10, R11, R12, R13, R14, R15 inputs+outputs+1
|
||||
using Reg = size_t;
|
||||
auto ops = f->get_ordered_ops();
|
||||
@@ -143,7 +144,7 @@ bool ngraph::snippets::pass::AssignRegisters::run_on_model(const std::shared_ptr
|
||||
|
||||
for (auto n : f->get_ordered_ops()) {
|
||||
auto& rt = n->get_rt_info();
|
||||
// nothing to do for function signature
|
||||
// nothing to do for model signature
|
||||
if (std::dynamic_pointer_cast<opset1::Parameter>(n) || std::dynamic_pointer_cast<opset1::Result>(n)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
|
||||
#include "remarks.hpp"
|
||||
#include "itt.hpp"
|
||||
#include <snippets/itt.hpp>
|
||||
|
||||
#include "snippets/pass/collapse_subgraph.hpp"
|
||||
#include "snippets/op/subgraph.hpp"
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <ngraph/opsets/opset1.hpp>
|
||||
#include <ngraph/rt_info.hpp>
|
||||
#include <ngraph/op/loop.hpp>
|
||||
#include "transformations/utils/utils.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -18,27 +19,29 @@
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <numeric>
|
||||
#include <climits>
|
||||
|
||||
NGRAPH_RTTI_DEFINITION(ngraph::snippets::pass::StartSubgraph, "CollapseSubgraph", 0);
|
||||
NGRAPH_RTTI_DEFINITION(ngraph::snippets::pass::AttachToSubgraph, "CollapseSubgraph", 0);
|
||||
NGRAPH_RTTI_DEFINITION(ngraph::snippets::pass::TokenizeSnippets, "CollapseSubgraph", 0);
|
||||
NGRAPH_RTTI_DEFINITION(ngraph::snippets::pass::TokenizeSnippets, "Snippets::TokenizeSnippets", 0);
|
||||
NGRAPH_RTTI_DEFINITION(ngraph::snippets::pass::EnumerateNodes, "Snippets::EnumerateNodes", 0);
|
||||
|
||||
namespace ngraph {
|
||||
namespace snippets {
|
||||
namespace pass {
|
||||
|
||||
using namespace ngraph;
|
||||
using namespace snippets;
|
||||
|
||||
namespace {
|
||||
|
||||
auto outputs_are_not_broadcastable(const std::shared_ptr<ngraph::Node>& node) -> bool {
|
||||
auto outputs_are_not_broadcastable(const std::shared_ptr<const Node>& node) -> bool {
|
||||
auto outputs = node->outputs();
|
||||
auto find_smallest_output_shape = [](const std::vector<ngraph::Output<ngraph::Node>>& outputs) -> ngraph::Shape {
|
||||
auto find_smallest_output_shape = [](const std::vector<Output<const Node>>& outputs) -> Shape {
|
||||
return std::accumulate(std::begin(outputs), std::end(outputs), ngraph::Shape(outputs.begin()->get_shape()),
|
||||
[](ngraph::Shape other_shape, ngraph::Output<ngraph::Node> output){
|
||||
return ngraph::shape_size(output.get_shape()) < ngraph::shape_size(other_shape) ? output.get_shape() : other_shape;
|
||||
[](Shape& other_shape, const Output<const Node>& output){
|
||||
return shape_size(output.get_shape()) < shape_size(other_shape) ? output.get_shape() : other_shape;
|
||||
});
|
||||
};
|
||||
auto ref_shape = find_smallest_output_shape(outputs);
|
||||
|
||||
auto check_shapes_broadcastable = [ref_shape](const ngraph::Output<ngraph::Node>& output) -> bool {
|
||||
auto check_shapes_broadcastable = [ref_shape](const Output<const Node>& output) -> bool {
|
||||
auto other_shape = output.get_shape();
|
||||
|
||||
if (other_shape.size() != ref_shape.size()) {
|
||||
@@ -46,300 +49,316 @@ auto outputs_are_not_broadcastable(const std::shared_ptr<ngraph::Node>& node) ->
|
||||
}
|
||||
|
||||
return std::inner_product(std::begin(other_shape), std::end(other_shape), std::begin(ref_shape), true,
|
||||
std::logical_and<bool>(), [](ngraph::Shape::value_type lsh, ngraph::Shape::value_type rsh){
|
||||
std::logical_and<bool>(), [](Shape::value_type lsh, Shape::value_type rsh){
|
||||
return rsh == 1 || lsh == rsh;
|
||||
});
|
||||
};
|
||||
|
||||
return std::find_if_not(std::begin(outputs), std::end(outputs), check_shapes_broadcastable) != std::end(outputs);
|
||||
};
|
||||
}
|
||||
|
||||
auto has_cycles_of_dependencies(const std::vector<std::set<ngraph::Input<ngraph::Node>>>& results,
|
||||
const std::vector<ngraph::Input<ngraph::Node>>& inputs) -> bool {
|
||||
auto BFS_from_to = [](ngraph::Node* from, ngraph::Node* to) -> bool {
|
||||
std::unordered_set<ngraph::Node*> visited;
|
||||
std::queue<ngraph::Node*> stack;
|
||||
stack.push(from);
|
||||
|
||||
while (stack.size() > 0) {
|
||||
ngraph::Node* curr = stack.front();
|
||||
visited.insert(curr);
|
||||
|
||||
stack.pop();
|
||||
|
||||
if (curr != to) {
|
||||
for (const auto& next : curr->get_users()) {
|
||||
if (visited.count(next.get()) == 0) {
|
||||
stack.push(next.get());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
auto is_layout_oblivious(const std::shared_ptr<const Node> &n) -> bool {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::is_layout_oblivious")
|
||||
auto is_layout_oblivious_binary = [](const std::shared_ptr<const Node> &n) -> bool {
|
||||
return ov::is_type<opset1::Add>(n)
|
||||
|| ov::is_type<opset1::Divide>(n)
|
||||
|| ov::is_type<opset1::Equal>(n)
|
||||
|| ov::is_type<opset1::FloorMod>(n)
|
||||
|| ov::is_type<opset1::Greater>(n)
|
||||
|| ov::is_type<opset1::GreaterEqual>(n)
|
||||
|| ov::is_type<opset1::Less>(n)
|
||||
|| ov::is_type<opset1::LessEqual>(n)
|
||||
|| ov::is_type<opset1::LogicalAnd>(n)
|
||||
|| ov::is_type<opset1::LogicalOr>(n)
|
||||
|| ov::is_type<opset1::LogicalXor>(n)
|
||||
|| ov::is_type<opset1::Maximum>(n)
|
||||
|| ov::is_type<opset1::Minimum>(n)
|
||||
|| ov::is_type<opset1::Mod>(n)
|
||||
|| ov::is_type<opset1::Multiply>(n)
|
||||
|| ov::is_type<opset1::NotEqual>(n)
|
||||
|| ov::is_type<opset1::PRelu>(n)
|
||||
|| ov::is_type<opset1::Power>(n)
|
||||
|| ov::is_type<opset1::SquaredDifference>(n)
|
||||
|| ov::is_type<opset1::Subtract>(n)
|
||||
|| ov::is_type<opset1::Xor>(n);
|
||||
};
|
||||
|
||||
for (auto& result : results) {
|
||||
for (auto& user : result) {
|
||||
for (auto& input : inputs) {
|
||||
auto source = input.get_source_output().get_node();
|
||||
auto containsLoop = BFS_from_to(user.get_node(), source);
|
||||
auto is_layout_oblivious_unary = [](const std::shared_ptr<const Node> &n) -> bool {
|
||||
return ov::is_type<opset1::Abs>(n)
|
||||
|| ov::is_type<opset1::Clamp>(n)
|
||||
|| ov::is_type<opset1::Elu>(n)
|
||||
|| ov::is_type<opset1::Erf>(n)
|
||||
|| ov::is_type<opset1::Exp>(n)
|
||||
|| ov::is_type<opset1::LogicalNot>(n)
|
||||
|| ov::is_type<opset1::Negative>(n)
|
||||
|| ov::is_type<opset1::Relu>(n)
|
||||
|| ov::is_type<opset1::Sigmoid>(n)
|
||||
|| ov::is_type<opset1::Sqrt>(n)
|
||||
|| ov::is_type<opset1::Tanh>(n)
|
||||
|| ov::is_type<ngraph::op::v0::Gelu>(n)
|
||||
|| ov::is_type<ngraph::op::v7::Gelu>(n)
|
||||
|| ov::is_type<ngraph::op::v4::HSwish>(n);
|
||||
};
|
||||
return is_layout_oblivious_unary(n) || is_layout_oblivious_binary(n);
|
||||
}
|
||||
|
||||
remark(1) << "checking path from "
|
||||
<< user.get_node()->get_friendly_name()
|
||||
<< " to " << source->get_friendly_name()
|
||||
<< " resulted in " << containsLoop << std::endl;
|
||||
|
||||
if (containsLoop) {
|
||||
return true;
|
||||
}
|
||||
auto has_supported_in_out(const std::shared_ptr<const Node> &n) -> bool {
|
||||
auto supported = [](descriptor::Tensor& t) -> bool {
|
||||
return t.get_element_type() == ngraph::element::f32 &&
|
||||
t.get_partial_shape().is_static();
|
||||
};
|
||||
const auto & inputs = n->inputs();
|
||||
const auto & outputs = n->outputs();
|
||||
// todo: Is this check necessary? Remove if not
|
||||
for (const auto& out : outputs) {
|
||||
for (const auto &in_out : out.get_target_inputs()) {
|
||||
if (ov::is_type<ngraph::op::v5::Loop>(in_out.get_node()->shared_from_this())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return std::all_of(inputs.begin(), inputs.end(), [&](const Input<const Node>& in) {return supported(in.get_tensor());}) &&
|
||||
std::all_of(outputs.begin(), outputs.end(), [&](const Output<const Node>& out) {return supported(out.get_tensor());});
|
||||
}
|
||||
|
||||
auto has_subgraph_as_input(std::shared_ptr<Node> node) -> bool {
|
||||
auto inputs = node->inputs();
|
||||
for (auto input : inputs) {
|
||||
auto parent = input.get_source_output().get_node_shared_ptr();
|
||||
if (!!ov::as_type_ptr<snippets::op::Subgraph>(parent)) {
|
||||
auto has_result_child(const std::shared_ptr<const Node> &node) -> bool {
|
||||
for (const auto &child : node->get_users()) {
|
||||
if (ov::is_type<ngraph::opset1::Result>(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto is_lo(std::shared_ptr<Node> n) -> bool {
|
||||
auto is_lob = [](std::shared_ptr<Node> n) -> bool {
|
||||
using ngraph::as_type_ptr;
|
||||
return !!ov::as_type_ptr<opset1::Add>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Divide>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Equal>(n)
|
||||
|| !!ov::as_type_ptr<opset1::FloorMod>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Greater>(n)
|
||||
|| !!ov::as_type_ptr<opset1::GreaterEqual>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Less>(n)
|
||||
|| !!ov::as_type_ptr<opset1::LessEqual>(n)
|
||||
|| !!ov::as_type_ptr<opset1::LogicalAnd>(n)
|
||||
|| !!ov::as_type_ptr<opset1::LogicalOr>(n)
|
||||
|| !!ov::as_type_ptr<opset1::LogicalXor>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Maximum>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Minimum>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Mod>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Multiply>(n)
|
||||
|| !!ov::as_type_ptr<opset1::NotEqual>(n)
|
||||
|| !!ov::as_type_ptr<opset1::PRelu>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Power>(n)
|
||||
|| !!ov::as_type_ptr<opset1::SquaredDifference>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Subtract>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Xor>(n);
|
||||
};
|
||||
|
||||
auto is_lou = [](std::shared_ptr<Node> n) -> bool {
|
||||
using ngraph::as_type_ptr;
|
||||
return !!ov::as_type_ptr<opset1::Abs>(n)
|
||||
// || !!ov::as_type_ptr<opset1::Acos>(n)
|
||||
// || !!ov::as_type_ptr<opset1::Asin>(n)
|
||||
// || !!ov::as_type_ptr<opset1::Atan>(n)
|
||||
// || !!ov::as_type_ptr<opset1::Ceiling>(n) ?
|
||||
|| !!ov::as_type_ptr<opset1::Clamp>(n)
|
||||
// || !!ov::as_type_ptr<opset1::Cos>(n)
|
||||
// || !!ov::as_type_ptr<opset1::Cosh>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Elu>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Erf>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Exp>(n)
|
||||
// || !!ov::as_type_ptr<opset1::Floor>(n) ?
|
||||
// || !!ov::as_type_ptr<opset1::Log>(n) ?
|
||||
|| !!ov::as_type_ptr<opset1::LogicalNot>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Negative>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Relu>(n)
|
||||
// || !!ov::as_type_ptr<opset1::Sign>(n) ?
|
||||
|| !!ov::as_type_ptr<opset1::Sigmoid>(n)
|
||||
// || !!ov::as_type_ptr<opset1::Sin>(n)
|
||||
// || !!ov::as_type_ptr<opset1::Sinh>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Sqrt>(n)
|
||||
// || !!ov::as_type_ptr<opset1::Tan>(n)
|
||||
|| !!ov::as_type_ptr<opset1::Tanh>(n);
|
||||
};
|
||||
|
||||
auto is_lot = [](std::shared_ptr<Node> n) -> bool {
|
||||
using ngraph::as_type_ptr;
|
||||
return false;
|
||||
// return !!ov::as_type_ptr<opset1::HardSigmoid>(n) // ternary with 2 constants
|
||||
// || !!ov::as_type_ptr<opset1::Selu>(n); // ternary with 2 constants / or DW
|
||||
};
|
||||
|
||||
auto is_fq = [](std::shared_ptr<Node> n) -> bool {
|
||||
using ngraph::as_type_ptr;
|
||||
return false;//!!ov::as_type_ptr<opset1::FakeQuantize>(n); // 4->1
|
||||
};
|
||||
|
||||
return is_lou(n) || is_lob(n) ||is_lot(n) || is_fq(n);
|
||||
}
|
||||
|
||||
auto has_supported_in_out(std::shared_ptr<Node> n) -> bool {
|
||||
for (auto in : n->inputs()) {
|
||||
if (in.get_tensor().get_element_type() != ngraph::element::f32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in.get_partial_shape().is_dynamic()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in.get_partial_shape().is_static() && in.get_shape().size() > 6) {
|
||||
return false;
|
||||
auto get_num_result_children(const std::shared_ptr<const Node> &node) -> size_t {
|
||||
size_t result = 0;
|
||||
for (const auto &child : node->get_users()) {
|
||||
if (ov::is_type<ngraph::opset1::Result>(child)) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto out : n->outputs()) {
|
||||
if (out.get_tensor().get_element_type() != ngraph::element::f32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out.get_partial_shape().is_dynamic()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out.get_partial_shape().is_static() && out.get_shape().size() > 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto in_out : out.get_target_inputs()) {
|
||||
if (!!ov::as_type_ptr<ngraph::op::v5::Loop>(in_out.get_node()->shared_from_this())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!!ov::as_type_ptr<ngraph::op::v0::Result>(in_out.get_node()->shared_from_this())) {
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
// Need to update tensor name manually, since MKLDNNGraph::Replicate() looks at input.get_tensor().get_name();
|
||||
// If subgraph->get_output_size() == 1, then the name will be restored correctly from the node name
|
||||
auto update_out_tensor_name(std::shared_ptr<ngraph::snippets::op::Subgraph> &subgraph) -> void {
|
||||
bool not_set = true;
|
||||
for (unsigned int i = 0; i < subgraph->get_output_size() && not_set; i++) {
|
||||
for (const auto &in : subgraph->get_output_target_inputs(i)) {
|
||||
if (ov::is_type<opset1::Result>(in.get_node())) {
|
||||
auto out_tensor = subgraph->output(i).get_tensor_ptr();
|
||||
NGRAPH_SUPPRESS_DEPRECATED_START
|
||||
if (out_tensor->get_name().empty()) {
|
||||
const auto& body_result = subgraph->get_body()->get_output_op(i);
|
||||
const std::string newTensorName = ngraph::op::util::get_ie_output_name(
|
||||
body_result->get_input_source_output(0));
|
||||
out_tensor->set_name(newTensorName);
|
||||
}
|
||||
NGRAPH_SUPPRESS_DEPRECATED_END
|
||||
not_set = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ngraph::snippets::pass::StartSubgraph::StartSubgraph(bool tokenize_by_node) : MatcherPass() {
|
||||
MATCHER_SCOPE(StartSubgraph);
|
||||
|
||||
auto has_multiple_output_edges = [](std::shared_ptr<Node> n) -> bool {
|
||||
for (auto out : n->outputs()) {
|
||||
if (out.get_target_inputs().size() != 1) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
register_matcher(std::make_shared<pattern::Matcher>(
|
||||
std::make_shared<pattern::op::Label>(pattern::any_input(),
|
||||
[tokenize_by_node, has_multiple_output_edges](std::shared_ptr<Node> n) {
|
||||
return is_lo(n) &&
|
||||
has_supported_in_out(n) &&
|
||||
(tokenize_by_node || !has_subgraph_as_input(n)) &&
|
||||
has_multiple_output_edges(n);
|
||||
})),
|
||||
[](ngraph::pattern::Matcher &m) -> bool {
|
||||
auto node = m.get_match_root();
|
||||
|
||||
remark(1) << "Match root"
|
||||
<< node->get_friendly_name()
|
||||
<< " " << node
|
||||
<< " Creating new snippet - no input subgraphs found" << std::endl;
|
||||
|
||||
auto subgraph = op::Subgraph::wrap_node_as_subgraph(node);
|
||||
ngraph::replace_node(node, subgraph);
|
||||
|
||||
remark(1) << "Replacement (new) done for: "
|
||||
<< subgraph->get_friendly_name()
|
||||
<< " with " << subgraph->inputs().size()
|
||||
<< " inputs and " << subgraph->outputs().size()
|
||||
<< " outputs and " << subgraph->get_body()->get_ops().size() << " ops total\n";
|
||||
return true;
|
||||
});
|
||||
bool AppropriateForSubgraph(const std::shared_ptr<const Node> &node) {
|
||||
return is_layout_oblivious(node) && has_supported_in_out(node);
|
||||
}
|
||||
|
||||
ngraph::snippets::pass::AttachToSubgraph::AttachToSubgraph(bool tokenize_by_node) : MatcherPass() {
|
||||
MATCHER_SCOPE(AttachToSubgraph);
|
||||
void SetSnippetsNodeType(const std::shared_ptr<Node> &node, SnippetsNodeType nodeType) {
|
||||
auto &rt = node->get_rt_info();
|
||||
rt["SnippetsNodeType"] = nodeType;
|
||||
}
|
||||
|
||||
SnippetsNodeType GetSnippetsNodeType(const std::shared_ptr<const Node> &node) {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::GetSnippetsNodeType")
|
||||
auto &rt = node->get_rt_info();
|
||||
const auto rinfo = rt.find("SnippetsNodeType");
|
||||
if (rinfo == rt.end())
|
||||
return SnippetsNodeType::NotSet;
|
||||
return rinfo->second.as<SnippetsNodeType>();
|
||||
}
|
||||
|
||||
void SetTopologicalOrder(const std::shared_ptr<Node> &node, int64_t order) {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::SetTopologicalOrder")
|
||||
auto &rt = node->get_rt_info();
|
||||
rt["TopologicalOrder"] = order;
|
||||
}
|
||||
|
||||
int64_t GetTopologicalOrder(const std::shared_ptr<const Node> &node) {
|
||||
auto &rt = node->get_rt_info();
|
||||
const auto rinfo = rt.find("TopologicalOrder");
|
||||
if (rinfo == rt.end())
|
||||
throw ngraph_error("Topological order is required, but not set.");
|
||||
return rinfo->second.as<int64_t>();
|
||||
}
|
||||
|
||||
bool EnumerateNodes::run_on_model(const std::shared_ptr<ov::Model> &m) {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::EnumerateNodes")
|
||||
int64_t order = 0;
|
||||
// Todo: We don't really have to set order for every node, just for subgraph parents and children would be enough
|
||||
for (auto &node : m->get_ordered_ops()) {
|
||||
SetTopologicalOrder(node, order++);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
TokenizeSnippets::TokenizeSnippets() {
|
||||
MATCHER_SCOPE(TokenizeSnippets);
|
||||
enum continuation_strategy {
|
||||
reset,
|
||||
abort
|
||||
};
|
||||
|
||||
continuation_strategy strategy = continuation_strategy::abort;
|
||||
|
||||
ngraph::graph_rewrite_callback continuation_callback = [strategy](ngraph::pattern::Matcher &m) -> bool {
|
||||
continuation_strategy strategy = continuation_strategy::reset;
|
||||
auto label = std::make_shared<pattern::op::Label>(pattern::any_input(),
|
||||
[](const std::shared_ptr<const Node> &n) {
|
||||
return GetSnippetsNodeType(n) != SnippetsNodeType::SkippedByPlugin && AppropriateForSubgraph(n);
|
||||
});
|
||||
ngraph::graph_rewrite_callback callback = [&, strategy](ngraph::pattern::Matcher &m) -> bool {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::CreateSubgraph_callback")
|
||||
auto node = m.get_match_root();
|
||||
if (transformation_callback(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
remark(1) << "Match root " << node->get_friendly_name() << " " << node << std::endl;
|
||||
remark(1) << "Match root: " << node->get_friendly_name() << " " << node << std::endl;
|
||||
|
||||
// inputs that are already subgraphs
|
||||
std::unordered_set<std::shared_ptr<Node>> input_subgraphs;
|
||||
// clone bodies because we need a rollback if loop is found
|
||||
std::map<std::shared_ptr<Node>, std::shared_ptr<ngraph::Function>> clones;
|
||||
const auto getFusedNames = [](const std::shared_ptr<Node>& n) -> std::string {
|
||||
auto rt_info = n->get_rt_info();
|
||||
auto it = rt_info.find("originalLayersNames");
|
||||
if (it != rt_info.end()) {
|
||||
return it->second.as<std::string>() + ",";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
ParameterVector body_parameters;
|
||||
OutputVector external_inputs;
|
||||
OutputVector internal_inputs;
|
||||
auto create_single_node_subgraph = [&](const std::shared_ptr<Node> &node) {
|
||||
auto subgraph = op::Subgraph::wrap_node_as_subgraph(node);
|
||||
subgraph->get_rt_info()["originalLayersNames"] = getFusedNames(node) + node->get_friendly_name();
|
||||
ngraph::replace_node(node, subgraph);
|
||||
update_out_tensor_name(subgraph);
|
||||
};
|
||||
|
||||
auto inputs = node->inputs();
|
||||
|
||||
auto is_recurrent = [inputs](const ngraph::Output<ngraph::Node>& to_find) -> bool {
|
||||
for (auto in : inputs) {
|
||||
if (in.get_source_output().get_node_shared_ptr() == to_find.get_node_shared_ptr()) {
|
||||
return true;
|
||||
auto abort_with_strategy = [&](const std::string& message_reset,
|
||||
const std::string& message_abort = "", int priority = 3) {
|
||||
if (strategy == continuation_strategy::reset) {
|
||||
create_single_node_subgraph(node);
|
||||
return true;
|
||||
} else if (strategy == continuation_strategy::abort) {
|
||||
if (!message_abort.empty()) {
|
||||
remark(priority) << message_abort << std::endl;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// inputs that are already subgraphs
|
||||
std::unordered_set<std::shared_ptr<Node>> input_subgraphs;
|
||||
// clone bodies because we need a rollback if loop is found
|
||||
std::map<std::shared_ptr<Node>, std::shared_ptr<ov::Model>> clones;
|
||||
|
||||
auto get_input_index = [](const Output<Node>& found) -> size_t {
|
||||
for (auto& input : found.get_target_inputs()) {
|
||||
remark(13) << input.get_node() << " " << input.get_source_output() << " vs "
|
||||
<< found << found.get_node() << " : " << input.get_index() << " " << found.get_index() << std::endl;
|
||||
}
|
||||
ParameterVector body_parameters;
|
||||
// inputs to merged subgraph
|
||||
OutputVector external_inputs;
|
||||
// inputs to the node before merge to subgraph
|
||||
OutputVector internal_inputs;
|
||||
|
||||
for (auto& input : found.get_target_inputs()) {
|
||||
remark(13) << input.get_node() << " " << input.get_source_output() << " vs "
|
||||
<< found << " : " << input.get_index() << " " << found.get_index() << std::endl;
|
||||
if (ov::as_type_ptr<op::Subgraph>(input.get_node()->shared_from_this()) != nullptr && input.get_source_output() == found) {
|
||||
return input.get_index();
|
||||
}
|
||||
auto input_values = node->input_values();
|
||||
/*
|
||||
* Called with subgraph->input_value(i) arg and used to
|
||||
* Check that the attached node input subgraph has the same input as the node itself.
|
||||
* If true, then ternary merge is initiated.
|
||||
* input
|
||||
* / \
|
||||
* subgraph--node
|
||||
*/
|
||||
auto is_recurrent = [&input_values](const ngraph::Output<ngraph::Node>& to_find) -> bool {
|
||||
return std::any_of(input_values.begin(), input_values.end(),
|
||||
[&](const ov::Output<ov::Node> &in) {return in == to_find;});
|
||||
};
|
||||
/*
|
||||
* Checks if the passed node introduces loop dependency for given topological bounds (pair of maxParentOrder, minChildOrder).
|
||||
* The bounds are presumed to be without dependency. The bounds are updated if no dependency is introduced by the node.
|
||||
*/
|
||||
const auto cyclicDependencyIsIntoduced = [&node](const std::shared_ptr<Node>& nodeToExamine, std::pair<int64_t, int64_t>& currentBounds) -> bool {
|
||||
assert(currentBounds.first < currentBounds.second && "Invalid currentBounds passed");
|
||||
const auto &parentNodes = ngraph::as_node_vector(nodeToExamine->input_values());
|
||||
const int64_t maxParentOrder = std::accumulate(parentNodes.begin(), parentNodes.end(), currentBounds.first,
|
||||
[](int64_t maxOrder, std::shared_ptr<Node> n){
|
||||
if (ngraph::op::is_constant(n) || ngraph::op::is_parameter(n))
|
||||
return maxOrder;
|
||||
return std::max(maxOrder, GetTopologicalOrder(n));
|
||||
});
|
||||
const auto &childNodes = nodeToExamine->get_users();
|
||||
// Skip the node being attached, since it will be a part of subgraph and can't introduce loop dependency
|
||||
const int64_t minChildOrder = std::accumulate(childNodes.begin(), childNodes.end(), currentBounds.second,
|
||||
[&node](int64_t minOrder, std::shared_ptr<Node> n){
|
||||
if (ngraph::op::is_constant(n) || ngraph::op::is_parameter(n) || n == node)
|
||||
return minOrder;
|
||||
return std::min(minOrder, GetTopologicalOrder(n));
|
||||
});
|
||||
if (maxParentOrder < minChildOrder) {
|
||||
currentBounds = std::pair<int64_t, int64_t>(maxParentOrder, minChildOrder);
|
||||
return false;
|
||||
}
|
||||
return 0;
|
||||
return true;
|
||||
};
|
||||
|
||||
for (auto input : inputs) {
|
||||
auto input_node = input.get_source_output().get_node_shared_ptr();
|
||||
|
||||
for (const auto &input_node : ngraph::as_node_vector(input_values)) {
|
||||
if (auto subgraph = ov::as_type_ptr<op::Subgraph>(input_node)) {
|
||||
if (!clones.count(input_node)) {
|
||||
auto f = ngraph::clone_function(*subgraph->get_body().get());
|
||||
auto f = ov::clone_model(*subgraph->get_body().get());
|
||||
f->set_friendly_name(subgraph->get_body()->get_friendly_name());
|
||||
clones[input_node] = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto input : inputs) {
|
||||
auto input_node = input.get_source_output().get_node_shared_ptr();
|
||||
|
||||
if (auto subgraph = ov::as_type_ptr<op::Subgraph>(input_node)) {
|
||||
// If there are no input subgraphs no need to go further, just create a new one.
|
||||
if (clones.empty()) {
|
||||
create_single_node_subgraph(node);
|
||||
remark(1) << "Starting subgraph at: " << node->get_friendly_name()
|
||||
<< " with " << node->inputs().size() << " inputs and " << node->outputs().size()
|
||||
<< " outputs" << std::endl;
|
||||
return true;
|
||||
}
|
||||
std::string newSubgraphName{};
|
||||
std::string fusedNames{};
|
||||
size_t num_result_children = 0;
|
||||
std::pair<int64_t, int64_t> currentTopoBounds {-1, LONG_MAX};
|
||||
cyclicDependencyIsIntoduced(node, currentTopoBounds);
|
||||
assert(!cyclicDependencyIsIntoduced(node, currentTopoBounds) && "Cyclic dependency is introduced by the node itself");
|
||||
for (const auto& input_value : input_values) {
|
||||
auto input_node = input_value.get_node_shared_ptr();
|
||||
if (ov::is_type<op::Subgraph>(input_node) &&
|
||||
!cyclicDependencyIsIntoduced(input_node, currentTopoBounds)) {
|
||||
auto subgraph = std::static_pointer_cast<op::Subgraph>(input_node);
|
||||
if (!input_subgraphs.count(input_node)) {
|
||||
input_subgraphs.insert(input_node);
|
||||
|
||||
fusedNames += getFusedNames(subgraph);
|
||||
if (newSubgraphName.empty())
|
||||
newSubgraphName = subgraph->get_friendly_name();
|
||||
num_result_children += has_result_child(subgraph);
|
||||
auto f = clones[input_node];
|
||||
const auto& input_body_parameters = f->get_parameters();
|
||||
|
||||
// Todo:
|
||||
// Some of the input subgraphs might have common parents, so some of the input_parameters might already be
|
||||
// in external_inputs and hence in body_parameters. Here we handle this case and remove repeated body_parameters.
|
||||
// Would it be better to incorporate all inputs first and then remove repeated params.
|
||||
for (size_t i = 0; i < input_body_parameters.size(); ++i) {
|
||||
auto found = std::find(external_inputs.begin(), external_inputs.end(), subgraph->input_value(i));
|
||||
if (found != external_inputs.end()) {
|
||||
auto current_input_index = get_input_index(*found);
|
||||
// Handling the case if multiple inputs referencing the same parameter comes from one subgraph => it's not introduced by SS.
|
||||
// It might be better to keep track if body parameter relationship rather than that
|
||||
// Todo: here we rely on friendly_name uniqueness. Propose a different algorithm.
|
||||
size_t current_input_index = body_parameters.size();
|
||||
for (size_t p_ind = 0; p_ind < body_parameters.size(); p_ind++) {
|
||||
const auto & p = body_parameters[p_ind];
|
||||
if (p->get_friendly_name() == found->get_node_shared_ptr()->get_friendly_name()) {
|
||||
current_input_index = p_ind;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_input_index < body_parameters.size()) {
|
||||
remark(13) << "replacing " << *found << " " << current_input_index << " with "
|
||||
<< body_parameters[current_input_index] << std::endl;
|
||||
@@ -354,16 +373,19 @@ ngraph::snippets::pass::AttachToSubgraph::AttachToSubgraph(bool tokenize_by_node
|
||||
auto internal = input_body_parameters[i];
|
||||
auto internal_consumers = internal->outputs();
|
||||
|
||||
for (auto output : internal->outputs()) {
|
||||
for (auto consumer : output.get_target_inputs()) {
|
||||
if (auto to_replace_with = ov::as_type_ptr<op::Subgraph>(subgraph->input_value(i).get_node_shared_ptr())) {
|
||||
auto other_body = clones[subgraph->input_value(i).get_node_shared_ptr()];
|
||||
auto other_body_result = other_body->get_results()[consumer.get_source_output().get_index()];
|
||||
auto result_producer = other_body_result->input(0).get_source_output();
|
||||
if (auto to_replace_with = ov::as_type_ptr<op::Subgraph>(subgraph->get_input_node_shared_ptr(i))) {
|
||||
for (auto output : internal_consumers) {
|
||||
for (auto consumer : output.get_target_inputs()) {
|
||||
auto other_body = clones[subgraph->get_input_node_shared_ptr(i)];
|
||||
auto other_body_result = other_body->get_results()[consumer.get_source_output().get_index()];
|
||||
auto result_producer = other_body_result->input(0).get_source_output();
|
||||
|
||||
consumer.replace_source_output(result_producer.get_node_shared_ptr());
|
||||
}
|
||||
}
|
||||
consumer.replace_source_output(result_producer.get_node_shared_ptr());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
external_inputs.push_back(subgraph->input_value(i));
|
||||
body_parameters.push_back(input_body_parameters[i]);
|
||||
}
|
||||
} else {
|
||||
external_inputs.push_back(subgraph->input_value(i));
|
||||
@@ -375,7 +397,7 @@ ngraph::snippets::pass::AttachToSubgraph::AttachToSubgraph(bool tokenize_by_node
|
||||
// this is there stitching happens, get result of a copy of a body of currently processed input and put it to the new inputs
|
||||
// internal output index == external output index
|
||||
auto& input_body = clones[input_node];
|
||||
size_t source_output_index = input.get_source_output().get_index();
|
||||
size_t source_output_index = input_value.get_index();
|
||||
auto source_result = input_body->get_results()[source_output_index];
|
||||
// Result op has a single input
|
||||
internal_inputs.push_back(source_result->input_value(0));
|
||||
@@ -383,15 +405,20 @@ ngraph::snippets::pass::AttachToSubgraph::AttachToSubgraph(bool tokenize_by_node
|
||||
if (op::is_scalar_constant(input_node)) {
|
||||
internal_inputs.push_back(input_node->output(0));
|
||||
} else {
|
||||
external_inputs.push_back(input.get_source_output());
|
||||
auto new_parameter = std::make_shared<opset1::Parameter>(input.get_element_type(), input.get_partial_shape());
|
||||
new_parameter->set_friendly_name(input.get_source_output().get_node()->get_friendly_name());
|
||||
external_inputs.push_back(input_value);
|
||||
auto new_parameter = std::make_shared<opset1::Parameter>(input_value.get_element_type(), input_value.get_partial_shape());
|
||||
new_parameter->set_friendly_name(input_node->get_friendly_name());
|
||||
body_parameters.push_back(new_parameter);
|
||||
body_parameters.back()->set_friendly_name(input.get_source_output().get_node()->get_friendly_name());
|
||||
internal_inputs.push_back(new_parameter->output(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
fusedNames += node->get_friendly_name();
|
||||
num_result_children += get_num_result_children(node);
|
||||
if (num_result_children > 1)
|
||||
return abort_with_strategy("New subgraph is created since too many Result children are detected");
|
||||
if (newSubgraphName.empty())
|
||||
newSubgraphName = node->get_friendly_name();
|
||||
|
||||
auto body_node = node->copy_with_new_inputs(internal_inputs);
|
||||
body_node->set_friendly_name(node->get_friendly_name());
|
||||
@@ -444,26 +471,20 @@ ngraph::snippets::pass::AttachToSubgraph::AttachToSubgraph(bool tokenize_by_node
|
||||
if (body_results.size() != subgraph_result_inputs.size()) {
|
||||
throw ngraph_error("body results and node results size mismatch during subgraph collaps");
|
||||
}
|
||||
|
||||
// todo: move this plugin-specific constraint to the plugin callback
|
||||
if (body_parameters.size() + body_results.size() > 7) {
|
||||
if (strategy == continuation_strategy::reset) {
|
||||
remark(13) << "new subgraph is created. Impossible to schedule subgraph with "
|
||||
<< body_parameters.size() << " inputs and " << body_results.size() << " outputs." << std::endl;
|
||||
|
||||
auto single_node_subgraph = op::Subgraph::wrap_node_as_subgraph(node);
|
||||
ngraph::replace_node(node, single_node_subgraph);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
const std::string message_reset = "new subgraph is created. Impossible to schedule subgraph with " +
|
||||
std::to_string(body_parameters.size()) + " inputs and " + std::to_string(body_results.size()) + " outputs.";
|
||||
const std::string message_abort = "failed to continue subgraph. Impossible to schedule subgraph with " +
|
||||
std::to_string(body_parameters.size()) + " inputs and " + std::to_string(body_results.size()) + " outputs.";
|
||||
return abort_with_strategy(message_reset, message_abort);
|
||||
}
|
||||
|
||||
auto body = op::create_body(node->get_friendly_name(), body_results, body_parameters);
|
||||
auto body = op::create_body(newSubgraphName, body_results, body_parameters);
|
||||
for (size_t i = 0; i < body->get_parameters().size(); i++) {
|
||||
body->get_parameters()[i]->set_friendly_name(body_parameters[i]->get_friendly_name());
|
||||
}
|
||||
|
||||
auto subgraph = op::build_subgraph(node, external_inputs, body);
|
||||
auto subgraph = op::build_subgraph(node, external_inputs, body, newSubgraphName);
|
||||
auto act_body = subgraph->get_body();
|
||||
for (size_t i = 0; i < act_body->get_parameters().size(); i++) {
|
||||
act_body->get_parameters()[i]->set_friendly_name(body_parameters[i]->get_friendly_name());
|
||||
@@ -473,37 +494,15 @@ ngraph::snippets::pass::AttachToSubgraph::AttachToSubgraph(bool tokenize_by_node
|
||||
throw ngraph_error("newly create subgraph doesn't much number of results");
|
||||
}
|
||||
|
||||
if (outputs_are_not_broadcastable(subgraph)) {
|
||||
if (strategy == continuation_strategy::reset) {
|
||||
remark(13) << "New subgraph is created due to outputs of a subgraph not broadcastable." << std::endl;
|
||||
|
||||
auto single_node_subgraph = op::Subgraph::wrap_node_as_subgraph(node);
|
||||
single_node_subgraph->validate_and_infer_types();
|
||||
ngraph::replace_node(node, single_node_subgraph);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_cycles_of_dependencies(subgraph_result_inputs, subgraph->inputs())) {
|
||||
if (strategy == continuation_strategy::reset) {
|
||||
remark(13) << "New subgraph is created due to loop dependency introduced by one of input subgraphs." << std::endl;
|
||||
|
||||
auto single_node_subgraph = op::Subgraph::wrap_node_as_subgraph(node);
|
||||
single_node_subgraph->validate_and_infer_types();
|
||||
ngraph::replace_node(node, single_node_subgraph);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (outputs_are_not_broadcastable(subgraph))
|
||||
return abort_with_strategy("New subgraph is created due to outputs of a subgraph not broadcastable.");
|
||||
|
||||
for (size_t i = 0; i < subgraph->get_output_size(); ++i) {
|
||||
for (auto target_input : subgraph_result_inputs[i]) {
|
||||
target_input.replace_source_output(subgraph->output(i));
|
||||
}
|
||||
}
|
||||
update_out_tensor_name(subgraph);
|
||||
|
||||
subgraph->validate_and_infer_types();
|
||||
|
||||
@@ -511,6 +510,7 @@ ngraph::snippets::pass::AttachToSubgraph::AttachToSubgraph(bool tokenize_by_node
|
||||
for (size_t i = 0; i < act_body1->get_parameters().size(); i++) {
|
||||
act_body1->get_parameters()[i]->set_friendly_name(body_parameters[i]->get_friendly_name());
|
||||
}
|
||||
subgraph->get_rt_info()["originalLayersNames"] = node->get_friendly_name();
|
||||
|
||||
remark(1) << "Replacement (merge) done for: "
|
||||
<< subgraph->get_friendly_name()
|
||||
@@ -520,11 +520,9 @@ ngraph::snippets::pass::AttachToSubgraph::AttachToSubgraph(bool tokenize_by_node
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
register_matcher(std::make_shared<pattern::Matcher>(
|
||||
std::make_shared<pattern::op::Label>(pattern::any_input(),
|
||||
[](std::shared_ptr<Node> n) {
|
||||
return is_lo(n) && has_supported_in_out(n) && has_subgraph_as_input(n);
|
||||
})),
|
||||
continuation_callback);
|
||||
auto matcher = std::make_shared<ngraph::pattern::Matcher>(label);
|
||||
register_matcher(matcher, callback);
|
||||
}
|
||||
} // namespace pass
|
||||
} // namespace snippets
|
||||
} // namespace ngraph
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "itt.hpp"
|
||||
#include <snippets/itt.hpp>
|
||||
#include "remarks.hpp"
|
||||
|
||||
#include "snippets/pass/insert_load_store.hpp"
|
||||
@@ -17,6 +17,7 @@ ngraph::snippets::pass::InsertLoad::InsertLoad() {
|
||||
register_matcher(std::make_shared<ngraph::pattern::Matcher>(
|
||||
ngraph::pattern::wrap_type<ngraph::opset1::Parameter>()),
|
||||
[this](ngraph::pattern::Matcher &m) {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::op::InsertLoad")
|
||||
auto root = m.get_match_root();
|
||||
|
||||
// check if already has Load as an output
|
||||
@@ -50,6 +51,7 @@ ngraph::snippets::pass::InsertStore::InsertStore() {
|
||||
register_matcher(std::make_shared<ngraph::pattern::Matcher>(
|
||||
ngraph::pattern::wrap_type<ngraph::opset1::Result>()),
|
||||
[this](ngraph::pattern::Matcher &m) {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::op::InsertStore")
|
||||
auto root = m.get_match_root();
|
||||
|
||||
// check if already has Store as an input
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
|
||||
#include "remarks.hpp"
|
||||
#include "itt.hpp"
|
||||
#include <snippets/itt.hpp>
|
||||
|
||||
#include "snippets/pass/insert_movebroadcast.hpp"
|
||||
#include "snippets/snippets_isa.hpp"
|
||||
@@ -138,6 +138,7 @@ auto reset_broacast_config(const std::shared_ptr<ngraph::Node>& op) -> void {
|
||||
ngraph::snippets::pass::InsertMoveBroadcast::InsertMoveBroadcast() {
|
||||
MATCHER_SCOPE(InsertMoveBroadcast);
|
||||
ngraph::graph_rewrite_callback callback = [this](ngraph::pattern::Matcher &m) {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::op::InsertMoveBroadcast")
|
||||
auto root = m.get_match_root();
|
||||
const auto& values = root->input_values();
|
||||
if (values.empty()) {
|
||||
@@ -173,8 +174,8 @@ ngraph::snippets::pass::InsertMoveBroadcast::InsertMoveBroadcast() {
|
||||
auto any = std::make_shared<pattern::op::Label>(pattern::any_input(),
|
||||
[](std::shared_ptr<Node> n) {
|
||||
// should add supports_auto_broadcast to SquaredDifference
|
||||
return (ngraph::op::supports_auto_broadcast(n) || !!as_type_ptr<opset1::SquaredDifference>(n) || !!as_type_ptr<opset1::Mod>(n))
|
||||
&& n->get_autob().m_type == ngraph::op::AutoBroadcastType::NUMPY; });
|
||||
return ((ngraph::op::supports_auto_broadcast(n) || is_type<opset1::SquaredDifference>(n) || is_type<opset1::Mod>(n)) &&
|
||||
n->get_autob().m_type == ngraph::op::AutoBroadcastType::NUMPY) || is_type<opset1::PRelu>(n); });
|
||||
|
||||
register_matcher(std::make_shared<ngraph::pattern::Matcher>(any), callback);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
|
||||
#include "remarks.hpp"
|
||||
#include "itt.hpp"
|
||||
#include <snippets/itt.hpp>
|
||||
|
||||
#include "snippets/pass/load_movebroadcast_to_broadcastload.hpp"
|
||||
#include "snippets/snippets_isa.hpp"
|
||||
@@ -20,6 +20,7 @@ ngraph::snippets::pass::LoadMoveBroadcastToBroadcastLoad::LoadMoveBroadcastToBro
|
||||
|
||||
register_matcher(std::make_shared<ngraph::pattern::Matcher>(fbn),
|
||||
[load_pattern, param_pattern](ngraph::pattern::Matcher &m) {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::op::LoadMoveBroadcastToBroadcastLoad")
|
||||
auto root = m.get_match_root();
|
||||
|
||||
const auto &pm = m.get_pattern_value_map();
|
||||
@@ -44,9 +45,9 @@ ngraph::snippets::pass::LoadMoveBroadcastToBroadcastLoad::LoadMoveBroadcastToBro
|
||||
bct[k] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Todo: consider refactoring BroadcastLoad, it seems we don't need broadcast_info at this point.
|
||||
broadcastload->set_broadcast_info(bct);
|
||||
if (broadcastload->is_broadcast(outshape.size()-1)) {
|
||||
if (inshape.back() == 1 && outshape.back() != 1) {
|
||||
ngraph::copy_runtime_info(root, broadcastload);
|
||||
ngraph::replace_node(root, broadcastload);
|
||||
return true;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "itt.hpp"
|
||||
#include <snippets/itt.hpp>
|
||||
|
||||
#include "snippets/pass/vector_to_scalar.hpp"
|
||||
#include "snippets/snippets_isa.hpp"
|
||||
@@ -16,6 +16,7 @@ ngraph::snippets::pass::ReplaceLoadsWithScalarLoads::ReplaceLoadsWithScalarLoads
|
||||
register_matcher(std::make_shared<ngraph::pattern::Matcher>(
|
||||
ngraph::pattern::wrap_type<ngraph::snippets::op::Load>()),
|
||||
[this](ngraph::pattern::Matcher &m) {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::op::ReplaceLoadsWithScalarLoads_callback")
|
||||
auto root = m.get_match_root();
|
||||
auto load = std::make_shared<ngraph::snippets::op::ScalarLoad> (root->input_value(0));
|
||||
load->set_friendly_name(root->get_friendly_name());
|
||||
@@ -30,6 +31,7 @@ ngraph::snippets::pass::ReplaceStoresWithScalarStores::ReplaceStoresWithScalarSt
|
||||
register_matcher(std::make_shared<ngraph::pattern::Matcher>(
|
||||
ngraph::pattern::wrap_type<ngraph::snippets::op::Store>()),
|
||||
[this](ngraph::pattern::Matcher &m) {
|
||||
OV_ITT_SCOPED_TASK(ngraph::pass::itt::domains::SnippetsTransform, "Snippets::op::ReplaceStoresWithScalarStores_callback")
|
||||
auto root = m.get_match_root();
|
||||
auto store = std::make_shared<ngraph::snippets::op::ScalarStore> (root->input_value(0));
|
||||
store->set_friendly_name(root->get_friendly_name());
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
// Todo: Replace remarks with DEBUG_CAPS
|
||||
class logstreambuf: public std::streambuf {
|
||||
public:
|
||||
static const int threshold {5};
|
||||
static const int threshold {50};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -15,5 +15,9 @@ std::vector<std::string> disabledTestPatterns() {
|
||||
".*/NmsLayerTest.*",
|
||||
// Issue 74365: azure is failing after #7351
|
||||
".*DetectionOutput8ToDetectionOutput1",
|
||||
// TODO: Snpippets rewrite this test
|
||||
".*TransformationTests\\.DontStartSubgraphSingleOutput.*",
|
||||
// TODO: Move this test to CPU-specific
|
||||
".*TransformationTests\\.DoNotStartSubgraphAfterInputs.*"
|
||||
};
|
||||
}
|
||||
@@ -14,67 +14,187 @@
|
||||
#include <transformations/init_node_info.hpp>
|
||||
|
||||
#include "common_test_utils/ngraph_test_utils.hpp"
|
||||
#include "functional_test_utils/skip_tests_config.hpp"
|
||||
|
||||
using namespace testing;
|
||||
using namespace ngraph;
|
||||
using namespace ov;
|
||||
using ngraph::snippets::op::Subgraph;
|
||||
using ngraph::pass::InitNodeInfo;
|
||||
using ngraph::snippets::pass::EnumerateNodes;
|
||||
using ngraph::snippets::pass::TokenizeSnippets;
|
||||
//using ngraph::snippets::pass::CreateSubgraph;
|
||||
|
||||
TEST(TransformationTests, StartSubgraphMultipleOutputs) {
|
||||
std::shared_ptr<Function> f(nullptr), f_ref(nullptr);
|
||||
// Todo: Move this test to CPU-specific
|
||||
TEST(TransformationTests, DoNotStartSubgraphAfterInputs) {
|
||||
// Do not start Subgraph after input parameters to avoid U8->FP32 and FP32->U8 conversion pairs
|
||||
// Todo: Remove this test when U8 support is enabled in SnippetS and StartSubgraph logics is updated
|
||||
SKIP_IF_CURRENT_TEST_IS_DISABLED()
|
||||
std::shared_ptr<Model> f(nullptr), f_ref(nullptr);
|
||||
{
|
||||
auto data0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<opset1::Add>(data0, data1);
|
||||
auto sub = std::make_shared<opset1::Subtract>(add, data1);
|
||||
auto mul = std::make_shared<opset1::Multiply>(add, sub);
|
||||
f = std::make_shared<Function>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
auto data0 = std::make_shared<op::v0::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
const std::vector<float> const_values{3, 2, 10};
|
||||
auto const_data = std::make_shared<op::v0::Constant>(element::f32, Shape{1, 3}, const_values);
|
||||
auto add = std::make_shared<op::v1::Add>(data0, data1);
|
||||
auto sub = std::make_shared<op::v1::Subtract>(add, const_data);
|
||||
auto mul = std::make_shared<op::v1::Multiply>(add, sub);
|
||||
f = std::make_shared<Model>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
|
||||
pass::Manager m;
|
||||
m.register_pass<pass::InitNodeInfo>();
|
||||
m.register_pass<snippets::pass::StartSubgraph>();
|
||||
m.register_pass<InitNodeInfo>();
|
||||
// Todo: When moved to CPU-specific tests, uncomment the markup transformation below.
|
||||
// m.register_pass<MKLDNNPlugin::SnippetsMarkFused>();
|
||||
m.register_pass<EnumerateNodes>();
|
||||
m.register_pass<TokenizeSnippets>();
|
||||
m.run_passes(f);
|
||||
ASSERT_NO_THROW(check_rt_info(f));
|
||||
}
|
||||
ASSERT_EQ(count_ops_of_type<Subgraph>(f), 0);
|
||||
}
|
||||
|
||||
TEST(TransformationTests, StartSubgraphMultipleOutputs) {
|
||||
SKIP_IF_CURRENT_TEST_IS_DISABLED()
|
||||
std::shared_ptr<Model> f(nullptr), f_ref(nullptr);
|
||||
{
|
||||
auto data0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto indata0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto indata1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<snippets::op::Subgraph>(NodeVector{data0, data1},
|
||||
std::make_shared<Function>(NodeVector{std::make_shared<opset1::Add>(indata0, indata1)}, ParameterVector{indata0, indata1}));
|
||||
auto sub = std::make_shared<opset1::Subtract>(add, data1);
|
||||
auto mul = std::make_shared<opset1::Multiply>(add, sub);
|
||||
f_ref = std::make_shared<Function>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
auto data0 = std::make_shared<op::v0::Parameter>(element::i32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<op::v0::Parameter>(element::i32, Shape{1, 3});
|
||||
auto convert0 = std::make_shared<op::v0::Convert>(data0, element::f32);
|
||||
auto convert1 = std::make_shared<op::v0::Convert>(data1, element::f32);
|
||||
const std::vector<float> const_values{3, 2, 10};
|
||||
auto const_data = std::make_shared<op::v0::Constant>(element::f32, Shape{1, 3}, const_values);
|
||||
auto add = std::make_shared<op::v1::Add>(convert0, convert1);
|
||||
auto sub = std::make_shared<op::v1::Subtract>(add, const_data);
|
||||
auto mul = std::make_shared<op::v1::Multiply>(add, sub);
|
||||
f = std::make_shared<Model>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
pass::Manager m;
|
||||
m.register_pass<InitNodeInfo>();
|
||||
m.register_pass<EnumerateNodes>();
|
||||
m.register_pass<TokenizeSnippets>();
|
||||
m.run_passes(f);
|
||||
ASSERT_NO_THROW(check_rt_info(f));
|
||||
}
|
||||
{
|
||||
auto data0 = std::make_shared<op::v0::Parameter>(element::i32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<op::v0::Parameter>(element::i32, Shape{1, 3});
|
||||
auto convert0 = std::make_shared<op::v0::Convert>(data0, element::f32);
|
||||
auto convert1 = std::make_shared<op::v0::Convert>(data1, element::f32);
|
||||
const std::vector<float> const_values{3, 2, 10};
|
||||
auto const_data = std::make_shared<op::v0::Constant>(element::f32, Shape{1, 3}, const_values);
|
||||
auto indata0 = std::make_shared<op::v0::Parameter>(element::f32, Shape{2, 3});
|
||||
auto indata1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
auto indata2 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<op::v1::Add>(indata0, indata1);
|
||||
auto sub = std::make_shared<op::v1::Subtract>(add, const_data);
|
||||
auto mul = std::make_shared<Subgraph>(NodeVector{convert0, convert1, const_data},
|
||||
std::make_shared<Model>(NodeVector{std::make_shared<op::v1::Multiply>(add, sub)},
|
||||
ParameterVector{indata0, indata1, indata2}));
|
||||
f_ref = std::make_shared<Model>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
}
|
||||
|
||||
auto res = compare_functions(f, f_ref);
|
||||
ASSERT_TRUE(res.first) << res.second;
|
||||
}
|
||||
|
||||
TEST(TransformationTests, DontStartSubgraphSingleOuptut) {
|
||||
std::shared_ptr<Function> f(nullptr), f_ref(nullptr);
|
||||
TEST(TransformationTests, TokenizeMulAddSubgraph) {
|
||||
SKIP_IF_CURRENT_TEST_IS_DISABLED()
|
||||
std::shared_ptr<Model> f(nullptr), f_ref(nullptr);
|
||||
{
|
||||
auto data0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<opset1::Add>(data0, data1);
|
||||
auto sub = std::make_shared<opset1::Subtract>(add, data1);
|
||||
auto mul = std::make_shared<opset1::Multiply>(data0, sub);
|
||||
f = std::make_shared<Function>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
auto data_1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{ 1, 3, 4, 4 });
|
||||
auto data_2 = std::make_shared<op::v0::Parameter>(element::f32, Shape{ 1, 3, 4, 4 });
|
||||
auto non_snippet_op = std::make_shared<op::v0::MatMul>(data_1, data_2);
|
||||
|
||||
auto mul_const_1 = op::v0::Constant::create(element::f32, { 1, 3, 1, 1 }, { 4.f });
|
||||
auto mul_1 = std::make_shared<op::v1::Multiply>(non_snippet_op, mul_const_1);
|
||||
auto add_const_1 = op::v0::Constant::create(element::f32, { 1, 3, 1, 1 }, { 16.4f });
|
||||
auto add_1 = std::make_shared<op::v1::Add>(mul_1, add_const_1);
|
||||
auto elu = std::make_shared<op::v0::Elu>(add_1, 0.01);
|
||||
|
||||
auto mul_const_2 = op::v0::Constant::create(element::f32, { 1, 3, 1, 1 }, { 4.f });
|
||||
auto mul_2 = std::make_shared<op::v1::Multiply>(non_snippet_op, mul_const_2);
|
||||
auto add_const_2 = op::v0::Constant::create(element::f32, { 1, 3, 1, 1 }, { 16.4f });
|
||||
auto add_2 = std::make_shared<op::v1::Add>(mul_2, add_const_2);
|
||||
auto relu = std::make_shared<op::v0::Relu>(add_2);
|
||||
|
||||
auto add = std::make_shared<op::v1::Add>(elu, relu);
|
||||
auto result = std::make_shared<op::v0::Result>(add);
|
||||
|
||||
f = std::make_shared<Model>(ResultVector{ result }, ParameterVector{ data_1, data_2 });
|
||||
|
||||
pass::Manager m;
|
||||
m.register_pass<pass::InitNodeInfo>();
|
||||
m.register_pass<snippets::pass::StartSubgraph>();
|
||||
m.register_pass<InitNodeInfo>();
|
||||
m.register_pass<EnumerateNodes>();
|
||||
m.register_pass<TokenizeSnippets>();
|
||||
m.run_passes(f);
|
||||
ASSERT_NO_THROW(check_rt_info(f));
|
||||
}
|
||||
{
|
||||
auto data_1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{ 1, 3, 4, 4 });
|
||||
auto data_2 = std::make_shared<op::v0::Parameter>(element::f32, Shape{ 1, 3, 4, 4 });
|
||||
|
||||
// snippet inputs
|
||||
auto non_snippet_op = std::make_shared<op::v0::MatMul>(data_1, data_2);
|
||||
auto mul_const_1 = op::v0::Constant::create(element::f32, { 1, 3, 1, 1 }, { 4.f });
|
||||
auto add_const_1 = op::v0::Constant::create(element::f32, { 1, 3, 1, 1 }, { 16.4f });
|
||||
auto mul_const_2 = op::v0::Constant::create(element::f32, { 1, 3, 1, 1 }, { 4.f });
|
||||
auto add_const_2 = op::v0::Constant::create(element::f32, { 1, 3, 1, 1 }, { 16.4f });
|
||||
|
||||
// snippet function
|
||||
auto snippet_input = std::make_shared<op::v0::Parameter>(element::f32, Shape{ 1, 3, 4, 4 });
|
||||
|
||||
auto sn_mul_const_1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{ 1, 3, 1, 1 });
|
||||
auto mul_1 = std::make_shared<op::v1::Multiply>(snippet_input, sn_mul_const_1);
|
||||
auto sn_add_const_1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{ 1, 3, 1, 1 });
|
||||
auto add_1 = std::make_shared<op::v1::Add>(mul_1, sn_add_const_1);
|
||||
auto elu = std::make_shared<op::v0::Elu>(add_1, 0.01);
|
||||
|
||||
auto sn_mul_const_2 = std::make_shared<op::v0::Parameter>(element::f32, Shape{ 1, 3, 1, 1 });
|
||||
auto mul_2 = std::make_shared<op::v1::Multiply>(snippet_input, sn_mul_const_2);
|
||||
auto sn_add_const_2 = std::make_shared<op::v0::Parameter>(element::f32, Shape{ 1, 3, 1, 1 });
|
||||
auto add_2 = std::make_shared<op::v1::Add>(mul_2, sn_add_const_2);
|
||||
auto relu = std::make_shared<op::v0::Relu>(add_2);
|
||||
|
||||
auto add = std::make_shared<op::v1::Add>(elu, relu);
|
||||
ParameterVector subgraph_params{ snippet_input, sn_mul_const_1, sn_add_const_1, sn_mul_const_2, sn_add_const_2 };
|
||||
auto snippet_function = std::make_shared<Model>(NodeVector{ add }, subgraph_params);
|
||||
|
||||
ngraph::NodeVector snippet_inputs{ non_snippet_op, mul_const_1, add_const_1, mul_const_2, add_const_2 };
|
||||
auto snippet = std::make_shared<Subgraph>(snippet_inputs, snippet_function);
|
||||
auto result = std::make_shared<op::v0::Result>(snippet);
|
||||
|
||||
f_ref = std::make_shared<Model>(NodeVector{ result }, ParameterVector{ data_1, data_2 });
|
||||
}
|
||||
|
||||
auto res = compare_functions(f, f_ref);
|
||||
ASSERT_TRUE(res.first) << res.second;
|
||||
}
|
||||
|
||||
|
||||
TEST(TransformationTests, DontStartSubgraphSingleOutput) {
|
||||
SKIP_IF_CURRENT_TEST_IS_DISABLED()
|
||||
std::shared_ptr<Model> f(nullptr), f_ref(nullptr);
|
||||
{
|
||||
auto data0 = std::make_shared<op::v0::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<op::v1::Add>(data0, data1);
|
||||
auto sub = std::make_shared<op::v1::Subtract>(add, data1);
|
||||
auto mul = std::make_shared<op::v1::Multiply>(data0, sub);
|
||||
f = std::make_shared<Model>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
|
||||
pass::Manager m;
|
||||
m.register_pass<InitNodeInfo>();
|
||||
m.register_pass<EnumerateNodes>();
|
||||
m.register_pass<TokenizeSnippets>();
|
||||
m.run_passes(f);
|
||||
ASSERT_NO_THROW(check_rt_info(f));
|
||||
}
|
||||
|
||||
{
|
||||
auto data0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<opset1::Add>(data0, data1);
|
||||
auto sub = std::make_shared<opset1::Subtract>(add, data1);
|
||||
auto mul = std::make_shared<opset1::Multiply>(data0, sub);
|
||||
f_ref = std::make_shared<Function>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
auto data0 = std::make_shared<op::v0::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<op::v1::Add>(data0, data1);
|
||||
auto sub = std::make_shared<op::v1::Subtract>(add, data1);
|
||||
auto mul = std::make_shared<op::v1::Multiply>(data0, sub);
|
||||
f_ref = std::make_shared<Model>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
}
|
||||
|
||||
auto res = compare_functions(f, f_ref);
|
||||
@@ -82,35 +202,37 @@ TEST(TransformationTests, DontStartSubgraphSingleOuptut) {
|
||||
}
|
||||
|
||||
TEST(TransformationTests, AttachToSubgraph) {
|
||||
std::shared_ptr<Function> f(nullptr), f_ref(nullptr);
|
||||
SKIP_IF_CURRENT_TEST_IS_DISABLED()
|
||||
std::shared_ptr<Model> f(nullptr), f_ref(nullptr);
|
||||
{
|
||||
auto data0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto indata0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto indata1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<snippets::op::Subgraph>(NodeVector{data0, data1},
|
||||
std::make_shared<Function>(NodeVector{std::make_shared<opset1::Add>(indata0, indata1)}, ParameterVector{indata0, indata1}));
|
||||
auto neg = std::make_shared<opset1::Negative>(add);
|
||||
auto concat = std::make_shared<opset1::Concat>(NodeVector{add, neg}, 0);
|
||||
f = std::make_shared<Function>(NodeVector{concat}, ParameterVector{data0, data1});
|
||||
auto data0 = std::make_shared<op::v0::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
auto indata0 = std::make_shared<op::v0::Parameter>(element::f32, Shape{2, 3});
|
||||
auto indata1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<Subgraph>(NodeVector{data0, data1},
|
||||
std::make_shared<Model>(NodeVector{std::make_shared<op::v1::Add>(indata0, indata1)}, ParameterVector{indata0, indata1}));
|
||||
auto neg = std::make_shared<op::v0::Negative>(add);
|
||||
auto concat = std::make_shared<op::v0::Concat>(NodeVector{add, neg}, 0);
|
||||
f = std::make_shared<Model>(NodeVector{concat}, ParameterVector{data0, data1});
|
||||
|
||||
pass::Manager m;
|
||||
m.register_pass<pass::InitNodeInfo>();
|
||||
m.register_pass<snippets::pass::AttachToSubgraph>();
|
||||
m.register_pass<InitNodeInfo>();
|
||||
m.register_pass<EnumerateNodes>();
|
||||
m.register_pass<TokenizeSnippets>();
|
||||
m.run_passes(f);
|
||||
ASSERT_NO_THROW(check_rt_info(f));
|
||||
}
|
||||
|
||||
{
|
||||
auto data0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto indata0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto indata1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto inner = std::make_shared<opset1::Add>(indata0, indata1);
|
||||
auto add = std::make_shared<snippets::op::Subgraph>(NodeVector{data0, data1},
|
||||
std::make_shared<Function>(NodeVector{std::make_shared<opset1::Negative>(inner), inner}, ParameterVector{indata0, indata1}));
|
||||
auto concat = std::make_shared<opset1::Concat>(OutputVector{add->output(0), add->output(1)}, 0);
|
||||
f_ref = std::make_shared<Function>(NodeVector{concat}, ParameterVector{data0, data1});
|
||||
auto data0 = std::make_shared<op::v0::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
auto indata0 = std::make_shared<op::v0::Parameter>(element::f32, Shape{2, 3});
|
||||
auto indata1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
auto inner = std::make_shared<op::v1::Add>(indata0, indata1);
|
||||
auto add = std::make_shared<Subgraph>(NodeVector{data0, data1},
|
||||
std::make_shared<Model>(NodeVector{std::make_shared<op::v0::Negative>(inner), inner}, ParameterVector{indata0, indata1}));
|
||||
auto concat = std::make_shared<op::v0::Concat>(OutputVector{add->output(0), add->output(1)}, 0);
|
||||
f_ref = std::make_shared<Model>(NodeVector{concat}, ParameterVector{data0, data1});
|
||||
}
|
||||
|
||||
auto res = compare_functions(f, f_ref);
|
||||
@@ -118,37 +240,48 @@ TEST(TransformationTests, AttachToSubgraph) {
|
||||
}
|
||||
|
||||
TEST(TransformationTests, DontAttachToSubgraphIfLoop) {
|
||||
std::shared_ptr<Function> f(nullptr), f_ref(nullptr);
|
||||
SKIP_IF_CURRENT_TEST_IS_DISABLED()
|
||||
std::shared_ptr<Model> f(nullptr), f_ref(nullptr);
|
||||
{
|
||||
auto data0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto indata0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto indata1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<snippets::op::Subgraph>(NodeVector{data0, data1},
|
||||
std::make_shared<Function>(NodeVector{std::make_shared<opset1::Add>(indata0, indata1)}, ParameterVector{indata0, indata1}));
|
||||
auto log = std::make_shared<opset1::Log>(add);
|
||||
auto mul = std::make_shared<opset1::Multiply>(add, log);
|
||||
f = std::make_shared<Function>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
auto data0 = std::make_shared<op::v0::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
auto indata0 = std::make_shared<op::v0::Parameter>(element::f32, Shape{2, 3});
|
||||
auto indata1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<Subgraph>(NodeVector{data0, data1},
|
||||
std::make_shared<Model>(NodeVector{std::make_shared<op::v1::Add>(indata0, indata1)}, ParameterVector{indata0, indata1}));
|
||||
auto log = std::make_shared<op::v0::Log>(add);
|
||||
auto mul = std::make_shared<op::v1::Multiply>(add, log);
|
||||
f = std::make_shared<Model>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
|
||||
pass::Manager m;
|
||||
m.register_pass<pass::InitNodeInfo>();
|
||||
m.register_pass<snippets::pass::AttachToSubgraph>();
|
||||
m.register_pass<InitNodeInfo>();
|
||||
m.register_pass<EnumerateNodes>();
|
||||
m.register_pass<TokenizeSnippets>();
|
||||
m.run_passes(f);
|
||||
ASSERT_NO_THROW(check_rt_info(f));
|
||||
}
|
||||
|
||||
{
|
||||
auto data0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto indata0 = std::make_shared<opset1::Parameter>(element::f32, Shape{2, 3});
|
||||
auto indata1 = std::make_shared<opset1::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<snippets::op::Subgraph>(NodeVector{data0, data1},
|
||||
std::make_shared<Function>(NodeVector{std::make_shared<opset1::Add>(indata0, indata1)}, ParameterVector{indata0, indata1}));
|
||||
auto log = std::make_shared<opset1::Log>(add);
|
||||
auto mul = std::make_shared<opset1::Multiply>(add, log);
|
||||
f_ref = std::make_shared<Function>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
auto data0 = std::make_shared<op::v0::Parameter>(element::f32, Shape{2, 3});
|
||||
auto data1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
auto indata0 = std::make_shared<op::v0::Parameter>(element::f32, Shape{2, 3});
|
||||
auto indata1 = std::make_shared<op::v0::Parameter>(element::f32, Shape{1, 3});
|
||||
auto add = std::make_shared<Subgraph>(NodeVector{data0, data1},
|
||||
std::make_shared<Model>(NodeVector{std::make_shared<op::v1::Add>(indata0, indata1)}, ParameterVector{indata0, indata1}));
|
||||
auto log = std::make_shared<op::v0::Log>(add);
|
||||
/*
|
||||
* Note that log is not currently supported by snippets, so it won't be converted to subgraph.
|
||||
* Mul will be converted for the "reset" continuation strategy, (present case)
|
||||
* or left as-is for the "abort" continuation strategy
|
||||
*/
|
||||
auto add_param = std::make_shared<op::v0::Parameter>(element::f32, add->get_output_shape(0));
|
||||
auto log_param = std::make_shared<op::v0::Parameter>(element::f32, log->get_output_shape(0));
|
||||
auto mul = std::make_shared<Subgraph>(NodeVector{add, log},
|
||||
std::make_shared<Model>(NodeVector{std::make_shared<op::v1::Multiply>(add_param, log_param)},
|
||||
ParameterVector{add_param, log_param}));
|
||||
f_ref = std::make_shared<Model>(NodeVector{mul}, ParameterVector{data0, data1});
|
||||
}
|
||||
|
||||
auto res = compare_functions(f, f_ref);
|
||||
ASSERT_TRUE(res.first) << res.second;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ addIeTargetTest(
|
||||
ov_shape_inference
|
||||
inference_engine_s
|
||||
unitTestUtils
|
||||
inference_engine_snippets
|
||||
ngraphFunctions
|
||||
ADD_CPPLINT
|
||||
LABELS
|
||||
CPU
|
||||
@@ -31,3 +33,8 @@ addIeTargetTest(
|
||||
ie_faster_build(${TARGET_NAME}
|
||||
UNITY
|
||||
)
|
||||
|
||||
target_include_directories(${TARGET_NAME} SYSTEM PRIVATE
|
||||
${IE_MAIN_SOURCE_DIR}/thirdparty/mkl-dnn/src/common
|
||||
${IE_MAIN_SOURCE_DIR}/thirdparty/mkl-dnn/src/cpu
|
||||
${IE_MAIN_SOURCE_DIR}/thirdparty/mkl-dnn/include)
|
||||
@@ -0,0 +1,427 @@
|
||||
// Copyright (C) 2020 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <map>
|
||||
#include <array>
|
||||
|
||||
#include <snippets/generator.hpp>
|
||||
|
||||
#include <ngraph/function.hpp>
|
||||
|
||||
#include <ngraph/opsets/opset1.hpp>
|
||||
#include <ngraph/opsets/opset2.hpp>
|
||||
|
||||
#include <ngraph/pass/constant_folding.hpp>
|
||||
#include <ngraph/pass/visualize_tree.hpp>
|
||||
|
||||
#include <snippets/op/subgraph.hpp>
|
||||
|
||||
#include <transformations/init_node_info.hpp>
|
||||
#include <transformations/utils/utils.hpp>
|
||||
|
||||
#include <ngraph_functions/utils/ngraph_helpers.hpp>
|
||||
|
||||
using namespace testing;
|
||||
|
||||
auto gen_inputs(const ngraph::Shape& shape, size_t n = 2) -> std::vector<std::vector<std::uint8_t>>{
|
||||
std::vector<std::vector<std::uint8_t>> referenceInputs(n);
|
||||
for (size_t k = 0; k < n; k++) {
|
||||
referenceInputs[k].resize(ngraph::shape_size(shape)*sizeof(float));
|
||||
float* in0 = reinterpret_cast<float*>(&referenceInputs[k][0]);
|
||||
|
||||
for (size_t i = 0; i < ngraph::shape_size(shape); i++) {
|
||||
if (k % 3 == 0) {
|
||||
in0[i] = i / 2048.f;
|
||||
} else if (k % 3 == 1) {
|
||||
in0[i] = 1 - i / 2048.f;
|
||||
} else {
|
||||
in0[i] = i / 1024.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
return referenceInputs;
|
||||
}
|
||||
|
||||
auto compare(std::shared_ptr<ngraph::Function>& s, std::shared_ptr<ngraph::Function>& f, std::vector<std::vector<std::uint8_t>>& in) -> bool{
|
||||
auto act = ngraph::helpers::interpreterFunction(s, in);
|
||||
auto exp = ngraph::helpers::interpreterFunction(f, in);
|
||||
|
||||
const float* pexp = reinterpret_cast<float*>(&exp[0].second[0]);
|
||||
const float* pact = reinterpret_cast<float*>(&act[0].second[0]);
|
||||
|
||||
bool isCorrect = true;
|
||||
for (int i = 0; i < ngraph::shape_size(f->get_result()->get_shape()); i++) {
|
||||
if (std::abs(pexp[i]-pact[i]) > std::numeric_limits<float>::epsilon()
|
||||
|| std::isnan(pexp[i]) != std::isnan(pact[i])) {
|
||||
isCorrect = false;
|
||||
std::cout << i << " expected " << pexp[i] << " actual " << pact[i] << " diff " << std::abs(pexp[i]-pact[i]) << std::endl;
|
||||
}
|
||||
}
|
||||
return isCorrect;
|
||||
}
|
||||
|
||||
auto wrapAsSnippet(std::shared_ptr<ngraph::Function>& f, const ngraph::Shape& shape0, const ngraph::Shape& shape1)
|
||||
-> std::shared_ptr<ngraph::Function>{
|
||||
auto input0 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape0);
|
||||
auto input1 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape1);
|
||||
auto snippet = std::make_shared<ngraph::snippets::op::Subgraph>(ngraph::OutputVector{input0, input1}, ngraph::clone_function(*f.get()));
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{snippet}, ngraph::ParameterVector{input0, input1});
|
||||
}
|
||||
|
||||
auto wrapAsSnippet(std::shared_ptr<ngraph::Function>& f, const ngraph::Shape& shape0)
|
||||
-> std::shared_ptr<ngraph::Function>{
|
||||
auto input0 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape0);
|
||||
auto snippet = std::make_shared<ngraph::snippets::op::Subgraph>(ngraph::OutputVector{input0}, ngraph::clone_function(*f.get()));
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{snippet}, ngraph::ParameterVector{input0});
|
||||
}
|
||||
|
||||
// Todo: Reimplement Snippets tests, so they won't require evaluate method for OP Subgraph
|
||||
// In more detail, we can't use interpreter backend for op::Subgraph evaluation, since it will
|
||||
// depend on the snippets lib in this (which is not allowed).
|
||||
TEST(SnippetsTests, GenerateAddParams) {
|
||||
GTEST_SKIP();
|
||||
auto shape = ngraph::Shape{1, 4, 16, 31};
|
||||
|
||||
auto f = ([] (const ngraph::Shape& shape) -> std::shared_ptr<ngraph::Function>{
|
||||
auto input0 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto input1 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto add = std::make_shared<ngraph::opset1::Add>(input0, input1);
|
||||
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{add}, ngraph::ParameterVector{input0, input1});
|
||||
})(shape);
|
||||
|
||||
auto s = wrapAsSnippet(f, shape, shape);
|
||||
auto referenceInputs = gen_inputs(shape, 2);
|
||||
bool isCorrect = compare(s, f, referenceInputs);
|
||||
ASSERT_TRUE(isCorrect) << "snippet and native implementation differs";
|
||||
}
|
||||
|
||||
TEST(SnippetsTests, GenerateAddConstant) {
|
||||
GTEST_SKIP();
|
||||
auto shape = ngraph::Shape{1, 4, 16, 31};
|
||||
|
||||
auto f = ([] (const ngraph::Shape& shape) -> std::shared_ptr<ngraph::Function>{
|
||||
auto input0 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
|
||||
std::vector<float> vals(ngraph::shape_size(shape));
|
||||
for (int i = 0; i < ngraph::shape_size(shape); i++) {
|
||||
vals[i] = 1-i/2048.f;
|
||||
}
|
||||
auto input1 = std::make_shared<ngraph::opset1::Constant>(ngraph::element::f32, shape, vals);
|
||||
auto add = std::make_shared<ngraph::opset1::Add>(input0, input1);
|
||||
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{add}, ngraph::ParameterVector{input0});
|
||||
})(shape);
|
||||
|
||||
auto s = wrapAsSnippet(f, shape);
|
||||
auto referenceInputs = gen_inputs(shape, 1);
|
||||
bool isCorrect = compare(s, f, referenceInputs);
|
||||
ASSERT_TRUE(isCorrect) << "snippet and native implementation differs";
|
||||
}
|
||||
|
||||
TEST(SnippetsTests, GenerateAddConstantScalar) {
|
||||
GTEST_SKIP();
|
||||
auto shape = ngraph::Shape{1, 4, 16, 31};
|
||||
|
||||
auto f = ([] (const ngraph::Shape& shape) -> std::shared_ptr<ngraph::Function>{
|
||||
auto input0 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto input1 = std::make_shared<ngraph::opset1::Constant>(ngraph::element::f32, ngraph::Shape{1}, std::vector<float>({42.f}));
|
||||
auto add = std::make_shared<ngraph::opset1::Add>(input0, input1);
|
||||
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{add}, ngraph::ParameterVector{input0});
|
||||
})(shape);
|
||||
|
||||
auto s = wrapAsSnippet(f, shape);
|
||||
auto referenceInputs = gen_inputs(shape, 1);
|
||||
bool isCorrect = compare(s, f, referenceInputs);
|
||||
ASSERT_TRUE(isCorrect) << "snippet and native implementation differs";
|
||||
}
|
||||
|
||||
TEST(SnippetsTests, GenerateAddConstantScalarEmptySize) {
|
||||
GTEST_SKIP();
|
||||
auto shape = ngraph::Shape{1, 4, 16, 31};
|
||||
|
||||
auto f = ([] (const ngraph::Shape& shape) -> std::shared_ptr<ngraph::Function>{
|
||||
auto input0 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto input1 = std::make_shared<ngraph::opset1::Constant>(ngraph::element::f32, ngraph::Shape(), std::vector<float>({42.f}));
|
||||
auto add = std::make_shared<ngraph::opset1::Add>(input0, input1);
|
||||
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{add}, ngraph::ParameterVector{input0});
|
||||
})(shape);
|
||||
|
||||
auto s = wrapAsSnippet(f, shape);
|
||||
auto referenceInputs = gen_inputs(shape, 1);
|
||||
bool isCorrect = compare(s, f, referenceInputs);
|
||||
ASSERT_TRUE(isCorrect) << "snippet and native implementation differs";
|
||||
}
|
||||
|
||||
TEST(SnippetsTests, GenerateAddBroadcastX2Edges) {
|
||||
GTEST_SKIP();
|
||||
auto shape0 = ngraph::Shape{1, 4, 16, 31};
|
||||
auto shape1 = ngraph::Shape{1, 4, 16, 1};
|
||||
|
||||
auto f = ([] (const ngraph::Shape& shape0, const ngraph::Shape& shape1) -> std::shared_ptr<ngraph::Function>{
|
||||
auto input0 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape0);
|
||||
auto input1 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape1);
|
||||
auto input2 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape1);
|
||||
|
||||
auto add = std::make_shared<ngraph::opset1::Add>(input0, input1);
|
||||
auto mul = std::make_shared<ngraph::opset1::Add>(input1, input2);
|
||||
|
||||
auto sub = std::make_shared<ngraph::opset1::Add>(add, mul);
|
||||
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{sub}, ngraph::ParameterVector{input0, input1, input2});
|
||||
})(shape0, shape1);
|
||||
|
||||
auto s = ([f] (const ngraph::Shape& shape0, const ngraph::Shape& shape1) -> std::shared_ptr<ngraph::Function>{
|
||||
auto input2 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape0);
|
||||
auto input3 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape1);
|
||||
auto input4 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape1);
|
||||
auto snippet = std::make_shared<ngraph::snippets::op::Subgraph>(ngraph::OutputVector{input2, input3, input4}, ngraph::clone_function(*f.get()));
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{snippet}, ngraph::ParameterVector{input2, input3, input4});
|
||||
})(shape0, shape1);
|
||||
|
||||
std::vector<std::vector<std::uint8_t>> referenceInputs(3);
|
||||
referenceInputs[0].resize(ngraph::shape_size(shape0)*sizeof(float));
|
||||
referenceInputs[1].resize(ngraph::shape_size(shape1)*sizeof(float));
|
||||
referenceInputs[2].resize(ngraph::shape_size(shape1)*sizeof(float));
|
||||
|
||||
float* in0 = reinterpret_cast<float*>(&referenceInputs[0][0]);
|
||||
float* in1 = reinterpret_cast<float*>(&referenceInputs[1][0]);
|
||||
float* in2 = reinterpret_cast<float*>(&referenceInputs[2][0]);
|
||||
for (int i = 0; i < ngraph::shape_size(shape0); i++) {
|
||||
in0[i] = i/2048.f;
|
||||
}
|
||||
|
||||
in1[0] = 1.f;
|
||||
in2[0] = 0.42f;
|
||||
|
||||
auto act = ngraph::helpers::interpreterFunction(s, referenceInputs);
|
||||
auto exp = ngraph::helpers::interpreterFunction(f, referenceInputs);
|
||||
|
||||
const float* pexp = reinterpret_cast<float*>(&exp[0].second[0]);
|
||||
const float* pact = reinterpret_cast<float*>(&act[0].second[0]);
|
||||
|
||||
bool isCorrect = true;
|
||||
for (int i = 0; i < ngraph::shape_size(shape0); i++) {
|
||||
if (pexp[i] != pact[i]) {
|
||||
isCorrect = false;
|
||||
std::cout << i << " expected " << pexp[i] << " actual " << pact[i] << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(isCorrect) << "snippet and native implementation differs";
|
||||
}
|
||||
|
||||
TEST(SnippetsTests, GenerateAddBroadcastY) {
|
||||
GTEST_SKIP();
|
||||
auto shape0 = ngraph::Shape{1, 4, 16, 31};
|
||||
auto shape1 = ngraph::Shape{1, 4, 1, 31};
|
||||
|
||||
auto f = ([] (const ngraph::Shape& shape0, const ngraph::Shape& shape1) -> std::shared_ptr<ngraph::Function>{
|
||||
auto input0 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape0);
|
||||
auto input1 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape1);
|
||||
auto add = std::make_shared<ngraph::opset1::Add>(input0, input1);
|
||||
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{add}, ngraph::ParameterVector{input0, input1});
|
||||
})(shape0, shape1);
|
||||
|
||||
auto s = wrapAsSnippet(f, shape0, shape1);
|
||||
|
||||
std::vector<std::vector<std::uint8_t>> referenceInputs(2);
|
||||
referenceInputs[0].resize(ngraph::shape_size(shape0)*sizeof(float));
|
||||
referenceInputs[1].resize(ngraph::shape_size(shape1)*sizeof(float));
|
||||
|
||||
float* in0 = reinterpret_cast<float*>(&referenceInputs[0][0]);
|
||||
float* in1 = reinterpret_cast<float*>(&referenceInputs[1][0]);
|
||||
for (size_t i = 0; i < ngraph::shape_size(shape0); i++) {
|
||||
in0[i] = i / 2048.f;
|
||||
}
|
||||
for (size_t i = 0; i < ngraph::shape_size(shape1); i++) {
|
||||
in1[i] = 1 - i / 2048.f;
|
||||
}
|
||||
|
||||
auto act = ngraph::helpers::interpreterFunction(s, referenceInputs);
|
||||
auto exp = ngraph::helpers::interpreterFunction(f, referenceInputs);
|
||||
|
||||
const float* pexp = reinterpret_cast<float*>(&exp[0].second[0]);
|
||||
const float* pact = reinterpret_cast<float*>(&act[0].second[0]);
|
||||
|
||||
bool isCorrect = true;
|
||||
for (size_t i = 0; i < ngraph::shape_size(shape0); i++) {
|
||||
if (pexp[i] != pact[i]) {
|
||||
isCorrect = false;
|
||||
std::cout << i << " expected " << pexp[i] << " actual " << pact[i] << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(isCorrect) << "snippet and native implementation differs";
|
||||
}
|
||||
|
||||
TEST(SnippetsTests, GenerateAddNegate) {
|
||||
GTEST_SKIP();
|
||||
auto shape = ngraph::Shape{1, 4, 16, 31};
|
||||
|
||||
auto f = ([] (const ngraph::Shape& shape) -> std::shared_ptr<ngraph::Function>{
|
||||
auto input0 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto input1 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto add = std::make_shared<ngraph::opset1::Add>(input0, input1);
|
||||
auto nagate = std::make_shared<ngraph::opset1::Negative>(add);
|
||||
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{nagate}, ngraph::ParameterVector{input0, input1});
|
||||
})(shape);
|
||||
|
||||
auto s = ([f] (const ngraph::Shape& shape) -> std::shared_ptr<ngraph::Function>{
|
||||
auto input2 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto input3 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto snippet = std::make_shared<ngraph::snippets::op::Subgraph>(ngraph::OutputVector{input2, input3}, ngraph::clone_function(*f.get()));
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{snippet}, ngraph::ParameterVector{input2, input3});
|
||||
})(shape);
|
||||
|
||||
auto referenceInputs = gen_inputs(shape, 2);
|
||||
bool isCorrect = compare(s, f, referenceInputs);
|
||||
|
||||
ASSERT_TRUE(isCorrect) << "snippet and native implementation differs";
|
||||
}
|
||||
|
||||
TEST(SnippetsTests, GenerateAddNegateAdd) {
|
||||
GTEST_SKIP();
|
||||
auto shape = ngraph::Shape{1, 4, 16, 31};
|
||||
auto input1 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto input2 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto add = std::make_shared<ngraph::opset1::Add>(input1, input2);
|
||||
auto nagate = std::make_shared<ngraph::opset1::Negative>(add);
|
||||
auto input3 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto add2 = std::make_shared<ngraph::opset1::Add>(nagate, input3);
|
||||
std::shared_ptr<ngraph::Function> f = std::make_shared<ngraph::Function>(ngraph::NodeVector{add2}, ngraph::ParameterVector{input1, input2, input3});
|
||||
|
||||
auto input11 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto input21 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto input31 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto snippet = std::make_shared<ngraph::snippets::op::Subgraph>(ngraph::OutputVector{input11, input21, input31}, ngraph::clone_function(*f.get()));
|
||||
std::shared_ptr<ngraph::Function> s = std::make_shared<ngraph::Function>(ngraph::NodeVector{snippet}, ngraph::ParameterVector{input11, input21, input31});
|
||||
|
||||
auto referenceInputs = gen_inputs(shape, 3);
|
||||
bool isCorrect = compare(s, f, referenceInputs);
|
||||
|
||||
ASSERT_TRUE(isCorrect) << "snippet and native implementation differs";
|
||||
}
|
||||
|
||||
TEST(SnippetsTests, GenerateAddNegateAddMultiEdge) {
|
||||
GTEST_SKIP();
|
||||
auto shape = ngraph::Shape{1, 4, 16, 31};
|
||||
auto input1 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto input2 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto add = std::make_shared<ngraph::opset1::Add>(input1, input2);
|
||||
auto nagate = std::make_shared<ngraph::opset1::Negative>(add);
|
||||
auto add2 = std::make_shared<ngraph::opset1::Add>(nagate, input1);
|
||||
std::shared_ptr<ngraph::Function> f = std::make_shared<ngraph::Function>(ngraph::NodeVector{add2}, ngraph::ParameterVector{input1, input2});
|
||||
|
||||
auto s = wrapAsSnippet(f, shape, shape);
|
||||
auto referenceInputs = gen_inputs(shape, 2);
|
||||
bool isCorrect = compare(s, f, referenceInputs);
|
||||
|
||||
ASSERT_TRUE(isCorrect) << "snippet and native implementation differs";
|
||||
}
|
||||
|
||||
TEST(SnippetsTests, GenerateAddNegateAddMultiEdgeConst) {
|
||||
GTEST_SKIP();
|
||||
auto shape = ngraph::Shape{1, 4, 16, 31};
|
||||
auto input1 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto input2 = ngraph::op::Constant::create(ngraph::element::f32, ngraph::Shape{}, {0.42});
|
||||
auto add = std::make_shared<ngraph::opset1::Add>(input1, input2);
|
||||
auto nagate = std::make_shared<ngraph::opset1::Negative>(add);
|
||||
auto add2 = std::make_shared<ngraph::opset1::Add>(nagate, input1);
|
||||
std::shared_ptr<ngraph::Function> f = std::make_shared<ngraph::Function>(ngraph::NodeVector{add2}, ngraph::ParameterVector{input1});
|
||||
|
||||
auto input11 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto snippet = std::make_shared<ngraph::snippets::op::Subgraph>(ngraph::OutputVector{input11}, ngraph::clone_function(*f.get()));
|
||||
std::shared_ptr<ngraph::Function> s = std::make_shared<ngraph::Function>(ngraph::NodeVector{snippet}, ngraph::ParameterVector{input11});
|
||||
|
||||
auto referenceInputs = gen_inputs(shape, 1);
|
||||
bool isCorrect = compare(s, f, referenceInputs);
|
||||
|
||||
ASSERT_TRUE(isCorrect) << "snippet and native implementation differs";
|
||||
}
|
||||
|
||||
TEST(SnippetsTests, GenerateErf) {
|
||||
GTEST_SKIP();
|
||||
auto shape = ngraph::Shape{1, 4, 16, 31};
|
||||
|
||||
auto input1 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto gelu = std::make_shared<ngraph::opset1::Erf>(input1);
|
||||
std::shared_ptr<ngraph::Function> f = std::make_shared<ngraph::Function>(ngraph::NodeVector{gelu}, ngraph::ParameterVector{input1});
|
||||
|
||||
auto input11 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape);
|
||||
auto snippet = std::make_shared<ngraph::snippets::op::Subgraph>(ngraph::OutputVector{input11}, ngraph::clone_function(*f.get()));
|
||||
std::shared_ptr<ngraph::Function> s = std::make_shared<ngraph::Function>(ngraph::NodeVector{snippet}, ngraph::ParameterVector{input11});
|
||||
|
||||
auto referenceInputs = gen_inputs(shape, 1);
|
||||
bool isCorrect = compare(s, f, referenceInputs);
|
||||
|
||||
ASSERT_TRUE(isCorrect) << "snippet and native implementation differs";
|
||||
}
|
||||
|
||||
// ToDO: implement tile selection logic & broadcast emission to make it working. Broadcast substitution works
|
||||
TEST(SnippetsTests, GenerateAddBroadcastAutomatic) {
|
||||
GTEST_SKIP();
|
||||
std::array<ngraph::Shape, 3> shapes {
|
||||
ngraph::Shape{1, 4, 16, 31},
|
||||
ngraph::Shape{1, 4, 16, 1},
|
||||
ngraph::Shape{1, 4, 16, 1}
|
||||
};
|
||||
|
||||
auto f = ([] (const ngraph::Shape& shape0, const ngraph::Shape& shape1, const ngraph::Shape& shape2) -> std::shared_ptr<ngraph::Function>{
|
||||
auto input0 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape0);
|
||||
auto input1 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape1);
|
||||
auto input2 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape2);
|
||||
|
||||
auto add = std::make_shared<ngraph::opset1::Add>(input0, input1);
|
||||
auto mul = std::make_shared<ngraph::opset1::Multiply>(input1, input2);
|
||||
auto sub = std::make_shared<ngraph::opset1::Subtract>(add, mul);
|
||||
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{sub}, ngraph::ParameterVector{input0, input1, input2});
|
||||
})(shapes[0], shapes[1], shapes[2]);
|
||||
|
||||
auto s = ([f] (const ngraph::Shape& shape0, const ngraph::Shape& shape1, const ngraph::Shape& shape2) -> std::shared_ptr<ngraph::Function>{
|
||||
auto input0 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape0);
|
||||
auto input1 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape1);
|
||||
auto input2 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, shape2);
|
||||
auto snippet = std::make_shared<ngraph::snippets::op::Subgraph>(ngraph::OutputVector{input0, input1, input2}, ngraph::clone_function(*f.get()));
|
||||
return std::make_shared<ngraph::Function>(ngraph::NodeVector{snippet}, ngraph::ParameterVector{input0, input1, input2});
|
||||
})(shapes[0], shapes[1], shapes[2]);
|
||||
|
||||
std::vector<std::vector<std::uint8_t>> referenceInputs(3);
|
||||
for (int k = 0; k < referenceInputs.size(); k++) {
|
||||
referenceInputs[k].resize(ngraph::shape_size(shapes[k]) * sizeof(float));
|
||||
|
||||
auto in0 = reinterpret_cast<float*>(&referenceInputs[k][0]);
|
||||
for (int i = 0; i < ngraph::shape_size(shapes[k]); i++) {
|
||||
in0[i] = k == 0 ? i/2048.f : (k == 1 ? 1.f : 0.42f);
|
||||
}
|
||||
}
|
||||
|
||||
auto exp = ngraph::helpers::interpreterFunction(f, referenceInputs);
|
||||
auto act = ngraph::helpers::interpreterFunction(s, referenceInputs);
|
||||
|
||||
const float* pexp = reinterpret_cast<float*>(&exp[0].second[0]);
|
||||
const float* pact = reinterpret_cast<float*>(&act[0].second[0]);
|
||||
|
||||
bool isCorrect = true;
|
||||
for (size_t i = 0; i < ngraph::shape_size(shapes[0]); i++) {
|
||||
if (pexp[i] != pact[i]) {
|
||||
isCorrect = false;
|
||||
std::cout << i << " expected " << pexp[i] << " actual " << pact[i] << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(isCorrect) << "snippet and native implementation differs";
|
||||
}
|
||||
Reference in new issue
Block a user