[GPU] Add FullyConnected custom op (#19539)

This commit is contained in:
Vladimir Paramuzov authored and GitHub committed 2023-09-05 16:47:05 +04:00
1 parent 3d679edf18
commit 77600c7701
16 files changed
+1360 -221

No files matched your search

@@ -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_gpu {
namespace op {
class FullyConnected : public ov::op::Op {
public:
OPENVINO_OP("FullyConnected", "gpu_opset");
FullyConnected() = default;
FullyConnected(const ov::Output<Node>& A,
const ov::Output<Node>& B,
const ov::element::Type output_type = ov::element::undefined);
bool visit_attributes(ov::AttributeVisitor &visitor) override;
void validate_and_infer_types() override;
std::shared_ptr<Node> clone_with_new_inputs(const ov::OutputVector& new_args) const override;
ov::element::Type get_output_type() const { return m_output_type; }
protected:
ov::element::Type m_output_type;
};
} // namespace op
} // namespace intel_gpu
} // namespace ov
@@ -261,3 +261,4 @@ REGISTER_FACTORY(internal, NonMaxSuppressionIEInternal);
REGISTER_FACTORY(internal, GenerateProposalsIEInternal);
REGISTER_FACTORY(internal, NmsStaticShapeIE8);
REGISTER_FACTORY(internal, MulticlassNmsIEInternal);
REGISTER_FACTORY(internal, FullyConnected);
@@ -35,8 +35,8 @@ enum class eltwise_mode : int32_t;
#define REGISTER_FACTORY_IMPL(op_version, op_name) \
void __register ## _ ## op_name ## _ ## op_version(); \
void __register ## _ ## op_name ## _ ## op_version() { \
ProgramBuilder::RegisterFactory<ov::op::op_version::op_name>( \
[](ProgramBuilder& p, const std::shared_ptr<ov::Node>& op) { \
ProgramBuilder::RegisterFactory<ov::op::op_version::op_name>( \
[](ProgramBuilder& p, const std::shared_ptr<ov::Node>& op) { \
auto op_casted = std::dynamic_pointer_cast<ov::op::op_version::op_name>(op); \
OPENVINO_ASSERT(op_casted, "[GPU] Invalid ov Node type passed into ", __PRETTY_FUNCTION__); \
Create##op_name##Op(p, op_casted); \
@@ -3,7 +3,7 @@
//
#pragma once
#include <string>
#include <cstring>
#include <mutex>
#include <vector>
#include <set>
@@ -0,0 +1,83 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "intel_gpu/plugin/program_builder.hpp"
#include "intel_gpu/plugin/common_utils.hpp"
#include "intel_gpu/op/fully_connected.hpp"
#include "intel_gpu/primitives/fully_connected.hpp"
#include "intel_gpu/primitives/reshape.hpp"
#include "intel_gpu/primitives/reorder.hpp"
namespace ov {
namespace op {
namespace internal {
using FullyConnected = ov::intel_gpu::op::FullyConnected;
} // namespace internal
} // namespace op
} // namespace ov
namespace ov {
namespace intel_gpu {
static void CreateFullyConnectedOp(ProgramBuilder& p, const std::shared_ptr<op::FullyConnected>& op) {
validate_inputs_count(op, {2});
auto inputs = p.GetInputInfo(op);
std::string layerName = layer_type_name_ID(op);
auto inputName = inputs[0].pid;
auto weightsName = inputs[1].pid;
auto shape_a = op->get_input_partial_shape(0);
auto shape_b = op->get_input_partial_shape(1);
auto rank_a = shape_a.rank().get_length();
auto rank_b = shape_b.rank().get_length();
auto fcPrim = cldnn::fully_connected(layerName,
cldnn::input_info(inputName),
weightsName,
"",
cldnn::element_type_to_data_type(op->get_output_element_type(0)),
cldnn::padding(),
rank_a,
rank_b);
p.add_primitive(*op, fcPrim);
if (shape_a.size() > 3 && !p.use_new_shape_infer()) {
auto lastLayerName = layerName;
auto outReshapeName = layerName + "_cldnn_out_reshape";
// add reorder
auto outDims = op->get_output_shape(0);
auto outTensor = tensor_from_dims(outDims);
if (outDims.size() > 4) {
cldnn::format outputFormat = cldnn::format::bfyx;
switch (outDims.size()) {
case 5: outputFormat = cldnn::format::bfzyx; break;
case 6: outputFormat = cldnn::format::bfwzyx; break;
default: break;
}
cldnn::primitive_id reorderId = "reorder:" + outReshapeName + "_reorder";
cldnn::layout outputLayout(cldnn::element_type_to_data_type(op->get_output_element_type(0)), outputFormat, outTensor);
auto reorder_prim = cldnn::reorder(reorderId, cldnn::input_info(layerName), outputLayout);
p.add_primitive(*op, reorder_prim);
lastLayerName = reorderId;
}
// add reshape
auto outReshapePrim = cldnn::reshape(outReshapeName, cldnn::input_info(lastLayerName), outTensor);
p.add_primitive(*op, outReshapePrim);
}
}
REGISTER_FACTORY_IMPL(internal, FullyConnected);
} // namespace intel_gpu
} // namespace ov
+81 -218
View File
@@ -20,57 +20,6 @@
namespace ov {
namespace intel_gpu {
/*
* get_aligned_shapes function align two input shapes to have the same size and
* the same batch dimensions (last two dimensions are not comparable).
* It also checks that dimensions are compatible so in case with two shapes
* for example: [2, 32, 64] [3, 64, 64] it will raise an exception.
*/
static std::tuple<bool, PartialShape, PartialShape> get_aligned_shapes(const PartialShape& shape_a,
const PartialShape& shape_b,
const std::shared_ptr<ov::op::v0::MatMul>& matmul) {
PartialShape shape_a_aligned(shape_a), shape_b_aligned(shape_b);
auto rank_a = shape_a_aligned.rank().get_length();
auto rank_b = shape_b_aligned.rank().get_length();
size_t max_size = std::max(rank_a, rank_b);
if (max_size == 1) {
return std::make_tuple(false, shape_a_aligned, shape_b_aligned);
}
for (size_t i = 0, cnt = max_size - rank_a; i < cnt; ++i) {
shape_a_aligned.insert(shape_a_aligned.begin(), 1);
}
for (size_t i = 0, cnt = max_size - rank_b; i < cnt; ++i) {
shape_b_aligned.insert(shape_b_aligned.begin(), 1);
}
if (matmul->get_transpose_a()) {
std::swap(*(shape_a_aligned.end() - 1), *(shape_a_aligned.end() - 2));
}
if (matmul->get_transpose_b()) {
std::swap(*(shape_b_aligned.end() - 1), *(shape_b_aligned.end() - 2));
}
for (size_t i = 0; i < max_size - 2; ++i) {
auto a_dim = shape_a_aligned[i], b_dim = shape_b_aligned[i];
if (a_dim.is_dynamic()) {
if (b_dim == 1) {
shape_a_aligned[i] = shape_b_aligned[i] = a_dim;
} else {
return std::make_tuple(false, shape_a_aligned, shape_b_aligned);
}
} else {
if (a_dim != b_dim && a_dim.get_length() > 1 && b_dim.get_length() > 1) {
OPENVINO_THROW("Shapes can't be aligned: ", shape_a_aligned, " ", shape_b_aligned);
}
auto max_value = std::max(a_dim.get_length(), b_dim.get_length());
shape_a_aligned[i] = shape_b_aligned[i] = max_value;
}
}
return {true, shape_a_aligned, shape_b_aligned};
}
static void CreateMatMulOp(ProgramBuilder& p, const std::shared_ptr<ov::op::v0::MatMul>& op) {
validate_inputs_count(op, {2});
auto inputs = p.GetInputInfo(op);
@@ -82,181 +31,95 @@ static void CreateMatMulOp(ProgramBuilder& p, const std::shared_ptr<ov::op::v0::
auto rank_a = shape_a.rank().get_length();
auto rank_b = shape_b.rank().get_length();
bool is_fc = IsNodeOnConstPath(op->get_input_node_shared_ptr(1));
is_fc &= std::count_if(shape_b.begin(), shape_b.end(), [](Dimension x) { return x != 1; }) <= 2;
// TODO: This conditions can be relaxed with proper handling in FC path
is_fc &= rank_a > 1 && rank_b > 1;
auto alpha = 1.0f;
auto beta = 0.0f;
auto transA = op->get_transpose_a();
auto transB = op->get_transpose_b();
PartialShape shape_a_aligned, shape_b_aligned;
bool aligned = false;
if (shape_b.is_static()) {
std::tie(aligned, shape_a_aligned, shape_b_aligned) = get_aligned_shapes(shape_a, shape_b, op);
std::array<ov::PartialShape, 2> inputShapes{
op->get_input_partial_shape(0),
op->get_input_partial_shape(1)
};
auto canTransposeInputs = [&] (const std::array<ov::PartialShape, 2>& shapes, bool transA, bool transB, ov::element::Type type) -> bool {
if (!transA && !transB)
return false;
// dynamic shapes and 1D tensors are not transposed
if (shapes[0].is_dynamic() || shapes[1].is_dynamic() ||
shapes[0].size() < 2 || shapes[1].size() < 2)
return false;
// don't transpose inputs if they're aligned to 16
bool inputsAligned = std::all_of(shapes[0].rbegin(), shapes[0].rbegin() + 2,
[] (const ov::Dimension& dim) { return dim.is_static() && dim.get_length() % 16 == 0; }) &&
std::all_of(shapes[1].rbegin(), shapes[1].rbegin() + 2,
[] (const ov::Dimension& dim) { return dim.is_static() && dim.get_length() % 16 == 0; });
if (inputsAligned)
return false;
// Heuristic condition for permute and tiled_opt kernel perform better than ref kernel.
bool in0_large = std::all_of(shapes[0].rbegin(), shapes[0].rbegin() + 2,
[] (const ov::Dimension& dim) { return dim.is_static() && dim.get_length() >= 64; });
bool in1_large = std::all_of(shapes[1].rbegin(), shapes[1].rbegin() + 2,
[] (const ov::Dimension& dim) { return dim.is_static() && dim.get_length() >= 64; });
// Optimized for clDNN
auto is_u8_i8 = (type == ov::element::Type_t::i8 || type == ov::element::Type_t::u8);
bool in0_very_large = tensor_from_dims(shapes[0].to_shape()).count() > 100000;
bool in1_very_large = tensor_from_dims(shapes[0].to_shape()).count() > 100000;
bool needs_to_transpose_inputs = (in0_very_large || in1_very_large) && !is_u8_i8 && !p.get_engine().get_device_info().supports_immad;
return (in0_large && in1_large) || needs_to_transpose_inputs;
};
auto transposeInput = [] (ProgramBuilder& p, const std::shared_ptr<ov::Node>& op, const ov::PartialShape& shape,
const std::string& suffix, const cldnn::primitive_id& primitiveId) -> cldnn::input_info {
std::vector<uint16_t> transposeOrder(shape.size());
std::iota(transposeOrder.begin(), transposeOrder.end(), 0);
std::swap(*(transposeOrder.end() - 1), *(transposeOrder.end() - 2));
auto permuteName = op->get_friendly_name() + suffix;
auto permutePrim = cldnn::permute(permuteName,
cldnn::input_info(primitiveId),
transposeOrder);
p.add_primitive(*op, permutePrim);
return cldnn::input_info(permuteName);
};
if (canTransposeInputs(inputShapes, transA, transB, op->get_input_element_type(0))) {
if (transA) {
inputs[0] = transposeInput(p, op, inputShapes[0], "/transpose_a", inputs[0].pid);
transA = false;
}
if (transB) {
inputs[1] = transposeInput(p, op, inputShapes[1], "/transpose_b", inputs[1].pid);
transB = false;
}
}
is_fc &= aligned;
if (is_fc) {
if (shape_a_aligned.size() < 2 || shape_b_aligned.size() < 2) {
OPENVINO_THROW("MatMul ", op->get_friendly_name(), " shapes are inconsistent.");
}
auto gemmPrim = cldnn::gemm(layerName,
inputs,
cldnn::element_type_to_data_type(op->get_output_element_type(0)),
transA,
transB,
alpha,
beta,
rank_a,
rank_b);
auto inputName = inputs[0].pid;
auto weightsName = inputs[1].pid;
p.add_primitive(*op, gemmPrim);
auto create_transpose = [&](const std::string& transposeName, const std::string& transposeInputName, size_t rank) {
std::vector<uint16_t> transpose_order(rank);
std::iota(transpose_order.begin(), transpose_order.end(), 0);
std::swap(*(transpose_order.end() - 1), *(transpose_order.end() - 2));
auto permutePrim = cldnn::permute(transposeName,
cldnn::input_info(transposeInputName),
transpose_order);
p.add_primitive(*op, permutePrim);
};
// Weights normalization
if (!op->get_transpose_b()) {
auto transposeName = op->get_friendly_name() + "/transpose_b";
create_transpose(transposeName, weightsName, rank_b);
weightsName = transposeName;
}
// Input normalization
if (op->get_transpose_a()) {
auto transposeName = op->get_friendly_name() + "/transpose_a";
create_transpose(transposeName, inputName, rank_a);
inputName = transposeName;
}
auto fcPrim = cldnn::fully_connected(layerName,
cldnn::input_info(inputName),
weightsName,
"",
cldnn::element_type_to_data_type(op->get_output_element_type(0)),
cldnn::padding(),
rank_a,
rank_b);
p.add_primitive(*op, fcPrim);
if (shape_a_aligned.size() > 3 && !p.use_new_shape_infer()) {
auto lastLayerName = layerName;
if (!p.use_new_shape_infer()) {
auto outDims = op->get_output_shape(0);
auto outDimsN = outDims.size();
// Reshape output if gemm specific shape does not match default one
if (outDimsN < 4) {
auto outputShape = tensor_from_dims(outDims);
auto outReshapeName = layerName + "_cldnn_out_reshape";
// add reorder
auto outDims = op->get_output_shape(0);
auto outTensor = tensor_from_dims(outDims);
if (outDims.size() > 4) {
cldnn::format outputFormat = cldnn::format::bfyx;
switch (outDims.size()) {
case 5: outputFormat = cldnn::format::bfzyx; break;
case 6: outputFormat = cldnn::format::bfwzyx; break;
default: break;
}
cldnn::primitive_id reorderId = "reorder:" + outReshapeName + "_reorder";
cldnn::layout outputLayout(cldnn::element_type_to_data_type(op->get_output_element_type(0)), outputFormat, outTensor);
auto reorder_prim = cldnn::reorder(reorderId, cldnn::input_info(layerName), outputLayout);
p.add_primitive(*op, reorder_prim);
lastLayerName = reorderId;
}
// add reshape
auto outReshapePrim = cldnn::reshape(outReshapeName, cldnn::input_info(lastLayerName), outTensor);
auto outReshapePrim = cldnn::reshape(outReshapeName, cldnn::input_info(layerName), outputShape);
p.add_primitive(*op, outReshapePrim);
}
} else {
// Add actual gemm
auto alpha = 1.0f;
auto beta = 0.0f;
auto transA = op->get_transpose_a();
auto transB = op->get_transpose_b();
std::array<ov::PartialShape, 2> inputShapes{
op->get_input_partial_shape(0),
op->get_input_partial_shape(1)
};
auto canTransposeInputs = [&] (const std::array<ov::PartialShape, 2>& shapes, bool transA, bool transB, ov::element::Type type) -> bool {
if (!transA && !transB)
return false;
// dynamic shapes and 1D tensors are not transposed
if (shapes[0].is_dynamic() || shapes[1].is_dynamic() ||
shapes[0].size() < 2 || shapes[1].size() < 2)
return false;
// don't transpose inputs if they're aligned to 16
bool inputsAligned = std::all_of(shapes[0].rbegin(), shapes[0].rbegin() + 2,
[] (const ov::Dimension& dim) { return dim.is_static() && dim.get_length() % 16 == 0; }) &&
std::all_of(shapes[1].rbegin(), shapes[1].rbegin() + 2,
[] (const ov::Dimension& dim) { return dim.is_static() && dim.get_length() % 16 == 0; });
if (inputsAligned)
return false;
// Heuristic condition for permute and tiled_opt kernel perform better than ref kernel.
bool in0_large = std::all_of(shapes[0].rbegin(), shapes[0].rbegin() + 2,
[] (const ov::Dimension& dim) { return dim.is_static() && dim.get_length() >= 64; });
bool in1_large = std::all_of(shapes[1].rbegin(), shapes[1].rbegin() + 2,
[] (const ov::Dimension& dim) { return dim.is_static() && dim.get_length() >= 64; });
// Optimized for clDNN
auto is_u8_i8 = (type == ov::element::Type_t::i8 || type == ov::element::Type_t::u8);
bool in0_very_large = tensor_from_dims(shapes[0].to_shape()).count() > 100000;
bool in1_very_large = tensor_from_dims(shapes[0].to_shape()).count() > 100000;
bool needs_to_transpose_inputs = (in0_very_large || in1_very_large) && !is_u8_i8 && !p.get_engine().get_device_info().supports_immad;
return (in0_large && in1_large) || needs_to_transpose_inputs;
};
auto transposeInput = [] (ProgramBuilder& p, const std::shared_ptr<ov::Node>& op, const ov::PartialShape& shape,
const std::string& suffix, const cldnn::primitive_id& primitiveId) -> cldnn::input_info {
std::vector<uint16_t> transposeOrder(shape.size());
std::iota(transposeOrder.begin(), transposeOrder.end(), 0);
std::swap(*(transposeOrder.end() - 1), *(transposeOrder.end() - 2));
auto permuteName = op->get_friendly_name() + suffix;
auto permutePrim = cldnn::permute(permuteName,
cldnn::input_info(primitiveId),
transposeOrder);
p.add_primitive(*op, permutePrim);
return cldnn::input_info(permuteName);
};
if (canTransposeInputs(inputShapes, transA, transB, op->get_input_element_type(0))) {
if (transA) {
inputs[0] = transposeInput(p, op, inputShapes[0], "/transpose_a", inputs[0].pid);
transA = false;
}
if (transB) {
inputs[1] = transposeInput(p, op, inputShapes[1], "/transpose_b", inputs[1].pid);
transB = false;
}
}
auto gemmPrim = cldnn::gemm(layerName,
inputs,
cldnn::element_type_to_data_type(op->get_output_element_type(0)),
transA,
transB,
alpha,
beta,
rank_a,
rank_b);
p.add_primitive(*op, gemmPrim);
if (!p.use_new_shape_infer()) {
auto outDims = op->get_output_shape(0);
auto outDimsN = outDims.size();
// Reshape output if gemm specific shape does not match default one
if (outDimsN < 4) {
auto outputShape = tensor_from_dims(outDims);
auto outReshapeName = layerName + "_cldnn_out_reshape";
auto outReshapePrim = cldnn::reshape(outReshapeName, cldnn::input_info(layerName), outputShape);
p.add_primitive(*op, outReshapePrim);
}
}
}
}
@@ -0,0 +1,172 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "intel_gpu/op/fully_connected.hpp"
#include "convert_matmul_to_fc.hpp"
#include "openvino/op/matmul.hpp"
#include "openvino/op/convert.hpp"
#include "openvino/op/transpose.hpp"
#include "openvino/op/reshape.hpp"
#include "openvino/core/rt_info.hpp"
#include "openvino/pass/pattern/op/wrap_type.hpp"
#include "transformations/utils/utils.hpp"
namespace ov {
namespace intel_gpu {
ConvertMatMulToFullyConnected::ConvertMatMulToFullyConnected() {
auto static_rank_gt_1 = [](const ov::Output<ov::Node>& output) {
const auto& r = output.get_partial_shape().rank();
return r.is_static() && r.get_length() > 1;
};
auto weights_path = [&static_rank_gt_1](const ov::Output<ov::Node>& output) {
const auto& pshape = output.get_partial_shape();
return ov::op::util::is_on_constant_path(output) &&
static_rank_gt_1(output) &&
pshape.is_static() &&
std::count_if(pshape.begin(), pshape.end(), [](const ov::Dimension& x) { return x != 1; }) <= 2;
};
auto activations_m = ov::pass::pattern::any_input(static_rank_gt_1);
auto weights_m = ov::pass::pattern::any_input(weights_path);
auto matmul_m = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({ activations_m, weights_m }, ov::pass::pattern::has_static_rank());
ov::matcher_pass_callback callback = [=](ov::pass::pattern::Matcher& m) {
const auto& pattern_map = m.get_pattern_value_map();
auto matmul = std::dynamic_pointer_cast<ov::op::v0::MatMul>(pattern_map.at(matmul_m).get_node_shared_ptr());
if (!matmul || transformation_callback(matmul)) {
return false;
}
// fc_input_a and fc_input_b - are the final inputs that will be set to FullyConnected.
// So in case of adding new operations that takes matmul inputs we need keep update fc_input_a and fc_input_b.
auto fc_input_a = pattern_map.at(activations_m);
auto fc_input_b = pattern_map.at(weights_m);
bool is_convert = false;
if (auto convert_node = std::dynamic_pointer_cast<ov::op::v0::Convert>(fc_input_b.get_node_shared_ptr())) {
if (is_decompression(convert_node)) {
is_convert = true;
fc_input_b = convert_node->get_input_node_shared_ptr(0);
} else {
return false;
}
}
auto shape_a = fc_input_a.get_partial_shape();
auto shape_b = fc_input_b.get_partial_shape();
OPENVINO_ASSERT(shape_b.is_static());
auto rank_a = shape_a.rank().get_length();
auto rank_b = shape_b.rank().get_length();
/*
* get_aligned_shapes function align two input shapes to have the same size and
* the same batch dimensions (last two dimensions are not comparable).
* It also checks that dimensions are compatible so in case with two shapes
* for example: [2, 32, 64] [3, 64, 64] it will raise an exception.
*/
auto get_aligned_shapes = [&shape_a, &shape_b, &rank_a, &rank_b, &matmul]() -> std::tuple<bool, ov::PartialShape, ov::PartialShape> {
ov::PartialShape shape_a_aligned(shape_a);
ov::PartialShape shape_b_aligned(shape_b);
size_t max_size = std::max(rank_a, rank_b);
for (size_t i = 0, cnt = max_size - rank_a; i < cnt; ++i) {
shape_a_aligned.insert(shape_a_aligned.begin(), 1);
}
for (size_t i = 0, cnt = max_size - rank_b; i < cnt; ++i) {
shape_b_aligned.insert(shape_b_aligned.begin(), 1);
}
if (matmul->get_transpose_a()) {
std::swap(*(shape_a_aligned.end() - 1), *(shape_a_aligned.end() - 2));
}
if (matmul->get_transpose_b()) {
std::swap(*(shape_b_aligned.end() - 1), *(shape_b_aligned.end() - 2));
}
// check on per-batch MatMul which can't be converted to FC
for (size_t i = 0; i < max_size - 2; ++i) {
if (shape_b_aligned[i] == 1) {
shape_b_aligned[i] = shape_a_aligned[i];
} else {
return std::make_tuple(false, std::move(shape_a_aligned), std::move(shape_b_aligned));
}
}
return std::make_tuple(true, std::move(shape_a_aligned), std::move(shape_b_aligned));
};
/*
* create_transpose function return Transpose operation to replace transpose_a or transpose_b
* arguments with an operation. In other words in this function we create Transpose operation
* with order length equal to output_shape length of given node and fill order with increasing
* sequence starting from 0 and replace last two dimension. For example for length = 4 the
* order will be [0, 1, 3, 2] that emulates transpose_a or transpose_b attribute.
*/
ov::NodeVector new_ops;
auto create_transpose = [this, &new_ops ](const ov::Output<ov::Node>& node, const std::string& transpose_name) {
std::vector<size_t> transpose_order(node.get_partial_shape().size());
std::iota(transpose_order.begin(), transpose_order.end(), 0);
std::swap(*(transpose_order.end() - 1), *(transpose_order.end() - 2));
auto transpose_const = ov::op::v0::Constant::create(ov::element::i32, ov::Shape{ transpose_order.size() }, transpose_order);
auto transpose = std::make_shared<ov::op::v1::Transpose>(node, transpose_const);
if (!ov::is_type<ov::op::v0::Constant>(transpose)) {
new_ops.push_back(transpose_const);
MatcherPass::register_new_node(transpose);
}
transpose->set_friendly_name(transpose_name);
ov::disable_constant_folding(transpose);
new_ops.push_back(transpose);
return transpose;
};
bool success = true;
ov::PartialShape shape_a_aligned;
ov::PartialShape shape_b_aligned;
std::tie(success, shape_a_aligned, shape_b_aligned) = get_aligned_shapes();
if (!success) {
return false;
}
auto aligned_a_rank = shape_a_aligned.rank();
auto aligned_b_rank = shape_b_aligned.rank();
if (aligned_a_rank.get_length() < 2 || aligned_b_rank.get_length() < 2) {
OPENVINO_THROW("MatMul " + matmul->get_friendly_name() + " shapes are inconsistent.");
}
// Weights normalization
if (!matmul->get_transpose_b()) {
fc_input_b = create_transpose(fc_input_b, matmul->get_friendly_name() + "/transpose_b");
}
// Input normalization
if (matmul->get_transpose_a()) {
fc_input_a = create_transpose(fc_input_a, matmul->get_friendly_name() + "/transpose_a");
}
// Connect Convert to new input if needed
if (is_convert) {
auto convert = pattern_map.at(weights_m).get_node_shared_ptr();
convert->input(0).replace_source_output(fc_input_b);
convert->validate_and_infer_types();
fc_input_b = convert;
}
// Create FullyConnected
auto fc = std::make_shared<op::FullyConnected>(fc_input_a, fc_input_b, matmul->get_output_element_type(0));
fc->set_friendly_name(matmul->get_friendly_name());
new_ops.push_back(fc);
ov::copy_runtime_info(matmul, new_ops);
ov::replace_node(matmul, fc);
return true;
};
auto m = std::make_shared<ov::pass::pattern::Matcher>(matmul_m);
this->register_matcher(m, callback);
}
} // namespace intel_gpu
} // namespace ov
@@ -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_gpu {
class ConvertMatMulToFullyConnected: public ov::pass::MatcherPass {
public:
OPENVINO_RTTI("ConvertMatMulToFullyConnected", "0");
ConvertMatMulToFullyConnected();
};
} // namespace intel_gpu
} // namespace ov
@@ -0,0 +1,112 @@
// Copyright (C) 2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "move_fc_reshape_to_weights.hpp"
#include "intel_gpu/op/fully_connected.hpp"
#include "transformations/utils/utils.hpp"
#include "openvino/pass/pattern/op/wrap_type.hpp"
#include "openvino/pass/pattern/op/or.hpp"
#include "openvino/op/matmul.hpp"
#include "openvino/op/constant.hpp"
#include "openvino/op/convert.hpp"
#include "openvino/op/subtract.hpp"
#include "openvino/op/multiply.hpp"
#include "openvino/op/transpose.hpp"
#include "openvino/op/reshape.hpp"
namespace ov {
namespace intel_gpu {
MoveFCReshapeToWeights::MoveFCReshapeToWeights() {
using namespace ov::pass::pattern;
auto weights_m = wrap_type<ov::op::v0::Constant>(consumers_count(1));
auto convert_m = wrap_type<ov::op::v0::Convert>({weights_m});
auto sub_const_m = wrap_type<ov::op::v0::Constant>(consumers_count(1));
auto subtract_m = wrap_type<ov::op::v1::Subtract>({convert_m, sub_const_m});
auto mul_const_m = wrap_type<ov::op::v0::Constant>(consumers_count(1));
auto mul_with_sub_m = wrap_type<ov::op::v1::Multiply>({subtract_m, mul_const_m}, rank_equals(3));
auto mul_no_sub_m = wrap_type<ov::op::v1::Multiply>({convert_m, mul_const_m}, rank_equals(3));
auto mul_m = std::make_shared<ov::pass::pattern::op::Or>(OutputVector{mul_with_sub_m, mul_no_sub_m});
auto one_consumer_rank_2 = [](const ov::Output<ov::Node>& out) {
return consumers_count(1)(out) && rank_equals(2)(out);
};
auto reshape_const_m = wrap_type<ov::op::v0::Constant>(consumers_count(1));
auto reshape_m = wrap_type<ov::op::v1::Reshape>({mul_m, reshape_const_m}, one_consumer_rank_2);
auto transpose_const_m = wrap_type<ov::op::v0::Constant>();
auto transpose_m = wrap_type<ov::op::v1::Transpose>({reshape_m, transpose_const_m});
auto weights_input_m = std::make_shared<ov::pass::pattern::op::Or>(ov::OutputVector{reshape_m, transpose_m});
auto data_m = any_input();
auto fully_connected_m = wrap_type<op::FullyConnected>({data_m, weights_input_m});
ov::matcher_pass_callback callback = [&](ov::pass::pattern::Matcher& m) {
const auto fully_connected = m.get_match_root();
const auto weights_path = fully_connected->get_input_node_shared_ptr(1);
const bool with_transpose = ov::is_type<ov::op::v1::Transpose>(weights_path);
if (with_transpose) {
const auto transpose_const = ov::as_type_ptr<ov::op::v0::Constant>(weights_path->get_input_node_shared_ptr(1));
if (transpose_const->cast_vector<int>() != std::vector<int>{1, 0}) {
return false;
}
}
const auto& fc_input_shape = fully_connected->get_input_shape(1);
const auto reshape = with_transpose ? weights_path->get_input_node_shared_ptr(0) : weights_path;
auto check_decompression_const = [&](const std::shared_ptr<ov::Node>& node) {
if (!ov::is_type<ov::op::v0::Constant>(node))
return false;
ov::Shape expected_shape(3, 1);
const size_t out_channels_idx = with_transpose ? 2 : 1;
expected_shape[out_channels_idx] = fc_input_shape[0];
return node->get_output_shape(0) == expected_shape;
};
const auto mul = reshape->get_input_node_shared_ptr(0);
if (!check_decompression_const(mul->get_input_node_shared_ptr(1)))
return false;
const auto mul_parent = mul->get_input_node_shared_ptr(0);
const bool with_subtract = ov::is_type<ov::op::v1::Subtract>(mul_parent);
if (with_subtract && !check_decompression_const(mul_parent->get_input_node_shared_ptr(1)))
return false;
const auto convert = with_subtract ? mul_parent->get_input_node_shared_ptr(0) : mul_parent;
const auto weights = convert->get_input_node_shared_ptr(0);
ov::Shape expected_weights_shape(3, 1);
expected_weights_shape[1] = fc_input_shape[with_transpose ? 1 : 0];
expected_weights_shape[2] = fc_input_shape[with_transpose ? 0 : 1];
if (weights->get_output_shape(0) != expected_weights_shape)
return false;
auto squeeze_constant = [](const std::shared_ptr<ov::Node>& node) {
const auto constant = ov::as_type_ptr<ov::op::v0::Constant>(node);
auto shape = constant->get_shape();
shape.erase(shape.begin());
const auto new_constant = std::make_shared<ov::op::v0::Constant>(*constant, shape);
ov::replace_node(constant, new_constant);
ov::copy_runtime_info(constant, new_constant);
new_constant->set_friendly_name(constant->get_friendly_name());
};
// We can remove 3D->2D reshape if we manually reshape all constants in the weights subgraph
ov::replace_output_update_name(reshape->output(0), reshape->input_value(0));
squeeze_constant(mul->get_input_node_shared_ptr(1));
squeeze_constant(weights);
if (with_subtract)
squeeze_constant(mul_parent->get_input_node_shared_ptr(1));
return true;
};
auto m = std::make_shared<ov::pass::pattern::Matcher>(fully_connected_m);
this->register_matcher(m, callback);
}
} // namespace intel_gpu
} // namespace ov
@@ -0,0 +1,38 @@
// Copyright (C) 2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/pass/graph_rewrite.hpp"
namespace ov {
namespace intel_gpu {
/**
* This transformation is applied to the FC with compressed 3D u8 weights. It moves Reshape at the weights path to the constants
* in order to constant fold the Reshape node.
* Example:
* Weights(3D) Weights(2D)
* | |
* Convert Subtract_const(3D) Convert Subtract_const(2D)
* | / | /
* Subtract(optional) Subtract(optional)
* | Multiply_const(3D) ====> | Multiply_const(2D)
* | / | /
* Multiply Multiply
* | |
* Reshape(2D) |
* | |
* Data Transpose(optional) Data Transpose(optional)
* \ / \ /
* FullyConnected FullyConnected
*/
class MoveFCReshapeToWeights: public ov::pass::MatcherPass {
public:
OPENVINO_RTTI("MoveFCReshapeToWeights", "0");
MoveFCReshapeToWeights();
};
} // namespace intel_gpu
} // namespace ov
@@ -0,0 +1,50 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "intel_gpu/op/fully_connected.hpp"
#include "matmul_shape_inference.hpp"
namespace ov {
namespace intel_gpu {
namespace op {
FullyConnected::FullyConnected(const ov::Output<Node>& A,
const ov::Output<Node>& B,
const ov::element::Type output_type)
: Op({A, B}), m_output_type(output_type) {
validate_and_infer_types();
}
std::shared_ptr<ov::Node> FullyConnected::clone_with_new_inputs(const ov::OutputVector& new_args) const {
check_new_args_count(this, new_args);
return std::make_shared<FullyConnected>(new_args.at(0), new_args.at(1), m_output_type);
}
void FullyConnected::validate_and_infer_types() {
const auto input_size = get_input_size();
NODE_VALIDATION_CHECK(this,
input_size >= 2,
"Number of inputs is incorrect. Current value is: ",
input_size,
", expected at least 2.");
ov::op::v0::MatMul op;
op.set_transpose_a(false);
op.set_transpose_b(true);
auto out_shapes = ov::op::v0::shape_infer(&op, std::vector<ov::PartialShape>{get_input_partial_shape(0), get_input_partial_shape(1)});
auto output_type = m_output_type == ov::element::undefined ? get_input_element_type(0) : m_output_type;
set_output_type(0, output_type, out_shapes[0]);
}
bool FullyConnected::visit_attributes(ov::AttributeVisitor &visitor) {
visitor.on_attribute("output_type", m_output_type);
return true;
}
} // namespace op
} // namespace intel_gpu
} // namespace ov
@@ -103,6 +103,10 @@
#include "transformations/convert_precision.hpp"
#include "transformations/init_node_info.hpp"
#include "transformations/rt_info/fused_names_attribute.hpp"
#include "transformations/smart_reshape/matmul_sr.hpp"
#include "plugin/transformations/convert_matmul_to_fc.hpp"
#include "plugin/transformations/move_fc_reshape_to_weights.hpp"
#include "transformations/low_precision/mark_dequantization_subgraph.hpp"
#include "low_precision/pull_reshape_through_dequantization.hpp"
@@ -252,6 +256,7 @@ void TransformationsPipeline::apply(std::shared_ptr<ov::Model> func) {
manager.register_pass<ov::pass::ConvertGather0D>();
manager.register_pass<ov::pass::ConvertPriorBox8To0, false>();
manager.register_pass<ov::pass::ConvertMulticlassNmsToMulticlassNmsIE>();
manager.register_pass<ov::pass::TransposeMatMul>();
precisions_map int_convert_precision_map {
{ov::element::i64, ov::element::i32},
@@ -606,6 +611,14 @@ void TransformationsPipeline::apply(std::shared_ptr<ov::Model> func) {
manager.run_passes(func);
}
{
ov::pass::Manager manager;
manager.register_pass<ov::intel_gpu::ConvertMatMulToFullyConnected>();
manager.register_pass<ov::intel_gpu::MoveFCReshapeToWeights>();
manager.run_passes(func);
}
}
} // namespace intel_gpu
} // namespace ov
@@ -0,0 +1,256 @@
// Copyright (C) 2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "ngraph_functions/builders.hpp"
#include "shared_test_classes/base/layer_test_utils.hpp"
#include "shared_test_classes/base/ov_subgraph.hpp"
#include "transformations/rt_info/decompression.hpp"
using namespace ngraph;
using namespace ov::test;
namespace SubgraphTestsDefinitions {
/*
* Subtract_const(U8)
* /
* Weights(U8) Convert(F32)
* | /
* Convert(F32) Reshape(optional)
* \ / Multiply_const(F32)
* Subtract(optional) /
* \ Reshape(optional)
* \ /
* Multiply
* |
* Data(F32) Transpose(optional)
* \ /
* Matmul
* |
* Bias
*/
using MatmulWeightsDecompressionParams = std::tuple<std::vector<InputShape>, // input shapes
ov::test::ElementType, // weights precision
ov::test::ElementType, // activations precision
bool, // transpose on weights
bool, // decompression subtract
bool, // reshape on decompression constants
std::map<std::string, std::string>>; // additional config
class MatmulWeightsDecompression : public testing::WithParamInterface<MatmulWeightsDecompressionParams>, public SubgraphBaseTest {
public:
static std::string get_test_case_name(testing::TestParamInfo<MatmulWeightsDecompressionParams> obj) {
std::vector<InputShape> inputShapes;
ov::test::ElementType weights_precision;
ov::test::ElementType activations_precision;
bool transpose;
bool decompression_sub;
bool reshape_on_decompression;
std::map<std::string, std::string> additional_config;
std::tie(inputShapes,
weights_precision,
activations_precision,
transpose,
decompression_sub,
reshape_on_decompression,
additional_config) = obj.param;
std::ostringstream result;
for (const auto& shape : inputShapes) {
result << ov::test::utils::partialShape2str({shape.first}) << "_";
}
result << "TS=";
for (const auto& shape : inputShapes) {
result << "(";
if (!shape.second.empty()) {
auto itr = shape.second.begin();
do {
result << ov::test::utils::vec2str(*itr);
} while (++itr != shape.second.end() && result << "_");
}
result << ")_";
}
result << "weights_precision=" << weights_precision << "_";
result << "activations_precision=" << activations_precision << "_";
result << "transpose_weights=" << transpose << "_";
result << "decompression_subtract=" << decompression_sub << "_";
result << "reshape_on_decompression=" << reshape_on_decompression << "_";
result << "config=(";
for (const auto& configEntry : additional_config) {
result << configEntry.first << ", " << configEntry.second << ":";
}
result << ")";
return result.str();
}
protected:
std::shared_ptr<ov::Model> init_subgraph(std::vector<ov::PartialShape>& inputShapes,
const ov::element::Type data_precision,
const ov::element::Type weights_precision,
const bool transpose_weights,
const bool add_subtract,
const bool reshape_on_decompression) {
ov::ParameterVector params{std::make_shared<ov::op::v0::Parameter>(data_precision, inputShapes[0])};
auto transpose_if_necessary = [&](const ov::Shape& shape) {
if (!transpose_weights)
return shape;
auto transposed_shape = shape;
std::swap(*transposed_shape.rbegin(), *(transposed_shape.rbegin() + 1));
return transposed_shape;
};
auto weights_shape = transpose_if_necessary(inputShapes[1].to_shape());
auto weights = ngraph::builder::makeConstant<uint8_t>(weights_precision, weights_shape, {}, true);
weights->set_friendly_name("Compressed_weights");
auto weights_convert = std::make_shared<ngraph::opset1::Convert>(weights, data_precision);
std::shared_ptr<ov::Node> mul_parent = weights_convert;
auto output_channels = transpose_weights ? *(weights_shape.rbegin() + 1) : *weights_shape.rbegin();
auto scaleshift_target_shape = transpose_if_necessary(ov::Shape{1, output_channels});
auto scaleshift_const_shape = reshape_on_decompression ? ov::Shape{output_channels} : scaleshift_target_shape;
if (add_subtract) {
auto shift_const = ngraph::builder::makeConstant<uint8_t>(weights_precision, scaleshift_const_shape, {}, true);
std::shared_ptr<ov::Node> shift_convert = std::make_shared<ngraph::opset1::Convert>(shift_const, data_precision);
if (reshape_on_decompression) {
auto shift_reshape_const = ov::opset10::Constant::create(ov::element::i32, {scaleshift_target_shape.size()}, scaleshift_target_shape);
auto shift_reshape = std::make_shared<ov::opset10::Reshape>(shift_convert, shift_reshape_const, false);
shift_convert = shift_reshape;
}
mul_parent = std::make_shared<ov::opset10::Subtract>(weights_convert, shift_convert);
}
std::shared_ptr<ov::Node> scale_const = ngraph::builder::makeConstant<float>(data_precision, scaleshift_const_shape, {}, true);
if (reshape_on_decompression) {
auto scale_reshape_const = ov::opset10::Constant::create(ov::element::i32, {scaleshift_target_shape.size()}, scaleshift_target_shape);
auto scale_reshape = std::make_shared<ov::opset10::Reshape>(scale_const, scale_reshape_const, false);
scale_const = scale_reshape;
}
auto multiply = std::make_shared<ov::opset10::Multiply>(mul_parent, scale_const);
std::shared_ptr<ov::Node> matmul_weights = multiply;
if (transpose_weights) {
const size_t rank = matmul_weights->get_output_partial_shape(0).size();
std::vector<int> order(rank);
std::iota(order.begin(), order.end(), 0);
std::swap(*order.rbegin(), *(order.rbegin() + 1));
auto transpose_constant = ov::opset10::Constant::create(ov::element::i32, {rank}, order);
auto transpose = std::make_shared<ov::opset10::Transpose>(matmul_weights, transpose_constant);
matmul_weights = transpose;
}
auto matMul = builder::makeMatMul(params[0], matmul_weights);
return std::make_shared<ov::Model>(NodeVector{matMul}, params, "MatmulWeightsDecompression");
}
void SetUp() override {
targetDevice = ov::test::utils::DEVICE_GPU;
std::vector<InputShape> inputShapes;
ov::test::ElementType weights_precision;
ov::test::ElementType activations_precision;
bool transpose_weights;
bool decompression_sub;
bool reshape_on_decompression;
std::map<std::string, std::string> additional_config;
std::tie(inputShapes,
weights_precision,
activations_precision,
transpose_weights,
decompression_sub,
reshape_on_decompression,
additional_config) = GetParam();
configuration.insert(additional_config.begin(), additional_config.end());
init_input_shapes(inputShapes);
inType = outType = activations_precision;
function = init_subgraph(inputDynamicShapes, activations_precision, weights_precision, transpose_weights, decompression_sub, reshape_on_decompression);
}
void checkResults() {
const auto& test_param = GetParam();
ov::test::ElementType weights_precision = std::get<1>(test_param);
for (const auto& n : compiledModel.get_runtime_model()->get_ordered_ops()) {
if (n->get_friendly_name() == "Compressed_weights") {
ASSERT_EQ(n->get_output_element_type(0), weights_precision);
}
}
}
};
TEST_P(MatmulWeightsDecompression, CompareWithRefs) {
SKIP_IF_CURRENT_TEST_IS_DISABLED()
run();
checkResults();
}
namespace {
const std::vector<ov::test::ElementType> activations_precisions = {ov::element::f32, ov::element::f16};
const std::vector<ov::test::ElementType> weights_precisions = {ov::element::u8};
const std::vector<std::vector<InputShape>> input_shapes_basic = {
{{{-1, -1, -1}, {{1, 4, 16}, {10, 16, 16}}}, {{}, {{16, 32}}}},
{{{}, {{1, 4, 16}}}, {{}, {{1, 16, 32}}}},
{{{}, {{10, 40, 496}}}, {{}, {{1, 496, 240}}}},
{{{}, {{1, 4, 48}}}, {{}, {{48, 256}}}},
{{{}, {{11, 339, 377}}}, {{}, {{377, 335}}}},
{{{-1, -1, -1}, {{10, 40, 480}, {11, 40, 480}}}, {{}, {{1, 480, 256}}}},
{{{}, {{1, 4, 32}}}, {{}, {{32, 256}}}},
{{{}, {{1, 4, 512}}}, {{}, {{512, 256}}}},
{{{}, {{1, 16, 32}}}, {{}, {{32, 64}}}},
{{{}, {{2, 4, 32}}}, {{}, {{32, 65}}}},
{{{}, {{3, 12, 768}}}, {{}, {{768, 1024}}}},
{{{}, {{11, 339, 577}}}, {{}, {{577, 335}}}},
};
INSTANTIATE_TEST_SUITE_P(smoke_MatMulCompressedWeights_basic,
MatmulWeightsDecompression,
::testing::Combine(::testing::ValuesIn(input_shapes_basic),
::testing::ValuesIn(weights_precisions),
::testing::ValuesIn(activations_precisions),
::testing::Values(true),
::testing::Values(true),
::testing::Values(true),
::testing::Values(std::map<std::string, std::string>())),
MatmulWeightsDecompression::get_test_case_name);
const std::vector<std::vector<InputShape>> input_shapes_corner_cases_basic = {
{{{-1, -1, -1}, {{1, 4, 16}}}, {{}, {{1, 16, 32}}}},
{{{-1, -1, -1}, {{1, 4, 16}}}, {{}, {{16, 32}}}},
};
const std::vector<std::vector<InputShape>> input_shapes_corner_cases_big = {
{{{-1, -1, -1}, {{10, 40, 480}, {11, 40, 480}}}, {{}, {{1, 480, 256}}}},
};
const std::vector<bool> transpose_weights = {true, false};
const std::vector<bool> add_decompression_sub = {true, false};
const std::vector<bool> reshape_on_decompression = {true, false};
INSTANTIATE_TEST_SUITE_P(smoke_MatMulCompressedWeights_corner_cases_basic,
MatmulWeightsDecompression,
::testing::Combine(::testing::ValuesIn(input_shapes_corner_cases_basic),
::testing::ValuesIn(weights_precisions),
::testing::ValuesIn(activations_precisions),
::testing::ValuesIn(transpose_weights),
::testing::ValuesIn(add_decompression_sub),
::testing::ValuesIn(reshape_on_decompression),
::testing::Values(std::map<std::string, std::string>{})),
MatmulWeightsDecompression::get_test_case_name);
INSTANTIATE_TEST_SUITE_P(smoke_MatMulCompressedWeights_corner_cases_big,
MatmulWeightsDecompression,
::testing::Combine(::testing::ValuesIn(input_shapes_corner_cases_big),
::testing::ValuesIn(weights_precisions),
::testing::ValuesIn(activations_precisions),
::testing::ValuesIn(transpose_weights),
::testing::ValuesIn(add_decompression_sub),
::testing::ValuesIn(reshape_on_decompression),
::testing::Values(std::map<std::string, std::string>{})),
MatmulWeightsDecompression::get_test_case_name);
} // namespace
} // namespace SubgraphTestsDefinitions
@@ -63,6 +63,7 @@ target_link_libraries(${TARGET_NAME} PRIVATE openvino_intel_gpu_graph
gtest
gtest_main
gflags
common_test_utils
openvino::reference
inference_engine_transformations
gmock)
@@ -0,0 +1,386 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <gtest/gtest.h>
#include <string>
#include <memory>
#include <openvino/core/model.hpp>
#include <openvino/opsets/opset1.hpp>
#include <openvino/opsets/opset3.hpp>
#include <openvino/opsets/opset7.hpp>
#include <intel_gpu/op/fully_connected.hpp>
#include <plugin/transformations/convert_matmul_to_fc.hpp>
#include <transformations/init_node_info.hpp>
#include <transformations/utils/utils.hpp>
#include <openvino/pass/manager.hpp>
#include <ov_ops/type_relaxed.hpp>
#include "common_test_utils/ov_test_utils.hpp"
#include "transformations/rt_info/decompression.hpp"
using namespace testing;
using namespace ov::intel_gpu;
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest1) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{ 3, 2, 2 });
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{ 1, 2, 2 }, { 1 });
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, true, false);
model = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{ 3, 2, 2 });
auto transpose_constant1 = ov::opset1::Constant::create(ov::element::i32, ov::Shape{ 3 }, { 0, 2, 1 });
auto transpose1 = std::make_shared<ov::opset1::Transpose>(input1, transpose_constant1);
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{ 1, 2, 2 }, { 1 });
auto transpose_constant2 = ov::opset1::Constant::create(ov::element::i32, ov::Shape{ 3 }, { 0, 2, 1 });
auto transpose2 = std::make_shared<ov::opset1::Transpose>(input2, transpose_constant2);
auto matmul = std::make_shared<op::FullyConnected>(transpose1, transpose2);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest2) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{3, 1, 2});
auto input2 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{3, 2, 1});
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, false);
model = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1, input2});
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{3, 1, 2});
auto input2 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{3, 2, 1});
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, false);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1, input2});
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest3) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{3, 2, 2});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{2, 2}, {1});
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{3, 2, 2});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{2, 2}, {1});
auto matmul = std::make_shared<op::FullyConnected>(input1, input2);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest4) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::PartialShape{-1, -1, 2});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{2, 2}, {1});
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::PartialShape{-1, -1, 2});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{2, 2}, {1});
auto matmul = std::make_shared<op::FullyConnected>(input1, input2);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest5) {
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::PartialShape{ -1, -1, 2 });
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{ 3, 2, 2 }, { 1 });
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
manager.register_pass<ConvertMatMulToFullyConnected>();
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest6) {
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::PartialShape{ -1, -1, 2 });
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{ 3, 1, 2 }, { 1 });
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
manager.register_pass<ConvertMatMulToFullyConnected>();
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest7) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{3, 2, 2});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{3, 2}, {1});
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{3, 2, 2});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{3, 2}, {1});
auto fc = std::make_shared<op::FullyConnected>(input1, input2);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{fc}, ov::ParameterVector{input1});
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest8) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::PartialShape{-1, -1, 2});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{3, 2}, {1});
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::PartialShape{-1, -1, 2});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{3, 2}, {1});
auto fc = std::make_shared<op::FullyConnected>(input1, input2);
auto a_shape = std::make_shared<ov::opset3::ShapeOf>(input1);
auto I = ov::op::util::node_to_get_shape_value_of_indices_from_shape_node(a_shape, {0, 1});
auto O = ov::opset1::Constant::create(ov::element::i64, { 1 }, { 3 });
auto output_shape = std::make_shared<ov::opset1::Concat>(ov::OutputVector{I, O}, 0);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{fc}, ov::ParameterVector{input1});
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest9) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{3, 2, 2});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{2, 2}, {1});
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{3, 2, 2});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{2, 2}, {1});
auto matmul = std::make_shared<op::FullyConnected>(input1, input2);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest10) {
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::PartialShape::dynamic());
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{ 2, 2 }, { 1 });
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
manager.register_pass<ConvertMatMulToFullyConnected>();
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest11) {
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::PartialShape{18, -1, 1});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{18, 80, 1}, {1});
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
manager.register_pass<ConvertMatMulToFullyConnected>();
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest12) {
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::PartialShape{1, -1, 1});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{2, 80, 1}, {1});
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
manager.register_pass<ConvertMatMulToFullyConnected>();
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest13) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::PartialShape{-1, -1, 1});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{1, 80, 1}, {1});
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::PartialShape{-1, -1, 1});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{1, 80, 1}, {1});
auto matmul = std::make_shared<op::FullyConnected>(input1, input2);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest14) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::u8, ov::PartialShape{-1, -1, 1});
auto input2 = ov::opset1::Constant::create(ov::element::i8, ov::Shape{1, 80, 1}, {1});
auto matmul = std::make_shared<ov::op::TypeRelaxed<ov::opset1::MatMul>>(
ov::element::TypeVector{ov::element::f32, ov::element::f32},
ov::element::TypeVector{ov::element::f32},
ov::op::TemporaryReplaceOutputType(input1, ov::element::f32).get(),
ov::op::TemporaryReplaceOutputType(input2, ov::element::f32).get(),
false,
true);
model = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::u8, ov::PartialShape{-1, -1, 1});
auto input2 = ov::opset1::Constant::create(ov::element::i8, ov::Shape{1, 80, 1}, {1});
auto matmul = std::make_shared<op::FullyConnected>(input1, input2, ov::element::f32);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest_second_input_rank_adj_1) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{5, 2, 3});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{1, 2, 3}, {1});
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, input2, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{5, 2, 3});
auto input2 = ov::opset1::Constant::create(ov::element::f32, ov::Shape{1, 2, 3}, {1});
auto matmul = std::make_shared<op::FullyConnected>(input1, input2);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{input1});
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest_second_input_rank_adj_2) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{ 2, 3 });
auto weights = ov::opset1::Constant::create(ov::element::f32, ov::Shape{ 2, 3 }, { 1 });
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, weights, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{ 2, 3 });
auto weights = ov::opset1::Constant::create(ov::element::f32, ov::Shape{ 2, 3 }, { 1 });
auto matmul = std::make_shared<op::FullyConnected>(input1, weights);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest_second_input_rank_adj_3) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{ 5, 2, 3 });
auto weights = ov::opset1::Constant::create(ov::element::f32, ov::Shape{ 1, 2, 3 }, { 1 });
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, weights, false, true);
model = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{ 5, 2, 3 });
auto weights = ov::opset1::Constant::create(ov::element::f32, ov::Shape{ 1, 2, 3 }, { 1 });
auto matmul = std::make_shared<op::FullyConnected>(input1, weights);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest_decompress_convert_0) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{ 3, 2, 2 });
auto input2 = ov::opset1::Constant::create(ov::element::f16, ov::Shape{ 1, 2, 2 }, { 1 });
auto convert = std::make_shared<ov::opset1::Convert>(input2, ov::element::f32);
ov::mark_as_decompression(convert);
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, convert, false, false);
model = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{ 3, 2, 2 });
auto input2 = ov::opset1::Constant::create(ov::element::f16, ov::Shape{ 1, 2, 2 }, { 1 });
auto transpose_constant = ov::opset1::Constant::create(ov::element::i32, ov::Shape{ 3 }, { 0, 2, 1 });
auto transpose = std::make_shared<ov::opset1::Transpose>(input2, transpose_constant);
auto convert = std::make_shared<ov::opset1::Convert>(transpose, ov::element::f32);
auto matmul = std::make_shared<op::FullyConnected>(input1, convert);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest_decompress_convert_1) {
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{ 3, 2, 2 });
auto input2 = ov::opset1::Constant::create(ov::element::f16, ov::Shape{ 1, 2, 2 }, { 1 });
auto convert = std::make_shared<ov::opset1::Convert>(input2, ov::element::f32);
ov::mark_as_decompression(convert);
auto matmul = std::make_shared<ov::opset1::MatMul>(input1, convert, true, false);
model = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto input1 = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{ 3, 2, 2 });
auto transpose_constant1 = ov::opset1::Constant::create(ov::element::i32, ov::Shape{ 3 }, { 0, 2, 1 });
auto transpose1 = std::make_shared<ov::opset1::Transpose>(input1, transpose_constant1);
auto input2 = ov::opset1::Constant::create(ov::element::f16, ov::Shape{ 1, 2, 2 }, { 1 });
auto transpose_constant2 = ov::opset1::Constant::create(ov::element::i32, ov::Shape{ 3 }, { 0, 2, 1 });
auto transpose2 = std::make_shared<ov::opset1::Transpose>(input2, transpose_constant2);
auto convert = std::make_shared<ov::opset1::Convert>(transpose2, ov::element::f32);
auto matmul = std::make_shared<op::FullyConnected>(transpose1, convert);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ input1 });
}
}
TEST_F(TransformationTestsF, ConvertMatMulToFullyConnectedTest_compressed_u8_weights) {
{
auto data = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{3, 2, 2});
auto weights = ov::opset1::Constant::create(ov::element::u8, ov::Shape{1, 2, 2}, {1});
auto convert = std::make_shared<ov::opset1::Convert>(weights, ov::element::f32);
auto sub_const = ov::opset1::Constant::create(ov::element::f32, ov::Shape{1, 1, 2}, {1});
auto sub = std::make_shared<ov::opset1::Subtract>(convert, sub_const);
auto mul_const = ov::opset1::Constant::create(ov::element::f32, ov::Shape{1, 1, 2}, {1});
auto mul = std::make_shared<ov::opset1::Multiply>(sub, mul_const);
auto matmul = std::make_shared<ov::opset1::MatMul>(data, mul);
model = std::make_shared<ov::Model>(ov::NodeVector{matmul}, ov::ParameterVector{data});
manager.register_pass<ConvertMatMulToFullyConnected>();
}
{
auto data = std::make_shared<ov::opset1::Parameter>(ov::element::f32, ov::Shape{3, 2, 2});
auto weights = ov::opset1::Constant::create(ov::element::u8, ov::Shape{1, 2, 2}, {1});
auto convert = std::make_shared<ov::opset1::Convert>(weights, ov::element::f32);
auto sub_const = ov::opset1::Constant::create(ov::element::f32, ov::Shape{1, 1, 2}, {1});
auto sub = std::make_shared<ov::opset1::Subtract>(convert, sub_const);
auto mul_const = ov::opset1::Constant::create(ov::element::f32, ov::Shape{1, 1, 2}, {1});
auto mul = std::make_shared<ov::opset1::Multiply>(sub, mul_const);
auto transpose_const = ov::opset1::Constant::create(ov::element::i32, {3}, {0, 2, 1});
auto transpose = std::make_shared<ov::opset1::Transpose>(mul, transpose_const);
auto matmul = std::make_shared<op::FullyConnected>(data, transpose);
model_ref = std::make_shared<ov::Model>(ov::NodeVector{ matmul }, ov::ParameterVector{ data });
}
}
@@ -0,0 +1,107 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <plugin/transformations/move_fc_reshape_to_weights.hpp>
#include <gtest/gtest.h>
#include <string>
#include <memory>
#include <openvino/core/model.hpp>
#include <openvino/opsets/opset1.hpp>
#include <intel_gpu/op/fully_connected.hpp>
#include <transformations/init_node_info.hpp>
#include <transformations/utils/utils.hpp>
#include "common_test_utils/ov_test_utils.hpp"
using namespace testing;
using namespace ov::intel_gpu;
using MoveFCReshapeToWeightsParams = std::tuple<std::pair<ov::PartialShape, ov::Shape>, // data_shape - weights_shape
bool, // add transpose
bool>; // add subtract
class MoveFCReshapeToWeightsTests : public TransformationTestsF, public WithParamInterface<MoveFCReshapeToWeightsParams> {
public:
static std::string get_test_case_name(testing::TestParamInfo<MoveFCReshapeToWeightsParams> obj) {
std::pair<ov::PartialShape, ov::Shape> input_shapes;
bool add_transpose;
bool add_subtract;
std::tie(input_shapes, add_transpose, add_subtract) = obj.param;
std::ostringstream result;
result << "Input_shape=(" << input_shapes.first << ")_Weights_shape=(" << input_shapes.second
<< ")_add_transpose=" << add_transpose << "_add_subtract=" << add_subtract;
return result.str();
}
static std::shared_ptr<ov::Model> init_model(const ov::PartialShape& data_shape,
const ov::Shape& weights_shape,
const bool add_transpose,
const bool add_subtract,
const bool add_reshape) {
auto data = std::make_shared<ov::opset1::Parameter>(ov::element::f32, data_shape);
auto transposed_shape = weights_shape;
if (add_transpose)
std::swap(*(transposed_shape.rbegin() + 1), *transposed_shape.rbegin());
std::shared_ptr<ov::Node> weights_path = ov::opset1::Constant::create(ov::element::u8, transposed_shape, {1});
weights_path = std::make_shared<ov::opset1::Convert>(weights_path, ov::element::f32);
ov::Shape decompression_shape(weights_shape.size(), 1);
const size_t n_idx = add_transpose ? transposed_shape.size() - 1 : transposed_shape.size() - 2;
decompression_shape[n_idx] = transposed_shape[n_idx];
if (add_subtract) {
auto sub_const = ov::opset1::Constant::create(ov::element::f32, decompression_shape, {1});
weights_path = std::make_shared<ov::opset1::Subtract>(weights_path, sub_const);
}
auto mul_const = ov::opset1::Constant::create(ov::element::f32, decompression_shape, {1});
weights_path = std::make_shared<ov::opset1::Multiply>(weights_path, mul_const);
if (add_reshape) {
auto target_shape = transposed_shape;
target_shape.erase(target_shape.begin());
auto reshape_const = ov::opset1::Constant::create(ov::element::i32, {2}, target_shape);
weights_path = std::make_shared<ov::opset1::Reshape>(weights_path, reshape_const, false);
}
if (add_transpose) {
auto transpose_const = ov::opset1::Constant::create(ov::element::i32, {2}, {1, 0});
weights_path = std::make_shared<ov::opset1::Transpose>(weights_path, transpose_const);
}
auto fully_connected = std::make_shared<op::FullyConnected>(data, weights_path);
return std::make_shared<ov::Model>(ov::NodeVector{fully_connected}, ov::ParameterVector{data});
}
protected:
void SetUp() override {
TransformationTestsF::SetUp();
std::pair<ov::PartialShape, ov::Shape> input_shapes;
bool add_transpose;
bool add_subtract;
std::tie(input_shapes, add_transpose, add_subtract) = this->GetParam();
ov::Shape ref_weights_shape = input_shapes.second;
ref_weights_shape.erase(ref_weights_shape.begin());
model = init_model(input_shapes.first, input_shapes.second, add_transpose, add_subtract, true);
model_ref = init_model(input_shapes.first, ref_weights_shape, add_transpose, add_subtract, false);
manager.register_pass<MoveFCReshapeToWeights>();
}
};
TEST_P(MoveFCReshapeToWeightsTests, CompareFunctions) {}
const std::vector<std::pair<ov::PartialShape, ov::Shape>> input_shapes_wo_transpose = {
{{-1, -1, -1}, {1, 4, 3}}
};
const std::vector<bool> add_transpose = {false, true};
const std::vector<bool> add_subtract = {false, true};
INSTANTIATE_TEST_SUITE_P(smoke_TransformationTests_wo_transpose, MoveFCReshapeToWeightsTests,
::testing::Combine(
::testing::ValuesIn(input_shapes_wo_transpose),
::testing::ValuesIn(add_transpose),
::testing::ValuesIn(add_subtract)),
MoveFCReshapeToWeightsTests::get_test_case_name);