diff --git a/src/core/shape_inference/include/tile_shape_inference.hpp b/src/core/shape_inference/include/tile_shape_inference.hpp index 17a83158a71..ec763860712 100644 --- a/src/core/shape_inference/include/tile_shape_inference.hpp +++ b/src/core/shape_inference/include/tile_shape_inference.hpp @@ -11,20 +11,20 @@ namespace ov { namespace op { namespace v0 { -template +template struct NegativeToZero { NegativeToZero() = default; template - T operator()(const U u) const { - return static_cast(std::max(0, ov::util::InTypeRange()(u))); + TShape operator()(const U u) const { + return static_cast(std::max(0, ov::util::InTypeRange()(u))); } }; -template -std::vector shape_infer(const Tile* op, - const std::vector& input_shapes, - const ITensorAccessor& tensor_accessor = make_tensor_accessor()) { - using TDim = typename T::value_type; +template > +std::vector shape_infer(const Tile* op, + const std::vector& input_shapes, + const ITensorAccessor& tensor_accessor = make_tensor_accessor()) { + using TDim = typename TShape::value_type; using TDimValue = typename TDim::value_type; NODE_VALIDATION_CHECK(op, input_shapes.size() == 2); @@ -34,12 +34,13 @@ std::vector shape_infer(const Tile* op, NODE_VALIDATION_CHECK(op, repeats_rank.compatible(1), "Tile repeats must be of rank 1"); const auto& arg_shape = input_shapes[0]; - T output_shape; + auto output_shapes = std::vector(1); + auto& output_shape = output_shapes[0]; // Get repeats and pre process values constexpr auto negative_repeats_to_zero = NegativeToZero(); - auto repeats = get_input_const_data_as_shape(op, 1, tensor_accessor, negative_repeats_to_zero); + auto repeats = get_input_const_data_as_shape(op, 1, tensor_accessor, negative_repeats_to_zero); const auto& arg_rank = arg_shape.rank(); if (arg_rank.is_static() && repeats) { @@ -66,7 +67,7 @@ std::vector shape_infer(const Tile* op, // can't deduce shape, set default value output_shape = PartialShape::dynamic(); } - return {output_shape}; + return output_shapes; } } // namespace v0 } // namespace op diff --git a/src/core/shape_inference/include/utils.hpp b/src/core/shape_inference/include/utils.hpp index 402f5a302d9..346d0f4a443 100644 --- a/src/core/shape_inference/include/utils.hpp +++ b/src/core/shape_inference/include/utils.hpp @@ -432,6 +432,30 @@ std::unique_ptr get_input_bounds(const ov::Node* op, } } // namespace op + +/** + * @brief Get correct return type of input shape when call `shape_infer`. + * + * The input shapes are vector like std::vector, where `TShape` can be `std::vector` + * This will provide correct return especially for static shape which can work as reference to dimension or hold them. + * + * @tparam TShape Type of input shape. + */ +template +struct result_shape { + using type = typename TShape::ShapeContainer; +}; + +/** + * @brief Get correct result shape for PartialShape which is same type. + */ +template <> +struct result_shape { + using type = PartialShape; +}; + +template +using result_shape_t = typename result_shape::type; } // namespace ov // Helper to reduce duplicates of code for get_data_as_... specific type functions. diff --git a/src/plugins/intel_cpu/src/utils/shape_inference/ov_optional.hpp b/src/plugins/intel_cpu/src/utils/shape_inference/ov_optional.hpp new file mode 100644 index 00000000000..1a9abcdcd79 --- /dev/null +++ b/src/plugins/intel_cpu/src/utils/shape_inference/ov_optional.hpp @@ -0,0 +1,129 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include + +namespace ov { + +/** + * @brief Store optional object of type T (basic version of std::optional). + * + * @note If cpp17 used this class should be replaced by std::optional. + * + * @tparam T Type of stored object. + */ +template +class optional { +public: + constexpr optional() noexcept = default; + template + constexpr optional(Args&&... args) : m_has_value{true}, + m_opt(std::forward(args)...) {} + + optional(const optional& other) : m_has_value{other.m_has_value}, m_opt{} { + if (other.m_has_value) { + construct(*other); + } + } + + optional(optional&& other) noexcept : m_has_value{other.m_has_value}, m_opt{} { + if (other.m_has_value) { + construct(std::move(*other)); + } + } + + ~optional() { + reset(); + } + + optional& operator=(const optional& other) { + if (other.m_has_value) { + if (m_has_value) { + m_opt.m_value = *other; + } else { + construct(*other); + } + m_has_value = true; + } else { + reset(); + } + return *this; + } + + optional& operator=(optional&& other) noexcept { + if (other.m_has_value) { + if (m_has_value) { + m_opt.m_value = std::move(*other); + } else { + construct(std::move(*other)); + } + m_has_value = true; + } else { + reset(); + } + return *this; + } + + constexpr operator bool() const { + return m_has_value; + } + + constexpr const T& operator*() const& noexcept { + return m_opt.m_value; + } + + T& operator*() & noexcept { + return m_opt.m_value; + } + + constexpr const T&& operator*() const&& noexcept { + return m_opt.m_value; + } + + T&& operator*() && noexcept { + return m_opt.m_value; + } + + constexpr const T* operator->() const noexcept { + return &m_opt.m_value; + } + + T* operator->() noexcept { + return &m_opt.m_value; + } + + void reset() { + if (m_has_value) { + m_opt.m_value.T::~T(); + m_has_value = false; + } + } + +private: + template + void construct(Args&&... args) { + new (std::addressof(m_opt)) T(std::forward(args)...); + } + + struct Empty {}; + + template + union Storage { + Empty m_empty; + T m_value; + + constexpr Storage() noexcept : m_empty{} {} + constexpr Storage(uint8_t) noexcept : Storage{} {} + + template + constexpr Storage(Args&&... args) : m_value(std::forward(args)...) {} + ~Storage() {} + }; + + bool m_has_value; + Storage m_opt; +}; +} // namespace ov diff --git a/src/plugins/intel_cpu/src/utils/shape_inference/shape_inference.cpp b/src/plugins/intel_cpu/src/utils/shape_inference/shape_inference.cpp index 7e6324eddc8..cc19444f33b 100644 --- a/src/plugins/intel_cpu/src/utils/shape_inference/shape_inference.cpp +++ b/src/plugins/intel_cpu/src/utils/shape_inference/shape_inference.cpp @@ -317,6 +317,11 @@ public: OPENVINO_THROW("Not implemented by base class"); } + ov::optional> infer(const std::vector& input_shapes, + const ov::ITensorAccessor& tensor_accessor) override { + OPENVINO_THROW("Not implemented by base class"); + } + const ov::CoordinateDiff& get_pads_begin() override { OPENVINO_ASSERT(false, "ShapeInferBase do not support get_pads_begin() by default."); } @@ -359,6 +364,46 @@ public: } }; +template +class ShapeInferenceTA : public ShapeInferBase { +public: + using ShapeInferBase::ShapeInferBase; + + IShapeInferCommon::Result infer(const std::vector& input_shapes, + const ov::ITensorAccessor& tensor_accessor) override { + // Temporary support of StaticShape. + auto in_shapes = std::vector(); + in_shapes.reserve(input_shapes.size()); + for (auto& s : input_shapes) { + in_shapes.emplace_back(reinterpret_cast(s)); + } + + auto out_shapes = infer(in_shapes, tensor_accessor); + Result result{{}, out_shapes ? ShapeInferStatus::success : ShapeInferStatus::skip}; + + if (out_shapes) { + result.shapes.reserve(out_shapes->size()); + std::transform(out_shapes->begin(), + out_shapes->end(), + std::back_inserter(result.shapes), + [](StaticShapeCon& s) { + return std::move(reinterpret_cast(*s)); + }); + } + + return result; + } + + ov::optional> infer(const std::vector& input_shapes, + const ov::ITensorAccessor& tensor_accessor) override { + return {shape_infer(static_cast(m_node.get()), input_shapes, tensor_accessor)}; + } + + port_mask_t get_port_mask() const override { + return MASK; + } +}; + /** * @brief Shape inference not using tensor accessor * @@ -637,6 +682,7 @@ const IStaticShapeInferFactory::TRegistry IStaticShapeInferFactory::registry{ // Default opset _OV_OP_SHAPE_INFER_MASK_REG(ExperimentalDetectronROIFeatureExtractor, ShapeInferTA, util::bit::mask()), _OV_OP_SHAPE_INFER_MASK_REG(Proposal, ShapeInferTA, util::bit::mask()), + _OV_OP_SHAPE_INFER_MASK_REG(Tile, ShapeInferenceTA, util::bit::mask(1)), _OV_OP_SHAPE_INFER_VA_REG(ReduceL1, ShapeInferTA, op::util::ArithmeticReductionKeepDims, util::bit::mask(1)), _OV_OP_SHAPE_INFER_VA_REG(ReduceL2, ShapeInferTA, op::util::ArithmeticReductionKeepDims, util::bit::mask(1)), _OV_OP_SHAPE_INFER_VA_REG(ReduceLogicalAnd, ShapeInferTA, op::util::LogicalReductionKeepDims, util::bit::mask(1)), @@ -646,7 +692,6 @@ const IStaticShapeInferFactory::TRegistry IStaticShapeInferFactory::registry{ _OV_OP_SHAPE_INFER_VA_REG(ReduceMin, ShapeInferTA, op::util::ArithmeticReductionKeepDims, util::bit::mask(1)), _OV_OP_SHAPE_INFER_VA_REG(ReduceProd, ShapeInferTA, op::util::ArithmeticReductionKeepDims, util::bit::mask(1)), _OV_OP_SHAPE_INFER_VA_REG(ReduceSum, ShapeInferTA, op::util::ArithmeticReductionKeepDims, util::bit::mask(1)), - _OV_OP_SHAPE_INFER_MASK_REG(Tile, ShapeInferTA, util::bit::mask(1)), // Operators shape inferences for specific opset version should be specified below // opset11 _OV_OP_SHAPE_INFER_MASK_REG(opset11::Interpolate, ShapeInferPaddingTA, util::bit::mask(1, 2, 3)), diff --git a/src/plugins/intel_cpu/src/utils/shape_inference/shape_inference.hpp b/src/plugins/intel_cpu/src/utils/shape_inference/shape_inference.hpp index d68175e7f2d..b6d29a20269 100644 --- a/src/plugins/intel_cpu/src/utils/shape_inference/shape_inference.hpp +++ b/src/plugins/intel_cpu/src/utils/shape_inference/shape_inference.hpp @@ -8,6 +8,7 @@ #include #include +#include "ov_optional.hpp" #include "shape_inference_status.hpp" #include "static_shape.hpp" #include "tensor_data_accessor.hpp" @@ -40,6 +41,16 @@ public: virtual Result infer(const std::vector& input_shapes, const ov::ITensorAccessor& tensor_accessor) = 0; + /** + * @brief Do shape inference. + * + * @param input_shapes Input shapes vector of static shape reference adapter. + * @param tensor_accessor Accessor to CPU constant data specific for operator. + * @return Optionally return vector of static shape adapters holding CPU dimensions. + */ + virtual ov::optional> infer(const std::vector& input_shapes, + const ov::ITensorAccessor& tensor_accessor) = 0; + /** * @brief Some shape inference implementation may require input data stored inside the input tensors. To define * which inputs data are required, the port mask is used. Each set bit corresponds to the specific input port diff --git a/src/plugins/intel_cpu/src/utils/shape_inference/static_shape.cpp b/src/plugins/intel_cpu/src/utils/shape_inference/static_shape.cpp index 9e9e3523ca2..47824e4cd40 100644 --- a/src/plugins/intel_cpu/src/utils/shape_inference/static_shape.cpp +++ b/src/plugins/intel_cpu/src/utils/shape_inference/static_shape.cpp @@ -7,6 +7,22 @@ namespace ov { namespace intel_cpu { +namespace { +void partial_shape_convert_throw() { + OPENVINO_THROW("[shape infer] Shouldn't convert from PartialShape to StaticShape at runtime."); +} +} // namespace + +template +bool merge_into(StaticShapeCon& dst, const T& src) { + auto success = (dst.size() != src.size()); + + for (size_t i = 0; success && (i < dst.size()); ++i) + success = StaticDimension::merge(dst[i], dst[i], src[i]); + + return success; +} + StaticShape::StaticShape(std::vector dimensions) : std::vector(std::move(dimensions)) {} @@ -16,6 +32,9 @@ StaticShape::StaticShape(const std::vector& dimensi StaticShape::StaticShape(std::initializer_list init) : std::vector(init.begin(), init.end()) {} +StaticShape::StaticShape(const ov::PartialShape&) : std::vector{} { + partial_shape_convert_throw(); +} ov::Shape StaticShape::get_max_shape() const { return (*this).to_shape(); @@ -104,25 +123,25 @@ bool StaticShape::merge_into(StaticShape& dst, const StaticShape& src) { bool StaticShape::broadcast_merge_into(StaticShape& dst, const StaticShape& src, - const ngraph::op::AutoBroadcastSpec& autob) { + const ov::op::AutoBroadcastSpec& autob) { switch (autob.m_type) { - case ngraph::op::AutoBroadcastType::NONE: - return true; - case ngraph::op::AutoBroadcastType::NUMPY: { - auto dst_rank = dst.size(); - auto src_rank = src.size(); - auto new_rank = std::max(dst_rank, src_rank); - std::vector dims(new_rank); - bool success = true; - for (size_t i = 0; i < new_rank; i++) { - auto dsti = i < (new_rank - dst_rank) ? StaticDimension(1) : dst[i - (new_rank - dst_rank)]; - auto srci = i < (new_rank - src_rank) ? StaticDimension(1) : src[i - (new_rank - src_rank)]; - success &= StaticDimension::broadcast_merge(dims[i], dsti, srci); - } - dst = StaticShape(std::move(dims)); - return success; + case ov::op::AutoBroadcastType::NONE: + return true; + case ov::op::AutoBroadcastType::NUMPY: { + auto dst_rank = dst.size(); + auto src_rank = src.size(); + auto new_rank = std::max(dst_rank, src_rank); + std::vector dims(new_rank); + bool success = true; + for (size_t i = 0; i < new_rank; i++) { + auto dsti = i < (new_rank - dst_rank) ? StaticDimension(1) : dst[i - (new_rank - dst_rank)]; + auto srci = i < (new_rank - src_rank) ? StaticDimension(1) : src[i - (new_rank - src_rank)]; + success &= StaticDimension::broadcast_merge(dims[i], dsti, srci); } - case ngraph::op::AutoBroadcastType::PDPD: { + dst = StaticShape(std::move(dims)); + return success; + } + case ov::op::AutoBroadcastType::PDPD: { // Ranks are both static. auto dst_rank = dst.rank().get_length(); auto src_rank = src.rank().get_length(); @@ -148,7 +167,7 @@ bool StaticShape::broadcast_merge_into(StaticShape& dst, return success; } default: - NGRAPH_CHECK(false, "Unsupported auto broadcast type: ", autob.m_type); + OPENVINO_THROW("Unsupported auto broadcast type: ", autob.m_type); } return false; } @@ -166,5 +185,84 @@ bool StaticShape::operator!=(const StaticShape& partial_shape) const { return !(*this == partial_shape); } +//-- Shape as container +StaticShapeCon::StaticShapeAdapter() : m_dims{} {} +StaticShapeCon::StaticShapeAdapter(const TDims& dims) : m_dims{dims} {} +StaticShapeCon::StaticShapeAdapter(TDims&& dims) noexcept : m_dims{std::move(dims)} {} +StaticShapeCon::StaticShapeAdapter(const StaticShapeCon& other) : StaticShapeAdapter(*other) {} +StaticShapeCon::StaticShapeAdapter(const ov::PartialShape&) : m_dims{} { + partial_shape_convert_throw(); +} + +ov::Rank StaticShapeCon::rank() const { + return {static_cast(size())}; +} + +ov::Shape StaticShapeCon::to_shape() const { + return {m_dims}; +} + +ov::Shape StaticShapeCon::get_max_shape() const { + return to_shape(); +} + +ov::Shape StaticShapeCon::get_min_shape() const { + return to_shape(); +} + +ov::Shape StaticShapeCon::get_shape() const { + return to_shape(); +} + +ov::PartialShape StaticShapeCon::to_partial_shape() const { + auto shape = PartialShape::dynamic(size()); + std::transform(m_dims.cbegin(), m_dims.cend(), shape.begin(), ov::util::Cast()); + return shape; +} + +bool StaticShapeCon::merge_rank(const ov::Rank& r) { + return r.is_dynamic() || (size() == static_cast(r.get_length())); +} + +bool StaticShapeCon::merge_into(StaticShapeAdapter& dst, const StaticShapeAdapter& src) { + return ov::intel_cpu::merge_into(dst, src); +} + +bool StaticShapeCon::broadcast_merge_into(StaticShapeCon& dst, + const StaticShapeCon& src, + const ov::op::AutoBroadcastSpec& autob) { + // Copy from StaticShape when broadcast shape infer reviewed. + return false; +} + +//-- Shape as reference +StaticShapeRef::StaticShapeAdapter(const ov::PartialShape&) : m_dims{} { + partial_shape_convert_throw(); +} + +ov::Rank StaticShapeRef::rank() const { + return {static_cast(size())}; +} + +bool StaticShapeRef::merge_rank(const ov::Rank& r) { + return r.is_dynamic() || (size() == static_cast(r.get_length())); +} + +ov::Shape StaticShapeRef::to_shape() const { + return m_dims ? ov::Shape{*m_dims} : ov::Shape{}; +} + +ov::Shape StaticShapeRef::get_max_shape() const { + return to_shape(); +} + +ov::Shape StaticShapeRef::get_min_shape() const { + return to_shape(); +} + +ov::Shape StaticShapeRef::get_shape() const { + return to_shape(); +} + } // namespace intel_cpu } // namespace ov diff --git a/src/plugins/intel_cpu/src/utils/shape_inference/static_shape.hpp b/src/plugins/intel_cpu/src/utils/shape_inference/static_shape.hpp index b84c3ab241f..ec92d2a2c4b 100644 --- a/src/plugins/intel_cpu/src/utils/shape_inference/static_shape.hpp +++ b/src/plugins/intel_cpu/src/utils/shape_inference/static_shape.hpp @@ -6,13 +6,14 @@ #include -#include "ngraph/op/util/attr_types.hpp" +#include "cpu_types.h" #include "openvino/core/attribute_adapter.hpp" -#include "static_dimension.hpp" +#include "openvino/core/except.hpp" +#include "openvino/core/partial_shape.hpp" #include "openvino/core/rank.hpp" #include "openvino/core/shape.hpp" -#include "openvino/core/partial_shape.hpp" -#include "openvino/core/except.hpp" +#include "shape_infer_type_utils.hpp" +#include "static_dimension.hpp" namespace ov { namespace op { @@ -20,23 +21,28 @@ namespace op { } // namespace op namespace intel_cpu { - /// \brief Class representing a shape that must be totally static. -class StaticShape : public std::vector { +class StaticShape : public std::vector { public: + using ShapeContainer = StaticShape; + StaticShape() = default; StaticShape(std::initializer_list init); StaticShape(const std::vector& dimensions); StaticShape(std::vector dimensions); - StaticShape(const PartialShape &) { - OPENVINO_THROW("[shape infer] Shouldn't convert from PartialShape to StaticShape at runtime."); + StaticShape(const PartialShape&); + + static bool is_static() { + return true; + } + static bool is_dynamic() { + return false; } - static bool is_static() { return true; } - static bool is_dynamic() { return false; } - - Rank rank() const { return Rank(size()); } + Rank rank() const { + return Rank(size()); + } bool compatible(const StaticShape& s) const; bool same_scheme(const StaticShape& s) const; @@ -57,13 +63,309 @@ public: /// Get the unique shape Shape get_shape() const; static bool merge_into(StaticShape& dst, const StaticShape& src); - static bool broadcast_merge_into(StaticShape& dst, - const StaticShape& src, - const ngraph::op::AutoBroadcastSpec& autob); + static bool broadcast_merge_into(StaticShape& dst, const StaticShape& src, const ov::op::AutoBroadcastSpec& autob); }; StaticShape operator+(const StaticShape& s1, const StaticShape& s2); std::ostream& operator<<(std::ostream& str, const StaticShape& shape); -} // namespace intel_cpu -} // namespace ov +/** + * @brief Main template for conditional static shape adapter which holds reference or container with CPU dimensions. + */ +template +class StaticShapeAdapter {}; + +using StaticShapeRef = StaticShapeAdapter; +using StaticShapeCon = StaticShapeAdapter; // Rename when when StaticShape class will be removed. + +template +constexpr bool is_static_shape_adapter() { + using U = typename std::decay::type; + return std::is_same::value || std::is_same::value; +} + +/** + * @brief The static shape adapter by copy value to VectorDims. + * + * This adapter is read/write stored VectorDims. + */ +template <> +class StaticShapeAdapter { + using TDims = VectorDims; + using dim_type = typename TDims::value_type; + +public: + using ShapeContainer = StaticShapeCon; + + using value_type = StaticDimension; + using iterator = typename TDims::iterator; + using const_iterator = typename TDims::const_iterator; + + static_assert(std::is_same::value, + "Static dimension must be of the same type as the CPU dimension."); + static_assert(std::is_standard_layout::value, + "StaticShape must be standard layout to cast on CPU dimension type."); + static_assert(sizeof(dim_type) == sizeof(StaticDimension), + "StaticDimension must have the same number of bytes as the CPU dimension type."); + + StaticShapeAdapter(); + StaticShapeAdapter(const TDims& dims); + StaticShapeAdapter(TDims&& dims) noexcept; + StaticShapeAdapter(std::initializer_list dims) noexcept : m_dims{dims} {} + StaticShapeAdapter(const StaticShapeCon& other); + StaticShapeAdapter(const ov::PartialShape&); + + const TDims& operator*() const& noexcept { + return m_dims; + } + + TDims& operator*() & noexcept { + return m_dims; + } + + const TDims&& operator*() const&& noexcept { + return std::move(m_dims); + } + + TDims&& operator*() && noexcept { + return std::move(m_dims); + } + + value_type& operator[](size_t i) { + return reinterpret_cast(m_dims[i]); + } + + const value_type& operator[](size_t i) const { + return reinterpret_cast(m_dims[i]); + } + + //-- Shape functions + static constexpr bool is_static() { + return true; + } + + static constexpr bool is_dynamic() { + return !is_static(); + } + + template + constexpr typename std::enable_if(), bool>::type compatible( + const T& other) const { + // for static shape compatible == both shape equals + return *this == other; + } + + template + constexpr typename std::enable_if(), bool>::type same_scheme( + const T& other) const { + // for static shape same_scheme == compatible; + return compatible(other); + } + + ov::Rank rank() const; + bool merge_rank(const ov::Rank& r); + ov::Shape to_shape() const; + ov::Shape get_max_shape() const; + ov::Shape get_min_shape() const; + ov::Shape get_shape() const; + ov::PartialShape to_partial_shape() const; + + static bool merge_into(StaticShapeAdapter& dst, const StaticShapeAdapter& src); + static bool broadcast_merge_into(StaticShapeAdapter& dst, + const StaticShapeAdapter& src, + const ov::op::AutoBroadcastSpec& autob); + + //-- Container functions + const_iterator cbegin() const noexcept { + return m_dims.cbegin(); + } + + const_iterator begin() const noexcept { + return cbegin(); + } + + iterator begin() noexcept { + return m_dims.begin(); + } + + const_iterator cend() const noexcept { + return m_dims.cend(); + } + + const_iterator end() const noexcept { + return cend(); + } + + iterator end() noexcept { + return m_dims.end(); + } + + size_t size() const { + return m_dims.size(); + } + + bool empty() const { + return m_dims.empty(); + } + + void resize(size_t n) { + m_dims.resize(n); + } + + void reserve(size_t n) { + m_dims.reserve(n); + } + + iterator insert(iterator position, const value_type& value) { + return m_dims.insert(position, value.get_length()); + } + + void insert(iterator position, size_t n, const value_type& val) { + m_dims.insert(position, n, val.get_length()); + } + + template + void insert(iterator position, InputIterator first, InputIterator last) { + m_dims.insert(position, first, last); + } + + void push_back(const dim_type value) { + m_dims.push_back(value); + } + + void push_back(const value_type& value) { + m_dims.push_back(value.get_length()); + } + + template + void emplace_back(Args&&... args) { + m_dims.emplace_back(std::forward(args)...); + } + +private: + TDims m_dims; +}; + +/** + * @brief The static shape adapter by reference to VectorDims. + * + * This adapter is read-only access for VectorDims. + */ +template <> +class StaticShapeAdapter { + using TDims = VectorDims; + using dim_type = typename VectorDims::value_type; + +public: + using ShapeContainer = StaticShapeCon; + + using value_type = StaticDimension; + using iterator = typename TDims::const_iterator; + using const_iterator = typename TDims::const_iterator; + + static_assert(std::is_same::value, + "Static dimension must be of the same type as the CPU dimension."); + static_assert(std::is_standard_layout::value, + "StaticShape must be standard layout to cast on CPU dimension type."); + static_assert(sizeof(dim_type) == sizeof(StaticDimension), + "StaticDimension must have the same number of bytes as the CPU dimension type."); + + constexpr StaticShapeAdapter() : m_dims{} {} + constexpr StaticShapeAdapter(const TDims& dims) : m_dims{&dims} {} + constexpr StaticShapeAdapter(const StaticShapeAdapter& other) : m_dims{other.m_dims} {} + + StaticShapeAdapter(const ov::PartialShape&); + + operator StaticShapeCon() const { + return m_dims ? StaticShapeCon(*m_dims) : StaticShapeCon(); + } + + const TDims& operator*() const& noexcept { + return *m_dims; + } + + const value_type& operator[](size_t i) const { + return reinterpret_cast((*m_dims)[i]); + } + + //-- Shape functions + static constexpr bool is_static() { + return true; + } + + static constexpr bool is_dynamic() { + return !is_static(); + } + + template + constexpr typename std::enable_if(), bool>::type compatible( + const T& other) const { + // for static shape compatible == both shape equals + return *this == other; + } + + template + constexpr typename std::enable_if(), bool>::type same_scheme( + const T& other) const { + // for static shape same_scheme == compatible; + return compatible(other); + } + + ov::Rank rank() const; + bool merge_rank(const ov::Rank& r); + ov::Shape to_shape() const; + ov::Shape get_max_shape() const; + ov::Shape get_min_shape() const; + ov::Shape get_shape() const; + ov::PartialShape to_partial_shape() const; + + //-- Container functions + const_iterator cbegin() const noexcept { + return m_dims ? m_dims->cbegin() : const_iterator{}; + } + + const_iterator begin() const noexcept { + return cbegin(); + } + + const_iterator cend() const noexcept { + return m_dims ? m_dims->cend() : const_iterator{}; + } + + const_iterator end() const noexcept { + return cend(); + } + + size_t size() const noexcept { + return m_dims ? m_dims->size() : 0; + } + + bool empty() const { + return m_dims ? m_dims->empty() : true; + } + +private: + const TDims* m_dims = nullptr; +}; + +template +typename std::enable_if(), std::ostream&>::type operator<<(std::ostream& out, const T& shape) { + out << '{'; + std::copy(shape.cbegin(), shape.cend() - 1, std::ostream_iterator(out, ",")); + if (!shape.empty()) { + out << shape[shape.size() - 1]; + } + out << '}'; + return out; +} + +template +constexpr typename std::enable_if() && is_static_shape_adapter(), bool>::type operator==( + const T& lhs, + const U& rhs) { + // The CPU dimension type and StaticDimension::value_type is same, + // use CPU dimension type to compare in order to reduce number of conversions to StaticDimension. + return (lhs.size() == rhs.size()) && (lhs.empty() || std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin())); +} +} // namespace intel_cpu +} // namespace ov diff --git a/src/plugins/intel_cpu/tests/unit/shape_inference_test/static_shape_test.cpp b/src/plugins/intel_cpu/tests/unit/shape_inference_test/static_shape_test.cpp new file mode 100644 index 00000000000..c5bfc438b04 --- /dev/null +++ b/src/plugins/intel_cpu/tests/unit/shape_inference_test/static_shape_test.cpp @@ -0,0 +1,268 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include "common_test_utils/test_assertions.hpp" +#include "utils/shape_inference/static_shape.hpp" + +using namespace testing; +using namespace ov::intel_cpu; + +class StaticShapeAdapterTest : public Test {}; + +TEST_F(StaticShapeAdapterTest, make_from_partial_shape) { + const auto partial_shape = ov::PartialShape{2, 6, 1}; + + OV_EXPECT_THROW(std::ignore = StaticShapeRef(partial_shape), + ov::Exception, + HasSubstr("[shape infer] Shouldn't convert from PartialShape to StaticShape at runtime")); + + OV_EXPECT_THROW(std::ignore = StaticShapeCon(partial_shape), + ov::Exception, + HasSubstr("[shape infer] Shouldn't convert from PartialShape to StaticShape at runtime")); +} + +TEST_F(StaticShapeAdapterTest, create_empty_ref) { + auto shape = StaticShapeRef(); + + EXPECT_TRUE(shape.is_static()); + EXPECT_FALSE(shape.is_dynamic()); + EXPECT_TRUE(shape.empty()); + EXPECT_EQ(shape.rank(), ov::Rank(0)); +} + +TEST_F(StaticShapeAdapterTest, create_as_container) { + auto shape = StaticShapeCon(VectorDims{2, 4, 5, 7}); + + shape[2] = 10; + + EXPECT_TRUE(shape.is_static()); + EXPECT_FALSE(shape.is_dynamic()); + EXPECT_EQ(shape.rank(), ov::Rank(4)); + + EXPECT_THAT(shape, ElementsAre(2, 4, 10, 7)); +} + +TEST_F(StaticShapeAdapterTest, create_from_list) { + auto shape = StaticShapeCon({2, 4, 5, 7}); + + shape[1] = 10; + + EXPECT_TRUE(shape.is_static()); + EXPECT_FALSE(shape.is_dynamic()); + EXPECT_EQ(shape.rank(), ov::Rank(4)); + + EXPECT_THAT(shape, ElementsAre(2, 10, 5, 7)); +} + +TEST_F(StaticShapeAdapterTest, make_from_const_dims) { + const auto dims = VectorDims{2, 5, 3, 8, 9, 1}; + + auto shape = StaticShapeRef(dims); + + EXPECT_TRUE(shape.is_static()); + EXPECT_FALSE(shape.is_dynamic()); + EXPECT_EQ(shape.rank(), ov::Rank(6)); + + EXPECT_THAT(shape, ElementsAre(2, 5, 3, 8, 9, 1)); +} + +TEST_F(StaticShapeAdapterTest, create_as_conatiner_from_dims) { + auto dims = VectorDims{2, 5, 3, 8, 9, 1}; + + auto shape = StaticShapeCon(dims); + std::iota(shape.begin(), shape.end(), 10); + + EXPECT_TRUE(shape.is_static()); + EXPECT_FALSE(shape.is_dynamic()); + EXPECT_EQ(shape.rank(), ov::Rank(6)); + + EXPECT_THAT(shape, ElementsAre(10, 11, 12, 13, 14, 15)); + EXPECT_THAT(dims, ElementsAre(2, 5, 3, 8, 9, 1)); +} + +TEST_F(StaticShapeAdapterTest, copy_const_reference_to_dims) { + const auto dims = VectorDims{10, 12, 1, 3}; + auto shape = StaticShapeRef(dims); + auto shape_copy = shape; + + EXPECT_EQ(std::addressof(*shape), std::addressof(dims)); + EXPECT_EQ(std::addressof(*shape_copy), std::addressof(dims)); + EXPECT_THAT(dims, ElementsAre(10, 12, 1, 3)); + EXPECT_THAT(shape, ElementsAre(10, 12, 1, 3)); + EXPECT_THAT(shape_copy, ElementsAre(10, 12, 1, 3)); +} + +TEST_F(StaticShapeAdapterTest, make_copy_from_empty_ref) { + auto shape = StaticShapeRef(); + StaticShapeCon shape_copy = shape; + shape_copy.resize(5); + shape_copy[shape_copy.size() - 1] = 1000; + + EXPECT_NE(std::addressof(*shape_copy), std::addressof(*shape)); + EXPECT_THAT(shape_copy, ElementsAre(0, 0, 0, 0, 1000)); +} + +TEST_F(StaticShapeAdapterTest, make_copy_from_other) { + const auto dims = VectorDims{10, 12, 1, 3}; + auto shape = StaticShapeRef(dims); + StaticShapeCon shape_copy = shape; + shape_copy.resize(shape_copy.size() + 1); + shape_copy[shape_copy.size() - 1] = 1000; + + EXPECT_EQ(std::addressof(*shape), std::addressof(dims)); + EXPECT_NE(std::addressof(*shape_copy), std::addressof(dims)); + EXPECT_THAT(dims, ElementsAre(10, 12, 1, 3)); + EXPECT_THAT(shape, ElementsAre(10, 12, 1, 3)); + EXPECT_THAT(shape_copy, ElementsAre(10, 12, 1, 3, 1000)); +} + +TEST_F(StaticShapeAdapterTest, emplace_and_push_back_dims) { + auto dims = VectorDims{2, 3, 4, 7, 8, 9}; + + auto shape = StaticShapeCon(dims); + shape.resize(4); + shape.emplace_back(11); + shape.push_back(21); + shape[2] = 13; + + EXPECT_THAT(dims, ElementsAre(2, 3, 4, 7, 8, 9)); + EXPECT_THAT(shape, ElementsAre(2, 3, 13, 7, 11, 21)); +} + +TEST_F(StaticShapeAdapterTest, are_compatible_same_object) { + const auto dims = VectorDims{2, 7, 1, 2, 3, 4}; + + auto shape1 = StaticShapeRef(dims); + auto shape2 = StaticShapeRef(dims); + + EXPECT_TRUE(shape1.compatible(shape2)); + EXPECT_TRUE(shape2.compatible(shape1)); +} + +TEST_F(StaticShapeAdapterTest, are_compatible) { + auto dims1 = VectorDims({2, 5, 6, 7}); + auto dims2 = VectorDims({2, 5, 6, 7}); + auto shape1 = StaticShapeRef(dims1); + auto shape2 = StaticShapeRef(dims2); + auto shape3 = StaticShapeCon(dims1); + + EXPECT_TRUE(shape1.compatible(shape2)); + EXPECT_TRUE(shape1.compatible(shape3)); + EXPECT_TRUE(shape2.compatible(shape1)); + EXPECT_TRUE(shape2.compatible(shape3)); + EXPECT_TRUE(shape3.compatible(shape2)); + EXPECT_TRUE(shape3.compatible(shape1)); +} + +TEST_F(StaticShapeAdapterTest, not_compatible_different_rank) { + auto dims1 = VectorDims{2, 5, 6, 7, 8}; + auto dims2 = VectorDims{2, 5, 6, 7}; + auto shape1 = StaticShapeRef(dims1); + auto shape2 = StaticShapeRef(dims2); + + EXPECT_FALSE(shape1.compatible(shape2)); + EXPECT_FALSE(shape2.compatible(shape1)); +} + +TEST_F(StaticShapeAdapterTest, not_compatible_different_dimensions) { + auto dims1 = VectorDims{2, 5, 68, 7}; + auto dims2 = VectorDims{2, 5, 6, 7}; + auto shape1 = StaticShapeRef(dims1); + auto shape2 = StaticShapeRef(dims2); + + EXPECT_FALSE(shape2.compatible(shape1)); +} + +TEST_F(StaticShapeAdapterTest, merge_rank) { + auto dims = VectorDims{2, 5, 6, 7}; + auto shape1 = StaticShapeRef(dims); + + EXPECT_TRUE(shape1.merge_rank(ov::Rank(4))); + EXPECT_FALSE(shape1.merge_rank(ov::Rank(10))); + EXPECT_FALSE(shape1.merge_rank(ov::Rank(1))); +} + +TEST_F(StaticShapeAdapterTest, dereference_as_rvalue_and_move) { + auto dims = VectorDims{2, 6, 3, 2, 5}; + auto shape = StaticShapeCon(dims); + + const auto shape_data_address = reinterpret_cast((*shape).data()); + auto out_dims = std::move(*shape); + const auto shape_data_mv_address = reinterpret_cast((*shape).data()); + const auto out_dims_address = reinterpret_cast(out_dims.data()); + + EXPECT_EQ(out_dims_address, shape_data_address); + EXPECT_NE(shape_data_mv_address, shape_data_address); +} + +TEST_F(StaticShapeAdapterTest, dereference_as_lvalue_and_copy) { + auto dims = VectorDims{2, 6, 3, 2, 5}; + auto shape = StaticShapeRef(dims); + + const auto shape_data_address = reinterpret_cast((*shape).data()); + const auto out_dims = *shape; + const auto shape_data_cp_address = reinterpret_cast((*shape).data()); + const auto out_dims_address = reinterpret_cast(out_dims.data()); + + EXPECT_NE(out_dims_address, shape_data_address); + EXPECT_EQ(shape_data_cp_address, shape_data_address); + EXPECT_THAT(out_dims, ElementsAreArray(*shape)); +} + +TEST_F(StaticShapeAdapterTest, move_shapes_into_vector_of_vector_dims) { + auto output_shapes = std::vector{{1, 2, 3}, {10, 12}, {100, 1, 2, 3, 4}}; + + std::vector output_vec_dims; + output_vec_dims.reserve(output_shapes.size()); + for (auto&& s : output_shapes) { + output_vec_dims.emplace_back(std::move(*s)); + } + + EXPECT_EQ(output_shapes.size(), 3); + EXPECT_THAT(output_vec_dims, ElementsAre(VectorDims{1, 2, 3}, VectorDims{10, 12}, VectorDims{100, 1, 2, 3, 4})); +} + +TEST_F(StaticShapeAdapterTest, compare_empty_ref_and_container) { + auto shape1 = StaticShapeRef(); + auto shape2 = StaticShapeCon(); + + EXPECT_TRUE(shape1 == shape2); +} + +TEST_F(StaticShapeAdapterTest, compare_ref_and_container) { + auto dims = VectorDims{2, 5, 3, 4}; + auto shape1 = StaticShapeCon{2, 5, 3}; + auto shape2 = StaticShapeCon{2, 5, 3, 4}; + auto shape3 = StaticShapeRef(dims); + + EXPECT_FALSE(shape1 == shape2); + EXPECT_FALSE(shape1 == shape3); + EXPECT_FALSE(shape2 == shape1); + EXPECT_FALSE(shape3 == shape1); + + EXPECT_TRUE(shape2 == shape3); + EXPECT_TRUE(shape3 == shape2); +} + +TEST_F(StaticShapeAdapterTest, subscript_op_on_reference) { + auto dims = VectorDims{2, 5, 3, 4}; + auto shape = StaticShapeRef(dims); + + EXPECT_EQ(shape[3], StaticDimension(4)); + EXPECT_EQ(shape[2], StaticDimension(3)); + EXPECT_EQ(shape[0], StaticDimension(2)); + EXPECT_EQ(shape[1], StaticDimension(5)); +} + +TEST_F(StaticShapeAdapterTest, subscript_op_on_container) { + auto shape = StaticShapeCon{10, 2, 123, 4, 3}; + + EXPECT_EQ(shape[3], StaticDimension(4)); + EXPECT_EQ(shape[1], StaticDimension(2)); + EXPECT_EQ(shape[2], StaticDimension(123)); + EXPECT_EQ(shape[0], StaticDimension(10)); + EXPECT_EQ(shape[4], StaticDimension(3)); +} diff --git a/src/plugins/intel_cpu/tests/unit/shape_inference_test/tile_shape_inference_test.cpp b/src/plugins/intel_cpu/tests/unit/shape_inference_test/tile_shape_inference_test.cpp index b053eb47d28..3b8e36f482c 100644 --- a/src/plugins/intel_cpu/tests/unit/shape_inference_test/tile_shape_inference_test.cpp +++ b/src/plugins/intel_cpu/tests/unit/shape_inference_test/tile_shape_inference_test.cpp @@ -62,3 +62,41 @@ TEST(StaticShapeInferenceTest, TileSmallDataRankTestRepeatsInConstMap) { ASSERT_EQ(output_shapes.front(), StaticShape({3, 32, 10})); } + +TEST(StaticShapeInferenceTest, TileStaticShapeRepeatsAsConst) { + auto param0 = std::make_shared(element::f32, PartialShape::dynamic(3)); + auto param1 = ov::op::v0::Constant::create(element::i64, Shape{2}, {4, 1}); + auto tile = std::make_shared(param0, param1); + + auto dims = std::vector{{6, 8, 10}, {2}}; + auto in_shapes = std::vector(dims.begin(), dims.end()); + + const auto op_infer = make_shape_inference(tile); + const auto outputs = op_infer->infer(in_shapes, ov::make_tensor_accessor()); + + ASSERT_TRUE(outputs); + EXPECT_EQ(outputs->size(), 1); + EXPECT_EQ(outputs->front(), StaticShapeCon({6, 32, 10})); + EXPECT_EQ(*outputs->front(), VectorDims({6, 32, 10})); +} + +TEST(StaticShapeInferenceTest, TileNewApiInputsStaticRank) { + auto param0 = std::make_shared(element::f32, PartialShape::dynamic(2)); + auto param1 = std::make_shared(element::i32, PartialShape::dynamic(1)); + auto tile = std::make_shared(param0, param1); + + int32_t repeats[] = {3, 4, 1, 2}; + const std::map>& constant_data = { + {1, std::make_shared(element::i32, Shape{4}, repeats)}}; + + auto dims = std::vector{{8, 10}, {4}}; + auto in_shapes = std::vector(dims.begin(), dims.end()); + + const auto op_infer = make_shape_inference(tile); + const auto outputs = op_infer->infer(in_shapes, ov::make_tensor_accessor(constant_data)); + + ASSERT_TRUE(outputs); + EXPECT_EQ(outputs->size(), 1); + EXPECT_EQ(outputs->front(), StaticShapeCon({3, 4, 8, 20})); + EXPECT_EQ(*outputs->front(), VectorDims({3, 4, 8, 20})); +}