ov::Model represents model with operations and their relations describing how data would flow through the function during the inference.
Data size estimations for output tensors of each operation is required for efficient model execution.
Such an estimation depends on the data type and shape of intermediate tensors of the model.
For OpenVINO supported operations you can find type and shape propagation rules in the documentation of the opset of your choice
Shape propagation is a process of output shape calculation for each operation in the graph.
It starts from all the Parameter operations in the ov::Model and continues to calculate output shapes for all the operations of the ov::Model
in the topological order.
Method ov::Op::validate_and_infer_types is responsible for operation validation, shape and type propagation.
Shapes could be represented as ov::Shape or as ov::PartialShape in OpenVINO.
ov::Shape could be used in case rank and all the shape dimensions are static.
ov::PartialShape could be used for both static and dynamic shape representations.
Depending on the amount of known information about the rank and shape dimensions we can formulate shapes in different ways. Here are some examples of shape creation:
- Shape with undefined rank:
ov::PartialShape::dynamic(); - Shape with defined rank and fully undefined dimensions:
ov::PartialShape({1, 3, Dimension::dynamic(), Dimension::dynamic()}); // rank == 4, two static dimensions and two fully dynamic dimension ov::PartialShape({Dimension::dynamic(), Dimension::dynamic()}); // rank == 2, all dimensions are fully dynamic ov::PartialShape::dynamic(5); // rank == 5, all dimensions are fully dynamic
- Shape with defined rank and undefined dimensions with specified range:
ov::PartialShape({{1, 8}, 3, 400, 400}); // rank == 4, first dimension is dynamic and can be any number from 1 to 8 inclusive, all the other dimensions are static ov::PartialShape({{2, -1}, 3, 400, 400}); // rank == 4, first dimension is dynamic and can be any number larger or equal 2, all the other dimensions are static ov::PartialShape({{-1, 8}, 3, 400, 400}); // rank == 4, first dimension is dynamic and will not be larger than 8, all the other dimensions are static
- Shape with defined rank and fully defined dimensions:
ov::PartialShape({1, 3, 400, 400}); // rank == 4 ov::Shape({1, 3, 400, 400}); // rank == 4 ov::PartialShape({5}); // rank == 1, one-dimensional tensor with five values in it ov::PartialShape({}); // rank == 0, scalar -- zero-dimensional tensor with single value in it ov::PartialShape({1}); // rank == 1, one-dimensional tensor with single value in it ov::PartialShape({1, 3, 0, 0}); // rank == 4, four-dimensional tensor with no value in it empty tensor
ov::PartialShape stores ov::Rank for the shape rank. It could be fully undefined or static.
ov::PartialShape stores shape values as a vector of ov::Dimension for the case if ov::Rank is static.
ov::Dimension is represented by an interval which is a pair of values -- maximum and minimum value for the dimension, for both static and dynamic cases.
They are equal for static dimensions and are set to different values for range and fully dynamic dimensions.
- dynamic ov::Dimension:
ov::Dimension::dynamic(); ov::Dimension(-1); // special value for Dimension ov::Dimension{0, MAX_INT};
- interval ov::Dimension:
ov::Dimension{1, 10}; ov::Dimension{0, MAX_INT}; - static ov::Dimension
ov::Dimension{3}; ov::Dimension{3, 3};
We allow creation of negative dimension on ov::Interval, ov::Dimension, ov::PartialShape level due to historical reasons,
but there is no point in it in terms of shape propagation.
NOTE: Dimension(-1) is equal to Dimension::dynamic(). There is no way to create static Dimension with value -1
The goal of shape propagation is to keep and pull all the known information about output shapes dimensions from the operation semantics. During the shape propagation we may need input ranks/shapes for the operation or input values. It is easy to lose detailed shape information. To provide practical guidance we have collected popular mistakes and the ways to avoid them:
Shape calculation DOs and DON`Ts:
- Using
ov::Shapeclass to work with shape instead ofov::PartialShapelead to losing detailed shape information - Calling
ov::PartialShape::to_shape()method which convertsov::PartialShapeobject toov::Shapeobject may result in exceptions thrown while running the code for dynamicov::Model. To avoid that introduce a check if the shape is dynamic before doing the conversion.In order to make the code work for dynamic shapes too express all the needed shape calculations using capabilities ofov::PartialShape shape = ...; if (shape.is_static())ov::PartialShape,ov::Rankandov::Dimensionclasses. - To get the rank of the shape instead of
ov::Shape::size()method do the following:ov::PartialShape shape = ...; ov::Rank = rank = shape.rank(); if (rank.is_static()) auto rank_value = rank.get_length(); ... - Mathematical operators are overloaded for the
ov::Dimensionclass and you can perform all kinds of manipulations on them. It would result in correct calculations for both static and dynamicov::Dimension. For example, multiplication of the first two dimensions of the shape is easy as:
ov::PartialShape shape = ...;
ov::Rank = rank = shape.rank();
if (rank.is_static() && rank.get_length() > 2)
ov::Dimension product_of_first_and_second_dims = shape[0] * shape[1];