[CPU] Ngram node fusion (#16131)

This commit is contained in:
Vladislav Golubev authored and GitHub committed 2023-03-29 13:58:41 +04:00
1 parent f4da729a19
commit df3c06ecb4
15 files changed
+2650 -5

No files matched your search

@@ -4,12 +4,17 @@
set(TARGET_NAME "ov_tensorflow_frontend_tests")
list(APPEND TF_TESTS_DEPENDENCIES tensorflow_test_models tensorflow_fe_standalone_build_test openvino_intel_cpu_plugin)
if (NOT ENABLE_INTEL_CPU)
list(REMOVE_ITEM TF_TESTS_DEPENDENCIES openvino_intel_cpu_plugin)
set(EXCLUDED_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/compilation.cpp)
endif()
ov_add_test_target(
NAME ${TARGET_NAME}
ROOT ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDENCIES
tensorflow_test_models
tensorflow_fe_standalone_build_test
EXCLUDED_SOURCE_PATHS ${EXCLUDED_TESTS}
DEPENDENCIES ${TF_TESTS_DEPENDENCIES}
LINK_LIBRARIES
gtest_main_manifest
frontend_shared_test_classes
@@ -0,0 +1,44 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <openvino/frontend/manager.hpp>
#include <openvino/openvino.hpp>
#include "gtest/gtest.h"
#include "tf_utils.hpp"
#include "utils.hpp"
namespace {
std::shared_ptr<ov::Model> convert_model(const std::string& model_path) {
ov::frontend::FrontEndManager fem;
auto front_end = fem.load_by_framework(TF_FE);
if (!front_end) {
throw "TensorFlow Frontend is not initialized";
}
auto model_filename = FrontEndTestUtils::make_model_path(std::string(TEST_TENSORFLOW_MODELS_DIRNAME) + model_path);
auto input_model = front_end->load(model_filename);
if (!input_model) {
throw "Input model is not read";
}
auto model = front_end->convert(input_model);
if (!model) {
throw "Model is not converted";
}
return model;
}
} // namespace
class CompileModelsTests : public ::testing::Test {};
TEST_F(CompileModelsTests, NgramCompilation) {
ov::Core core;
auto model = convert_model("model_ngram/model_ngram.pb");
ov::CompiledModel compiled_model = core.compile_model(model, "CPU");
const auto runtime_model = compiled_model.get_runtime_model();
EXPECT_EQ(runtime_model->get_ordered_ops().size(), 4);
EXPECT_EQ(runtime_model->get_parameters().size(), 2);
EXPECT_EQ(runtime_model->get_results().size(), 1);
}
File diff suppressed because it is too large. Load diff
+4 -1
View File
@@ -205,7 +205,8 @@ const InferenceEngine::details::caseless_unordered_map<std::string, Type> type_t
{ "PriorBoxClustered", Type::PriorBoxClustered},
{"Interaction", Type::Interaction},
{ "MHA", Type::MHA},
{ "Unique", Type::Unique}
{ "Unique", Type::Unique},
{ "Ngram", Type::Ngram}
};
Type TypeFromName(const std::string& type) {
@@ -403,6 +404,8 @@ std::string NameFromType(const Type type) {
return "MHA";
case Type::Unique:
return "Unique";
case Type::Ngram:
return "Ngram";
default:
return "Unknown";
}
+2 -1
View File
@@ -110,7 +110,8 @@ enum class Type {
PriorBoxClustered,
Interaction,
MHA,
Unique
Unique,
Ngram
};
enum class Algorithm {
+2
View File
@@ -9,6 +9,7 @@
#include "ngraph_transformations/op/power_static.hpp"
#include "ngraph_transformations/op/swish_cpu.hpp"
#include "ngraph_transformations/op/mha.hpp"
#include "ngraph_transformations/op/ngram.hpp"
#include "snippets_transformations/op/load_convert.hpp"
#include "snippets_transformations/op/store_convert.hpp"
#include "snippets_transformations/op/brgemm_cpu.hpp"
@@ -52,6 +53,7 @@ std::map<std::string, ngraph::OpSet> Extension::getOpSets() {
NGRAPH_OP(PowerStaticNode, ov::intel_cpu)
NGRAPH_OP(SwishNode, ov::intel_cpu)
NGRAPH_OP(MHANode, ov::intel_cpu)
NGRAPH_OP(NgramNode, ov::intel_cpu)
NGRAPH_OP(LoadConvertSaturation, ov::intel_cpu)
NGRAPH_OP(LoadConvertTruncation, ov::intel_cpu)
NGRAPH_OP(StoreConvertSaturation, ov::intel_cpu)
@@ -19,6 +19,7 @@
#include "transformations/utils/utils.hpp"
#include "rnn_sequences_optimization.hpp"
#include "transformations/common_optimizations/reshape_sequence_fusion.hpp"
#include "ngram_fusion.hpp"
#include "itt.hpp"
@@ -45,6 +46,7 @@ inline void ConvertToCPUSpecificOpset(std::shared_ptr<ngraph::Function> &nGraphF
manager.register_pass<ov::pass::ReshapeSequenceFusion>();
manager.register_pass<ngraph::pass::ConstantFolding>();
manager.register_pass<ov::pass::ConvertPrecision>(precisions_map {{ ngraph::element::i64, ngraph::element::i32 }});
manager.register_pass<NgramFusion>();
manager.register_pass<ov::pass::Validate>();
manager.run_passes(nGraphFunc);
@@ -0,0 +1,185 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "ngram_fusion.hpp"
#include "op/ngram.hpp"
#include <openvino/opsets/opset1.hpp>
#include <openvino/opsets/opset10.hpp>
#include <openvino/core/graph_util.hpp>
#include <openvino/pass/pattern/op/wrap_type.hpp>
#include <openvino/pass/pattern/op/or.hpp>
#include <transformations/utils/utils.hpp>
#include "itt.hpp"
using namespace ov::pass::pattern;
ov::intel_cpu::NgramFusion::NgramFusion() {
MATCHER_SCOPE(NgramFusion);
auto concat_matches = [](ov::Output<ov::Node> output) -> bool {
if (auto concat = ov::as_type_ptr<ov::opset1::Concat>(output.get_node_shared_ptr())) {
return ov::pass::pattern::rank_equals(2)(output) && concat->get_axis() == 1;
}
return false;
};
auto concat_m = ov::pass::pattern::wrap_type<ov::opset1::Concat>(concat_matches);
ov::matcher_pass_callback callback = [=](ov::pass::pattern::Matcher& m) {
const auto concat = m.get_match_root();
const auto& inputs = concat->input_values();
const size_t k = inputs.size();
const size_t as_is_idx = k % 2 == 0 ? (k - 1) / 2 : k / 2;
// Ngram must contain only one non-select branch (that propagates original tensor as is)
for (size_t i = 0; i < inputs.size(); ++i) {
const bool is_select = ov::is_type<ov::opset1::Select>(inputs[i].get_node());
if ((i == as_is_idx && is_select) || (i != as_is_idx && !is_select)) {
return false;
}
}
auto check_bias = [](const PatternValueMap& pattern_map,
const std::shared_ptr<ov::Node> matched_constant_to_check,
const size_t expected_bias) {
auto out_it = pattern_map.find(matched_constant_to_check);
if (expected_bias == 0) {
return out_it == pattern_map.end();
} else if (out_it == pattern_map.end()) {
return false;
}
const auto constant = ov::as_type_ptr<ov::opset1::Constant>(out_it->second.get_node_shared_ptr());
return constant != nullptr && ov::op::util::constantIsEqualTo(constant, expected_bias);
};
auto tokens_match = [](ov::Output<ov::Node> output) -> bool {
return ov::pass::pattern::rank_equals(2)(output) && ov::pass::pattern::type_matches(ov::element::f32)(output);
};
auto idces_match = [](ov::Output<ov::Node> output) -> bool {
return ov::pass::pattern::rank_equals(2)(output) && ov::pass::pattern::type_matches(ov::element::i32)(output);
};
ov::Output<ov::Node> tokens;
// "as_is" input validation
{
using namespace ov::opset1;
auto tokens_m = any_input(tokens_match);
auto padded_tokens_inputs = as_is_idx == 0
? ov::OutputVector{tokens_m, wrap_type<Constant>()}
: ov::OutputVector{wrap_type<Constant>(), tokens_m, wrap_type<Constant>()};
auto padded_tokens_m = wrap_type<Concat>(padded_tokens_inputs);
auto shape_of_m = wrap_type<ShapeOf>({tokens_m});
auto cropped_shape_m = wrap_type<StridedSlice>({shape_of_m, wrap_type<Constant>(), wrap_type<Constant>(), wrap_type<Constant>()});
auto ss_bias = wrap_type<Constant>();
auto ss_biased_shape_m =
as_is_idx == 0 ? cropped_shape_m : wrap_type<Add>({cropped_shape_m, ss_bias});
auto ss_end_reshape_m = wrap_type<Reshape>({ss_biased_shape_m, any_input()});
auto cropped_tokens_m = wrap_type<StridedSlice>({padded_tokens_m, wrap_type<Constant>(), ss_end_reshape_m, wrap_type<Constant>()});
Matcher matcher(cropped_tokens_m);
if (!matcher.match(inputs[as_is_idx])) {
return false;
}
const auto& pattern_map = matcher.get_pattern_value_map();
tokens = pattern_map.at(tokens_m);
const auto& concat_shape = concat->get_output_partial_shape(0);
const auto& tokens_shape = tokens.get_partial_shape();
// To confirm that a subgraph can be replaced with NgramNode we only need to
// 1. Check Add's constant to make sure that data values have a right bias in result tensor
// 2. Check subgraph input and output shapes to make sure that all rest constants in the subgraph are correct
if (!check_bias(pattern_map, ss_bias, as_is_idx) ||
concat_shape.rank() != tokens_shape.rank() || tokens_shape[1] * k != concat_shape[1]) {
return false;
}
}
ov::Output<ov::Node> indices;
// select branches validation
{
using namespace ov::opset1;
auto tokens_m = any_input(tokens_match);
auto shape_of_m = wrap_type<ShapeOf>({tokens_m});
auto cropped_shape_m = wrap_type<StridedSlice>({shape_of_m, wrap_type<Constant>(), wrap_type<Constant>(), wrap_type<Constant>()});
auto idces_m = any_input(idces_match);
auto idces_concat_inputs = as_is_idx == 0
? ov::OutputVector{idces_m, wrap_type<Constant>()}
: ov::OutputVector{wrap_type<Constant>(), idces_m, wrap_type<Constant>()};
auto idces_concat_m = wrap_type<Concat>(idces_concat_inputs);
// left equal branch
auto crop_left_bias_m = wrap_type<Constant>();
auto crop_left_cropped_shape_m = std::make_shared<ov::pass::pattern::op::Or>(
ov::OutputVector{cropped_shape_m, wrap_type<Add>({cropped_shape_m, crop_left_bias_m})});
auto crop_left_reshape_m = wrap_type<Reshape>({crop_left_cropped_shape_m, any_input()});
auto idxes_crop_left_concat_m = wrap_type<Concat>({crop_left_reshape_m, wrap_type<Constant>()});
auto idxes_crop_left_m = wrap_type<StridedSlice>({idces_concat_m, wrap_type<Constant>(), idxes_crop_left_concat_m, wrap_type<Constant>()});
// right equal branch
auto crop_right_bias_m = wrap_type<Constant>();
auto crop_right_cropped_shape_m = std::make_shared<ov::pass::pattern::op::Or>(
ov::OutputVector{cropped_shape_m, wrap_type<Add>({cropped_shape_m, crop_right_bias_m})});
auto crop_right_reshape_m = wrap_type<Reshape>({crop_right_cropped_shape_m, any_input()});
auto idxes_crop_right_concat_m = wrap_type<Concat>({crop_right_reshape_m, wrap_type<Constant>()});
auto idxes_crop_right_m = wrap_type<StridedSlice>({idces_concat_m, wrap_type<Constant>(), idxes_crop_right_concat_m, wrap_type<Constant>()});
auto equal_m = wrap_type<Equal>({idxes_crop_left_m, idxes_crop_right_m});
auto condition_m = wrap_type<Reshape>({equal_m, any_input()});
// then branch
auto padded_tokens_inputs = as_is_idx == 0
? ov::OutputVector{tokens_m, wrap_type<Constant>()}
: ov::OutputVector{wrap_type<Constant>(), tokens_m, wrap_type<Constant>()};
auto padded_tokens_m = wrap_type<Concat>(padded_tokens_inputs);
auto then_cropped_shape_bias_m = wrap_type<Constant>();
auto then_cropped_shape_m = std::make_shared<ov::pass::pattern::op::Or>(
ov::OutputVector{cropped_shape_m, wrap_type<Add>({cropped_shape_m, then_cropped_shape_bias_m})});
auto then_crop_end_reshape_m = wrap_type<Reshape>({then_cropped_shape_m, wrap_type<Constant>()});
auto then_m = wrap_type<StridedSlice>({padded_tokens_m, wrap_type<Constant>(), then_crop_end_reshape_m, wrap_type<Constant>()});
// else branch
auto else_target_shape_reshape_m = wrap_type<Reshape>({cropped_shape_m, any_input()});
auto else_target_shape_concat_m = wrap_type<Concat>({else_target_shape_reshape_m, wrap_type<Constant>()});
auto else_m = wrap_type<Broadcast>({wrap_type<Constant>(), else_target_shape_concat_m, wrap_type<Constant>()});
auto select_m = wrap_type<Select>({condition_m, then_m, else_m});
Matcher select_matcher(select_m);
for (size_t i = 0; i < inputs.size(); ++i) {
if (i == as_is_idx)
continue;
if (!select_matcher.match(inputs[i])) {
return false;
}
const auto& pattern_map = select_matcher.get_pattern_value_map();
const auto& cur_tokens_input = pattern_map.at(tokens_m);
const auto& cur_indices_input = pattern_map.at(idces_m);
if (indices.get_node() == nullptr) {
indices = cur_indices_input;
}
// To confirm that a subgraph can be replaced with NgramNode we need to
// 1. Check that "tokens" input is equal to the input that was matched earlier
// 2. Check that "indices" input for all Select branches is the same
// 3. Check Add's constants to make sure that data values have a right bias in result tensor
const bool validate_eq_biases = (check_bias(pattern_map, crop_left_bias_m, i) && check_bias(pattern_map, crop_right_bias_m, as_is_idx)) ||
(check_bias(pattern_map, crop_right_bias_m, i) && check_bias(pattern_map, crop_left_bias_m, as_is_idx));
if (cur_tokens_input != tokens || cur_indices_input != indices ||
!validate_eq_biases || !check_bias(pattern_map, then_cropped_shape_bias_m, i)) {
return false;
}
}
}
const auto ngram = std::make_shared<ov::intel_cpu::NgramNode>(tokens, indices, k);
ngram->set_friendly_name(concat->get_friendly_name());
ov::copy_runtime_info(concat, ngram);
ov::replace_node(concat, ngram);
return true;
};
auto m = std::make_shared<ov::pass::pattern::Matcher>(concat_m, matcher_name);
this->register_matcher(m, callback);
}
@@ -0,0 +1,19 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include <openvino/pass/graph_rewrite.hpp>
namespace ov {
namespace intel_cpu {
class NgramFusion: public ov::pass::MatcherPass {
public:
OPENVINO_RTTI("NgramFusion", "0");
NgramFusion();
};
} // namespace intel_cpu
} // namespace ov
@@ -0,0 +1,46 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "ngram.hpp"
#include "../itt.hpp"
ov::intel_cpu::NgramNode::NgramNode(const ov::Output<Node>& embeddings, const ov::Output<Node>& batch_idces, const size_t k)
: Op({embeddings, batch_idces}), m_k(k) {
validate_and_infer_types();
}
std::shared_ptr<ov::Node> ov::intel_cpu::NgramNode::clone_with_new_inputs(const ov::OutputVector& new_args) const {
INTERNAL_OP_SCOPE(NgramNode_clone_with_new_inputs);
check_new_args_count(this, new_args);
return std::make_shared<ov::intel_cpu::NgramNode>(new_args.at(0), new_args.at(1), m_k);
}
bool ov::intel_cpu::NgramNode::visit_attributes(ov::AttributeVisitor &visitor) {
INTERNAL_OP_SCOPE(NgramNode_visit_attributes);
visitor.on_attribute("k", m_k);
return true;
}
void ov::intel_cpu::NgramNode::validate_and_infer_types() {
INTERNAL_OP_SCOPE(NgramNode_validate_and_infer_types);
NGRAPH_CHECK(m_k > 0, "k attribute must be greater than zero");
const auto& idces_et = get_input_element_type(1);
const auto& idces_shape = get_input_partial_shape(0);
NGRAPH_CHECK(idces_shape.rank() == 2, "'batch_idces' input must have 2D shape whereas current shape is", idces_shape);
NGRAPH_CHECK(idces_et.is_integral_number(), "'batch_idces' input must be integer whereas current element type is", idces_et);
const auto& embeddings_et = get_input_element_type(0);
const auto& embeddings_shape = get_input_partial_shape(0);
NGRAPH_CHECK(embeddings_et.is_real(), "'embeddings' input must be real whereas current element type is", embeddings_et);
NGRAPH_CHECK(embeddings_shape.rank() == 2, "'embeddings' input must have 2D shape whereas current shape is", embeddings_shape);
auto out_shape = embeddings_shape;
out_shape[1] *= m_k;
set_output_type(0, embeddings_et, out_shape);
}
size_t ov::intel_cpu::NgramNode::get_k() const {
return m_k;
}
@@ -0,0 +1,38 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include <openvino/core/node.hpp>
#include <openvino/op/op.hpp>
namespace ov {
namespace intel_cpu {
/**
* The operation flattens embedding tensor of token vectors and traverse it by a sliding window of size times the original embedding sizes.
* Inputs:
* 1. Embedding vectors of type T1 - shape [N, m], where N - number of tokens, m - embedding size. Required
* 2. Indices of type T2 - shape [N, 2]. Contains pairs <batch_idx;idx> for the corresponding tokens. This op uses only batch indices. Required
* Outputs:
* 1. New embedding vector of type T1 and of shape [N, m * k], where k - operation attribute.
* Types:
* T1 - only FP32 is supported
* T2 - I32 and I64 are supported
*/
class NgramNode : public ov::op::Op {
public:
OPENVINO_OP("Ngram", "cpu_plugin_opset");
NgramNode() = default;
NgramNode(const ov::Output<Node>& embeddings, const ov::Output<Node>& batch_idces, const size_t k);
std::shared_ptr<ov::Node> clone_with_new_inputs(const ov::OutputVector& new_args) const override;
bool visit_attributes(ov::AttributeVisitor& visitor) override;
void validate_and_infer_types() override;
size_t get_k() const;
private:
size_t m_k;
};
} // namespace intel_cpu
} // namespace ov
+182
View File
@@ -0,0 +1,182 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <string>
#include <vector>
#include "ngram.h"
#include "ie_parallel.hpp"
#include "common/cpu_memcpy.h"
#include "ngraph_transformations/op/ngram.hpp"
namespace ov {
namespace intel_cpu {
namespace node {
namespace {
class NgramShapeInfer : public ShapeInferEmptyPads {
public:
NgramShapeInfer(const size_t k) : m_k(k) {}
Result infer(
const std::vector<std::reference_wrapper<const VectorDims>>& input_shapes,
const std::unordered_map<size_t, MemoryPtr>& data_dependency) override {
auto output_shape = input_shapes[0].get();
output_shape[1] *= m_k;
return {{std::move(output_shape)}, ShapeInferStatus::success};
}
port_mask_t get_port_mask() const override {
return EMPTY_PORT_MASK;
}
private:
size_t m_k;
};
class NgramShapeInferFactory : public ShapeInferFactory {
public:
NgramShapeInferFactory(const std::shared_ptr<ov::Node>& op) : m_op(op) {}
ShapeInferPtr makeShapeInfer() const override {
auto ngram = ov::as_type_ptr<NgramNode>(m_op);
if (!ngram) {
IE_THROW(Unexpected) << "Wrong operation type";
}
return std::make_shared<NgramShapeInfer>(ngram->get_k());
}
private:
std::shared_ptr<ov::Node> m_op;
};
} // namespace
bool Ngram::isSupportedOperation(const std::shared_ptr<const ov::Node>& op, std::string& errorMessage) noexcept {
try {
const auto ngram = ov::as_type_ptr<const NgramNode>(op);
if (!ngram) {
errorMessage = "Only Ngram from CPU internal opset is supported";
return false;
}
} catch (...) {
return false;
}
return true;
}
Ngram::Ngram(const std::shared_ptr<ov::Node>& op, const GraphContext::CPtr& context)
: Node(op, context, NgramShapeInferFactory(op)) {
std::string errorMessage;
if (!isSupportedOperation(op, errorMessage)) {
IE_THROW(NotImplemented) << errorMessage;
}
const auto ngram = ov::as_type_ptr<const NgramNode>(op);
k = ngram->get_k();
leftPad = k % 2 == 0 ? (k - 1) / 2 : k / 2;
rightPad = k / 2;
const auto& windowStrideDim = ngram->get_input_partial_shape(0)[1];
if (windowStrideDim.is_static()) {
windowStride = windowStrideDim.get_length();
windowSize = k * windowStride;
leftPaddingSize = windowStride * leftPad;
rightPaddingSize = windowStride * rightPad;
}
}
void Ngram::initSupportedPrimitiveDescriptors() {
if (!supportedPrimitiveDescriptors.empty())
return;
idcesPrecision = getOriginalInputPrecisionAtPort(1);
if (idcesPrecision != InferenceEngine::Precision::I32 && idcesPrecision != InferenceEngine::Precision::I64) {
idcesPrecision = InferenceEngine::Precision::I32;
}
addSupportedPrimDesc({{LayoutType::ncsp, InferenceEngine::Precision::FP32},
{LayoutType::ncsp, idcesPrecision}},
{{LayoutType::ncsp, InferenceEngine::Precision::FP32}},
ref_any,
isDynamicNode());
}
void Ngram::prepareParams() {
const auto& srcDataDims = getParentEdgeAt(0)->getMemoryPtr()->getStaticDims();
const auto& srcIndicesDims = getParentEdgeAt(1)->getMemoryPtr()->getStaticDims();
const auto& outDims = getChildEdgeAt(0)->getMemoryPtr()->getStaticDims();;
idcesShapeSize = std::accumulate(srcIndicesDims.begin(), srcIndicesDims.end(), 1, std::multiplies<size_t>());
numOutElems = std::accumulate(outDims.begin(), outDims.end(), 1, std::multiplies<size_t>());
idcesStride = getParentEdgeAt(1)->getMemoryPtr()->GetDescWithType<BlockedMemoryDesc>()->getStrides()[0];
numIdces = srcIndicesDims[0];
windowStride = srcDataDims[1];
windowSize = k * windowStride;
leftPaddingSize = windowStride * leftPad;
rightPaddingSize = windowStride * rightPad;
}
template <typename idces_type>
std::vector<size_t> Ngram::computeBatchLenghts() {
auto* srcIndices = reinterpret_cast<const idces_type*>(getParentEdgeAt(1)->getMemoryPtr()->GetPtr());
std::vector<size_t> batchLenghts{0};
batchLenghts.reserve(numIdces + 1);
for (size_t i = idcesStride; i < idcesShapeSize; i += idcesStride) {
if (srcIndices[i - idcesStride] != srcIndices[i]) {
batchLenghts.push_back(i / idcesStride);
}
}
batchLenghts.push_back(idcesShapeSize / idcesStride);
return batchLenghts;
}
void Ngram::execute(dnnl::stream strm) {
auto* srcData = reinterpret_cast<const float*>(getParentEdgeAt(0)->getMemoryPtr()->GetPtr());
auto* dstData = reinterpret_cast<float*>(getChildEdgeAt(0)->getMemoryPtr()->GetPtr());
std::vector<size_t> batchLenghts;
if (idcesPrecision == InferenceEngine::Precision::I32) {
batchLenghts = computeBatchLenghts<std::int32_t>();
} else if (idcesPrecision == InferenceEngine::Precision::I64) {
batchLenghts = computeBatchLenghts<std::int64_t>();
} else {
IE_THROW() << "Unsupported idces precision: " << idcesPrecision;
}
/* The following procedure applied to each batch:
1. Pad both corners of current embedding with zeros. Left/Right pad are computed depending on k.
2. Apply sliding window of windowSize with a step windowStride and form k new embedding vectors for the embedding
*/
memset(dstData, 0, numOutElems * sizeof(float));
parallel_for(batchLenghts.size() - 1, [&](const size_t batchIdx) {
size_t srcWindowBias = 0;
size_t dstWindowBias = 0;
const size_t niter = batchLenghts[batchIdx + 1] - batchLenghts[batchIdx];
const size_t srcBatchBias = batchLenghts[batchIdx] * windowStride;
const size_t dstBatchBias = batchLenghts[batchIdx] * windowStride * k;
for (size_t i = 0; i < niter; ++i) {
const size_t curLeftPad = leftPad >= i ? leftPaddingSize - i * windowStride : 0;
const size_t curRightPad = rightPad >= niter - 1 - i ? rightPaddingSize - (niter - 1 - i) * windowStride : 0;
const size_t dataSize = windowSize - curLeftPad - curRightPad;
dstWindowBias += curLeftPad;
cpu_memcpy(dstData + dstBatchBias + dstWindowBias, srcData + srcBatchBias + srcWindowBias, dataSize * sizeof(float));
dstWindowBias += dataSize + curRightPad;
if (curLeftPad == 0)
srcWindowBias += windowStride;
}
});
}
void Ngram::executeDynamicImpl(dnnl::stream strm) {
execute(strm);
}
bool Ngram::created() const {
return getType() == Type::Ngram;
}
} // namespace node
} // namespace intel_cpu
} // namespace ov
+55
View File
@@ -0,0 +1,55 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include <node.h>
#include <memory>
#include <string>
#include <vector>
namespace ov {
namespace intel_cpu {
namespace node {
class Ngram : public Node {
public:
Ngram(const std::shared_ptr<ov::Node>& op, const GraphContext::CPtr& context);
void getSupportedDescriptors() override {};
void initSupportedPrimitiveDescriptors() override;
void execute(dnnl::stream strm) override;
bool created() const override;
static bool isSupportedOperation(const std::shared_ptr<const ov::Node>& op, std::string& errorMessage) noexcept;
protected:
void executeDynamicImpl(dnnl::stream strm) override;
void prepareParams() override;
private:
template <typename idces_type>
std::vector<size_t> computeBatchLenghts();
size_t k = 0;
size_t windowSize = 0;
size_t windowStride = 0;
size_t leftPad = 0;
size_t rightPad = 0;
size_t leftPaddingSize = 0;
size_t rightPaddingSize = 0;
size_t idcesShapeSize = 0;
size_t idcesStride = 0;
size_t numIdces = 0;
size_t numOutElems = 0;
InferenceEngine::Precision idcesPrecision;
};
} // namespace node
} // namespace intel_cpu
} // namespace ov
@@ -91,6 +91,7 @@
#include "nodes/interaction.h"
#include "nodes/mha.h"
#include "nodes/unique.hpp"
#include "nodes/ngram.h"
namespace ov {
namespace intel_cpu {
@@ -194,6 +195,7 @@ Node::NodesFactory::NodesFactory()
INTEL_CPU_NODE(Interaction, Type::Interaction);
INTEL_CPU_NODE(MHA, Type::MHA);
INTEL_CPU_NODE(Unique, Type::Unique);
INTEL_CPU_NODE(Ngram, Type::Ngram);
}
#undef INTEL_CPU_NODE
@@ -0,0 +1,240 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <tuple>
#include <string>
#include <vector>
#include <memory>
#include <debug.h>
#include <shared_test_classes/base/ov_subgraph.hpp>
#include <ngraph_functions/builders.hpp>
#include "common_test_utils/common_utils.hpp"
#include <common_test_utils/ov_tensor_utils.hpp>
#include "test_utils/cpu_test_utils.hpp"
#include <openvino/opsets/opset1.hpp>
using namespace CPUTestUtils;
using namespace ov::test;
using namespace ngraph::helpers;
namespace CPUSubgraphTestsDefinitions {
typedef std::tuple<
std::vector<InputShape>,
ElementType,
ElementType,
size_t
> NgramTestParams;
static std::shared_ptr<ov::Node> getStridedSlice(const std::shared_ptr<ov::Node>& data,
const std::shared_ptr<ov::Node>& begin,
const std::shared_ptr<ov::Node>& end,
const std::vector<int64_t>& shrink_axis_mask = {}) {
std::vector<int64_t> default_mask(begin->get_shape()[0], 0);
return std::make_shared<ov::opset1::StridedSlice>(data, begin, end, default_mask, default_mask,
std::vector<int64_t>{}, shrink_axis_mask);
}
static std::shared_ptr<ov::Node> getReshape(const std::shared_ptr<ov::Node>& data,
const std::vector<int64_t>& requested_shape,
const ov::element::Type& prc) {
auto requested_shape_node = ov::opset1::Constant::create(prc, {requested_shape.size()}, requested_shape);
return std::make_shared<ov::opset1::Reshape>(data, requested_shape_node, true);
}
static std::shared_ptr<ov::Model> initNgram(std::vector<ov::PartialShape>& input_shapes,
const ov::element::Type& data_et,
const ov::element::Type& idces_et,
const size_t k) {
const size_t left_pad = k % 2 == 0 ? (k - 1) / 2 : k / 2;
const size_t right_pad = k / 2;
const size_t mid_idx = left_pad;
ov::element::TypeVector input_precisions{data_et, idces_et};
auto params = ngraph::builder::makeDynamicParams(input_precisions, input_shapes);
auto shape_of = std::make_shared<ov::opset10::ShapeOf>(params[0], idces_et);
auto shape_ss_begin = ov::opset1::Constant::create(idces_et, {1}, {0});
auto shape_ss_end = ov::opset1::Constant::create(idces_et, {1}, {1});
auto shape_ss = getStridedSlice(shape_of, shape_ss_begin, shape_ss_end, {1});
auto getInputsToPad = [&](const ov::Output<ov::Node> data, const int pad_value) {
const size_t length = data.get_partial_shape()[1].get_length();
ov::OutputVector inputs;
if (left_pad > 0) {
inputs.push_back(ov::opset1::Constant::create(data.get_element_type(), {left_pad, length}, {pad_value}));
}
inputs.push_back(data);
if (right_pad > 0) {
inputs.push_back(ov::opset1::Constant::create(data.get_element_type(), {right_pad, length}, {pad_value}));
}
return inputs;
};
auto data_padded = std::make_shared<ov::opset1::Concat>(getInputsToPad(params[0], 0), 0);
auto idces_padded = std::make_shared<ov::opset1::Concat>(getInputsToPad(params[1], -1), 0);
std::shared_ptr<ov::Node> as_is_bias = shape_ss;
if (mid_idx != 0) {
auto bias_const = ov::opset1::Constant::create(idces_et, {}, {mid_idx});
as_is_bias = std::make_shared<ov::opset1::Add>(shape_ss, bias_const);
}
auto as_is_ss_begin = ov::opset1::Constant::create(idces_et, {1}, {mid_idx});
auto as_is_ss_end = getReshape(as_is_bias, {1}, idces_et);
auto as_is_ss = getStridedSlice(data_padded, as_is_ss_begin, as_is_ss_end);
auto getSelectBranch = [&](const size_t cur_idx, const size_t mid_idx) {
std::shared_ptr<ov::Node> eq_left_bias = shape_ss;
if (cur_idx != 0) {
auto bias_const = ov::opset1::Constant::create(idces_et, {}, {cur_idx});
eq_left_bias = std::make_shared<ov::opset1::Add>(shape_ss, bias_const);
}
auto eq_left_reshape = getReshape(eq_left_bias, {1}, idces_et);
auto eq_left_concat_const = ov::opset1::Constant::create(idces_et, {1}, {1});
auto eq_left_concat = std::make_shared<ov::opset1::Concat>(ov::OutputVector{eq_left_reshape, eq_left_concat_const}, 0);
auto eq_left_ss_begin = ov::opset1::Constant::create(idces_et, {2}, std::vector<size_t>{cur_idx, 0ul});
auto eq_left_ss = getStridedSlice(idces_padded, eq_left_ss_begin, eq_left_concat, {0, 1});
std::shared_ptr<ov::Node> eq_right_bias = shape_ss;
if (mid_idx != 0) {
auto bias_const = ov::opset1::Constant::create(idces_et, {}, {mid_idx});
eq_right_bias = std::make_shared<ov::opset1::Add>(shape_ss, bias_const);
}
auto eq_right_reshape = getReshape(eq_right_bias, {1}, idces_et);
auto eq_right_concat_const = ov::opset1::Constant::create(idces_et, {1}, {1});
auto eq_right_concat = std::make_shared<ov::opset1::Concat>(ov::OutputVector{eq_right_reshape, eq_right_concat_const}, 0);
auto eq_right_ss_begin = ov::opset1::Constant::create(idces_et, {2}, std::vector<size_t>{mid_idx, 0ul});
auto eq_right_ss = getStridedSlice(idces_padded, eq_right_ss_begin, eq_right_concat, {0, 1});
auto equal = std::make_shared<ov::opset1::Equal>(eq_left_ss, eq_right_ss);
auto cond = getReshape(equal, {-1, 1}, idces_et);
std::shared_ptr<ov::Node> then_bias = shape_ss;
if (cur_idx != 0) {
auto bias_const = ov::opset1::Constant::create(idces_et, {}, {cur_idx});
then_bias = std::make_shared<ov::opset1::Add>(shape_ss, bias_const);
}
auto then_reshape = getReshape(then_bias, {1}, idces_et);
auto then_ss_begin = ov::opset1::Constant::create(idces_et, {1}, {cur_idx});
auto then = getStridedSlice(data_padded, then_ss_begin, then_reshape);
auto else_reshape = getReshape(shape_ss, {1}, idces_et);
auto else_concat_const = ov::opset1::Constant::create(idces_et, {1}, {input_shapes[0][1].get_length()});
auto else_concat = std::make_shared<ov::opset1::Concat>(ov::OutputVector{else_reshape, else_concat_const}, 0);
auto else_bcast_const = ov::opset1::Constant::create(data_et, {}, {0});
auto else_bcast = std::make_shared<ov::opset1::Broadcast>(else_bcast_const, else_concat);
auto select = std::make_shared<ov::opset1::Select>(cond, then, else_bcast);
return select;
};
ov::OutputVector concat_inputs(k);
concat_inputs[mid_idx] = as_is_ss;
for (size_t i = 0; i < k; ++i) {
if (i == mid_idx)
continue;
concat_inputs[i] = getSelectBranch(i, mid_idx);
}
auto final_concat = std::make_shared<ov::opset10::Concat>(concat_inputs, 1);
return std::make_shared<ov::Model>(final_concat, params, "ngram");
}
class NgramCPUTest : public testing::WithParamInterface<NgramTestParams>, virtual public SubgraphBaseTest, public CPUTestsBase {
public:
static std::string getTestCaseName(const testing::TestParamInfo<NgramTestParams> &obj) {
std::vector<InputShape> input_shapes;
size_t k;
ElementType data_et;
ElementType idces_et;
std::tie(input_shapes, data_et, idces_et, k) = obj.param;
std::ostringstream results;
results << "IS=(";
for (const auto& shape : input_shapes) {
results << CommonTestUtils::partialShape2str({shape.first}) << "_";
}
results << ")_TS=(";
for (const auto& shape : input_shapes) {
for (const auto& item : shape.second) {
results << CommonTestUtils::vec2str(item) << "_";
}
}
results << ")_data_prc=" << data_et << "_idces_prc=" << idces_et << "_k=" << k;
return results.str();
}
void generate_inputs(const std::vector<ov::Shape>& targetInputStaticShapes) override {
inputs.clear();
const auto& model_inputs = function->inputs();
const auto& data_et = model_inputs[0].get_element_type();
const auto& data_shape = targetInputStaticShapes[0];
auto embeddings_tensor = ov::test::utils::create_and_fill_tensor_consistently(data_et, data_shape, 100, 1, 1);
inputs.insert({model_inputs[0].get_node_shared_ptr(), embeddings_tensor});
const auto& indices_et = model_inputs[1].get_element_type();
const auto& indices_shape = targetInputStaticShapes[1];
const size_t batch_size = data_shape[0];
auto indices_tensor = ov::test::utils::create_and_fill_tensor(indices_et, indices_shape, batch_size, 0);
if (indices_et == ov::element::i32) {
auto* indices_data = indices_tensor.data<int32_t>();
std::sort(indices_data, indices_data + indices_tensor.get_size());
} else if (indices_et == ov::element::i64) {
auto* indices_data = indices_tensor.data<int64_t>();
std::sort(indices_data, indices_data + indices_tensor.get_size());
} else {
IE_THROW() << "Unexpected indices precision: " << indices_et;
}
inputs.insert({model_inputs[1].get_node_shared_ptr(), indices_tensor});
}
protected:
void SetUp() override {
targetDevice = CommonTestUtils::DEVICE_CPU;
std::vector<InputShape> inputShapes;
ElementType data_et;
ElementType idces_et;
size_t k;
std::tie(inputShapes, data_et, idces_et, k) = this->GetParam();
init_input_shapes(inputShapes);
function = initNgram(inputDynamicShapes, data_et, idces_et, k);
}
};
TEST_P(NgramCPUTest, CompareWithRefs) {
run();
CheckNumberOfNodesWithType(compiledModel, "Ngram", 1);
}
namespace {
std::vector<std::vector<InputShape>> inputShapes = {
{
InputShape{{-1, 2}, {{3, 2}, {5, 2}, {7, 2}}},
InputShape{{-1, 2}, {{3, 2}, {5, 2}, {7, 2}}}
},
{
InputShape{{-1, 256}, {{12, 256}, {25, 256}}},
InputShape{{-1, 2}, {{12, 2}, {25, 2}}}
},
{
InputShape{{-1, 1}, {{12, 1}}},
InputShape{{-1, 2}, {{12, 2}}}
},
};
std::vector<size_t> k_values = {2, 3, 5, 7};
std::vector<ElementType> idces_precisions = {ElementType::i32, ElementType::i64};
INSTANTIATE_TEST_SUITE_P(smoke_Ngram, NgramCPUTest,
::testing::Combine(::testing::ValuesIn(inputShapes),
::testing::Values(ElementType::f32),
::testing::ValuesIn(idces_precisions),
::testing::ValuesIn(k_values)),
NgramCPUTest::getTestCaseName);
} // namespace
} // namespace CPUSubgraphTestsDefinitions