This commit is contained in:
Pawel Raasz 2024-06-12 03:27:41 +02:00 committed by GitHub
commit 64edc4b25f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 571 additions and 88 deletions

View File

@ -84,6 +84,50 @@ static void set_correct_variables_for_assign_ops(const std::shared_ptr<ov::Model
}
}
static ov::Output<ov::Node> output_from_handle(ov::Model& model, const py::handle& handle) {
if (py::isinstance<py::int_>(handle)) {
return model.input(handle.cast<size_t>());
} else if (py::isinstance<py::str>(handle)) {
return model.input(handle.cast<std::string>());
} else if (py::isinstance<ov::Output<ov::Node>>(handle)) {
return handle.cast<ov::Output<ov::Node>>();
} else {
throw py::type_error("Incorrect key type " + std::string(py::str(handle.get_type())) +
" to reshape a model, expected keys as openvino.runtime.Output, int or str.");
}
}
static ov::PartialShape partial_shape_from_handle(const py::handle& handle) {
if (py::isinstance<ov::PartialShape>(handle)) {
return handle.cast<ov::PartialShape>();
} else if (py::isinstance<py::list>(handle) || py::isinstance<py::tuple>(handle)) {
return Common::partial_shape_from_list(handle.cast<py::list>());
} else if (py::isinstance<py::str>(handle)) {
return ov::PartialShape(handle.cast<std::string>());
} else {
throw py::type_error(
"Incorrect value type " + std::string(py::str(handle.get_type())) +
" to reshape a model, expected values as openvino.runtime.PartialShape, str, list or tuple.");
}
}
static std::string string_from_handle(const py::handle& handle) {
if (py::isinstance<py::str>(handle)) {
return handle.cast<std::string>();
} else {
throw py::type_error("Incorrect key type " + std::string(py::str(handle.get_type())) +
" to reshape a model, expected values as str.");
}
}
static std::unordered_map<std::string, ov::PartialShape> get_variables_shapes(const py::dict& variables_shapes) {
std::unordered_map<std::string, ov::PartialShape> variables_shape_map;
for (const auto& item : variables_shapes) {
variables_shape_map.emplace(string_from_handle(item.first), partial_shape_from_handle(item.second));
}
return variables_shape_map;
}
void regclass_graph_Model(py::module m) {
py::class_<ov::Model, std::shared_ptr<ov::Model>> model(m, "Model", py::module_local());
model.doc() = "openvino.runtime.Model wraps ov::Model";
@ -309,109 +353,186 @@ void regclass_graph_Model(py::module m) {
model.def(
"reshape",
[](ov::Model& self, const ov::PartialShape& partial_shape) {
self.reshape(partial_shape);
[](ov::Model& self, const ov::PartialShape& partial_shape, const py::dict& variables_shapes) {
const auto new_variable_shapes = get_variables_shapes(variables_shapes);
py::gil_scoped_release release;
self.reshape(partial_shape, new_variable_shapes);
},
py::call_guard<py::gil_scoped_release>(),
py::arg("partial_shape"),
py::arg("variables_shapes") = py::dict(),
R"(
Reshape model input.
The allowed types of keys in the `variables_shapes` dictionary is `str`.
The allowed types of values in the `variables_shapes` are:
(1) `openvino.runtime.PartialShape`
(2) `list` consisting of dimensions
(3) `tuple` consisting of dimensions
(4) `str`, string representation of `openvino.runtime.PartialShape`
When list or tuple are used to describe dimensions, each dimension can be written in form:
(1) non-negative `int` which means static value for the dimension
(2) `[min, max]`, dynamic dimension where `min` specifies lower bound and `max` specifies upper bound;
the range includes both `min` and `max`; using `-1` for `min` or `max` means no known bound (3) `(min,
max)`, the same as above (4) `-1` is a dynamic dimension without known bounds (4)
`openvino.runtime.Dimension` (5) `str` using next syntax:
'?' - to define fully dynamic dimension
'1' - to define dimension which length is 1
'1..10' - to define bounded dimension
'..10' or '1..' to define dimension with only lower or only upper limit
GIL is released while running this function.
:param partial_shape: New shape.
:type partial_shape: openvino.runtime.PartialShape
:param variables_shapes: New shapes for variables
:type variables_shapes: Dict[keys, values]
:return : void
)");
model.def(
"reshape",
[](ov::Model& self, const py::list& partial_shape) {
ov::PartialShape new_shape(Common::partial_shape_from_list(partial_shape));
[](ov::Model& self, const py::list& partial_shape, const py::dict& variables_shapes) {
const auto new_shape = Common::partial_shape_from_list(partial_shape);
const auto new_variables_shapes = get_variables_shapes(variables_shapes);
py::gil_scoped_release release;
self.reshape(new_shape);
self.reshape(new_shape, new_variables_shapes);
},
py::arg("partial_shape"),
py::arg("variables_shapes") = py::dict(),
R"(
Reshape model input.
The allowed types of keys in the `variables_shapes` dictionary is `str`.
The allowed types of values in the `variables_shapes` are:
(1) `openvino.runtime.PartialShape`
(2) `list` consisting of dimensions
(3) `tuple` consisting of dimensions
(4) `str`, string representation of `openvino.runtime.PartialShape`
When list or tuple are used to describe dimensions, each dimension can be written in form:
(1) non-negative `int` which means static value for the dimension
(2) `[min, max]`, dynamic dimension where `min` specifies lower bound and `max` specifies upper bound;
the range includes both `min` and `max`; using `-1` for `min` or `max` means no known bound (3) `(min,
max)`, the same as above (4) `-1` is a dynamic dimension without known bounds (4)
`openvino.runtime.Dimension` (5) `str` using next syntax:
'?' - to define fully dynamic dimension
'1' - to define dimension which length is 1
'1..10' - to define bounded dimension
'..10' or '1..' to define dimension with only lower or only upper limit
GIL is released while running this function.
:param partial_shape: New shape.
:type partial_shape: list
:param variables_shapes: New shapes for variables
:type variables_shapes: Dict[keys, values]
:return : void
)");
model.def(
"reshape",
[](ov::Model& self, const py::tuple& partial_shape) {
ov::PartialShape new_shape(Common::partial_shape_from_list(partial_shape.cast<py::list>()));
[](ov::Model& self, const py::tuple& partial_shape, const py::dict& variables_shapes) {
const auto new_shape = Common::partial_shape_from_list(partial_shape.cast<py::list>());
const auto new_variables_shapes = get_variables_shapes(variables_shapes);
py::gil_scoped_release release;
self.reshape(new_shape);
self.reshape(new_shape, new_variables_shapes);
},
py::arg("partial_shape"),
py::arg("variables_shapes") = py::dict(),
R"(
Reshape model input.
The allowed types of keys in the `variables_shapes` dictionary is `str`.
The allowed types of values in the `variables_shapes` are:
(1) `openvino.runtime.PartialShape`
(2) `list` consisting of dimensions
(3) `tuple` consisting of dimensions
(4) `str`, string representation of `openvino.runtime.PartialShape`
When list or tuple are used to describe dimensions, each dimension can be written in form:
(1) non-negative `int` which means static value for the dimension
(2) `[min, max]`, dynamic dimension where `min` specifies lower bound and `max` specifies upper bound;
the range includes both `min` and `max`; using `-1` for `min` or `max` means no known bound (3) `(min,
max)`, the same as above (4) `-1` is a dynamic dimension without known bounds (4)
`openvino.runtime.Dimension` (5) `str` using next syntax:
'?' - to define fully dynamic dimension
'1' - to define dimension which length is 1
'1..10' - to define bounded dimension
'..10' or '1..' to define dimension with only lower or only upper limit
GIL is released while running this function.
:param partial_shape: New shape.
:type partial_shape: tuple
:param variables_shapes: New shapes for variables
:type variables_shapes: Dict[keys, values]
:return : void
)");
model.def(
"reshape",
[](ov::Model& self, const std::string& partial_shape) {
self.reshape(ov::PartialShape(partial_shape));
[](ov::Model& self, const std::string& partial_shape, const py::dict& variables_shapes) {
const auto new_variables_shape = get_variables_shapes(variables_shapes);
py::gil_scoped_release release;
self.reshape(ov::PartialShape(partial_shape), new_variables_shape);
},
py::call_guard<py::gil_scoped_release>(),
py::arg("partial_shape"),
py::arg("variables_shapes") = py::dict(),
R"(
Reshape model input.
The allowed types of keys in the `variables_shapes` dictionary is `str`.
The allowed types of values in the `variables_shapes` are:
(1) `openvino.runtime.PartialShape`
(2) `list` consisting of dimensions
(3) `tuple` consisting of dimensions
(4) `str`, string representation of `openvino.runtime.PartialShape`
When list or tuple are used to describe dimensions, each dimension can be written in form:
(1) non-negative `int` which means static value for the dimension
(2) `[min, max]`, dynamic dimension where `min` specifies lower bound and `max` specifies upper bound;
the range includes both `min` and `max`; using `-1` for `min` or `max` means no known bound (3) `(min,
max)`, the same as above (4) `-1` is a dynamic dimension without known bounds (4)
`openvino.runtime.Dimension` (5) `str` using next syntax:
'?' - to define fully dynamic dimension
'1' - to define dimension which length is 1
'1..10' - to define bounded dimension
'..10' or '1..' to define dimension with only lower or only upper limit
GIL is released while running this function.
:param partial_shape: New shape.
:type partial_shape: str
:param variables_shapes: New shapes for variables
:type variables_shapes: Dict[keys, values]
:return : void
)");
model.def(
"reshape",
[](ov::Model& self, const py::dict& partial_shapes) {
[](ov::Model& self, const py::dict& partial_shapes, const py::dict& variables_shapes) {
std::map<ov::Output<ov::Node>, ov::PartialShape> new_shapes;
for (const auto& item : partial_shapes) {
std::pair<ov::Output<ov::Node>, ov::PartialShape> new_shape;
// check keys
if (py::isinstance<py::int_>(item.first)) {
new_shape.first = self.input(item.first.cast<size_t>());
} else if (py::isinstance<py::str>(item.first)) {
new_shape.first = self.input(item.first.cast<std::string>());
} else if (py::isinstance<ov::Output<ov::Node>>(item.first)) {
new_shape.first = item.first.cast<ov::Output<ov::Node>>();
} else {
throw py::type_error("Incorrect key type " + std::string(py::str(item.first.get_type())) +
" to reshape a model, expected keys as openvino.runtime.Output, int or str.");
}
// check values
if (py::isinstance<ov::PartialShape>(item.second)) {
new_shape.second = item.second.cast<ov::PartialShape>();
} else if (py::isinstance<py::list>(item.second) || py::isinstance<py::tuple>(item.second)) {
new_shape.second = Common::partial_shape_from_list(item.second.cast<py::list>());
} else if (py::isinstance<py::str>(item.second)) {
new_shape.second = ov::PartialShape(item.second.cast<std::string>());
} else {
throw py::type_error(
"Incorrect value type " + std::string(py::str(item.second.get_type())) +
" to reshape a model, expected values as openvino.runtime.PartialShape, str, list or tuple.");
}
new_shapes.insert(new_shape);
new_shapes.emplace_hint(new_shapes.end(),
output_from_handle(self, item.first),
partial_shape_from_handle(item.second));
}
const auto new_variables_shapes = get_variables_shapes(variables_shapes);
py::gil_scoped_release release;
self.reshape(new_shapes);
self.reshape(new_shapes, new_variables_shapes);
},
py::arg("partial_shapes"),
py::arg("variables_shapes") = py::dict(),
R"( Reshape model inputs.
The allowed types of keys in the `partial_shapes` dictionary are:
@ -440,12 +561,34 @@ void regclass_graph_Model(py::module m) {
'1..10' - to define bounded dimension
'..10' or '1..' to define dimension with only lower or only upper limit
Reshape model input.
The allowed types of keys in the `variables_shapes` dictionary is `str`.
The allowed types of values in the `variables_shapes` are:
(1) `openvino.runtime.PartialShape`
(2) `list` consisting of dimensions
(3) `tuple` consisting of dimensions
(4) `str`, string representation of `openvino.runtime.PartialShape`
When list or tuple are used to describe dimensions, each dimension can be written in form:
(1) non-negative `int` which means static value for the dimension
(2) `[min, max]`, dynamic dimension where `min` specifies lower bound and `max` specifies upper bound;
the range includes both `min` and `max`; using `-1` for `min` or `max` means no known bound (3) `(min,
max)`, the same as above (4) `-1` is a dynamic dimension without known bounds (4)
`openvino.runtime.Dimension` (5) `str` using next syntax:
'?' - to define fully dynamic dimension
'1' - to define dimension which length is 1
'1..10' - to define bounded dimension
'..10' or '1..' to define dimension with only lower or only upper limit
Reshape model inputs.
GIL is released while running this function.
:param partial_shapes: New shapes.
:type partial_shapes: Dict[keys, values]
:param variables_shapes: New shapes for variables
:type variables_shapes: Dict[keys, values]
)");
model.def("get_output_size",

View File

@ -27,7 +27,18 @@ from openvino import (
from openvino.runtime import Output
from openvino.runtime.op.util import VariableInfo, Variable
from tests.utils.helpers import generate_add_model, generate_model_with_memory, create_filename_for_test
from tests.utils.helpers import (
generate_add_model,
generate_model_with_memory,
create_filename_for_test,
)
def make_add_with_variable_model(shape, variable_id: str, dtype=np.float32) -> Model:
param1 = ops.parameter(Shape(shape), dtype=dtype, name="data1")
param2 = ops.parameter(Shape(shape), dtype=dtype, name="data2")
read_value = ops.read_value(param1, variable_id, dtype, shape)
return Model(ops.add(read_value, param2), [param1, param2])
def test_descriptor_tensor():
@ -288,6 +299,55 @@ def test_reshape(device):
assert compiled_model.input().partial_shape == ref_shape
def test_reshape_with_ports_and_variable():
new_shape = PartialShape([46, 1])
var_id = "ID1"
model = make_add_with_variable_model([1, 5], var_id)
for model_input in model.inputs:
assert isinstance(model_input, Output)
model.reshape({model_input: new_shape}, {var_id: new_shape})
assert model_input.partial_shape == new_shape
def test_reshape_with_indexes_and_variable():
var_id = "ID1"
model = make_add_with_variable_model([1, 5], var_id)
new_shape = PartialShape([1, 4])
model.reshape(
{i: new_shape for i, model_input in enumerate(model.inputs)},
{var_id: new_shape},
)
for model_input in model.inputs:
assert model_input.partial_shape == new_shape
def test_reshape_with_names_and_variables():
var_id = "ID1"
model = make_add_with_variable_model([1, 25], var_id)
new_shape = PartialShape([4, 1])
model.reshape(
{model_input.any_name: new_shape for model_input in model.inputs},
{var_id: new_shape},
)
for model_input in model.inputs:
assert model_input.partial_shape == new_shape
def test_reshape_with_variable(device):
shape = PartialShape([1, 10])
param = ops.parameter(shape, dtype=np.float32)
read_value = ops.read_value(param, "MyVar", Type.f32, shape)
model = Model(ops.relu(read_value), [param])
ref_shape = model.input().partial_shape
ref_shape[0] = 3
model.reshape(ref_shape, {"MyVar": ref_shape})
core = Core()
compiled_model = core.compile_model(model, device)
assert compiled_model.input().partial_shape == ref_shape
def test_reshape_with_python_types(device):
model = generate_add_model()
@ -366,6 +426,85 @@ def test_reshape_with_python_types(device):
)
def test_reshape_with_python_types_for_variable(device):
var_id = "ID1"
model = make_add_with_variable_model([1, 2, 5], var_id)
def check_shape(new_shape):
for model_input in model.inputs:
assert model_input.partial_shape == new_shape
shape1 = [1, 4]
new_shapes = {input: PartialShape(shape1) for input in model.inputs}
model.reshape(new_shapes, {var_id: shape1})
check_shape(PartialShape(shape1))
shape2 = [1, 6]
new_shapes = {input.any_name: PartialShape(shape2) for input in model.inputs}
model.reshape(new_shapes, {var_id: shape2})
check_shape(PartialShape(shape2))
shape3 = [1, 8]
new_shapes = {i: PartialShape(shape3) for i, _ in enumerate(model.inputs)}
model.reshape(new_shapes, {var_id: shape3})
check_shape(PartialShape(shape3))
shape4 = [1, -1]
new_shapes = {input: PartialShape(shape4) for input in model.inputs}
model.reshape(new_shapes, {var_id: shape4})
check_shape(PartialShape([Dimension(1), Dimension(-1)]))
shape5 = [1, (1, 10)]
new_shapes = {input: PartialShape(shape5) for input in model.inputs}
model.reshape(new_shapes, {var_id: shape5})
check_shape(PartialShape([Dimension(1), Dimension(1, 10)]))
shape6 = [Dimension(3), Dimension(3, 10)]
new_shapes = {input: PartialShape(shape6) for input in model.inputs}
model.reshape(new_shapes, {var_id: shape6})
check_shape(PartialShape(shape6))
shape7 = "[1..10, ?]"
new_shapes = {input: PartialShape(shape7) for input in model.inputs}
model.reshape(new_shapes, {var_id: shape7})
check_shape(PartialShape(shape7))
# reshape mixed keys
shape8 = [(1, 20), -1]
new_shapes = {"data1": PartialShape(shape8), 1: shape8}
model.reshape(new_shapes, {var_id: shape8})
check_shape(PartialShape([Dimension(1, 20), Dimension(-1)]))
# reshape with one input
param = ops.parameter([1, 3, 28, 28])
read_value = ops.read_value(param, var_id, Type.f32, param.get_partial_shape())
model = Model(ops.relu(read_value), [param])
shape9 = [-1, 3, (28, 56), (28, 56)]
model.reshape(shape9, {var_id: shape9})
check_shape(PartialShape([Dimension(-1), Dimension(3), Dimension(28, 56), Dimension(28, 56)]))
shape10 = "[?,3,..224,..224]"
model.reshape(shape10, {var_id: shape10})
check_shape(PartialShape([Dimension(-1), Dimension(3), Dimension(-1, 224), Dimension(-1, 224)]))
# check exceptions
shape10 = [1, 1, 1, 1]
with pytest.raises(TypeError) as e:
model.reshape({0: shape10}, {0: shape10})
assert (
"Incorrect key type <class 'int'> to reshape a model, expected values as str." in str(e.value)
)
with pytest.raises(TypeError) as e:
model.reshape({0: shape10}, {var_id: range(1, 9)})
assert (
"Incorrect value type <class 'range'> to reshape a model, "
"expected values as openvino.runtime.PartialShape, str, list or tuple."
in str(e.value)
)
# request - https://docs.pytest.org/en/7.1.x/reference/reference.html#request
def test_serialize_rt_info(request, tmp_path):
version = "TestVersion"

View File

@ -140,10 +140,14 @@ public:
ov::Output<ov::Node> add_output(const std::string& op_name, size_t output_idx);
ov::Output<ov::Node> add_output(const ov::Output<ov::Node>& port);
void reshape(const ov::PartialShape& partial_shape);
void reshape(const std::map<size_t, ov::PartialShape>& partial_shapes);
void reshape(const std::map<std::string, ov::PartialShape>& partial_shapes);
void reshape(const std::map<ov::Output<ov::Node>, ov::PartialShape>& partial_shapes);
void reshape(const ov::PartialShape& partial_shape,
const std::unordered_map<std::string, ov::PartialShape>& variable_shapes = {});
void reshape(const std::map<size_t, ov::PartialShape>& partial_shapes,
const std::unordered_map<std::string, ov::PartialShape>& variable_shapes = {});
void reshape(const std::map<std::string, ov::PartialShape>& partial_shapes,
const std::unordered_map<std::string, ov::PartialShape>& variable_shapes = {});
void reshape(const std::map<ov::Output<ov::Node>, ov::PartialShape>& partial_shapes,
const std::unordered_map<std::string, ov::PartialShape>& variable_shapes = {});
/// Return the element type of output i
const ov::element::Type& get_output_element_type(size_t i) const;

View File

@ -88,6 +88,43 @@ const std::shared_ptr<ov::Node>& verify_node(const std::shared_ptr<ov::Node>& no
return node;
}
std::map<ov::Output<ov::Node>, ov::PartialShape> port_shapes_to_node_shapes(
ov::Model* model,
const std::map<size_t, ov::PartialShape>& partial_shapes) {
std::map<ov::Output<ov::Node>, ov::PartialShape> node_shapes;
for (const auto& it : partial_shapes) {
const auto port = model->input(it.first);
node_shapes[port] = it.second;
}
return node_shapes;
}
std::map<ov::Output<ov::Node>, ov::PartialShape> tensor_names_shapes_to_node_shapes(
ov::Model* model,
const std::map<std::string, ov::PartialShape>& partial_shapes) {
std::map<ov::Output<ov::Node>, ov::PartialShape> const_pshape;
std::unordered_map<ov::Node*, std::string> port_tensor_map;
for (const auto& it : partial_shapes) {
const auto port = model->input(it.first);
if (port_tensor_map.find(port.get_node()) != port_tensor_map.end()) {
OPENVINO_ASSERT(it.second == const_pshape.at(port),
"Tensor with names {'",
it.first,
"', '",
port_tensor_map[port.get_node()],
"'} has "
"conflicting shapes ",
it.second,
" and ",
const_pshape.at(port),
", but they define the same tensor");
}
port_tensor_map[port.get_node()] = it.first;
const_pshape[port] = it.second;
}
return const_pshape;
}
} // namespace
ov::Model::Model(const ResultVector& results, const ov::ParameterVector& parameters, const std::string& name)
@ -740,50 +777,25 @@ ov::Output<ov::Node> ov::Model::input(const std::string& tensor_name) {
OPENVINO_THROW("Input for tensor name '", tensor_name, "' is not found.");
}
void ov::Model::reshape(const ov::PartialShape& partial_shape) {
OPENVINO_ASSERT(m_parameters.size() == 1,
"reshape(const ov::PartialShape&) must be called on a Model with exactly one parameter.");
std::map<size_t, ov::PartialShape> shapes;
shapes[0] = partial_shape;
reshape(shapes);
void ov::Model::reshape(const ov::PartialShape& partial_shape,
const std::unordered_map<std::string, ov::PartialShape>& variable_shapes) {
OPENVINO_ASSERT(m_parameters.size() == 1, "must be called on a Model with exactly one parameter.");
std::map<size_t, ov::PartialShape> shapes{{0, partial_shape}};
reshape(shapes, variable_shapes);
}
void ov::Model::reshape(const std::map<size_t, ov::PartialShape>& partial_shapes) {
std::map<ov::Output<ov::Node>, ov::PartialShape> const_pshape;
std::unordered_map<ov::Node*, std::string> port_tensor_map;
for (const auto& it : partial_shapes) {
const auto port = input(it.first);
port_tensor_map[port.get_node()] = std::to_string(it.first);
const_pshape[port] = it.second;
}
reshape(const_pshape);
void ov::Model::reshape(const std::map<size_t, ov::PartialShape>& partial_shapes,
const std::unordered_map<std::string, ov::PartialShape>& variable_shapes) {
reshape(port_shapes_to_node_shapes(this, partial_shapes), variable_shapes);
}
void ov::Model::reshape(const std::map<std::string, ov::PartialShape>& partial_shapes) {
std::map<ov::Output<ov::Node>, ov::PartialShape> const_pshape;
std::unordered_map<ov::Node*, std::string> port_tensor_map;
for (const auto& it : partial_shapes) {
const auto port = input(it.first);
if (port_tensor_map.find(port.get_node()) != port_tensor_map.end()) {
OPENVINO_ASSERT(it.second == const_pshape.at(port),
"Tensor with names {'",
it.first,
"', '",
port_tensor_map[port.get_node()],
"'} has "
"conflicting shapes ",
it.second,
" and ",
const_pshape.at(port),
", but they define the same tensor");
}
port_tensor_map[port.get_node()] = it.first;
const_pshape[port] = it.second;
}
reshape(const_pshape);
void ov::Model::reshape(const std::map<std::string, ov::PartialShape>& partial_shapes,
const std::unordered_map<std::string, ov::PartialShape>& variable_shapes) {
reshape(tensor_names_shapes_to_node_shapes(this, partial_shapes), variable_shapes);
}
void ov::Model::reshape(const std::map<ov::Output<ov::Node>, ov::PartialShape>& partial_shapes) {
void ov::Model::reshape(const std::map<ov::Output<ov::Node>, ov::PartialShape>& partial_shapes,
const std::unordered_map<std::string, ov::PartialShape>& variables_shapes) {
if (partial_shapes.empty())
return;
@ -818,17 +830,42 @@ void ov::Model::reshape(const std::map<ov::Output<ov::Node>, ov::PartialShape>&
if (!need_reshape)
return;
std::unordered_map<op::util::Variable*, PartialShape> new_vars_shapes;
std::unordered_map<op::util::Variable*, PartialShape> original_vars_shapes;
for (const auto& variable : get_variables()) {
const auto& var_info = variable->get_info();
for (const auto& var_id_new_shape : variables_shapes) {
const auto& variable_id = var_id_new_shape.first;
const auto& new_shape = var_id_new_shape.second;
if (variable_id == var_info.variable_id && new_shape != var_info.data_shape) {
original_vars_shapes[variable.get()] = var_info.data_shape;
new_vars_shapes[variable.get()] = new_shape;
}
}
}
// save original parameters shape
std::unordered_map<ov::op::v0::Parameter*, ov::PartialShape> original_input_shapes;
for (const auto& param : params) {
original_input_shapes[param.get()] = param->get_output_partial_shape(0);
}
auto reshape_only = [&](const std::unordered_map<ov::op::v0::Parameter*, ov::PartialShape>& pshapes) {
std::unordered_map<op::util::Variable*, PartialShape> original_var_shapes;
for (const auto& v : get_variables()) {
original_var_shapes[v.get()] = v->get_info().data_shape;
}
auto reshape_only = [this](const std::unordered_map<op::v0::Parameter*, PartialShape>& pshapes,
const std::unordered_map<op::util::Variable*, PartialShape>& vars_shapes) {
for (const auto& pshape : pshapes) {
pshape.first->set_partial_shape(pshape.second);
}
for (const auto& shape : vars_shapes) {
shape.first->update_data_shape(shape.second);
}
validate_nodes_and_infer_types();
};
@ -837,10 +874,10 @@ void ov::Model::reshape(const std::map<ov::Output<ov::Node>, ov::PartialShape>&
ssr_manager.register_pass<ov::pass::SmartReshape>();
ssr_manager.run_passes(shared_from_this());
reshape_only(new_param_shapes);
reshape_only(new_param_shapes, new_vars_shapes);
} catch (...) {
// restore shapes to original ones
reshape_only(original_input_shapes);
reshape_only(original_input_shapes, original_vars_shapes);
throw;
}
}

View File

@ -7,13 +7,18 @@
#include <gtest/gtest.h>
#include <memory>
#include <shared_node_info.hpp>
#include "common_test_utils/graph_comparator.hpp"
#include "common_test_utils/test_common.hpp"
#include "openvino/core/except.hpp"
#include "openvino/core/partial_shape.hpp"
#include "openvino/opsets/opset8.hpp"
#include "shared_node_info.hpp"
using ov::op::util::Variable;
using ov::op::util::VariableInfo;
using ov::op::v0::Parameter;
using ov::op::v0::Result;
TEST(model, get_input_by_tensor_name) {
auto arg0 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1});
@ -932,7 +937,7 @@ TEST(model_reshape, ReshapeBatchReLUWithOneInput) {
EXPECT_EQ(model->get_results()[0]->get_shape(), ov::Shape({2, 3, 22, 22}));
}
TEST(model_reshape, IncoreectReshapeBatchWithMultipleInputs) {
TEST(model_reshape, IncorrectReshapeBatchWithMultipleInputs) {
auto arg0 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1, 3, 3, 3});
arg0->set_friendly_name("data");
arg0->get_output_tensor(0).set_names({"input1"});
@ -957,6 +962,161 @@ TEST(model_reshape, IncoreectReshapeBatchWithMultipleInputs) {
EXPECT_THROW(f->reshape(shape), ov::Exception);
}
TEST(model_reshape, ReshapeWithStaticVariableSingleInput) {
auto arg0 = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{1, 3, 4, 5});
auto variable = std::make_shared<Variable>(VariableInfo{arg0->get_output_partial_shape(0), ov::element::f32, "ID"});
auto read_value = std::make_shared<ov::op::v6::ReadValue>(arg0, variable);
auto assign = std::make_shared<ov::op::v6::Assign>(read_value, variable);
auto result1 = std::make_shared<Result>(assign);
auto shape_of = std::make_shared<ov::opset8::ShapeOf>(arg0);
auto result2 = std::make_shared<Result>(shape_of);
auto model = std::make_shared<ov::Model>(ov::ResultVector{result1, result2}, ov::ParameterVector{arg0});
model->validate_nodes_and_infer_types();
EXPECT_EQ(model->get_parameters()[0]->get_shape(), ov::Shape({1, 3, 4, 5}));
EXPECT_EQ(model->get_results()[0]->get_shape(), ov::Shape({1, 3, 4, 5}));
EXPECT_EQ(model->get_results()[1]->get_shape(), ov::Shape({4}));
{
ov::PartialShape shape({1, 4, 3, 3});
model->reshape(shape, {{"ID", shape}});
}
EXPECT_EQ(model->get_parameters()[0]->get_shape(), ov::Shape({1, 4, 3, 3}));
EXPECT_EQ(model->get_results()[0]->get_shape(), ov::Shape({1, 4, 3, 3}));
EXPECT_EQ(model->get_results()[1]->get_shape(), ov::Shape({4}));
}
TEST(model_reshape, ReshapeWithStaticVariablesSingleInput) {
auto arg0 = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{1, 3, 4, 5});
auto variable =
std::make_shared<Variable>(VariableInfo{arg0->get_output_partial_shape(0), ov::element::f32, "ID1"});
auto read_value = std::make_shared<ov::op::v6::ReadValue>(arg0, variable);
auto assign = std::make_shared<ov::op::v6::Assign>(read_value, variable);
auto add = std::make_shared<ov::op::v1::Add>(read_value, read_value);
auto var_add = std::make_shared<Variable>(VariableInfo{add->get_output_partial_shape(0), ov::element::f32, "ID2"});
auto read_value_add = std::make_shared<ov::op::v6::ReadValue>(add, var_add);
auto assign_add = std::make_shared<ov::op::v6::Assign>(read_value_add, var_add);
auto result1 = std::make_shared<Result>(assign);
auto shape_of = std::make_shared<ov::opset8::ShapeOf>(add);
auto result2 = std::make_shared<Result>(shape_of);
auto model = std::make_shared<ov::Model>(ov::ResultVector{result1, result2}, ov::ParameterVector{arg0});
model->validate_nodes_and_infer_types();
EXPECT_EQ(model->get_parameters()[0]->get_shape(), ov::Shape({1, 3, 4, 5}));
EXPECT_EQ(model->get_results()[0]->get_shape(), ov::Shape({1, 3, 4, 5}));
EXPECT_EQ(model->get_results()[1]->get_shape(), ov::Shape({4}));
{
ov::PartialShape shape({1, 4, 3, 3});
model->reshape(shape, {{"ID2", shape}, {"ID1", shape}});
}
EXPECT_EQ(model->get_parameters()[0]->get_shape(), ov::Shape({1, 4, 3, 3}));
EXPECT_EQ(model->get_results()[0]->get_shape(), ov::Shape({1, 4, 3, 3}));
EXPECT_EQ(model->get_results()[1]->get_shape(), ov::Shape({4}));
}
TEST(model_reshape, ReshapeWithDynamicVariableSingleInput) {
auto arg0 = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{1, 3, 4, 5});
arg0->get_output_tensor(0).set_names({"input"});
auto variable = std::make_shared<Variable>(VariableInfo{ov::PartialShape::dynamic(4), ov::element::f32, "ID"});
auto read_value = std::make_shared<ov::op::v6::ReadValue>(arg0, variable);
auto assign = std::make_shared<ov::op::v6::Assign>(read_value, variable);
auto result1 = std::make_shared<Result>(assign);
auto shape_of = std::make_shared<ov::opset8::ShapeOf>(arg0);
auto result2 = std::make_shared<Result>(shape_of);
auto model = std::make_shared<ov::Model>(ov::ResultVector{result1, result2}, ov::ParameterVector{arg0});
model->validate_nodes_and_infer_types();
EXPECT_EQ(model->get_parameters()[0]->get_shape(), ov::Shape({1, 3, 4, 5}));
EXPECT_EQ(model->get_results()[0]->get_output_partial_shape(0), ov::PartialShape::dynamic(4));
EXPECT_EQ(model->get_results()[1]->get_output_partial_shape(0), ov::Shape({4}));
model->reshape({{"input", ov::PartialShape{1, 4, 8, 9}}});
EXPECT_EQ(model->get_parameters()[0]->get_shape(), ov::Shape({1, 4, 8, 9}));
EXPECT_EQ(model->get_results()[0]->get_output_partial_shape(0), ov::PartialShape::dynamic(4));
EXPECT_EQ(model->get_results()[1]->get_shape(), ov::Shape({4}));
}
TEST(model_reshape, ReshapeStaticWithVariableMultipleInputs) {
auto arg0 = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{1, 3, 4, 5});
auto arg1 = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{1, 2, 4, 5});
auto concat = std::make_shared<ov::op::v0::Concat>(ov::NodeVector{arg0, arg1}, 1);
auto variable =
std::make_shared<Variable>(VariableInfo{concat->get_output_partial_shape(0), ov::element::f32, "ID"});
auto read_value = std::make_shared<ov::op::v6::ReadValue>(concat, variable);
auto assign = std::make_shared<ov::op::v6::Assign>(read_value, variable);
auto result1 = std::make_shared<Result>(assign);
auto shape_of = std::make_shared<ov::opset8::ShapeOf>(concat);
auto result2 = std::make_shared<Result>(shape_of);
auto model = std::make_shared<ov::Model>(ov::ResultVector{result1, result2}, ov::ParameterVector{arg0, arg1});
model->validate_nodes_and_infer_types();
EXPECT_EQ(model->get_parameters()[0]->get_shape(), ov::Shape({1, 3, 4, 5}));
EXPECT_EQ(model->get_parameters()[1]->get_shape(), ov::Shape({1, 2, 4, 5}));
EXPECT_EQ(model->get_results()[0]->get_shape(), ov::Shape({1, 5, 4, 5}));
EXPECT_EQ(model->get_results()[1]->get_shape(), ov::Shape({4}));
{
ov::PartialShape shape({1, 14, 4, 5});
model->reshape({{0, shape}, {1, shape}}, {{"ID", {1, 28, 4, 5}}});
}
EXPECT_EQ(model->get_parameters()[0]->get_shape(), ov::Shape({1, 14, 4, 5}));
EXPECT_EQ(model->get_parameters()[1]->get_shape(), ov::Shape({1, 14, 4, 5}));
EXPECT_EQ(model->get_results()[0]->get_shape(), ov::Shape({1, 28, 4, 5}));
EXPECT_EQ(model->get_results()[1]->get_shape(), ov::Shape({4}));
}
TEST(model_reshape, ReshapeWithStaticVariableIncorrectVariable) {
auto arg0 = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{1, 3, 4, 5});
auto arg1 = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{1, 2, 4, 5});
auto concat = std::make_shared<ov::op::v0::Concat>(ov::NodeVector{arg0, arg1}, 1);
auto variable =
std::make_shared<Variable>(VariableInfo{concat->get_output_partial_shape(0), ov::element::f32, "ID"});
auto read_value = std::make_shared<ov::op::v6::ReadValue>(concat, variable);
auto assign = std::make_shared<ov::op::v6::Assign>(read_value, variable);
auto result1 = std::make_shared<Result>(assign);
auto shape_of = std::make_shared<ov::opset8::ShapeOf>(concat);
auto result2 = std::make_shared<Result>(shape_of);
auto model = std::make_shared<ov::Model>(ov::ResultVector{result1, result2}, ov::ParameterVector{arg0, arg1});
model->validate_nodes_and_infer_types();
EXPECT_EQ(model->get_parameters()[0]->get_shape(), ov::Shape({1, 3, 4, 5}));
EXPECT_EQ(model->get_parameters()[1]->get_shape(), ov::Shape({1, 2, 4, 5}));
EXPECT_EQ(model->get_results()[0]->get_shape(), ov::Shape({1, 5, 4, 5}));
EXPECT_EQ(model->get_results()[1]->get_shape(), ov::Shape({4}));
ov::PartialShape shape({1, 14, 4, 5});
ov::PartialShape wrong_var_shape({1, 20, 4, 5});
EXPECT_THROW(model->reshape({{0, shape}, {1, shape}}), ov::AssertFailure);
EXPECT_THROW(model->reshape({{0, shape}, {1, shape}}, {{"WRONG_ID", {1, 28, 4, 5}}}), ov::AssertFailure);
EXPECT_THROW(model->reshape({{0, shape}, {1, shape}}, {{"ID", wrong_var_shape}}), ov::AssertFailure);
}
TEST(model, add_output_tensor_name) {
auto arg0 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1});
arg0->set_friendly_name("data");