[GPU] Add FullyConnected custom op (#19539)
This commit is contained in:
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
|
||||
@@ -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
|
||||
+256
@@ -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);
|
||||
Reference in new issue
Block a user