[PyOV] Shared memory improvements for Tensor and Constant classes (#15814)
This commit is contained in:
parent
d9fc5bac80
commit
3373c6743f
@ -6,6 +6,7 @@
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Python.h"
|
||||
#include "openvino/util/common_util.hpp"
|
||||
|
||||
#define C_CONTIGUOUS py::detail::npy_api::constants::NPY_ARRAY_C_CONTIGUOUS_
|
||||
@ -51,78 +52,22 @@ const std::map<std::string, ov::element::Type>& dtype_to_ov_type() {
|
||||
return dtype_to_ov_type_mapping;
|
||||
}
|
||||
|
||||
ov::Tensor tensor_from_pointer(py::array& array, const ov::Shape& shape, const ov::element::Type& type) {
|
||||
bool is_contiguous = C_CONTIGUOUS == (array.flags() & C_CONTIGUOUS);
|
||||
auto element_type = (type == ov::element::undefined) ? Common::dtype_to_ov_type().at(py::str(array.dtype())) : type;
|
||||
namespace array_helpers {
|
||||
|
||||
if (is_contiguous) {
|
||||
return ov::Tensor(element_type, shape, const_cast<void*>(array.data(0)), {});
|
||||
} else {
|
||||
throw ov::Exception("Tensor with shared memory must be C contiguous!");
|
||||
}
|
||||
bool is_contiguous(const py::array& array) {
|
||||
return C_CONTIGUOUS == (array.flags() & C_CONTIGUOUS);
|
||||
}
|
||||
|
||||
ov::Tensor tensor_from_numpy(py::array& array, bool shared_memory) {
|
||||
// Check if passed array has C-style contiguous memory layout.
|
||||
bool is_contiguous = C_CONTIGUOUS == (array.flags() & C_CONTIGUOUS);
|
||||
auto type = Common::dtype_to_ov_type().at(py::str(array.dtype()));
|
||||
std::vector<size_t> shape(array.shape(), array.shape() + array.ndim());
|
||||
|
||||
// If memory is going to be shared it needs to be contiguous before
|
||||
// passing to the constructor. This case should be handled by advanced
|
||||
// users on their side of the code.
|
||||
if (shared_memory) {
|
||||
if (is_contiguous) {
|
||||
std::vector<size_t> strides(array.strides(), array.strides() + array.ndim());
|
||||
return ov::Tensor(type, shape, const_cast<void*>(array.data(0)), strides);
|
||||
} else {
|
||||
throw ov::Exception("Tensor with shared memory must be C contiguous!");
|
||||
}
|
||||
}
|
||||
// Convert to contiguous array if not already C-style.
|
||||
if (!is_contiguous) {
|
||||
array = Common::as_contiguous(array, type);
|
||||
}
|
||||
// Create actual Tensor and copy data.
|
||||
auto tensor = ov::Tensor(type, shape);
|
||||
// If ndim of py::array is 0, array is a numpy scalar. That results in size to be equal to 0.
|
||||
// To gain access to actual raw/low-level data, it is needed to use buffer protocol.
|
||||
py::buffer_info buf = array.request();
|
||||
std::memcpy(tensor.data(), buf.ptr, buf.ndim == 0 ? buf.itemsize : buf.itemsize * buf.size);
|
||||
return tensor;
|
||||
ov::element::Type get_ov_type(const py::array& array) {
|
||||
return Common::dtype_to_ov_type().at(py::str(array.dtype()));
|
||||
}
|
||||
|
||||
ov::PartialShape partial_shape_from_list(const py::list& shape) {
|
||||
using value_type = ov::Dimension::value_type;
|
||||
ov::PartialShape pshape;
|
||||
for (py::handle dim : shape) {
|
||||
if (py::isinstance<py::int_>(dim)) {
|
||||
pshape.insert(pshape.end(), ov::Dimension(dim.cast<value_type>()));
|
||||
} else if (py::isinstance<py::str>(dim)) {
|
||||
pshape.insert(pshape.end(), ov::Dimension(dim.cast<std::string>()));
|
||||
} else if (py::isinstance<ov::Dimension>(dim)) {
|
||||
pshape.insert(pshape.end(), dim.cast<ov::Dimension>());
|
||||
} else if (py::isinstance<py::list>(dim) || py::isinstance<py::tuple>(dim)) {
|
||||
py::list bounded_dim = dim.cast<py::list>();
|
||||
if (bounded_dim.size() != 2) {
|
||||
throw py::type_error("Two elements are expected in tuple(lower, upper) for dynamic dimension, but " +
|
||||
std::to_string(bounded_dim.size()) + " elements were given.");
|
||||
}
|
||||
if (!(py::isinstance<py::int_>(bounded_dim[0]) && py::isinstance<py::int_>(bounded_dim[1]))) {
|
||||
throw py::type_error("Incorrect pair of types (" + std::string(bounded_dim[0].get_type().str()) + ", " +
|
||||
std::string(bounded_dim[1].get_type().str()) +
|
||||
") for dynamic dimension, ints are expected.");
|
||||
}
|
||||
pshape.insert(pshape.end(),
|
||||
ov::Dimension(bounded_dim[0].cast<value_type>(), bounded_dim[1].cast<value_type>()));
|
||||
} else {
|
||||
throw py::type_error("Incorrect type " + std::string(dim.get_type().str()) +
|
||||
" for dimension. Expected types are: "
|
||||
"int, str, openvino.runtime.Dimension, list/tuple with lower and upper values for "
|
||||
"dynamic dimension.");
|
||||
}
|
||||
}
|
||||
return pshape;
|
||||
std::vector<size_t> get_shape(const py::array& array) {
|
||||
return std::vector<size_t>(array.shape(), array.shape() + array.ndim());
|
||||
}
|
||||
|
||||
std::vector<size_t> get_strides(const py::array& array) {
|
||||
return std::vector<size_t>(array.strides(), array.strides() + array.ndim());
|
||||
}
|
||||
|
||||
py::array as_contiguous(py::array& array, ov::element::Type type) {
|
||||
@ -165,6 +110,120 @@ py::array as_contiguous(py::array& array, ov::element::Type type) {
|
||||
}
|
||||
}
|
||||
|
||||
}; // namespace array_helpers
|
||||
|
||||
template <>
|
||||
ov::op::v0::Constant create_copied(py::array& array) {
|
||||
// Convert to contiguous array if not already in C-style.
|
||||
if (!array_helpers::is_contiguous(array)) {
|
||||
array = array_helpers::as_contiguous(array, array_helpers::get_ov_type(array));
|
||||
}
|
||||
// Create actual Constant and a constructor is copying data.
|
||||
return ov::op::v0::Constant(array_helpers::get_ov_type(array),
|
||||
array_helpers::get_shape(array),
|
||||
const_cast<void*>(array.data(0)));
|
||||
}
|
||||
|
||||
template <>
|
||||
ov::op::v0::Constant create_copied(ov::Tensor& tensor) {
|
||||
// Create actual Constant and a constructor is copying data.
|
||||
return ov::op::v0::Constant(tensor.get_element_type(), tensor.get_shape(), const_cast<void*>(tensor.data()));
|
||||
}
|
||||
|
||||
template <>
|
||||
ov::op::v0::Constant create_shared(py::array& array) {
|
||||
// Check if passed array has C-style contiguous memory layout.
|
||||
// If memory is going to be shared it needs to be contiguous before passing to the constructor.
|
||||
if (array_helpers::is_contiguous(array)) {
|
||||
auto memory =
|
||||
std::make_shared<ngraph::runtime::SharedBuffer<py::array>>(static_cast<char*>(array.mutable_data(0)),
|
||||
array.nbytes(),
|
||||
array);
|
||||
return ov::op::v0::Constant(array_helpers::get_ov_type(array), array_helpers::get_shape(array), memory);
|
||||
}
|
||||
// If passed array is not C-style, throw an error.
|
||||
throw ov::Exception(
|
||||
"SHARED MEMORY MODE FOR THIS CONSTANT IS NOT APPLICABLE! Passed numpy array must be C contiguous.");
|
||||
}
|
||||
|
||||
template <>
|
||||
ov::op::v0::Constant create_shared(ov::Tensor& tensor) {
|
||||
return ov::op::v0::Constant(tensor);
|
||||
}
|
||||
|
||||
template <>
|
||||
ov::Tensor create_copied(py::array& array) {
|
||||
// Convert to contiguous array if not already in C-style.
|
||||
if (!array_helpers::is_contiguous(array)) {
|
||||
array = array_helpers::as_contiguous(array, array_helpers::get_ov_type(array));
|
||||
}
|
||||
// Create actual Tensor and copy data.
|
||||
auto tensor = ov::Tensor(array_helpers::get_ov_type(array), array_helpers::get_shape(array));
|
||||
// If ndim of py::array is 0, array is a numpy scalar. That results in size to be equal to 0.
|
||||
// To gain access to actual raw/low-level data, it is needed to use buffer protocol.
|
||||
py::buffer_info buf = array.request();
|
||||
std::memcpy(tensor.data(), buf.ptr, buf.ndim == 0 ? buf.itemsize : buf.itemsize * buf.size);
|
||||
return tensor;
|
||||
}
|
||||
|
||||
template <>
|
||||
ov::Tensor create_shared(py::array& array) {
|
||||
// Check if passed array has C-style contiguous memory layout.
|
||||
// If memory is going to be shared it needs to be contiguous before passing to the constructor.
|
||||
if (array_helpers::is_contiguous(array)) {
|
||||
return ov::Tensor(array_helpers::get_ov_type(array),
|
||||
array_helpers::get_shape(array),
|
||||
const_cast<void*>(array.data(0)),
|
||||
array_helpers::get_strides(array));
|
||||
}
|
||||
// If passed array is not C-style, throw an error.
|
||||
throw ov::Exception(
|
||||
"SHARED MEMORY MODE FOR THIS TENSOR IS NOT APPLICABLE! Passed numpy array must be C contiguous.");
|
||||
}
|
||||
|
||||
ov::Tensor tensor_from_pointer(py::array& array, const ov::Shape& shape, const ov::element::Type& type) {
|
||||
auto element_type = (type == ov::element::undefined) ? Common::dtype_to_ov_type().at(py::str(array.dtype())) : type;
|
||||
|
||||
if (array_helpers::is_contiguous(array)) {
|
||||
return ov::Tensor(element_type, shape, const_cast<void*>(array.data(0)), {});
|
||||
}
|
||||
throw ov::Exception(
|
||||
"SHARED MEMORY MODE FOR THIS TENSOR IS NOT APPLICABLE! Passed numpy array must be C contiguous.");
|
||||
}
|
||||
|
||||
ov::PartialShape partial_shape_from_list(const py::list& shape) {
|
||||
using value_type = ov::Dimension::value_type;
|
||||
ov::PartialShape pshape;
|
||||
for (py::handle dim : shape) {
|
||||
if (py::isinstance<py::int_>(dim)) {
|
||||
pshape.insert(pshape.end(), ov::Dimension(dim.cast<value_type>()));
|
||||
} else if (py::isinstance<py::str>(dim)) {
|
||||
pshape.insert(pshape.end(), ov::Dimension(dim.cast<std::string>()));
|
||||
} else if (py::isinstance<ov::Dimension>(dim)) {
|
||||
pshape.insert(pshape.end(), dim.cast<ov::Dimension>());
|
||||
} else if (py::isinstance<py::list>(dim) || py::isinstance<py::tuple>(dim)) {
|
||||
py::list bounded_dim = dim.cast<py::list>();
|
||||
if (bounded_dim.size() != 2) {
|
||||
throw py::type_error("Two elements are expected in tuple(lower, upper) for dynamic dimension, but " +
|
||||
std::to_string(bounded_dim.size()) + " elements were given.");
|
||||
}
|
||||
if (!(py::isinstance<py::int_>(bounded_dim[0]) && py::isinstance<py::int_>(bounded_dim[1]))) {
|
||||
throw py::type_error("Incorrect pair of types (" + std::string(bounded_dim[0].get_type().str()) + ", " +
|
||||
std::string(bounded_dim[1].get_type().str()) +
|
||||
") for dynamic dimension, ints are expected.");
|
||||
}
|
||||
pshape.insert(pshape.end(),
|
||||
ov::Dimension(bounded_dim[0].cast<value_type>(), bounded_dim[1].cast<value_type>()));
|
||||
} else {
|
||||
throw py::type_error("Incorrect type " + std::string(dim.get_type().str()) +
|
||||
" for dimension. Expected types are: "
|
||||
"int, str, openvino.runtime.Dimension, list/tuple with lower and upper values for "
|
||||
"dynamic dimension.");
|
||||
}
|
||||
}
|
||||
return pshape;
|
||||
}
|
||||
|
||||
const ov::Tensor& cast_to_tensor(const py::handle& tensor) {
|
||||
return tensor.cast<const ov::Tensor&>();
|
||||
}
|
||||
|
@ -10,8 +10,10 @@
|
||||
#include <pybind11/iostream.h>
|
||||
|
||||
#include <openvino/core/type/element_type.hpp>
|
||||
#include <ngraph/runtime/shared_buffer.hpp>
|
||||
#include <string>
|
||||
#include <iterator>
|
||||
#include <climits>
|
||||
|
||||
#include "Python.h"
|
||||
#include "openvino/runtime/compiled_model.hpp"
|
||||
@ -20,22 +22,62 @@
|
||||
#include "openvino/pass/serialize.hpp"
|
||||
#include "pyopenvino/core/containers.hpp"
|
||||
#include "pyopenvino/graph/any.hpp"
|
||||
#include "pyopenvino/graph/ops/constant.hpp"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace values {
|
||||
|
||||
// Minimum amount of bits for common numpy types. Used to perform checks against OV types.
|
||||
constexpr size_t min_bitwidth = sizeof(int8_t) * CHAR_BIT;
|
||||
|
||||
}; // namespace values
|
||||
|
||||
const std::map<ov::element::Type, py::dtype>& ov_type_to_dtype();
|
||||
|
||||
const std::map<std::string, ov::element::Type>& dtype_to_ov_type();
|
||||
|
||||
ov::Tensor tensor_from_pointer(py::array& array, const ov::Shape& shape, const ov::element::Type& ov_type);
|
||||
// Helpers for numpy arrays
|
||||
namespace array_helpers {
|
||||
|
||||
ov::Tensor tensor_from_numpy(py::array& array, bool shared_memory);
|
||||
bool is_contiguous(const py::array& array);
|
||||
|
||||
ov::PartialShape partial_shape_from_list(const py::list& shape);
|
||||
ov::element::Type get_ov_type(const py::array& array);
|
||||
|
||||
std::vector<size_t> get_shape(const py::array& array);
|
||||
|
||||
std::vector<size_t> get_strides(const py::array& array);
|
||||
|
||||
py::array as_contiguous(py::array& array, ov::element::Type type);
|
||||
|
||||
}; // namespace array_helpers
|
||||
|
||||
template <typename T>
|
||||
T create_copied(py::array& array);
|
||||
|
||||
template <typename T>
|
||||
T create_copied(ov::Tensor& array);
|
||||
|
||||
template <typename T>
|
||||
T create_shared(py::array& array);
|
||||
|
||||
template <typename T>
|
||||
T create_shared(ov::Tensor& array);
|
||||
|
||||
template <typename T, typename D>
|
||||
T object_from_data(D& data, bool shared_memory) {
|
||||
if (shared_memory) {
|
||||
return create_shared<T>(data);
|
||||
}
|
||||
return create_copied<T>(data);
|
||||
}
|
||||
|
||||
ov::Tensor tensor_from_pointer(py::array& array, const ov::Shape& shape, const ov::element::Type& ov_type);
|
||||
|
||||
ov::PartialShape partial_shape_from_list(const py::list& shape);
|
||||
|
||||
const ov::Tensor& cast_to_tensor(const py::handle& tensor);
|
||||
|
||||
const Containers::TensorNameMap cast_to_tensor_name_map(const py::dict& inputs);
|
||||
|
@ -17,7 +17,7 @@ void regclass_Tensor(py::module m) {
|
||||
cls.doc() = "openvino.runtime.Tensor holding either copy of memory or shared host memory.";
|
||||
|
||||
cls.def(py::init([](py::array& array, bool shared_memory) {
|
||||
return Common::tensor_from_numpy(array, shared_memory);
|
||||
return Common::object_from_data<ov::Tensor>(array, shared_memory);
|
||||
}),
|
||||
py::arg("array"),
|
||||
py::arg("shared_memory") = false,
|
||||
@ -209,7 +209,7 @@ void regclass_Tensor(py::module m) {
|
||||
[](ov::Tensor& self) {
|
||||
auto ov_type = self.get_element_type();
|
||||
auto dtype = Common::ov_type_to_dtype().at(ov_type);
|
||||
if (ov_type.bitwidth() < 8) {
|
||||
if (ov_type.bitwidth() < Common::values::min_bitwidth) {
|
||||
return py::array(dtype, self.get_byte_size(), self.data(), py::cast(self));
|
||||
}
|
||||
return py::array(dtype, self.get_shape(), self.get_strides(), self.data(), py::cast(self));
|
||||
|
@ -310,7 +310,7 @@ void regclass_frontend_InputModel(py::module m) {
|
||||
"set_tensor_value",
|
||||
[](ov::frontend::InputModel& self, const ov::frontend::Place::Ptr& place, py::array& value) {
|
||||
// Convert to contiguous array if not already C-style.
|
||||
auto tensor = Common::tensor_from_numpy(value, false);
|
||||
auto tensor = Common::object_from_data<ov::Tensor>(value, false);
|
||||
self.set_tensor_value(place, (const void*)tensor.data());
|
||||
},
|
||||
py::arg("place"),
|
||||
|
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "openvino/op/constant.hpp"
|
||||
#include "pyopenvino/graph/ops/constant.hpp"
|
||||
|
||||
#include <pybind11/buffer_info.h>
|
||||
#include <pybind11/numpy.h>
|
||||
@ -10,10 +10,10 @@
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "openvino/core/shape.hpp"
|
||||
#include "pyopenvino/graph/ops/constant.hpp"
|
||||
#include "openvino/runtime/tensor.hpp"
|
||||
#include "pyopenvino/core/common.hpp"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
@ -27,6 +27,38 @@ std::vector<size_t> _get_byte_strides(const ov::Shape& s) {
|
||||
return byte_strides;
|
||||
}
|
||||
|
||||
std::vector<size_t> _get_strides(const ov::op::v0::Constant& self) {
|
||||
auto element_type = self.get_element_type();
|
||||
auto shape = self.get_shape();
|
||||
if (element_type == ov::element::boolean) {
|
||||
return _get_byte_strides<char>(shape);
|
||||
} else if (element_type == ov::element::f16) {
|
||||
return _get_byte_strides<ov::float16>(shape);
|
||||
} else if (element_type == ov::element::f32) {
|
||||
return _get_byte_strides<float>(shape);
|
||||
} else if (element_type == ov::element::f64) {
|
||||
return _get_byte_strides<double>(shape);
|
||||
} else if (element_type == ov::element::i8) {
|
||||
return _get_byte_strides<int8_t>(shape);
|
||||
} else if (element_type == ov::element::i16) {
|
||||
return _get_byte_strides<int16_t>(shape);
|
||||
} else if (element_type == ov::element::i32) {
|
||||
return _get_byte_strides<int32_t>(shape);
|
||||
} else if (element_type == ov::element::i64) {
|
||||
return _get_byte_strides<int64_t>(shape);
|
||||
} else if (element_type == ov::element::u8 || element_type == ov::element::u1) {
|
||||
return _get_byte_strides<uint8_t>(shape);
|
||||
} else if (element_type == ov::element::u16) {
|
||||
return _get_byte_strides<uint16_t>(shape);
|
||||
} else if (element_type == ov::element::u32) {
|
||||
return _get_byte_strides<uint32_t>(shape);
|
||||
} else if (element_type == ov::element::u64) {
|
||||
return _get_byte_strides<uint64_t>(shape);
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported data type!");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
py::buffer_info _get_buffer_info(const ov::op::v0::Constant& c) {
|
||||
ov::Shape shape = c.get_shape();
|
||||
@ -68,6 +100,18 @@ void regclass_graph_op_Constant(py::module m) {
|
||||
"Constant",
|
||||
py::buffer_protocol());
|
||||
constant.doc() = "openvino.runtime.op.Constant wraps ov::op::v0::Constant";
|
||||
// Numpy-based constructor
|
||||
constant.def(py::init([](py::array& array, bool shared_memory) {
|
||||
return Common::object_from_data<ov::op::v0::Constant>(array, shared_memory);
|
||||
}),
|
||||
py::arg("array"),
|
||||
py::arg("shared_memory") = false);
|
||||
// Tensor-based constructors
|
||||
constant.def(py::init([](ov::Tensor& tensor, bool shared_memory) {
|
||||
return Common::object_from_data<ov::op::v0::Constant>(tensor, shared_memory);
|
||||
}),
|
||||
py::arg("tensor"),
|
||||
py::arg("shared_memory") = false);
|
||||
constant.def(py::init<const ov::element::Type&, const ov::Shape&, const std::vector<char>&>());
|
||||
constant.def(py::init<const ov::element::Type&, const ov::Shape&, const std::vector<ov::float16>&>());
|
||||
constant.def(py::init<const ov::element::Type&, const ov::Shape&, const std::vector<float>&>());
|
||||
@ -151,4 +195,26 @@ void regclass_graph_op_Constant(py::module m) {
|
||||
throw std::runtime_error("Unsupported data type!");
|
||||
}
|
||||
});
|
||||
|
||||
constant.def_property_readonly(
|
||||
"data",
|
||||
[](ov::op::v0::Constant& self) {
|
||||
auto ov_type = self.get_element_type();
|
||||
auto dtype = Common::ov_type_to_dtype().at(ov_type);
|
||||
if (ov_type.bitwidth() < Common::values::min_bitwidth) {
|
||||
return py::array(dtype, self.get_byte_size(), self.get_data_ptr(), py::cast(self));
|
||||
}
|
||||
return py::array(dtype, self.get_shape(), _get_strides(self), self.get_data_ptr(), py::cast(self));
|
||||
},
|
||||
R"(
|
||||
Access to Constant's data.
|
||||
|
||||
Returns numpy array with corresponding shape and dtype.
|
||||
For Constants with openvino specific element type, such as u1,
|
||||
it returns linear array, with uint8 / int8 numpy dtype.
|
||||
|
||||
Note: this access method reflects shared memory if it was applied during initialization.
|
||||
|
||||
:rtype: numpy.array
|
||||
)");
|
||||
}
|
||||
|
@ -4,8 +4,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include "openvino/op/constant.hpp"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
std::vector<size_t> _get_strides(const ov::op::v0::Constant& self);
|
||||
|
||||
void regclass_graph_op_Constant(py::module m);
|
||||
|
@ -322,7 +322,7 @@ static void regclass_graph_InputTensorInfo(py::module m) {
|
||||
"set_from",
|
||||
[](ov::preprocess::InputTensorInfo& self, py::array& numpy_array) {
|
||||
// Convert to contiguous array if not already C-style.
|
||||
return &self.set_from(Common::tensor_from_numpy(numpy_array, false));
|
||||
return &self.set_from(Common::object_from_data<ov::Tensor>(numpy_array, false));
|
||||
},
|
||||
py::arg("runtime_tensor"),
|
||||
R"(
|
||||
|
@ -6,9 +6,12 @@
|
||||
|
||||
#include <pybind11/numpy.h>
|
||||
|
||||
#include <openvino/core/node_output.hpp>
|
||||
|
||||
#include "openvino/core/graph_util.hpp"
|
||||
#include "openvino/core/validation_util.hpp"
|
||||
#include "openvino/pass/manager.hpp"
|
||||
#include "pyopenvino/graph/ops/constant.hpp"
|
||||
#include "pyopenvino/utils/utils.hpp"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
127
src/bindings/python/tests/test_runtime/test_memory_modes.py
Normal file
127
src/bindings/python/tests/test_runtime/test_memory_modes.py
Normal file
@ -0,0 +1,127 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2018-2023 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import openvino.runtime as ov
|
||||
from openvino.runtime import Tensor
|
||||
from openvino.runtime.op import Constant
|
||||
|
||||
from tests.test_utils.test_utils import generate_image
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("cls", "cls_str"), [
|
||||
(Tensor, "TENSOR"),
|
||||
(Constant, "CONSTANT"),
|
||||
])
|
||||
def test_init_with_numpy_fail(cls, cls_str):
|
||||
arr = np.asfortranarray(generate_image()) # F-style array
|
||||
|
||||
with pytest.raises(RuntimeError) as e:
|
||||
_ = cls(array=arr, shared_memory=True)
|
||||
|
||||
assert "SHARED MEMORY MODE FOR THIS " + cls_str + " IS NOT APPLICABLE!" in str(e.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cls", [Tensor, Constant])
|
||||
@pytest.mark.parametrize("shared_flag", [True, False])
|
||||
@pytest.mark.parametrize(("ov_type", "numpy_dtype"), [
|
||||
(ov.Type.f32, np.float32),
|
||||
(ov.Type.f64, np.float64),
|
||||
(ov.Type.f16, np.float16),
|
||||
(ov.Type.i8, np.int8),
|
||||
(ov.Type.u8, np.uint8),
|
||||
(ov.Type.i32, np.int32),
|
||||
(ov.Type.u32, np.uint32),
|
||||
(ov.Type.i16, np.int16),
|
||||
(ov.Type.u16, np.uint16),
|
||||
(ov.Type.i64, np.int64),
|
||||
(ov.Type.u64, np.uint64),
|
||||
(ov.Type.boolean, bool),
|
||||
])
|
||||
def test_with_numpy_memory(cls, shared_flag, ov_type, numpy_dtype):
|
||||
arr = np.ascontiguousarray(generate_image().astype(numpy_dtype))
|
||||
ov_object = cls(array=arr, shared_memory=shared_flag)
|
||||
|
||||
assert ov_object.get_element_type() == ov_type
|
||||
assert tuple(ov_object.shape) == arr.shape
|
||||
|
||||
assert isinstance(ov_object.data, np.ndarray)
|
||||
assert ov_object.data.dtype == numpy_dtype
|
||||
assert ov_object.data.shape == arr.shape
|
||||
assert np.array_equal(ov_object.data, arr)
|
||||
|
||||
if shared_flag is True:
|
||||
assert np.shares_memory(arr, ov_object.data)
|
||||
else:
|
||||
assert not (np.shares_memory(arr, ov_object.data))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cls", [Tensor, Constant])
|
||||
@pytest.mark.parametrize("shared_flag", [True, False])
|
||||
def test_with_external_memory(cls, shared_flag):
|
||||
class ArrayLikeObject:
|
||||
# Array-like object to test inputs similar to torch.Tensor and tf.Tensor
|
||||
def __init__(self, array) -> None:
|
||||
self.data = array
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.data.shape
|
||||
|
||||
@property
|
||||
def dtype(self):
|
||||
return self.data.dtype
|
||||
|
||||
def to_numpy(self):
|
||||
return self.data
|
||||
|
||||
external_object = ArrayLikeObject(np.ascontiguousarray(generate_image()))
|
||||
ov_object = cls(array=external_object.to_numpy(), shared_memory=shared_flag)
|
||||
|
||||
assert np.array_equal(ov_object.data.dtype, external_object.dtype)
|
||||
assert np.array_equal(ov_object.data.shape, external_object.shape)
|
||||
assert np.array_equal(ov_object.data, external_object.to_numpy())
|
||||
|
||||
if shared_flag is True:
|
||||
assert np.shares_memory(external_object.to_numpy(), ov_object.data)
|
||||
else:
|
||||
assert not (np.shares_memory(external_object.to_numpy(), ov_object.data))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cls", [Constant])
|
||||
@pytest.mark.parametrize("shared_flag_one", [True, False])
|
||||
@pytest.mark.parametrize("shared_flag_two", [True, False])
|
||||
@pytest.mark.parametrize(("ov_type", "numpy_dtype"), [
|
||||
(ov.Type.f32, np.float32),
|
||||
(ov.Type.f64, np.float64),
|
||||
(ov.Type.f16, np.float16),
|
||||
(ov.Type.i8, np.int8),
|
||||
(ov.Type.u8, np.uint8),
|
||||
(ov.Type.i32, np.int32),
|
||||
(ov.Type.u32, np.uint32),
|
||||
(ov.Type.i16, np.int16),
|
||||
(ov.Type.u16, np.uint16),
|
||||
(ov.Type.i64, np.int64),
|
||||
(ov.Type.u64, np.uint64),
|
||||
(ov.Type.boolean, bool),
|
||||
])
|
||||
def test_with_tensor_memory(cls, shared_flag_one, shared_flag_two, ov_type, numpy_dtype):
|
||||
arr = np.ascontiguousarray(generate_image().astype(numpy_dtype))
|
||||
ov_tensor = Tensor(arr, shared_memory=shared_flag_one)
|
||||
ov_object = cls(tensor=ov_tensor, shared_memory=shared_flag_two)
|
||||
|
||||
# Case 1: all data is shared
|
||||
if shared_flag_one is True and shared_flag_two is True:
|
||||
assert np.shares_memory(arr, ov_object.data)
|
||||
assert np.shares_memory(ov_tensor.data, ov_object.data)
|
||||
# Case 2: data is shared only between object and Tensor
|
||||
elif shared_flag_one is False and shared_flag_two is True:
|
||||
assert not (np.shares_memory(arr, ov_object.data))
|
||||
assert np.shares_memory(ov_tensor.data, ov_object.data)
|
||||
# Case 3: data is not shared, copy occurs in the object's constructor
|
||||
else:
|
||||
assert not (np.shares_memory(arr, ov_object.data))
|
||||
assert not (np.shares_memory(ov_tensor.data, ov_object.data))
|
@ -148,13 +148,6 @@ def test_init_with_numpy_copy_memory(ov_type, numpy_dtype):
|
||||
assert ov_tensor.byte_size == arr.nbytes
|
||||
|
||||
|
||||
def test_init_with_numpy_fail():
|
||||
arr = np.asfortranarray(generate_image())
|
||||
with pytest.raises(RuntimeError) as e:
|
||||
_ = Tensor(array=arr, shared_memory=True)
|
||||
assert "Tensor with shared memory must be C contiguous" in str(e.value)
|
||||
|
||||
|
||||
def test_init_with_roi_tensor():
|
||||
array = np.random.normal(size=[1, 3, 48, 48])
|
||||
ov_tensor1 = Tensor(array)
|
||||
|
Loading…
Reference in New Issue
Block a user