Improved ov::Tensor behavior (#7683)

* Improved ov::Tensor behavior

* Fixed python test for setShape on preallocated

* Fixed clang-format
This commit is contained in:
Ilya Lavrenov 2021-09-28 12:50:51 +03:00 committed by GitHub
parent 6bd0873a40
commit 068d31511b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 127 additions and 52 deletions

View File

@ -130,11 +130,14 @@ def test_set_shape():
assert blob.tensor_desc.dims == [1, 4, 128, 128] assert blob.tensor_desc.dims == [1, 4, 128, 128]
assert blob.buffer.shape == (1, 4, 128, 128) assert blob.buffer.shape == (1, 4, 128, 128)
def test_cannot_set_shape_preallocated_memory():
tensor_desc = TensorDesc("FP32", [1, 3, 127, 127], "NHWC")
array = np.ones([1, 3, 127, 127], dtype=np.float32) array = np.ones([1, 3, 127, 127], dtype=np.float32)
blob = Blob(tensor_desc, array) blob = Blob(tensor_desc, array)
with pytest.raises(RuntimeError) as e:
blob.set_shape([1, 4, 128, 128]) blob.set_shape([1, 4, 128, 128])
assert blob.tensor_desc.dims == [1, 4, 128, 128] assert "Cannot call setShape for Blobs created on top of preallocated memory" in str(e.value)
assert blob.buffer.shape == (1, 4, 128, 128)
@pytest.mark.ngraph_dependent_test @pytest.mark.ngraph_dependent_test

View File

@ -192,22 +192,7 @@ public:
* *
* @param dims new shape * @param dims new shape
*/ */
void setShape(const SizeVector& dims) { void setShape(const SizeVector& dims);
if (properProduct(dims) > properProduct(getTensorDesc().getDims())) {
// New blob shape requires more memory than old one -- reallocate
if (!deallocate())
IE_THROW() << "Cannot deallocate blob while an attempt to enlarge blob area in setShape.";
// Old and new ranks should match as well as layouts
getTensorDesc().setDims(dims);
allocate();
// no way to detect if allocation is successful other than map/unmap that we wouldn't like to do here
} else {
// Don't shrink area when new size fit the existing area
getTensorDesc().setDims(dims);
}
}
/** /**
* @deprecated Cast to MemoryBlob and use new wlock/rwlock API instead. * @deprecated Cast to MemoryBlob and use new wlock/rwlock API instead.

View File

@ -84,7 +84,7 @@ public:
/** /**
* @brief Returns the vector of order * @brief Returns the vector of order
* *
* @return order * @return order of dimensions
*/ */
const SizeVector& getOrder() const { const SizeVector& getOrder() const {
return order; return order;
@ -93,7 +93,7 @@ public:
/** /**
* @brief Returns the per-dimension offset vector * @brief Returns the per-dimension offset vector
* *
* @return offsets * @return offsets in elements
*/ */
const SizeVector& getOffsetPaddingToData() const { const SizeVector& getOffsetPaddingToData() const {
return offsetPaddingToData; return offsetPaddingToData;
@ -102,7 +102,7 @@ public:
/** /**
* @brief Returns the offset to the current memory block * @brief Returns the offset to the current memory block
* *
* @return offset * @return offset in elements
*/ */
size_t getOffsetPadding() const { size_t getOffsetPadding() const {
return offsetPadding; return offsetPadding;
@ -111,7 +111,7 @@ public:
/** /**
* @brief Returns strides for each dimension * @brief Returns strides for each dimension
* *
* @return strides * @return strides in elements
*/ */
const SizeVector& getStrides() const { const SizeVector& getStrides() const {
return strides; return strides;

View File

@ -7,8 +7,55 @@
#include <vector> #include <vector>
#include "ie_blob.h" #include "ie_blob.h"
#include "system_allocator.hpp"
namespace InferenceEngine { namespace InferenceEngine {
void Blob::setShape(const SizeVector& dims) {
// we don't want to allow setShape for:
// 1. ROI cases
{
size_t denseStride = 1;
const auto& blockedDims = getTensorDesc().getBlockingDesc().getBlockDims();
const auto& strides = getTensorDesc().getBlockingDesc().getStrides();
for (size_t i = 1; i <= strides.size(); i++) {
if (denseStride != strides[strides.size() - i]) {
IE_THROW() << "Blob::setShape requires dense blob";
}
denseStride *= blockedDims[blockedDims.size() - i];
}
}
// 2. Blobs created on top of preallocated memory
if (std::dynamic_pointer_cast<InferenceEngine::details::PreAllocator>(getAllocator())) {
IE_THROW() << "Cannot call setShape for Blobs created on top of preallocated memory.";
}
if (properProduct(dims) > properProduct(getTensorDesc().getDims())) {
// New blob shape requires more memory than old one -- reallocate
if (!deallocate()) {
IE_THROW() << "Cannot deallocate blob while an attempt to enlarge blob area in setShape.";
}
// Old and new ranks should match as well as layouts
getTensorDesc().setDims(dims);
allocate();
// no way to detect if allocation is successful other than map/unmap
// that we wouldn't like to do here; but for cases when we use SystemMemoryAllocator
// we can do it
if (std::dynamic_pointer_cast<InferenceEngine::SystemMemoryAllocator>(getAllocator())) {
if (buffer() == nullptr) {
IE_THROW() << "Failed to allocate memory in Blob::setShape";
}
}
} else {
// Don't shrink area when new size fit the existing area
getTensorDesc().setDims(dims);
}
}
Blob::Ptr Blob::createROI(const ROI& roi) const { Blob::Ptr Blob::createROI(const ROI& roi) const {
if (getTensorDesc().getLayout() == Layout::NCHW || getTensorDesc().getLayout() == Layout::NHWC) { if (getTensorDesc().getLayout() == Layout::NCHW || getTensorDesc().getLayout() == Layout::NHWC) {
return createROI({roi.id, 0, roi.posY, roi.posX}, return createROI({roi.id, 0, roi.posY, roi.posX},

View File

@ -288,6 +288,21 @@ BlockingDesc::BlockingDesc(const SizeVector& blocked_dims,
if (blocked_dims.size() != dimOffsets.size()) if (blocked_dims.size() != dimOffsets.size())
IE_THROW() << "Offsets are not initialized for all dimensions."; IE_THROW() << "Offsets are not initialized for all dimensions.";
this->offsetPaddingToData = dimOffsets; this->offsetPaddingToData = dimOffsets;
// check that strides are valid
{
size_t denseStride = 1;
for (size_t i = 1; i <= strides.size(); i++) {
if (denseStride > strides[strides.size() - i]) {
IE_THROW() << "Stride in " << (strides.size() - i)
<< "-th dimension "
"is not valid; actual "
<< strides[strides.size() - i] << ", should be >= " << denseStride << std::endl;
}
denseStride = std::max(strides[strides.size() - i], denseStride) * blocked_dims[blocked_dims.size() - i];
}
}
} }
BlockingDesc::BlockingDesc(const SizeVector& dims, Layout layout) : offsetPadding(0) { BlockingDesc::BlockingDesc(const SizeVector& dims, Layout layout) : offsetPadding(0) {

View File

@ -18,6 +18,7 @@ set(IE_SHARED_SRCS
"${IE_SRC_ROOT}/system_allocator.cpp" "${IE_SRC_ROOT}/system_allocator.cpp"
"${IE_SRC_ROOT}/blob_factory.cpp" "${IE_SRC_ROOT}/blob_factory.cpp"
"${IE_SRC_ROOT}/ie_blob_common.cpp" "${IE_SRC_ROOT}/ie_blob_common.cpp"
"${IE_SRC_ROOT}/system_allocator.cpp"
"${IE_SRC_ROOT}/ie_layouts.cpp") "${IE_SRC_ROOT}/ie_layouts.cpp")
set(MIXED_SRC ${IE_SHARED_SRCS} set(MIXED_SRC ${IE_SHARED_SRCS}
"${CMAKE_CURRENT_SOURCE_DIR}/src/runtime/allocator.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/runtime/allocator.cpp"

View File

@ -69,16 +69,10 @@ public:
* @param type Tensor element type * @param type Tensor element type
* @param shape Tensor shape * @param shape Tensor shape
* @param host_ptr Pointer to pre-allocated host memory * @param host_ptr Pointer to pre-allocated host memory
* @param size Optional size of allocated host memory in elements. If it is not set (default is `0`), the size of
* memory supposed to be not less then ov::shape_size(shape) * type.size() in bytes.
* @param strides Optional strides parameters in elements. Strides are supposed to be equal to shape if they are not * @param strides Optional strides parameters in elements. Strides are supposed to be equal to shape if they are not
* set * set
*/ */
Tensor(const element::Type type, Tensor(const element::Type type, const Shape& shape, void* host_ptr, const Strides& strides = {});
const Shape& shape,
void* host_ptr,
const size_t size = 0,
const Strides& strides = {});
/** /**
* @brief Constructs region of interest (ROI) tensor form another tensor. * @brief Constructs region of interest (ROI) tensor form another tensor.

View File

@ -38,11 +38,7 @@ Tensor::Tensor(const element::Type element_type, const Shape& shape, const Alloc
_impl->allocate(); _impl->allocate();
} }
Tensor::Tensor(const element::Type element_type, Tensor::Tensor(const element::Type element_type, const Shape& shape, void* host_ptr, const Strides& strides) {
const Shape& shape,
void* host_ptr,
const size_t size,
const Strides& strides) {
ie::SizeVector blk_order(shape.size()); ie::SizeVector blk_order(shape.size());
std::iota(blk_order.begin(), blk_order.end(), 0); std::iota(blk_order.begin(), blk_order.end(), 0);
ie::SizeVector dim_offset(shape.size(), 0); ie::SizeVector dim_offset(shape.size(), 0);
@ -50,12 +46,6 @@ Tensor::Tensor(const element::Type element_type,
if (strides.empty()) { if (strides.empty()) {
blk_strides = ov::row_major_strides(shape); blk_strides = ov::row_major_strides(shape);
} else { } else {
OPENVINO_ASSERT(shape.size() == strides.size(),
"shape.size() (",
shape.size(),
") must be equal to strides.size() (",
strides.size(),
")");
blk_strides.assign(strides.begin(), strides.end()); blk_strides.assign(strides.begin(), strides.end());
} }
@ -64,8 +54,7 @@ Tensor::Tensor(const element::Type element_type,
ie::TensorDesc{ie::details::convertPrecision(element_type), ie::TensorDesc{ie::details::convertPrecision(element_type),
shape, shape,
ie::BlockingDesc{shape, blk_order, 0, dim_offset, blk_strides}}, ie::BlockingDesc{shape, blk_order, 0, dim_offset, blk_strides}},
host_ptr, host_ptr);
size);
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
throw ov::Exception(ex.what()); throw ov::Exception(ex.what());
} catch (...) { } catch (...) {

View File

@ -11,6 +11,7 @@
#include <openvino/core/strides.hpp> #include <openvino/core/strides.hpp>
#include <openvino/core/type/element_type.hpp> #include <openvino/core/type/element_type.hpp>
#include "openvino/core/except.hpp"
#include "openvino/runtime/allocator.hpp" #include "openvino/runtime/allocator.hpp"
#include "openvino/runtime/tensor.hpp" #include "openvino/runtime/tensor.hpp"
@ -58,7 +59,7 @@ TEST_F(OVTensorTest, canCreateTensorUsingMockAllocator) {
TEST_F(OVTensorTest, canAccessExternalData) { TEST_F(OVTensorTest, canAccessExternalData) {
ov::Shape shape = {1, 1, 3}; ov::Shape shape = {1, 1, 3};
float data[] = {5.f, 6.f, 7.f}; float data[] = {5.f, 6.f, 7.f};
ov::runtime::Tensor t{ov::element::f32, shape, data, 3}; ov::runtime::Tensor t{ov::element::f32, shape, data};
{ {
float* ptr = t.data<float>(); float* ptr = t.data<float>();
ASSERT_EQ(ptr[2], 7); ASSERT_EQ(ptr[2], 7);
@ -74,7 +75,8 @@ TEST_F(OVTensorTest, canAccessExternalData) {
TEST_F(OVTensorTest, canAccessExternalDataWithStrides) { TEST_F(OVTensorTest, canAccessExternalDataWithStrides) {
ov::Shape shape = {2, 3}; ov::Shape shape = {2, 3};
float data[] = {5.f, 6.f, 7.f, 0.f, 1.f, 42.f, 3.f, 0.f}; float data[] = {5.f, 6.f, 7.f, 0.f, 1.f, 42.f, 3.f, 0.f};
ov::runtime::Tensor t{ov::element::f32, shape, data, 8, {4, 1}}; ov::runtime::Tensor t{ov::element::f32, shape, data, {4, 1}};
ASSERT_EQ(ov::Strides({4, 1}), t.get_strides());
{ {
ASSERT_EQ((ov::Shape{2, 3}), t.get_shape()); ASSERT_EQ((ov::Shape{2, 3}), t.get_shape());
float* ptr = t.data<float>(); float* ptr = t.data<float>();
@ -90,7 +92,17 @@ TEST_F(OVTensorTest, cannotCreateTensorWithExternalNullptr) {
TEST_F(OVTensorTest, cannotCreateTensorWithWrongStrides) { TEST_F(OVTensorTest, cannotCreateTensorWithWrongStrides) {
ov::Shape shape = {2, 3}; ov::Shape shape = {2, 3};
float data[] = {5.f, 6.f, 7.f, 0.f, 1.f, 42.f, 3.f, 0.f}; float data[] = {5.f, 6.f, 7.f, 0.f, 1.f, 42.f, 3.f, 0.f};
ASSERT_THROW(ov::runtime::Tensor(ov::element::f32, shape, data, 8, {4, 1, 2}), ov::Exception); {
// strides.size() != shape.size()
EXPECT_THROW(ov::runtime::Tensor(ov::element::f32, shape, data, {6, 3, 1}), ov::Exception);
}
{
// strides values are element-wise >= ov::row_major_strides(shape) values
EXPECT_THROW(ov::runtime::Tensor(ov::element::f32, shape, data, {2, 1}), ov::Exception);
EXPECT_THROW(ov::runtime::Tensor(ov::element::f32, shape, data, {3, 0}), ov::Exception);
EXPECT_THROW(ov::runtime::Tensor(ov::element::f32, shape, data, {3, 2}), ov::Exception);
EXPECT_NO_THROW(ov::runtime::Tensor(ov::element::f32, shape, data, {6, 2}));
}
} }
TEST_F(OVTensorTest, saveDimsAndSizeAfterMove) { TEST_F(OVTensorTest, saveDimsAndSizeAfterMove) {
@ -115,36 +127,65 @@ TEST_F(OVTensorTest, saveDimsAndSizeAfterMove) {
// SetShape // SetShape
TEST_F(OVTensorTest, canSetShape) { TEST_F(OVTensorTest, canSetShape) {
const ov::Shape origShape({1, 2, 3});
ov::runtime::Tensor t{ov::element::f32, {1, 2, 3}}; ov::runtime::Tensor t{ov::element::f32, {1, 2, 3}};
const ov::Shape newShape({4, 5, 6}); const ov::Shape newShape({4, 5, 6});
ASSERT_EQ(t.get_shape(), (ov::Shape{1, 2, 3}));
const void* orig_data = t.data();
ASSERT_EQ(t.get_shape(), origShape);
ASSERT_NO_THROW(t.set_shape({4, 5, 6})); ASSERT_NO_THROW(t.set_shape({4, 5, 6}));
ASSERT_EQ(newShape, t.get_shape()); ASSERT_EQ(newShape, t.get_shape());
ASSERT_EQ(ov::row_major_strides(newShape), t.get_strides()); ASSERT_EQ(ov::row_major_strides(newShape), t.get_strides());
ASSERT_NE(orig_data, t.data());
// check that setShape for copy changes original Tensor // check that setShape for copy changes original Tensor
{ {
ov::runtime::Tensor t2 = t; ov::runtime::Tensor t2 = t;
t2.set_shape(newShape); ASSERT_NO_THROW(t2.set_shape(newShape));
ASSERT_EQ(newShape, t.get_shape()); ASSERT_EQ(newShape, t.get_shape());
ASSERT_EQ(t2.get_shape(), t.get_shape()); ASSERT_EQ(t2.get_shape(), t.get_shape());
orig_data = t.data();
}
// set_shape for smaller memory - does not perform reallocation
{
t.set_shape(origShape);
ASSERT_EQ(origShape, t.get_shape());
ASSERT_EQ(orig_data, t.data());
} }
} }
TEST_F(OVTensorTest, cannotSetShapeOnPreallocatedMemory) {
float data[4 * 5 * 6 * 2];
ov::runtime::Tensor t{ov::element::f32, {1, 2, 3}, data};
const ov::Shape newShape({4, 5, 6});
ASSERT_THROW(t.set_shape(newShape), ov::Exception);
}
TEST_F(OVTensorTest, makeRangeRoiTensor) { TEST_F(OVTensorTest, makeRangeRoiTensor) {
ov::runtime::Tensor t{ov::element::i8, {1, 3, 6, 5}}; // RGBp picture of size (WxH) = 5x6 ov::runtime::Tensor t{ov::element::i32, {1, 3, 6, 5}}; // RGBp picture of size (WxH) = 5x6
ov::runtime::Tensor roi_tensor{t, {0, 0, 1, 2}, {1, 3, 5, 4}}; ov::runtime::Tensor roi_tensor{t, {0, 0, 1, 2}, {1, 3, 5, 4}};
ov::Shape ref_shape = {1, 3, 4, 2}; ov::Shape ref_shape = {1, 3, 4, 2};
ptrdiff_t ref_offset = 7; ptrdiff_t ref_offset_elems = 7;
ptrdiff_t ref_offset_bytes = ref_offset_elems * ov::element::i32.size();
ov::Strides ref_strides = {90, 30, 5, 1}; ov::Strides ref_strides = {90, 30, 5, 1};
ASSERT_EQ(roi_tensor.get_shape(), ref_shape); ASSERT_EQ(roi_tensor.get_shape(), ref_shape);
ASSERT_EQ(roi_tensor.data<int8_t>() - t.data<int8_t>(), ref_offset); ASSERT_EQ(roi_tensor.data<int32_t>() - t.data<int32_t>(), ref_offset_elems);
ASSERT_EQ(reinterpret_cast<uint8_t*>(roi_tensor.data()) - reinterpret_cast<uint8_t*>(t.data()), ref_offset); ASSERT_EQ(reinterpret_cast<uint8_t*>(roi_tensor.data()) - reinterpret_cast<uint8_t*>(t.data()), ref_offset_bytes);
ASSERT_EQ(roi_tensor.get_strides(), t.get_strides()); ASSERT_EQ(roi_tensor.get_strides(), t.get_strides());
ASSERT_EQ(ref_strides, roi_tensor.get_strides()); ASSERT_EQ(ref_strides, roi_tensor.get_strides());
ASSERT_EQ(roi_tensor.get_element_type(), t.get_element_type()); ASSERT_EQ(roi_tensor.get_element_type(), t.get_element_type());
} }
TEST_F(OVTensorTest, cannotSetShapeOnRoiTensor) {
ov::runtime::Tensor t{ov::element::i32, {1, 3, 6, 5}}; // RGBp picture of size (WxH) = 5x6
ov::runtime::Tensor roi_tensor{t, {0, 0, 1, 2}, {1, 3, 5, 4}};
const ov::Shape newShape({4, 5, 6});
ASSERT_THROW(roi_tensor.set_shape(newShape), ov::Exception);
}
TEST_F(OVTensorTest, makeRangeRoiTensorInt4) { TEST_F(OVTensorTest, makeRangeRoiTensorInt4) {
ov::runtime::Tensor t{ov::element::i4, {1, 6, 5, 3}}; // RGB picture of size (WxH) = 5x6 ov::runtime::Tensor t{ov::element::i4, {1, 6, 5, 3}}; // RGB picture of size (WxH) = 5x6
ov::runtime::Tensor roi_tensor{t, {0, 1, 2, 0}, {1, 5, 4, 3}}; ov::runtime::Tensor roi_tensor{t, {0, 1, 2, 0}, {1, 5, 4, 3}};