Add static shape adapters to reduce dimension conversion in shape_infer for CPU (#17862)

* Add static shape adapter
- Adapters holds CPU dimension which can be reference to it or vector
- Add ov::optional for holding optional result from shape inference
- Add new `infer` function in `IStaticShapeInfer`

* Temporary support of StaticShape

* Fix build issues

* Correct shape adapter compare
- minor static shape adapter refactor

* Minor corrections in ShapeInferenceTA

* Fix subscript operator in StaticShapeRef
This commit is contained in:
Pawel Raasz authored and GitHub committed 2023-06-12 07:05:46 +02:00
1 parent 70e0caca4f
commit 16bde0bba6
9 files changed
+963 -47

No files matched your search

@@ -11,20 +11,20 @@ namespace ov {
namespace op {
namespace v0 {
template <class T>
template <class TShape>
struct NegativeToZero {
NegativeToZero() = default;
template <class U>
T operator()(const U u) const {
return static_cast<T>(std::max<U>(0, ov::util::InTypeRange<U>()(u)));
TShape operator()(const U u) const {
return static_cast<TShape>(std::max<U>(0, ov::util::InTypeRange<U>()(u)));
}
};
template <class T>
std::vector<T> shape_infer(const Tile* op,
const std::vector<T>& input_shapes,
const ITensorAccessor& tensor_accessor = make_tensor_accessor()) {
using TDim = typename T::value_type;
template <class TShape, class TRShape = result_shape_t<TShape>>
std::vector<TRShape> shape_infer(const Tile* op,
const std::vector<TShape>& 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<T> 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<TRShape>(1);
auto& output_shape = output_shapes[0];
// Get repeats and pre process values
constexpr auto negative_repeats_to_zero = NegativeToZero<TDimValue>();
auto repeats = get_input_const_data_as_shape<T>(op, 1, tensor_accessor, negative_repeats_to_zero);
auto repeats = get_input_const_data_as_shape<TRShape>(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<T> 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
@@ -432,6 +432,30 @@ std::unique_ptr<TResult> 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<TShape>, where `TShape` can be `std::vector<const size_t>`
* 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 <class TShape>
struct result_shape {
using type = typename TShape::ShapeContainer;
};
/**
* @brief Get correct result shape for PartialShape which is same type.
*/
template <>
struct result_shape<PartialShape> {
using type = PartialShape;
};
template <class TShape>
using result_shape_t = typename result_shape<TShape>::type;
} // namespace ov
// Helper to reduce duplicates of code for get_data_as_... specific type functions.
@@ -0,0 +1,129 @@
// Copyright (C) 2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include <cstddef>
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 T>
class optional {
public:
constexpr optional() noexcept = default;
template <class... Args>
constexpr optional(Args&&... args) : m_has_value{true},
m_opt(std::forward<Args>(args)...) {}
optional(const optional<T>& other) : m_has_value{other.m_has_value}, m_opt{} {
if (other.m_has_value) {
construct(*other);
}
}
optional(optional<T>&& 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 <class... Args>
void construct(Args&&... args) {
new (std::addressof(m_opt)) T(std::forward<Args>(args)...);
}
struct Empty {};
template <class U>
union Storage {
Empty m_empty;
T m_value;
constexpr Storage() noexcept : m_empty{} {}
constexpr Storage(uint8_t) noexcept : Storage{} {}
template <class... Args>
constexpr Storage(Args&&... args) : m_value(std::forward<Args>(args)...) {}
~Storage() {}
};
bool m_has_value;
Storage<T> m_opt;
};
} // namespace ov
@@ -317,6 +317,11 @@ public:
OPENVINO_THROW("Not implemented by base class");
}
ov::optional<std::vector<StaticShapeCon>> infer(const std::vector<StaticShapeRef>& 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 TOp, uint32_t MASK>
class ShapeInferenceTA : public ShapeInferBase {
public:
using ShapeInferBase::ShapeInferBase;
IShapeInferCommon::Result infer(const std::vector<StaticShape>& input_shapes,
const ov::ITensorAccessor& tensor_accessor) override {
// Temporary support of StaticShape.
auto in_shapes = std::vector<StaticShapeRef>();
in_shapes.reserve(input_shapes.size());
for (auto& s : input_shapes) {
in_shapes.emplace_back(reinterpret_cast<const VectorDims&>(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<StaticShape&&>(*s));
});
}
return result;
}
ov::optional<std::vector<StaticShapeCon>> infer(const std::vector<StaticShapeRef>& input_shapes,
const ov::ITensorAccessor& tensor_accessor) override {
return {shape_infer(static_cast<TOp*>(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)),
@@ -8,6 +8,7 @@
#include <openvino/core/core.hpp>
#include <openvino/core/node.hpp>
#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<StaticShape>& 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<std::vector<StaticShapeCon>> infer(const std::vector<StaticShapeRef>& 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
@@ -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 <class T>
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<StaticDimension> dimensions)
: std::vector<StaticDimension>(std::move(dimensions)) {}
@@ -16,6 +32,9 @@ StaticShape::StaticShape(const std::vector<StaticDimension::value_type>& dimensi
StaticShape::StaticShape(std::initializer_list<StaticDimension> init)
: std::vector<StaticDimension>(init.begin(), init.end()) {}
StaticShape::StaticShape(const ov::PartialShape&) : std::vector<StaticDimension>{} {
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<StaticDimension> 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<StaticDimension> 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<typename ov::Rank::value_type>(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<typename PartialShape::value_type>());
return shape;
}
bool StaticShapeCon::merge_rank(const ov::Rank& r) {
return r.is_dynamic() || (size() == static_cast<size_t>(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<typename ov::Rank::value_type>(size())};
}
bool StaticShapeRef::merge_rank(const ov::Rank& r) {
return r.is_dynamic() || (size() == static_cast<size_t>(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
@@ -6,13 +6,14 @@
#include <cstddef>
#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<StaticDimension> {
class StaticShape : public std::vector<StaticDimension> {
public:
using ShapeContainer = StaticShape;
StaticShape() = default;
StaticShape(std::initializer_list<StaticDimension> init);
StaticShape(const std::vector<StaticDimension::value_type>& dimensions);
StaticShape(std::vector<StaticDimension> 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 TDims>
class StaticShapeAdapter {};
using StaticShapeRef = StaticShapeAdapter<const VectorDims>;
using StaticShapeCon = StaticShapeAdapter<VectorDims>; // Rename when when StaticShape class will be removed.
template <class T>
constexpr bool is_static_shape_adapter() {
using U = typename std::decay<T>::type;
return std::is_same<U, StaticShapeRef>::value || std::is_same<U, StaticShapeCon>::value;
}
/**
* @brief The static shape adapter by copy value to VectorDims.
*
* This adapter is read/write stored VectorDims.
*/
template <>
class StaticShapeAdapter<VectorDims> {
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<dim_type, typename StaticDimension::value_type>::value,
"Static dimension must be of the same type as the CPU dimension.");
static_assert(std::is_standard_layout<StaticDimension>::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<dim_type> 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<value_type&>(m_dims[i]);
}
const value_type& operator[](size_t i) const {
return reinterpret_cast<const value_type&>(m_dims[i]);
}
//-- Shape functions
static constexpr bool is_static() {
return true;
}
static constexpr bool is_dynamic() {
return !is_static();
}
template <class T>
constexpr typename std::enable_if<is_static_shape_adapter<T>(), bool>::type compatible(
const T& other) const {
// for static shape compatible == both shape equals
return *this == other;
}
template <class T>
constexpr typename std::enable_if<is_static_shape_adapter<T>(), 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 <class InputIterator>
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 <class... Args>
void emplace_back(Args&&... args) {
m_dims.emplace_back(std::forward<Args>(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<const VectorDims> {
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<dim_type, typename StaticDimension::value_type>::value,
"Static dimension must be of the same type as the CPU dimension.");
static_assert(std::is_standard_layout<StaticDimension>::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<const TDims>& 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<const value_type&>((*m_dims)[i]);
}
//-- Shape functions
static constexpr bool is_static() {
return true;
}
static constexpr bool is_dynamic() {
return !is_static();
}
template <class T>
constexpr typename std::enable_if<is_static_shape_adapter<T>(), bool>::type compatible(
const T& other) const {
// for static shape compatible == both shape equals
return *this == other;
}
template <class T>
constexpr typename std::enable_if<is_static_shape_adapter<T>(), 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 <class T>
typename std::enable_if<is_static_shape_adapter<T>(), std::ostream&>::type operator<<(std::ostream& out, const T& shape) {
out << '{';
std::copy(shape.cbegin(), shape.cend() - 1, std::ostream_iterator<StaticDimension>(out, ","));
if (!shape.empty()) {
out << shape[shape.size() - 1];
}
out << '}';
return out;
}
template <class T, class U>
constexpr typename std::enable_if<is_static_shape_adapter<T>() && is_static_shape_adapter<U>(), 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
@@ -0,0 +1,268 @@
// Copyright (C) 2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <gmock/gmock.h>
#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<uintptr_t>((*shape).data());
auto out_dims = std::move(*shape);
const auto shape_data_mv_address = reinterpret_cast<uintptr_t>((*shape).data());
const auto out_dims_address = reinterpret_cast<uintptr_t>(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<uintptr_t>((*shape).data());
const auto out_dims = *shape;
const auto shape_data_cp_address = reinterpret_cast<uintptr_t>((*shape).data());
const auto out_dims_address = reinterpret_cast<uintptr_t>(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<StaticShapeCon>{{1, 2, 3}, {10, 12}, {100, 1, 2, 3, 4}};
std::vector<VectorDims> 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));
}
@@ -62,3 +62,41 @@ TEST(StaticShapeInferenceTest, TileSmallDataRankTestRepeatsInConstMap) {
ASSERT_EQ(output_shapes.front(), StaticShape({3, 32, 10}));
}
TEST(StaticShapeInferenceTest, TileStaticShapeRepeatsAsConst) {
auto param0 = std::make_shared<ov::op::v0::Parameter>(element::f32, PartialShape::dynamic(3));
auto param1 = ov::op::v0::Constant::create(element::i64, Shape{2}, {4, 1});
auto tile = std::make_shared<op::v0::Tile>(param0, param1);
auto dims = std::vector<VectorDims>{{6, 8, 10}, {2}};
auto in_shapes = std::vector<StaticShapeRef>(dims.begin(), dims.end());
const auto op_infer = make_shape_inference<IStaticShapeInfer>(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<ov::op::v0::Parameter>(element::f32, PartialShape::dynamic(2));
auto param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, PartialShape::dynamic(1));
auto tile = std::make_shared<op::v0::Tile>(param0, param1);
int32_t repeats[] = {3, 4, 1, 2};
const std::map<size_t, std::shared_ptr<ngraph::runtime::HostTensor>>& constant_data = {
{1, std::make_shared<HostTensor>(element::i32, Shape{4}, repeats)}};
auto dims = std::vector<VectorDims>{{8, 10}, {4}};
auto in_shapes = std::vector<StaticShapeRef>(dims.begin(), dims.end());
const auto op_infer = make_shape_inference<IStaticShapeInfer>(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}));
}