Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9034b31
Done
Jarvis2001 Nov 2, 2025
326382d
Formatting
Jarvis2001 Nov 12, 2025
fea21a4
Formatting
Jarvis2001 Nov 12, 2025
0191c57
Merge branch 'master' into variable-shape
Jarvis2001 Nov 25, 2025
6fd0438
Merge branch 'openvinotoolkit:master' into variable-shape
Jarvis2001 Dec 9, 2025
0c9e259
Modified tests, added helper functions
Jarvis2001 Dec 11, 2025
506f599
Merge branch 'openvinotoolkit:master' into variable-shape
Jarvis2001 Dec 18, 2025
eaa8ec1
Merge branch 'openvinotoolkit:master' into variable-shape
Jarvis2001 Dec 22, 2025
9c9115f
Fixed linter errors and added one test
Jarvis2001 Dec 29, 2025
ce80c25
-
Jarvis2001 Dec 29, 2025
a98a4ac
Merge branch 'master' into variable-shape
Jarvis2001 Jan 26, 2026
041ddca
No linter issues now I hope. The CI failed due to whitespace on line …
Jarvis2001 Jan 28, 2026
3f1361f
Merge branch 'master' into variable-shape
Jarvis2001 Jan 28, 2026
f29e8b3
fixed the linter errors(line too long) in test_model.py
Jarvis2001 Jan 28, 2026
46e4cd7
fixed linter E501 in helper.py
Jarvis2001 Jan 28, 2026
1074c44
Merge branch 'master' into variable-shape
Jarvis2001 Feb 8, 2026
84e2e72
Merge branch 'master' into variable-shape
Jarvis2001 Feb 14, 2026
3f7fc15
Merge branch 'master' into variable-shape
Jarvis2001 Feb 20, 2026
2a279f6
Merge branch 'master' into variable-shape
mlukasze Feb 25, 2026
a93273a
Fix linter and CI issues
Practice2001 Feb 25, 2026
43b8877
Reapply Practice2001 changes under Jarvis2001
Jarvis2001 Feb 25, 2026
ad81015
Reapply Practice2001 changes under Jarvis2001
Jarvis2001 Feb 25, 2026
11dd04c
Revert "Fix linter and CI issues"
Jarvis2001 Feb 26, 2026
df977ff
Applied changes suggested by copilot. Reformatted test on line 815 in…
Jarvis2001 Feb 26, 2026
56a45f2
Small changes
Jarvis2001 Feb 26, 2026
649c3ba
Merge branch 'master' into variable-shape
Jarvis2001 Mar 6, 2026
7372886
Merge branch 'master' into variable-shape
Jarvis2001 Mar 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 121 additions & 26 deletions src/bindings/python/src/pyopenvino/graph/model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -533,44 +533,139 @@ void regclass_graph_Model(py::module m) {
model.def(
"reshape",
[](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& inputs = self.inputs();

if (partial_shape.size() == 0) {
throw std::runtime_error("Shapes list cannot be empty.");
}

bool is_multi_input_format = false;
bool has_list = false;
bool has_int = false;

for (size_t i = 0; i < partial_shape.size(); ++i) {
py::handle elem = partial_shape[i];
if (py::isinstance<py::list>(elem)) {
has_list = true;
} else if (py::isinstance<py::int_>(elem)) {
has_int = true;
} else if (!py::isinstance<py::tuple>(elem)) {
throw std::runtime_error("Invalid shape format");
}
}

if (has_list) {
is_multi_input_format = true;
} else {
is_multi_input_format = !has_int;
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The multi-vs-flat detection is incorrect and causes regressions for valid flat shapes that include list-based dimension intervals (e.g. [1, 3, [224, 256], [224, 256]]). Any list element currently forces is_multi_input_format = true, which would then throw on partial_shape.size() != inputs.size(). Suggest basing multi-input detection on partial_shape.size() == inputs.size() and all_of(top-level elems are list/tuple/PartialShape-like shape containers), otherwise treat it as the flat-shape form.

Suggested change
bool has_list = false;
bool has_int = false;
for (size_t i = 0; i < partial_shape.size(); ++i) {
py::handle elem = partial_shape[i];
if (py::isinstance<py::list>(elem)) {
has_list = true;
} else if (py::isinstance<py::int_>(elem)) {
has_int = true;
} else if (!py::isinstance<py::tuple>(elem)) {
throw std::runtime_error("Invalid shape format");
}
}
if (has_list) {
is_multi_input_format = true;
} else {
is_multi_input_format = !has_int;
}
bool all_containers = true;
bool has_int = false;
for (size_t i = 0; i < partial_shape.size(); ++i) {
py::handle elem = partial_shape[i];
if (py::isinstance<py::int_>(elem)) {
has_int = true;
all_containers = false;
} else if (py::isinstance<py::list>(elem) || py::isinstance<py::tuple>(elem)) {
// list/tuple are treated as shape containers at the top level
} else {
throw std::runtime_error("Invalid shape format");
}
}
// Multi-input format is used only when the number of provided shapes
// matches the number of model inputs and all top-level elements are
// shape containers (lists/tuples). Otherwise treat as a flat shape.
is_multi_input_format = (partial_shape.size() == inputs.size()) && all_containers;

Copilot uses AI. Check for mistakes.

if (!is_multi_input_format) {
// FLAT FORMAT
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, new_variables_shapes);
return;
}

// MULTI-INPUT FORMAT
std::map<std::string, ov::PartialShape> new_shapes_map;

auto parse_dimension = [](py::handle dim_obj) -> ov::Dimension {
if (py::isinstance<py::list>(dim_obj)) {
throw std::runtime_error("Unexpected nested list in dimension specification.");
}
if (py::isinstance<py::tuple>(dim_obj)) {
py::tuple t = dim_obj.cast<py::tuple>();
if (t.size() != 2) {
throw std::runtime_error(
"Two elements are expected in tuple(lower, upper) for dynamic dimension, but " +
std::to_string(t.size()) + " elements were given.");
}
if (!py::isinstance<py::int_>(t[0]) || !py::isinstance<py::int_>(t[1])) {
throw std::runtime_error("Tuple elements must be integers.");
}
int lower = t[0].cast<int>();
int upper = t[1].cast<int>();
return ov::Dimension(lower, upper);
}
if (py::isinstance<py::int_>(dim_obj)) {
return ov::Dimension(dim_obj.cast<int>());
}
throw std::runtime_error("Invalid dimension type. Must be int or (lower, upper) tuple.");
};
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The multi-input parsing path only supports int and (lower, upper) tuples, and explicitly rejects list-based intervals. This is inconsistent with the existing reshape dimension syntax described in the docstring (and typically supported elsewhere via partial_shape_from_handle(...)). To avoid behavior divergence between flat vs multi-input forms, reuse the existing parsing helpers per shape (e.g., parse each inner shape with the same logic used by get_variables_shapes), instead of introducing a reduced/parallel parser.

Copilot uses AI. Check for mistakes.

auto parse_shape = [&parse_dimension](py::sequence shape_seq) -> ov::PartialShape {
std::vector<ov::Dimension> dims;
for (size_t i = 0; i < shape_seq.size(); ++i) {
dims.push_back(parse_dimension(shape_seq[i]));
}
return ov::PartialShape(dims);
};

if (partial_shape.size() != inputs.size()) {
throw std::runtime_error("Number of shapes does not match number of model inputs.");
}

for (size_t i = 0; i < partial_shape.size(); ++i) {
py::handle shape_handle = partial_shape[i];
if (!py::isinstance<py::list>(shape_handle) && !py::isinstance<py::tuple>(shape_handle)) {
throw std::runtime_error("Each shape must be a list or tuple.");
}
py::sequence shape_seq = shape_handle.cast<py::sequence>();
new_shapes_map[inputs[i].get_any_name()] = parse_shape(shape_seq);
}

const auto new_variables_shapes = get_variables_shapes(variables_shapes);
py::gil_scoped_release release;
self.reshape(new_shape, new_variables_shapes);
self.reshape(new_shapes_map, new_variables_shapes);
},
py::arg("partial_shape"),
py::arg("variables_shapes") = py::dict(),
R"(
Reshape model input.
Reshape model input(s).

The allowed types of keys in the `variables_shapes` dictionary is `str`.
The allowed types of values in the `variables_shapes` are:
The allowed types of keys in the `variables_shapes` dictionary is `str`.
The allowed types of values in the `variables_shapes` are:

(1) `openvino.PartialShape`
(2) `list` consisting of dimensions
(3) `tuple` consisting of dimensions
(4) `str`, string representation of `openvino.PartialShape`
(1) `openvino.PartialShape`
(2) `list` consisting of dimensions
(3) `tuple` consisting of dimensions
(4) `str`, string representation of `openvino.PartialShape`

When list or tuple are used to describe dimensions, each dimension can be written in form:
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.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
(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.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

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated docstring claims [min, max], openvino.Dimension, and str forms are supported for dimensions, but the newly added multi-input parsing rejects list-based dims and any non-int/tuple dim types. Either expand the implementation to match the documented accepted forms (preferred), or update the docstring to reflect the narrower support.

Suggested change
(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.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
(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) `-1` is a dynamic dimension without known bounds

Copilot uses AI. Check for mistakes.
GIL is released while running this function.
Multi-input format:
- Pass a list of shapes: [[shape1], [shape2], ...]
- Number of shapes must match number of inputs

Single-input format:
- Pass a flat list: [dim1, dim2, ...]

:param partial_shape: New shape.
:type partial_shape: list
:param variables_shapes: New shapes for variables
:type variables_shapes: dict[keys, values]
:return : void
)");
Examples:
>>> model.reshape([[2, 2], [1, 3, 224, 244], [10]]) # Multi-input
>>> model.reshape([2, 2]) # Single-input
>>> model.reshape([(1, 8), 3, (112, 448), (112, 448)]) # With dynamic dims

GIL is released while running this function.

:param partial_shape: New shape(s).
:type partial_shape: list
:param variables_shapes: New shapes for variables
:type variables_shapes: dict[keys, values]
:return : void
)");

model.def(
"reshape",
Expand Down
Loading
Loading