[FrontEnd] add control flow, basic tensor array support for paddle faster_rcnn model (#10684)

* support control flow, tensor array

* fix clang error

* support ppdet2.3 model

* 1. code clean; 2. more comments inlined.

* fix after clone ov::Model no more in need.

* support dynamic shape; more comments

* only process same rank

* remove unused function

* simplify the loop logic

* fix review comments

* support shape{1} {}

* disable FoldSubgraphEmptyInputs because loop in loop

* fix review comments

* remove scalar{}->shape{1} testcase

* allow re-infer shape when backedge changes input shape

* fix condition shape to {1}

* support output rank is not same or dynamic

* fix refactor error

* apply review comments

* fix win warnings

* remove TransformEliminateConvert

Co-authored-by: jialipen <cecilia.peng@intel.com>
Co-authored-by: Evgenya Stepyreva <evgenya.stepyreva@intel.com>
This commit is contained in:
Luo Cheng
2022-10-27 23:36:24 +08:00
committed by GitHub
parent fbd6d14c3b
commit 4ec5499c5d
43 changed files with 2776 additions and 44 deletions

View File

@@ -13,6 +13,13 @@ namespace ov {
namespace pass {
class TRANSFORMATIONS_API FoldSubgraphEmptyInputs;
class TRANSFORMATIONS_API DisableFoldSubgraphEmptyInputs;
TRANSFORMATIONS_API void disable_fold_subgraph_empty_inputs(const std::shared_ptr<Node>& node);
TRANSFORMATIONS_API void enable_fold_subgraph_empty_inputs(const std::shared_ptr<Node>& node);
TRANSFORMATIONS_API bool fold_subgraph_empty_inputs_is_disabled(const std::shared_ptr<Node>& node);
} // namespace pass
} // namespace ov
@@ -29,3 +36,12 @@ public:
OPENVINO_RTTI("FoldSubgraphEmptyInputs", "0");
FoldSubgraphEmptyInputs();
};
class ov::pass::DisableFoldSubgraphEmptyInputs : public ov::RuntimeAttribute {
public:
OPENVINO_RTTI("DisableFoldSubgraphEmptyInputs");
DisableFoldSubgraphEmptyInputs() = default;
bool is_copyable() const override {
return false;
}
};

View File

@@ -12,10 +12,8 @@
namespace ov {
namespace pass {
class TRANSFORMATIONS_API RemoveConcatZeroDimInput;
} // namespace pass
} // namespace ov
class OPENVINO_API RemoveConcatZeroDimInput;
class OPENVINO_API DisableRemoveConcatZeroDimInput;
/**
* @ingroup ie_transformation_common_api
@@ -23,8 +21,26 @@ class TRANSFORMATIONS_API RemoveConcatZeroDimInput;
* removes input of Concat if the tensor size is equal to 0
*/
class ov::pass::RemoveConcatZeroDimInput : public ov::pass::MatcherPass {
class RemoveConcatZeroDimInput : public ov::pass::MatcherPass {
public:
OPENVINO_RTTI("RemoveConcatZeroDimInput", "0");
RemoveConcatZeroDimInput();
};
OPENVINO_API void disable_remove_concat_zerodim_input(const std::shared_ptr<Node>& node);
OPENVINO_API void enable_remove_concat_zerodim_input(const std::shared_ptr<Node>& node);
OPENVINO_API bool remove_concat_zerodim_input_is_disabled(const std::shared_ptr<Node>& node);
class DisableRemoveConcatZeroDimInput : public ov::RuntimeAttribute {
public:
OPENVINO_RTTI("DisableRemoveConcatZeroDimInput");
DisableRemoveConcatZeroDimInput() = default;
bool is_copyable() const override {
return false;
}
};
} // namespace pass
} // namespace ov

View File

@@ -22,6 +22,10 @@ ov::pass::FoldSubgraphEmptyInputs::FoldSubgraphEmptyInputs() {
if (multi_subgraph_op == nullptr) {
return false;
}
const auto& rt_info = multi_subgraph_op->get_rt_info();
if (rt_info.count(DisableFoldSubgraphEmptyInputs::get_type_info_static())) {
return false;
}
auto multi_subgraph_op_inputs = multi_subgraph_op->input_values();
std::vector<ov::Output<ov::Node>> empty_inputs;
@@ -60,3 +64,16 @@ ov::pass::FoldSubgraphEmptyInputs::FoldSubgraphEmptyInputs() {
auto m = std::make_shared<ngraph::pattern::Matcher>(multi_subgraph_op_pattern, matcher_name);
this->register_matcher(m, callback);
}
void ov::pass::disable_fold_subgraph_empty_inputs(const std::shared_ptr<ov::Node>& node) {
node->get_rt_info().emplace(DisableFoldSubgraphEmptyInputs::get_type_info_static(),
DisableFoldSubgraphEmptyInputs{});
}
void ov::pass::enable_fold_subgraph_empty_inputs(const std::shared_ptr<ov::Node>& node) {
node->get_rt_info().erase(DisableFoldSubgraphEmptyInputs::get_type_info_static());
}
bool ov::pass::fold_subgraph_empty_inputs_is_disabled(const std::shared_ptr<ov::Node>& node) {
return node->get_rt_info().count(DisableFoldSubgraphEmptyInputs::get_type_info_static());
}

View File

@@ -19,6 +19,11 @@ ov::pass::RemoveConcatZeroDimInput::RemoveConcatZeroDimInput() {
auto concat_pattern = pattern::wrap_type<opset8::Concat>();
ngraph::matcher_pass_callback callback = [=](pattern::Matcher& m) {
auto concat = m.get_match_root();
const auto& rt_info = concat->get_rt_info();
if (rt_info.count(DisableRemoveConcatZeroDimInput::get_type_info_static())) {
return false;
}
auto concat_inputs = concat->input_values();
concat_inputs.erase(
std::remove_if(
@@ -47,3 +52,16 @@ ov::pass::RemoveConcatZeroDimInput::RemoveConcatZeroDimInput() {
auto m = std::make_shared<ngraph::pattern::Matcher>(concat_pattern, matcher_name);
this->register_matcher(m, callback);
}
void ov::pass::disable_remove_concat_zerodim_input(const std::shared_ptr<Node>& node) {
node->get_rt_info().emplace(DisableRemoveConcatZeroDimInput::get_type_info_static(),
DisableRemoveConcatZeroDimInput{});
}
void ov::pass::enable_remove_concat_zerodim_input(const std::shared_ptr<Node>& node) {
node->get_rt_info().erase(DisableRemoveConcatZeroDimInput::get_type_info_static());
}
bool ov::pass::remove_concat_zerodim_input_is_disabled(const std::shared_ptr<Node>& node) {
return node->get_rt_info().count(DisableRemoveConcatZeroDimInput::get_type_info_static());
}

View File

@@ -37,7 +37,9 @@ bool ngraph::pass::UnrollIf::run_on_model(const std::shared_ptr<ngraph::Function
for (const auto& input_descr : input_descriptions) {
auto in_data = if_node->input_value(input_descr->m_input_index);
auto& param = body->get_parameters()[input_descr->m_body_parameter_index];
ngraph::replace_node(param, {in_data});
for (const auto& input : param->output(0).get_target_inputs()) {
input.replace_source_output(in_data);
}
}
for (const auto& output_desc : output_descriptions) {
std::shared_ptr<opset8::Result> result = body->get_results()[output_desc->m_body_value_index];

View File

@@ -164,9 +164,25 @@ void loop(const std::shared_ptr<Function>& func,
}
// Back-edge processing
bool need_validate = false;
for (auto& back_edge : back_edges) {
const auto& input_shape = inputs_to_body[back_edge.param_idx]->get_shape();
const auto& result_shape = body_outputs[back_edge.result_idx]->get_shape();
// when output shape does not equal to input shape in a back-edge, such as
// Parameter(out:1)->|
// |->Concat(out:2)->Result(out:2)
// Const(out:1)->|
// after iteration completed, should update (out:2) to input, then use new input
// shape to propagate others.
if (input_shape != result_shape) {
const auto& param = func->get_parameters().at(back_edge.param_idx);
param->set_partial_shape(result_shape);
need_validate = true;
}
inputs_to_body[back_edge.param_idx] = body_outputs[back_edge.result_idx];
}
if (need_validate)
func->validate_nodes_and_infer_types();
}
for (const auto& desc : out_descs) {

View File

@@ -4,6 +4,7 @@
#include "ngraph/op/loop.hpp"
#include <climits>
#include <ngraph/validation_util.hpp>
#include "itt.hpp"
@@ -152,6 +153,10 @@ void op::v5::Loop::validate_and_infer_types() {
get_input_size() == m_input_descriptions[0].size() + input_offset,
"Number of inputs must be the same as number of input descriptions");
// if output shape is not same with input in a back-edge, here will update the input shape
// Port map processing: output -> input
std::map<uint64_t, uint64_t> back_edges;
// Input
for (const auto& input_description : m_input_descriptions[0]) {
auto index = input_description->m_input_index;
@@ -177,6 +182,7 @@ void op::v5::Loop::validate_and_infer_types() {
auto input_partial_shape = input(index).get_partial_shape();
body_parameter->set_partial_shape(input_partial_shape);
back_edges[merged_input_description->m_body_value_index] = merged_input_description->m_body_parameter_index;
} else if (auto invariant_input_description =
ov::as_type_ptr<v0::TensorIterator::InvariantInputDescription>(input_description)) {
auto body_parameter = m_bodies[0]->get_parameters().at(invariant_input_description->m_body_parameter_index);
@@ -191,6 +197,72 @@ void op::v5::Loop::validate_and_infer_types() {
// Body
m_bodies[0]->validate_nodes_and_infer_types();
if (!back_edges.empty()) {
// if an exact value is available, limit the number of iterations.
size_t i, max_num_of_iterations = m_num_iterations == -1 ? INT_MAX : m_num_iterations;
bool need_reinvalidate = false;
for (i = 0; i < max_num_of_iterations; i++) {
need_reinvalidate = false;
for (const auto& output_description : m_output_descriptions[0]) {
auto body_value = m_bodies[0]->get_results().at(output_description->m_body_value_index)->input_value(0);
if (auto body_output_description =
ov::as_type_ptr<v0::TensorIterator::BodyOutputDescription>(output_description)) {
if (!back_edges.count(output_description->m_body_value_index))
continue;
const ov::PartialShape& body_value_shape = body_value.get_partial_shape();
auto input_param =
m_bodies[0]->get_parameters().at(back_edges[output_description->m_body_value_index]);
const auto& input_param_ps = input_param->get_partial_shape();
if (body_value_shape.rank().is_static()) {
// handle the case: when sub-model's output shape does not compatible input shape in a
// back-edge, such as
// Parameter(out:-1, 1)->|
// |->Concat(out:-1, 2)->Result(out:-1, 2)
// Parameter(out:-1, 1)->| (axis==1)
// when iteration number is unknown or sub-model output shape may be vary, the Result shape
// should infer as (-1, -1), then set changed one to input and propagate to others.
if (input_param_ps.rank().is_static()) {
const auto body_rank_len = body_value_shape.rank().get_length();
const auto input_rank_len = input_param_ps.rank().get_length();
ov::PartialShape new_ps;
bool shape_changed = false;
if (body_rank_len == input_rank_len) {
new_ps = body_value_shape;
for (auto j = 0; j < body_rank_len; j++) {
if (!body_value_shape[j].compatible(input_param_ps[j])) {
new_ps[j] = Dimension::dynamic();
shape_changed = true;
}
}
} else {
new_ps = ov::PartialShape::dynamic();
shape_changed = true;
}
// reset sub model input shape
if (shape_changed) {
need_reinvalidate = true;
input_param->set_partial_shape(new_ps);
}
}
} else {
if (input_param_ps.rank().is_static()) {
// output shape is dynamic, let the input known now we are dynamic shape
input_param->set_partial_shape(body_value_shape);
need_reinvalidate = true;
}
}
}
}
// only input shape changed we will re-compute output shape
if (need_reinvalidate) {
m_bodies[0]->validate_nodes_and_infer_types();
} else {
break;
}
}
}
// Output
for (const auto& output_description : m_output_descriptions[0]) {
auto index = output_description->m_output_index;
@@ -221,11 +293,11 @@ void op::v5::Loop::validate_and_infer_types() {
else if (auto body_output_description =
ov::as_type_ptr<v0::TensorIterator::BodyOutputDescription>(output_description)) {
const ov::PartialShape& ps = body_value.get_partial_shape();
if (ps.is_dynamic()) {
set_output_type(index, body_value.get_element_type(), ps);
const ov::PartialShape& body_value_shape = body_value.get_partial_shape();
if (body_value_shape.is_dynamic()) {
set_output_type(index, body_value.get_element_type(), body_value_shape);
} else {
auto shape = ps.get_shape();
auto shape = body_value_shape.get_shape();
if (zero_number_of_iter) {
shape.at(0) = 0;
}

View File

@@ -62,6 +62,40 @@ static const std::vector<std::string> models{
std::string("bmm"),
std::string("ceil"),
std::string("clip"),
// could not support dynamic rank
// std::string("conditional_block_const/conditional_block_const.pdmodel"),
// std::string("conditional_block_const_2outputs/conditional_block_const_2outputs.pdmodel"),
std::string("conditional_block_2inputs/conditional_block_2inputs.pdmodel"),
std::string("conditional_block_2inputs_2outputs/conditional_block_2inputs_2outputs.pdmodel"),
std::string("conditional_block_2inputs_dyn/conditional_block_2inputs_dyn.pdmodel"),
std::string("conditional_block_2inputs_dyn_2outputs/conditional_block_2inputs_dyn_2outputs.pdmodel"),
std::string("conditional_block_dyn_multiple_consumers/conditional_block_dyn_multiple_consumers.pdmodel"),
std::string("conditional_block_dyn_multiple_blocks/conditional_block_dyn_multiple_blocks.pdmodel"),
std::string("conditional_block_dyn_multiple_blocks2/conditional_block_dyn_multiple_blocks2.pdmodel"),
// TensorArray case
std::string("conditional_block_concat/conditional_block_concat.pdmodel"),
std::string("conditional_block_concat_dyn/conditional_block_concat_dyn.pdmodel"),
std::string("conditional_block_concat_dyn_2axes/conditional_block_concat_dyn_2axes.pdmodel"),
std::string("conditional_block_conditional_block_concat/conditional_block_conditional_block_concat.pdmodel"),
std::string(
"conditional_block_conditional_block_concat_dyn/conditional_block_conditional_block_concat_dyn.pdmodel"),
std::string("conditional_block_slice0/conditional_block_slice0.pdmodel"),
std::string("conditional_block_slice0_dyn/conditional_block_slice0_dyn.pdmodel"),
std::string("conditional_block_slice0_else/conditional_block_slice0_else.pdmodel"),
std::string("conditional_block_slice0_scaler/conditional_block_slice0_scaler.pdmodel"),
std::string("conditional_block_slice0_scaler_dyn/conditional_block_slice0_scaler_dyn.pdmodel"),
// std::string("conditional_block_slice0_axis2/conditional_block_slice0_axis2.pdmodel"), // No such case in model,
// as paddle.concat always concat along axis 0.
// std::string("conditional_block_slice0_axis2_dyn/conditional_block_slice0_axis2_dyn.pdmodel"),
// std::string("conditional_block_slice0_axis1_axis2/conditional_block_slice0_axis1_axis2.pdmodel"),
// std::string("conditional_block_slice0_axis1_axis2_dyn/conditional_block_slice0_axis1_axis2_dyn.pdmodel"),
std::string("conditional_block_concat_false/conditional_block_concat_false.pdmodel"),
std::string("conditional_block_concat_false_dyn/conditional_block_concat_false_dyn.pdmodel"),
std::string("conditional_block_slice0_2tensorarrays/conditional_block_slice0_2tensorarrays.pdmodel"),
std::string("conditional_block_slice0_2tensorarrays_dyn/conditional_block_slice0_2tensorarrays_dyn.pdmodel"),
std::string("conditional_block_slice0_2tensorarrays_extra/conditional_block_slice0_2tensorarrays_extra.pdmodel"),
std::string(
"conditional_block_slice0_2tensorarrays_extra_dyn/conditional_block_slice0_2tensorarrays_extra_dyn.pdmodel"),
std::string("conv2d_dilation_assymetric_pads_strides"),
std::string("conv2d_SAME_padding"),
std::string("conv2d_strides_assymetric_padding"),
@@ -223,6 +257,18 @@ static const std::vector<std::string> models{
std::string("logical_not"),
std::string("logical_or"),
std::string("logical_xor"),
std::string("loop/loop.pdmodel"),
std::string("loop_dyn/loop_dyn.pdmodel"),
std::string("loop_dyn_x/loop_dyn_x.pdmodel"),
std::string("loop_if/loop_if.pdmodel"),
std::string("loop_if_loop/loop_if_loop.pdmodel"),
std::string("loop_if_loop_if/loop_if_loop_if.pdmodel"),
std::string("loop_if_loop_complex/loop_if_loop_complex.pdmodel"),
// disabed due to slice could not produce full dynamic shape
// std::string("loop_if_tensor_array/loop_if_tensor_array.pdmodel"),
std::string("loop_t/loop_t.pdmodel"),
std::string("loop_tensor_array/loop_tensor_array.pdmodel"),
std::string("loop_x/loop_x.pdmodel"),
std::string("matmul_xt"),
std::string("matmul_xt_yt"),
std::string("matmul_yt"),

View File

@@ -0,0 +1,202 @@
import os
import sys
import numpy as np
import paddle
from save_model import exportModel
'''
test: simple conditiona_block pair + select_input without input to conditional_block.
'''
x = np.full(shape=[1], dtype='float32', fill_value=0.1)
y = np.full(shape=[1], dtype='float32', fill_value=0.23)
data = np.less(y,x)
@paddle.jit.to_static
def test_model(pred):
# pred: A boolean tensor whose numel should be 1.
def true_func():
return paddle.full(shape=[3, 4], dtype='float32', # TODO: FAILED with different dtype
fill_value=1)
def false_func():
return paddle.full(shape=[1, 2], dtype='float32',
fill_value=3)
return paddle.static.nn.cond(pred, true_func, false_func)
exportModel('conditional_block_const', test_model, [data], target_dir=sys.argv[1])
'''
more than one select_input with constant inputs.
'''
@paddle.jit.to_static
def test_model_2outputs(pred):
# pred: A boolean tensor whose numel should be 1.
def true_func():
return paddle.full(shape=[1, 2], dtype='float32',
fill_value=1), paddle.full(shape=[1, 3], dtype='float32', # TODO: FAILED with different dtype
fill_value=3)
def false_func():
return paddle.full(shape=[3, 4], dtype='float32',
fill_value=3), paddle.full(shape=[1, 4], dtype='float32',
fill_value=4)
return paddle.static.nn.cond(pred, true_func, false_func)
exportModel('conditional_block_const_2outputs', test_model_2outputs, [data], target_dir=sys.argv[1])
'''
more than one select_input with 2 inputs and 2 select_input nodes.
'''
@paddle.jit.to_static
def test_model_2inputs_2outputs(a, b):
return paddle.static.nn.cond(a < b, lambda: (a, a * b), lambda: (b, a * b) )
a = np.full(shape=[1], dtype='float32', fill_value=0.1)
b = np.full(shape=[1], dtype='float32', fill_value=0.23)
exportModel('conditional_block_2inputs_2outputs', test_model_2inputs_2outputs, [a, b], target_dir=sys.argv[1])
'''
simple test case with 2 inputs to conditional_block node.
'''
@paddle.jit.to_static
def test_model2(a, b):
c = a * b
return paddle.static.nn.cond(a < b, lambda: a + c, lambda: b * b)
a = np.full(shape=[1], dtype='float32', fill_value=0.1)
b = np.full(shape=[1], dtype='float32', fill_value=0.23)
exportModel('conditional_block_2inputs', test_model2, [a, b], target_dir=sys.argv[1])
'''
'''
@paddle.jit.to_static
def test_model_dyn(a, b):
c = a * b
return a + c if a < b else b * b
a = np.full(shape=[1], dtype='float32', fill_value=0.1)
b = np.full(shape=[1], dtype='float32', fill_value=0.23)
exportModel('conditional_block_2inputs_dyn', test_model_dyn, [a, b], target_dir=sys.argv[1])
'''
more than one select_input
# looks there are bugs in paddle dyngraph to static... failed to generate 2 select_inputs.
'''
@paddle.jit.to_static
def test_model_dyn_2outputs(a, b):
c = a * b
return a, c if a < b else b, c
a = np.full(shape=[1], dtype='float32', fill_value=0.1)
b = np.full(shape=[1], dtype='float32', fill_value=0.23)
exportModel('conditional_block_2inputs_dyn_2outputs', test_model_dyn_2outputs, [a, b], target_dir=sys.argv[1])
""""""""""""""""""""""""""""""""""""""""""""""""""""""
"""question: how to make test case for only one
conditional_block ?? """
""""""""""""""""""""""""""""""""""""""""""""""""""""""
@paddle.jit.to_static
def test_model_dyn_conditionalblock_only(a):
rpn_rois_list = []
if a.shape[0] >= 1:
rpn_rois_list.append(a)
return rpn_rois_list[0]
a = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
exportModel('conditional_block_dyn_conditionalblock_only', test_model_dyn_conditionalblock_only, [a], target_dir=sys.argv[1])
""""""""""""""""""""""""""""""""""""""""""""""""""""""
"""return type: tensor, tuple(tensor), list(tensor)
question: how to work with LoDTensorArray ?? """
""""""""""""""""""""""""""""""""""""""""""""""""""""""
# '''
# test: only conditiona_block node in the pattern.
# '''
# @paddle.jit.to_static
# def test_model_return_tuple(a, b):
# return paddle.static.nn.cond(a < b, lambda: (tuple((a, a * b))), lambda: (b, a * b) )
# a = np.full(shape=[1], dtype='float32', fill_value=0.1)
# b = np.full(shape=[1], dtype='float32', fill_value=0.23)
# exportModel('conditional_block_return_tuple', test_model_return_tuple, [a, b], target_dir=sys.argv[1])
# x = np.full(shape=[1], dtype='float32', fill_value=0.1)
# y = np.full(shape=[1], dtype='float32', fill_value=0.23)
# data = np.less(y,x)
# def test_model(pred):
# # pred: A boolean tensor whose numel should be 1.
# def true_func():
# return (paddle.full(shape=[3, 4], dtype='float32', # TODO: FAILED with different dtype
# fill_value=1), paddle.full(shape=[3, 4], dtype='float32', # TODO: FAILED with different dtype
# fill_value=1))
# def false_func():
# return (paddle.full(shape=[1, 2], dtype='float32',
# fill_value=3),paddle.full(shape=[1, 2], dtype='float32',
# fill_value=3))
# return paddle.static.nn.cond(pred, true_func, false_func)
# exportModel('conditional_block_return_tuple2', test_model, [data], target_dir=sys.argv[1])
""""""""""""""""""""""""""""""""""""""""""""""""""""""
"""stack blocks"""
""""""""""""""""""""""""""""""""""""""""""""""""""""""
'''
select_output with multiple consumers.
'''
@paddle.jit.to_static
def test_model_dyn_multiple_consumers(a, b):
c = a * b
cond0 = a + c if a < b else b * b
o1 = cond0 + a
o2 = cond0 + b
return o1, o2
a = np.full(shape=[1], dtype='float32', fill_value=0.1)
b = np.full(shape=[1], dtype='float32', fill_value=0.23)
exportModel('conditional_block_dyn_multiple_consumers', test_model_dyn_multiple_consumers, [a, b], target_dir=sys.argv[1])
'''
stack if-else blocks
'''
@paddle.jit.to_static
def test_model_dyn_multiple_blocks(a, b, c):
cond0 = a + c if a < b else b * b
cond1 = a - c if b < c else b * b
return cond0, cond1
a = np.full(shape=[1], dtype='float32', fill_value=0.1)
b = np.full(shape=[1], dtype='float32', fill_value=0.23)
c = np.full(shape=[1], dtype='float32', fill_value=0.9)
exportModel('conditional_block_dyn_multiple_blocks', test_model_dyn_multiple_blocks, [a, b, c], target_dir=sys.argv[1])
@paddle.jit.to_static
def test_model_dyn_multiple_blocks2(a, b, c):
cond0 = a + c if a < b else b * b
cond1 = a - c if cond0 < c else b * b
return cond0, cond1
a = np.full(shape=[1], dtype='float32', fill_value=0.1)
b = np.full(shape=[1], dtype='float32', fill_value=0.23)
c = np.full(shape=[1], dtype='float32', fill_value=0.9)
exportModel('conditional_block_dyn_multiple_blocks2', test_model_dyn_multiple_blocks2, [a, b, c], target_dir=sys.argv[1])

View File

@@ -0,0 +1,288 @@
# ref: https://www.paddlepaddle.org.cn/tutorials/projectdetail/1998893#anchor-2
import os
import sys
import numpy as np
import paddle
from save_model import exportModel, saveModel
def loop():
paddle.enable_static()
x = np.full(shape=[1], fill_value=0, dtype='int64')
def cond(i, ten):
return ten >= i
def body(i, dummy):
i = i + 1
return i, dummy
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
node_x = paddle.static.data(name='x', shape=x.shape, dtype=x.dtype)
node_i = paddle.full(shape=[1], fill_value=0, dtype='int64', name='i')
node_i = paddle.fluid.layers.nn.elementwise_add(node_i, node_x)
node_ten = paddle.full(shape=[1], fill_value=10, dtype='int64', name='ten')
out, dummy = paddle.static.nn.while_loop(cond, body, [node_i, node_ten], name='while_loop')
exe = paddle.static.Executor(place=paddle.CPUPlace())
res = exe.run(paddle.static.default_main_program(), feed={'x':x}, fetch_list=out)
saveModel('loop', exe, feedkeys=['x'], fetchlist=[out], inputs=[x], outputs=[res[0]], target_dir=sys.argv[1])
return res
def loop_x():
paddle.enable_static()
x = np.full(shape=[1], fill_value=1, dtype='int64')
def cond(i, ten):
return ten >= i
def body(i, t):
i = i + x
return i, t
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
node_x = paddle.static.data(name='x', shape=x.shape, dtype=x.dtype)
node_i = paddle.full(shape=[1], fill_value=0, dtype='int64', name='i')
node_i = paddle.fluid.layers.nn.elementwise_add(node_i, node_x)
node_ten = paddle.full(shape=[1], fill_value=10, dtype='int64', name='ten')
out, dummy = paddle.static.nn.while_loop(cond, body, [node_i, node_ten], name='while_loop')
exe = paddle.static.Executor(place=paddle.CPUPlace())
res = exe.run(paddle.static.default_main_program(), feed={'x':x}, fetch_list=out)
saveModel('loop_x', exe, feedkeys=['x'], fetchlist=[out], inputs=[x], outputs=[res[0]], target_dir=sys.argv[1])
return res
def loop_t():
paddle.enable_static()
x = np.full(shape=[1], fill_value=0, dtype='int64')
def cond(i, ten):
return ten >= i
def body(i, t):
i = i + 1
t = t - 1
return i, t
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
node_x = paddle.static.data(name='x', shape=x.shape, dtype=x.dtype)
node_i = paddle.full(shape=[1], fill_value=0, dtype='int64', name='i')
node_i = paddle.fluid.layers.nn.elementwise_add(node_i, node_x)
node_ten = paddle.full(shape=[1], fill_value=10, dtype='int64', name='ten')
out_i,out_t = paddle.static.nn.while_loop(cond, body, [node_i, node_ten], name='while_loop')
exe = paddle.static.Executor(place=paddle.CPUPlace())
res = exe.run(paddle.static.default_main_program(), feed={'x':x}, fetch_list=[out_i,out_t])
saveModel('loop_t', exe, feedkeys=['x'], fetchlist=[out_i,out_t], inputs=[x], outputs=res, target_dir=sys.argv[1])
return res
def loop_dyn():
paddle.disable_static()
@paddle.jit.to_static
def test_model(x):
i = paddle.full(shape=[1], fill_value=0, dtype='int64')
i = i + x
t = paddle.full(shape=[1], fill_value=10, dtype='int64')
j = i + 1
while t >= i:
i = i + 1
return i, j
x = np.full(shape=[1], fill_value=0, dtype='int64')
return exportModel('loop_dyn', test_model, [x], target_dir=sys.argv[1])
def loop_dyn_x():
paddle.disable_static()
@paddle.jit.to_static
def test_model(x):
i = paddle.full(shape=[1], fill_value=0, dtype='int64')
i = i + x
t = paddle.full(shape=[1], fill_value=10, dtype='int64')
while t >= i:
i = i + x
return i
x = np.full(shape=[1], fill_value=1, dtype='int64')
return exportModel('loop_dyn_x', test_model, [x], target_dir=sys.argv[1])
def loop_if():
paddle.disable_static()
@paddle.jit.to_static
def test_model(x):
i = paddle.full(shape=[1], fill_value=0, dtype='int64')
i = i + x
t = paddle.full(shape=[1], fill_value=10, dtype='int64')
while t >= i:
if i < 5:
i = i + x
else:
i = i + 2 * x
return i
x = np.full(shape=[1], fill_value=1, dtype='int64')
return exportModel('loop_if', test_model, [x], target_dir=sys.argv[1])
def loop_if_loop():
paddle.disable_static()
@paddle.jit.to_static
def test_model(x):
i = paddle.full(shape=[1], fill_value=0, dtype='int64')
i = i + x
t = paddle.full(shape=[1], fill_value=10, dtype='int64')
if x < 5:
while t >= i:
i = i + x * 2
else:
while t >= i:
i = i + x
return i
x = np.full(shape=[1], fill_value=1, dtype='int64')
return exportModel('loop_if_loop', test_model, [x], target_dir=sys.argv[1])
def loop_if_loop_if():
paddle.disable_static()
@paddle.jit.to_static
def test_model(x):
i = paddle.full(shape=[1], fill_value=0, dtype='int64')
i = i + x
t = paddle.full(shape=[1], fill_value=10, dtype='int64')
if x < 5:
while t >= i:
if x == 0:
i = i + x * 2
else:
i = i + x * 3
else:
while t >= i:
i = i + x
return i
x = np.full(shape=[1], fill_value=1, dtype='int64')
return exportModel('loop_if_loop_if', test_model, [x], target_dir=sys.argv[1])
def loop_if_loop_complex():
paddle.disable_static()
@paddle.jit.to_static
def test_model(x, y):
i = paddle.full(shape=[1], fill_value=0, dtype='int64')
j = paddle.full(shape=[1], fill_value=0, dtype='int64')
i = i + x
j = j + y
t = paddle.full(shape=[1], fill_value=10, dtype='int64')
if x < 5:
if y < 44:
while t >= i:
while t >= i:
if x == 0:
i = i + x * 2
j = j + y * 2
else:
i = i + x * 3
j = j + y * 3
else:
while t >= i:
i = i + x
return i, j
x = np.full(shape=[1], fill_value=1, dtype='int64')
y = np.full(shape=[1], fill_value=1, dtype='int64')
return exportModel('loop_if_loop_complex', test_model, [x, y], target_dir=sys.argv[1])
def loop_tensor_array():
paddle.disable_static()
@paddle.jit.to_static
def test_model(x):
i = paddle.full(shape=[1], fill_value=0, dtype='int32')
t = paddle.full(shape=[1], fill_value=10, dtype='int32')
y = paddle.full(shape=[30,3], fill_value=2, dtype='float32')
result = []
while t >= i:
i = i + 1
result.append(x[0:2,:])
return paddle.concat(result)
x = np.full(shape=[30,3], fill_value=1, dtype='float32')
return exportModel('loop_tensor_array', test_model, [x], target_dir=sys.argv[1])
def loop_if_tensor_array():
paddle.disable_static()
@paddle.jit.to_static
def test_model(x):
i = paddle.full(shape=[1], fill_value=0, dtype='int32')
t = paddle.full(shape=[1], fill_value=10, dtype='int32')
y = paddle.full(shape=[30,3], fill_value=2, dtype='float32')
result1 = []
result2 = []
while t >= i:
i = i + 1
if i >= 0:
v = i + 1
result1.append(x[0:i,:])
result2.append(x[0:v,:] + 2)
else:
result1.append(x[0:i,:])
#result2.append(x[0:i,:])
return paddle.concat(result1), paddle.concat(result2)
x = np.full(shape=[30,3], fill_value=1, dtype='float32')
return exportModel('loop_if_tensor_array', test_model, [x], target_dir=sys.argv[1])
if __name__ == "__main__":
print(loop())
print(loop_dyn())
print(loop_t())
print(loop_x())
print(loop_dyn_x().numpy())
print(loop_if().numpy())
print(loop_if_loop().numpy())
print(loop_if_loop_if().numpy())
print(loop_if_loop_complex())
print(loop_tensor_array().numpy())
x, y = loop_if_tensor_array()

View File

@@ -0,0 +1,357 @@
import os
import sys
import numpy as np
import paddle
from save_model import exportModel
""""""""""""""""""""""""""""""""""""""""""""""""""""""
# tensorarray case: conditional_block + slice[0]
""""""""""""""""""""""""""""""""""""""""""""""""""""""
def test_conditional_block_slice0():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a):
rpn_rois_list = []
if a.shape[0] >= 1:
rpn_rois_list.append(a)
return rpn_rois_list[0]
exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
test('conditional_block_slice0', [a])
a_shape = a.shape
a_shape[0] = -1
test('conditional_block_slice0_dyn', [a], [a_shape])
def test_conditional_block_slice0_else():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a):
rpn_rois_list = []
if a.shape[0] >= 1:
rpn_rois_list.append(a)
else:
rpn_rois_list.append(a)
return rpn_rois_list[0]
exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
test('conditional_block_slice0_else', [a])
# wrong test case. paddle throw error " IndexError: list index out of range" in paddle.jit.save()
def test_conditional_block_slice0_empty():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a):
rpn_rois_list = []
return rpn_rois_list[0]
exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
test('conditional_block_slice0_empty', [a])
# wrong test case. paddle throw error " IndexError: list index out of range" in paddle.jit.save()
def test_conditional_block_concat_empty():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a):
rpn_rois_list = []
return paddle.concat(rpn_rois_list)
exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
test('conditional_block_concat_empty', [a])
# could generate paddle model, but paddle throw exception during inferencing.
def test_conditional_block_slice0_empty_false():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a):
rpn_rois_list = []
if a.shape[0] >= 1000: # false condition
rpn_rois_list.append(a)
return rpn_rois_list[0]
exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
test('conditional_block_slice0_empty_false', [a])
def test_conditional_block_slice0_scaler():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a):
rpn_rois_list = []
if a.shape[0] >= 1:
rpn_rois_list.append(a)
return rpn_rois_list[0]
return exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor(10.0)
print(test('conditional_block_slice0_scaler', [a]))
a_shape = a.shape
a_shape[0] = -1
print(test('conditional_block_slice0_scaler_dyn', [a], [a_shape]))
# No such case in faster/mask rcnn... as paddle.concat always concat along axis 0.
def test_conditional_block_slice0_axis2():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a):
rpn_rois_list = []
if a.shape[0] >= 1:
rpn_rois_list.append(a)
return rpn_rois_list[0]
return exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.arange(2*3*4*5)
a = paddle.reshape(a, (2, 3, 4, 5)).astype('float32')
print(test('conditional_block_slice0_axis2', [a]))
a_shape = a.shape
a_shape[2] = -1 # instead of 0
print(test('conditional_block_slice0_axis2_dyn', [a], [a_shape]))
# No such case in faster/mask rcnn... as paddle.concat always concat along axis 0.
def test_conditional_block_slice0_aixs1_axis2():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a):
rpn_rois_list = []
if a.shape[0] >= 1:
rpn_rois_list.append(a)
return rpn_rois_list[0]
return exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.arange(2,2,3)
a = paddle.reshape(a, (2,2,3)).astype('float32')
print(test('conditional_block_slice0_axis1_axis2', [a]))
a_shape = a.shape
a_shape[1] = -1 # instead of 0
a_shape[2] = -1 # instead of 0
print(test('conditional_block_slice0_axis1_axis2_dyn', [a], [a_shape]))
def test_conditional_block_slice1():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a, b):
rpn_rois_list = []
if a.shape[0] >= 1:
rpn_rois_list.append(a)
rpn_rois_list.append(b)
return rpn_rois_list[1]
exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
b = paddle.to_tensor( [[7.0, 8.0, 9.0]])
test('conditional_block_slice1', [a, b])
a_shape = a.shape
a_shape[0] = -1
test('conditional_block_slice1_dyn', [a, b], [a_shape, b.shape])
def test_conditional_block_slice0_slice1():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a, b):
rpn_rois_list = []
if a.shape[0] >= 1:
rpn_rois_list.append(a)
rpn_rois_list.append(b)
return rpn_rois_list[0], rpn_rois_list[1]
exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
b = paddle.to_tensor( [[7.0, 8.0, 9.0]])
test('conditional_block_slice0_slice1', [a, b])
a_shape = a.shape
a_shape[0] = -1
test('conditional_block_slice0_slice1_dyn', [a, b], [a_shape, b.shape])
def test_conditional_block_slice0_2tensorarrays():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a, b):
rpn_rois_list = []
rpn_rois_list1 = []
if a.shape[0] >= 1:
rpn_rois_list.append(a)
rpn_rois_list1.append(b)
return rpn_rois_list[0], rpn_rois_list1[0]
exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
b = paddle.to_tensor( [[7.0, 8.0, 9.0]])
test('conditional_block_slice0_2tensorarrays', [a, b])
a_shape = a.shape
a_shape[0] = -1
b_shape = b.shape
b_shape[0] = -1
test('conditional_block_slice0_2tensorarrays_dyn', [a, b], [a_shape, b_shape])
def test_conditional_block_slice0_2tensorarrays_extra():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a, b):
rpn_rois_list = []
rpn_rois_list1 = []
if a.shape[0] >= 1:
c = a + b
rpn_rois_list.append(a)
rpn_rois_list.append(c)
rpn_rois_list1.append(b)
return paddle.concat(rpn_rois_list), rpn_rois_list1[0]
exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
b = paddle.to_tensor( [[7.0, 8.0, 9.0]])
test('conditional_block_slice0_2tensorarrays_extra', [a, b])
a_shape = a.shape
a_shape[0] = -1
b_shape = b.shape
b_shape[0] = -1
test('conditional_block_slice0_2tensorarrays_extra_dyn', [a, b], [a_shape, b_shape])
def test_conditional_block_concat():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a, b):
rpn_rois_list = []
if a.shape[0] >= 1:
rpn_rois_list.append(a)
rpn_rois_list.append(b)
return paddle.concat(rpn_rois_list)
exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor([[[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]]])
b = paddle.to_tensor([[[7.0, 8.0, 9.0],
[10.0, 11.0, 12.0]]])
test('conditional_block_concat', [a, b])
a_shape = a.shape
a_shape[1] = -1
test('conditional_block_concat_dyn', [a, b], [a_shape, b.shape])
# the case of mask_rcnn_r50_1x_coco block13.
a_shape = a.shape
a_shape[1] = -1
a_shape[2] = -1
test('conditional_block_concat_dyn_2axes', [a, b], [a_shape, b.shape])
# the condition is false
def test_conditional_block_concat_false():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a, b):
rpn_rois_list = []
if a.shape[0] >= 1:
rpn_rois_list.append(a)
if a.shape[0] >= 100: # False condition
rpn_rois_list.append(b)
return paddle.concat(rpn_rois_list)
exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
b = paddle.to_tensor([[7.0, 8.0, 9.0],
[10.0, 11.0, 12.0]])
test('conditional_block_concat_false', [a, b])
a_shape = a.shape
a_shape[0] = -1
test('conditional_block_concat_false_dyn', [a, b], [a_shape, b.shape])
"""
conditional_block connects to another conditional_block.
This is the case of faster/mask rcnn with fpn.
"""
def test_conditional_block_conditional_block_concat():
def test(model_name, inputs:list, input_shapes=[]):
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
@paddle.jit.to_static
def test_model(a):
rpn_rois_list = []
for i in range(5):
if a.shape[0] >= 1:
rpn_rois_list.append(a)
return paddle.concat(rpn_rois_list)
exportModel(model_name, test_model, inputs, target_dir=sys.argv[1], dyn_shapes=input_shapes)
a = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
test('conditional_block_conditional_block_concat', [a])
a_shape = a.shape
a_shape[0] = -1
test('conditional_block_conditional_block_concat_dyn', [a], [a_shape])
if __name__ == "__main__":
test_conditional_block_slice0()
#test_conditional_block_slice1()
#test_conditional_block_slice0_slice1()
test_conditional_block_slice0_scaler()
test_conditional_block_concat()
test_conditional_block_concat_false()
test_conditional_block_conditional_block_concat()
test_conditional_block_slice0_axis2()
#test_conditional_block_slice0_aixs1_axis2()
test_conditional_block_slice0_2tensorarrays()
test_conditional_block_slice0_2tensorarrays_extra()
test_conditional_block_slice0_else()
# test_conditional_block_slice0_empty()
# test_conditional_block_concat_empty()
# test_conditional_block_slice0_empty_false()

View File

@@ -346,12 +346,12 @@ TEST(type_prop, loop_operation_for_and_condition_mode_dynamic_iter_dynamic_shape
// trip_count = 10
// execution_condition = true
// body_condition is not a Constant
// inputs have partially known shape
// inputs have dyanamic shape
// concat output has dynamic dimension on axis position, another outputs are static
TEST(type_prop, loop_operation_for_and_condition_mode_dynamic_iter_partially_dynamic_shapes) {
// That which we iterate over
auto X = make_shared<opset5::Parameter>(element::f32, PartialShape{1, 2, 3, Dimension::dynamic()});
auto Y = make_shared<opset5::Parameter>(element::f32, PartialShape{1, 2, 3, Dimension::dynamic()});
auto X = make_shared<opset5::Parameter>(element::f32, PartialShape{Dimension::dynamic()});
auto Y = make_shared<opset5::Parameter>(element::f32, PartialShape{Dimension::dynamic()});
auto M = make_shared<opset5::Parameter>(element::f32, Shape{1});
// Set up the cell body, a function from (Xi, Yi) -> (Zo)
@@ -397,8 +397,8 @@ TEST(type_prop, loop_operation_for_and_condition_mode_dynamic_iter_partially_dyn
// Output 0 is last Zo
auto out0 = loop->get_iter_value(body_condition, -1);
auto out1 = loop->get_iter_value(Zo, -1);
// axis=1 so sliced output on this dimension will be dynamic
auto out2 = loop->get_concatenated_slices(Zo, 0, 1, 1, -1, 1);
// axis=0 so sliced output on this dimension will be dynamic
auto out2 = loop->get_concatenated_slices(Zo, 0, 1, 1, -1, 0);
// check output descriptors
for (auto& desc : loop->get_output_descriptions()) {
@@ -415,8 +415,8 @@ TEST(type_prop, loop_operation_for_and_condition_mode_dynamic_iter_partially_dyn
auto result1 = make_shared<opset5::Result>(out1);
auto result2 = make_shared<opset5::Result>(out2);
Shape out0_shape{1};
PartialShape out1_shape{1, 2, 3, Dimension::dynamic()};
PartialShape out2_shape{1, Dimension::dynamic(), 3, Dimension::dynamic()};
PartialShape out1_shape{Dimension::dynamic()};
PartialShape out2_shape{Dimension::dynamic()};
auto results = ResultVector{result0, result1, result2};
auto f = make_shared<Function>(results, ParameterVector{X, Y, M});
@@ -1016,3 +1016,405 @@ TEST(type_prop, loop_operation_dynamic_iter_dynamic_shapes_sliced_inputs_concate
EXPECT_EQ(loop->get_output_partial_shape(1), out1_shape);
EXPECT_EQ(loop->get_output_partial_shape(2), out2_shape);
}
// dynamic output
// trip_count = dynamic
// execution_condition = true
// body_condition = true
// input is static shape, sub-model output shapes has one dynamic dimension and this output is a backedge to a
// parameter, other shapes are static
TEST(type_prop, loop_operation_dynamic_iter_static_shapes_inputs_dynamic_shape_outputs) {
// That which we iterate over
auto X = make_shared<opset5::Parameter>(element::f32, PartialShape{1, 1, 10});
auto T = make_shared<opset5::Parameter>(element::i64, Shape{});
// Set up the cell body, a function from (Xi) -> Concat(Xi, C) -> (Zo)
// Body parameters
auto Xi = make_shared<opset5::Parameter>(element::f32, PartialShape{1, Dimension::dynamic(), 10});
auto body_condition = make_shared<opset5::Constant>(element::boolean, Shape{}, true);
auto trip_count = std::make_shared<ngraph::opset5::Constant>(ngraph::element::i64, ngraph::Shape{1}, 10);
auto exec_condition = make_shared<opset5::Constant>(element::boolean, Shape{}, true);
// Body
auto C = opset5::Constant::create(element::f32, {1, 1, 10}, {0});
auto Zo = make_shared<opset5::Concat>(NodeVector{Xi, C}, 1);
auto Z = make_shared<opset5::Result>(Zo);
auto body = make_shared<Function>(OutputVector{Z, body_condition}, ParameterVector{Xi});
auto loop = make_shared<opset5::Loop>(T, exec_condition);
loop->set_function(body);
loop->set_special_body_ports(opset5::Loop::SpecialBodyPorts{-1, 1});
loop->set_merged_input(Xi, X, Z);
// check input descriptors
for (auto& desc : loop->get_input_descriptions()) {
auto type_info = desc->get_type_info();
if (std::strcmp(type_info.name, "InvariantInputDescription") == 0) {
auto input_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::InvariantInputDescription>(desc);
EXPECT_NE(input_desc, nullptr);
} else if (std::strcmp(type_info.name, "SliceInputDescription") == 0) {
auto input_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::SliceInputDescription>(desc);
EXPECT_NE(input_desc, nullptr);
} else if (std::strcmp(type_info.name, "MergedInputDescription") == 0) {
auto input_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::MergedInputDescription>(desc);
EXPECT_NE(input_desc, nullptr);
}
}
// Output 1 is last Z
auto out0 = loop->get_iter_value(body_condition, -1);
auto out1 = loop->get_iter_value(Z, -1);
// check output descriptors
for (auto& desc : loop->get_output_descriptions()) {
auto type_info = desc->get_type_info();
if (std::strcmp(type_info.name, "ConcatOutputDescription") == 0) {
auto output_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::ConcatOutputDescription>(desc);
EXPECT_NE(output_desc, nullptr);
} else if (std::strcmp(type_info.name, "BodyOutputDescription") == 0) {
auto output_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::BodyOutputDescription>(desc);
EXPECT_NE(output_desc, nullptr);
}
}
auto result0 = make_shared<opset5::Result>(out0);
auto result1 = make_shared<opset5::Result>(out1);
Shape out0_shape{};
PartialShape out1_shape{1, Dimension::dynamic(), 10};
auto results = ResultVector{result0, result1};
auto f = make_shared<Function>(results, ParameterVector{X, T});
EXPECT_EQ(f->get_output_size(), 2);
EXPECT_EQ(result0->get_output_shape(0), out0_shape);
// should be dynamic
EXPECT_TRUE(result1->get_output_partial_shape(0).compatible(out1_shape));
const auto inp0_shape = PartialShape{1, Dimension::dynamic(), 10};
const auto inp1_shape = Shape{};
EXPECT_EQ(body->get_parameters().size(), 1);
// backedge, should be also dynamic
EXPECT_EQ(body->get_parameters().at(0)->get_partial_shape(), inp0_shape);
EXPECT_EQ(loop->get_output_size(), 2);
EXPECT_EQ(loop->get_output_shape(0), out0_shape);
// map from the submodel, should be dynamic
EXPECT_TRUE(loop->get_output_partial_shape(1).compatible(out1_shape));
}
// dynamic output
// trip_count = dynamic
// execution_condition = true
// body_condition = true
// input is dynamic shape, sub-model output shapes has one dynamic dimension and this output is a backedge to a
// parameter, one dynamic shape and one static shape
TEST(type_prop, loop_operation_dynamic_iter_dynamic_shapes_inputs_dynamic_shape_outputs) {
// That which we iterate over
auto X = make_shared<opset5::Parameter>(element::f32, PartialShape{-1, 1, 10});
auto T = make_shared<opset5::Parameter>(element::i64, Shape{});
// Set up the cell body, a function from (Xi) -> Concat(Xi, Xi, 1) -> (Zo)
// Body parameters
auto Xi = make_shared<opset5::Parameter>(element::f32, PartialShape{-1, -1, 10});
auto body_condition = make_shared<opset5::Constant>(element::boolean, Shape{}, true);
auto trip_count = std::make_shared<ngraph::opset5::Constant>(ngraph::element::i64, ngraph::Shape{1}, 10);
auto exec_condition = make_shared<opset5::Constant>(element::boolean, Shape{}, true);
// Body
auto Zo = make_shared<opset5::Concat>(NodeVector{Xi, Xi}, 1);
auto Z = make_shared<opset5::Result>(Zo);
auto body = make_shared<Function>(OutputVector{Z, body_condition}, ParameterVector{Xi});
auto loop = make_shared<opset5::Loop>(T, exec_condition);
loop->set_function(body);
loop->set_special_body_ports(opset5::Loop::SpecialBodyPorts{-1, 1});
loop->set_merged_input(Xi, X, Z);
// check input descriptors
for (auto& desc : loop->get_input_descriptions()) {
auto type_info = desc->get_type_info();
if (std::strcmp(type_info.name, "InvariantInputDescription") == 0) {
auto input_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::InvariantInputDescription>(desc);
EXPECT_NE(input_desc, nullptr);
} else if (std::strcmp(type_info.name, "SliceInputDescription") == 0) {
auto input_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::SliceInputDescription>(desc);
EXPECT_NE(input_desc, nullptr);
} else if (std::strcmp(type_info.name, "MergedInputDescription") == 0) {
auto input_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::MergedInputDescription>(desc);
EXPECT_NE(input_desc, nullptr);
}
}
// Output 1 is last Z
auto out0 = loop->get_iter_value(body_condition, -1);
auto out1 = loop->get_iter_value(Z, -1);
// check output descriptors
for (auto& desc : loop->get_output_descriptions()) {
auto type_info = desc->get_type_info();
if (std::strcmp(type_info.name, "ConcatOutputDescription") == 0) {
auto output_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::ConcatOutputDescription>(desc);
EXPECT_NE(output_desc, nullptr);
} else if (std::strcmp(type_info.name, "BodyOutputDescription") == 0) {
auto output_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::BodyOutputDescription>(desc);
EXPECT_NE(output_desc, nullptr);
}
}
auto result0 = make_shared<opset5::Result>(out0);
auto result1 = make_shared<opset5::Result>(out1);
Shape out0_shape{};
PartialShape out1_shape{-1, -1, 10};
auto results = ResultVector{result0, result1};
auto f = make_shared<Function>(results, ParameterVector{X, T});
EXPECT_EQ(f->get_output_size(), 2);
EXPECT_EQ(result0->get_output_shape(0), out0_shape);
// should be dynamic
EXPECT_EQ(result1->get_output_partial_shape(0), out1_shape);
const auto inp0_shape = PartialShape{-1, -1, 10};
const auto inp1_shape = Shape{};
EXPECT_EQ(body->get_parameters().size(), 1);
// backedge, should be also dynamic
EXPECT_EQ(body->get_parameters().at(0)->get_partial_shape(), inp0_shape);
EXPECT_EQ(loop->get_output_size(), 2);
EXPECT_EQ(loop->get_output_shape(0), out0_shape);
// map from the submodel, should be dynamic
EXPECT_EQ(loop->get_output_partial_shape(1), out1_shape);
}
// dynamic output
// trip_count = dynamic
// execution_condition = true
// body_condition = true
// 2 inputs is dynamic shape, sub-model's 2 output shapes has one dynamic dimension and this output is a backedge to a
// parameter, other shapes are static
// main model:
// Parameter(-1,1,10) Parameter(-1,1,10) Const/Condition()...
// | | |
// |_________Loop________|________________|
// |
// ________________________________
// | | | |
// r0() r1(-1,-1,10) r2(-1,-1,10) r3(-1,-1,10)
//
// sub model:
// Parameter1 (-1,-1,10) Parameter2 (-1,-1,10) Const/Condition
// | | | | |
// |_Concat(-1,-1,10)_| |_Concat(-1,-1,10)_| |
// | | | |
// Result(-1,-1,10) Result(-1,-1,10) Result(-1,-1,10) Result()
// | |
// backedge to Parameter1 backedge to Parameter2
TEST(type_prop, loop_operation_dynamic_iter_dynamic_shapes2_inputs_dynamic_shape_outputs3) {
// That which we iterate over
auto X0 = make_shared<opset5::Parameter>(element::f32, PartialShape{-1, 1, 10});
auto X1 = make_shared<opset5::Parameter>(element::f32, PartialShape{-1, 1, 10});
auto T = make_shared<opset5::Parameter>(element::i64, Shape{});
// Set up the cell body, a function from (Xi0) -> Concat(Xi0, Xi0, 1) -> (Zo0)
// (Xi1) -> Concat(Xi1, Xi1, 1) -> (Zo1)
// Body parameters
auto Xi0 = make_shared<opset5::Parameter>(element::f32, PartialShape{-1, 1, 10});
auto Xi1 = make_shared<opset5::Parameter>(element::f32, PartialShape{-1, 1, 10});
auto body_condition = make_shared<opset5::Constant>(element::boolean, Shape{}, true);
auto trip_count = std::make_shared<ngraph::opset5::Constant>(ngraph::element::i64, ngraph::Shape{1}, 10);
auto exec_condition = make_shared<opset5::Constant>(element::boolean, Shape{}, true);
// Body
auto Zo0 = make_shared<opset5::Concat>(NodeVector{Xi0, Xi0}, 1);
auto Zo1 = make_shared<opset5::Concat>(NodeVector{Xi1, Xi1}, 1);
auto Y = make_shared<opset5::Result>(Zo0);
auto Z0 = make_shared<opset5::Result>(Zo0);
auto Z1 = make_shared<opset5::Result>(Zo1);
auto body = make_shared<Function>(OutputVector{Y, Z0, Z1, body_condition}, ParameterVector{Xi0, Xi1});
auto loop = make_shared<opset5::Loop>(T, exec_condition);
loop->set_function(body);
loop->set_special_body_ports(opset5::Loop::SpecialBodyPorts{-1, 3});
loop->set_merged_input(Xi0, X0, Z0);
loop->set_merged_input(Xi1, X1, Z1);
// check input descriptors
for (auto& desc : loop->get_input_descriptions()) {
auto type_info = desc->get_type_info();
if (std::strcmp(type_info.name, "InvariantInputDescription") == 0) {
auto input_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::InvariantInputDescription>(desc);
EXPECT_NE(input_desc, nullptr);
} else if (std::strcmp(type_info.name, "SliceInputDescription") == 0) {
auto input_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::SliceInputDescription>(desc);
EXPECT_NE(input_desc, nullptr);
} else if (std::strcmp(type_info.name, "MergedInputDescription") == 0) {
auto input_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::MergedInputDescription>(desc);
EXPECT_NE(input_desc, nullptr);
}
}
// Output 1 is last Z
auto out0 = loop->get_iter_value(body_condition, -1);
auto out1 = loop->get_iter_value(Y, -1);
auto out2 = loop->get_iter_value(Z0, -1);
auto out3 = loop->get_iter_value(Z1, -1);
// check output descriptors
for (auto& desc : loop->get_output_descriptions()) {
auto type_info = desc->get_type_info();
if (std::strcmp(type_info.name, "ConcatOutputDescription") == 0) {
auto output_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::ConcatOutputDescription>(desc);
EXPECT_NE(output_desc, nullptr);
} else if (std::strcmp(type_info.name, "BodyOutputDescription") == 0) {
auto output_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::BodyOutputDescription>(desc);
EXPECT_NE(output_desc, nullptr);
}
}
auto result0 = make_shared<opset5::Result>(out0);
auto result1 = make_shared<opset5::Result>(out1);
auto result2 = make_shared<opset5::Result>(out2);
auto result3 = make_shared<opset5::Result>(out3);
Shape out0_shape{};
PartialShape out1_shape{-1, -1, 10};
auto results = ResultVector{result0, result1, result2, result3};
auto f = make_shared<Function>(results, ParameterVector{X0, X1, T});
EXPECT_EQ(f->get_output_size(), 4);
EXPECT_EQ(result0->get_output_shape(0), out0_shape);
// should be dynamic
EXPECT_EQ(result1->get_output_partial_shape(0), out1_shape);
EXPECT_EQ(result2->get_output_partial_shape(0), out1_shape);
EXPECT_EQ(result3->get_output_partial_shape(0), out1_shape);
const auto inp0_shape = PartialShape{-1, -1, 10};
const auto inp1_shape = Shape{};
EXPECT_EQ(body->get_parameters().size(), 2);
// backedge, should be also dynamic
EXPECT_EQ(body->get_parameters().at(0)->get_partial_shape(), inp0_shape);
EXPECT_EQ(body->get_parameters().at(1)->get_partial_shape(), inp0_shape);
EXPECT_EQ(loop->get_output_size(), 4);
EXPECT_EQ(loop->get_output_shape(0), out0_shape);
// map from the submodel, should be dynamic
EXPECT_EQ(loop->get_output_partial_shape(1), out1_shape);
EXPECT_EQ(loop->get_output_partial_shape(2), out1_shape);
EXPECT_EQ(loop->get_output_partial_shape(3), out1_shape);
}
// dynamic output
// trip_count = dynamic
// execution_condition = true
// body_condition = true
// input is 1D shape, sub-model output shapes has one dynamic dimension and this output is a backedge to a
// parameter, one dynamic shape and one static shape
TEST(type_prop, loop_operation_dynamic_iter_1d_shapes_inputs_dynamic_shape_outputs) {
// That which we iterate over
auto X = make_shared<opset5::Parameter>(element::f32, PartialShape{1});
auto T = make_shared<opset5::Parameter>(element::i64, Shape{});
// Set up the cell body, a function from (Xi) -> Concat(Xi, Xi, 1) -> (Zo)
// Body parameters
auto Xi = make_shared<opset5::Parameter>(element::f32, PartialShape{1});
auto body_condition = make_shared<opset5::Constant>(element::boolean, Shape{}, true);
auto trip_count = std::make_shared<ngraph::opset5::Constant>(ngraph::element::i64, ngraph::Shape{1}, 10);
auto exec_condition = make_shared<opset5::Constant>(element::boolean, Shape{}, true);
// Body
auto X0 = make_shared<opset5::Reshape>(Xi, opset5::Constant::create(ov::element::i32, {1}, {-1}), false);
auto Zo = make_shared<opset5::Concat>(NodeVector{X0, X0}, 0);
auto Z = make_shared<opset5::Result>(Zo);
auto body = make_shared<Function>(OutputVector{Z, body_condition}, ParameterVector{Xi});
auto loop = make_shared<opset5::Loop>(T, exec_condition);
loop->set_function(body);
loop->set_special_body_ports(opset5::Loop::SpecialBodyPorts{-1, 1});
loop->set_merged_input(Xi, X, Z);
// check input descriptors
for (auto& desc : loop->get_input_descriptions()) {
auto type_info = desc->get_type_info();
if (std::strcmp(type_info.name, "InvariantInputDescription") == 0) {
auto input_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::InvariantInputDescription>(desc);
EXPECT_NE(input_desc, nullptr);
} else if (std::strcmp(type_info.name, "SliceInputDescription") == 0) {
auto input_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::SliceInputDescription>(desc);
EXPECT_NE(input_desc, nullptr);
} else if (std::strcmp(type_info.name, "MergedInputDescription") == 0) {
auto input_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::MergedInputDescription>(desc);
EXPECT_NE(input_desc, nullptr);
}
}
// Output 1 is last Z
auto out0 = loop->get_iter_value(body_condition, -1);
auto out1 = loop->get_iter_value(Z, -1);
// check output descriptors
for (auto& desc : loop->get_output_descriptions()) {
auto type_info = desc->get_type_info();
if (std::strcmp(type_info.name, "ConcatOutputDescription") == 0) {
auto output_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::ConcatOutputDescription>(desc);
EXPECT_NE(output_desc, nullptr);
} else if (std::strcmp(type_info.name, "BodyOutputDescription") == 0) {
auto output_desc = ov::as_type_ptr<ngraph::opset5::TensorIterator::BodyOutputDescription>(desc);
EXPECT_NE(output_desc, nullptr);
}
}
auto result0 = make_shared<opset5::Result>(out0);
auto result1 = make_shared<opset5::Result>(out1);
Shape out0_shape{};
PartialShape out1_shape{-1};
auto results = ResultVector{result0, result1};
auto f = make_shared<Function>(results, ParameterVector{X, T});
EXPECT_EQ(f->get_output_size(), 2);
EXPECT_EQ(result0->get_output_shape(0), out0_shape);
// should be dynamic
EXPECT_EQ(result1->get_output_partial_shape(0), out1_shape);
const auto inp0_shape = PartialShape{-1};
const auto inp1_shape = Shape{};
EXPECT_EQ(body->get_parameters().size(), 1);
// backedge, should be also dynamic
EXPECT_EQ(body->get_parameters().at(0)->get_partial_shape(), inp0_shape);
EXPECT_EQ(loop->get_output_size(), 2);
EXPECT_EQ(loop->get_output_shape(0), out0_shape);
// map from the submodel, should be dynamic
EXPECT_EQ(loop->get_output_partial_shape(1), out1_shape);
}
// dynamic output
// trip_count = -1
// execution_condition = true
// body_condition = true
// model could be described like so:
// Parameter([-1, -1])
// while (true) {
// input = unsqueeze(input, 0);
// }
TEST(type_prop, loop_operation_dynamic_iter_dynamic_shapes_unsqueeze) {
// Inner model
const auto inner_parameter = std::make_shared<opset5::Parameter>(element::dynamic, ov::PartialShape::dynamic());
const auto unsqueeze =
std::make_shared<opset5::Unsqueeze>(inner_parameter, opset5::Constant::create(element::i64, {1}, {0}));
const auto true_const = opset5::Constant::create(element::boolean, {1}, {1});
auto body = std::make_shared<Function>(OutputVector{unsqueeze, true_const}, ParameterVector{inner_parameter});
// Outer model
const auto outer_parameter = std::make_shared<opset5::Parameter>(element::dynamic, ov::PartialShape::dynamic(2));
const auto trip_count = opset5::Constant::create(element::i64, {1}, {-1});
const auto execution_condition = opset5::Constant::create(element::boolean, {1}, {1});
const auto loop = std::make_shared<opset5::Loop>(trip_count, execution_condition);
loop->set_function(body);
loop->set_merged_input(inner_parameter, outer_parameter, unsqueeze);
loop->set_special_body_ports(ngraph::opset5::Loop::SpecialBodyPorts{-1, 1});
auto outer_result = make_shared<opset5::Result>(loop->get_iter_value(unsqueeze, -1));
auto outer_model = std::make_shared<Function>(ResultVector{outer_result}, ParameterVector{outer_parameter});
PartialShape outer_shape = PartialShape::dynamic();
EXPECT_EQ(outer_model->get_output_size(), 1);
EXPECT_EQ(outer_result->get_output_partial_shape(0), outer_shape);
}

View File

@@ -33,9 +33,12 @@ public:
/// \brief Get the output names
virtual std::vector<OutPortName> get_output_names() const = 0;
virtual std::vector<TensorName> get_output_var_names(const std::string& var_name) const = 0;
virtual std::vector<TensorName> get_input_var_names(const std::string& var_name) const = 0;
/// \brief Get the output size
virtual size_t get_output_size() const = 0;
virtual size_t get_output_size(const std::string& port_name) const = 0;
/// \brief Get output port type
///
@@ -47,6 +50,8 @@ public:
///
/// \return Type of specified output port
virtual ov::element::Type get_out_port_type(const std::string& port_name) const = 0;
virtual std::vector<std::pair<ov::element::Type, ov::PartialShape>> get_output_port_infos(
const std::string& port_name) const = 0;
/// \brief Get the type of the operation
virtual std::string get_op_type() const = 0;

View File

@@ -19,6 +19,7 @@ namespace frontend {
namespace paddle {
class OpPlace;
class TensorPlace;
class PADDLE_API FrontEnd : public ov::frontend::FrontEnd {
public:
@@ -72,10 +73,19 @@ protected:
InputModel::Ptr load_impl(const std::vector<ov::Any>& params) const override;
protected:
static std::shared_ptr<Model> convert_each_node(
void try_remove_internal_ops(const std::vector<std::shared_ptr<Model>>& models) const;
static std::vector<std::shared_ptr<Model>> convert_each_node(
const std::shared_ptr<InputModel>& frontend_model,
std::function<std::map<std::string, OutputVector>(const std::map<std::string, Output<Node>>&,
const std::shared_ptr<OpPlace>&)> func);
static std::map<int32_t, std::shared_ptr<Model>> convert_each_node_recursive(
const std::shared_ptr<InputModel>& frontend_model,
const int32_t block_idx,
const std::vector<std::shared_ptr<TensorPlace>>& input_tensors,
const std::vector<std::shared_ptr<TensorPlace>>& output_tensors,
std::function<std::map<std::string, OutputVector>(const std::map<std::string, Output<Node>>&,
const std::shared_ptr<OpPlace>&)> func);
TelemetryExtension::Ptr m_telemetry;
std::vector<DecoderTransformationExtension::Ptr> m_transformation_extensions;

View File

@@ -48,6 +48,16 @@ public:
return name_map.at(name);
}
/// Returns all inputs in order they appear in map. This is used for FrameworkNode
/// creation
OutputVector get_all_ng_inputs() const {
OutputVector res;
for (const auto& entry : name_map) {
res.insert(res.end(), entry.second.begin(), entry.second.end());
}
return res;
}
Output<Node> get_input(const std::string& name, int idx) const override {
return name_map.at(name).at(idx);
}
@@ -60,6 +70,14 @@ public:
return decoder.get_output_names();
}
std::vector<TensorName> get_output_var_names(const std::string& var_name) const {
return decoder.get_output_var_names(var_name);
}
std::vector<TensorName> get_input_var_names(const std::string& var_name) const {
return decoder.get_input_var_names(var_name);
}
ov::element::Type get_out_port_type(const std::string& port_name) const {
return decoder.get_out_port_type(port_name);
}
@@ -72,6 +90,15 @@ public:
return res;
}
size_t get_output_size(const std::string& port_name) const {
return decoder.get_output_size(port_name);
}
std::vector<std::pair<ov::element::Type, ov::PartialShape>> get_output_port_infos(
const std::string& port_name) const {
return decoder.get_output_port_infos(port_name);
}
private:
ov::Any apply_additional_conversion_rules(const ov::Any& any, const std::type_info& type_info) const override {
auto res = decoder.convert_attribute(any, type_info);

View File

@@ -92,6 +92,35 @@ std::vector<paddle::OutPortName> DecoderProto::get_output_names() const {
return output_names;
}
std::vector<paddle::TensorName> DecoderProto::get_output_var_names(const std::string& var_name) const {
std::vector<std::string> output_names;
for (const auto& output : op_place->get_desc().outputs()) {
if (output.parameter() == var_name) {
for (int idx = 0; idx < output.arguments_size(); ++idx) {
output_names.push_back(output.arguments()[idx]);
}
}
}
return output_names;
}
std::vector<paddle::TensorName> DecoderProto::get_input_var_names(const std::string& var_name) const {
std::vector<std::string> input_names;
for (const auto& input : op_place->get_desc().inputs()) {
if (input.parameter() == var_name) {
for (int idx = 0; idx < input.arguments_size(); ++idx) {
input_names.push_back(input.arguments()[idx]);
}
}
}
return input_names;
}
size_t DecoderProto::get_output_size(const std::string& port_name) const {
const auto out_port = op_place->get_output_ports().at(port_name);
return out_port.size();
}
size_t DecoderProto::get_output_size() const {
size_t res = 0;
for (const auto& output : op_place->get_desc().outputs()) {
@@ -110,6 +139,16 @@ std::map<std::string, std::vector<ov::element::Type>> DecoderProto::get_output_t
return output_types;
}
std::vector<std::pair<ov::element::Type, ov::PartialShape>> DecoderProto::get_output_port_infos(
const std::string& port_name) const {
std::vector<std::pair<ov::element::Type, ov::PartialShape>> output_types;
for (const auto& out_port : op_place->get_output_ports().at(port_name)) {
output_types.push_back({out_port->get_target_tensor_paddle()->get_element_type(),
out_port->get_target_tensor_paddle()->get_partial_shape()});
}
return output_types;
}
ov::element::Type DecoderProto::get_out_port_type(const std::string& port_name) const {
std::vector<ov::element::Type> output_types;
for (const auto& out_port : op_place->get_output_ports().at(port_name)) {

View File

@@ -30,17 +30,23 @@ public:
ov::Any get_attribute(const std::string& name) const override;
std::vector<TensorName> get_output_var_names(const std::string& var_name) const override;
std::vector<TensorName> get_input_var_names(const std::string& var_name) const override;
ov::Any convert_attribute(const ov::Any& data, const std::type_info& type_info) const override;
std::vector<paddle::OutPortName> get_output_names() const override;
size_t get_output_size() const override;
size_t get_output_size(const std::string& port_name) const override;
ov::element::Type get_out_port_type(const std::string& port_name) const override;
std::string get_op_type() const override;
std::map<std::string, std::vector<ov::element::Type>> get_output_type_map() const;
std::vector<std::pair<ov::element::Type, ov::PartialShape>> get_output_port_infos(
const std::string& port_name) const override;
std::map<std::string, OutputVector> map_for_each_input(
const std::function<Output<Node>(const std::string&, size_t)>& func) const;

View File

@@ -10,19 +10,22 @@
#include <vector>
#include "decoder_proto.hpp"
#include "default_opset.hpp"
#include "framework.pb.h"
#include "input_model.hpp"
#include "internal/pass/transform_if.hpp"
#include "internal/pass/transform_tensorarray.hpp"
#include "internal/pass/transform_while.hpp"
#include "op_table.hpp"
#include "openvino/frontend/extension/conversion.hpp"
#include "openvino/frontend/paddle/node_context.hpp"
#include "openvino/opsets/opset7.hpp"
#include "openvino/util/common_util.hpp"
#include "paddle_fw_node.hpp"
#include "paddle_utils.hpp"
#include "place.hpp"
#include "so_extension.hpp"
using namespace ov::opset7;
using namespace ov::frontend::paddle::op::default_opset;
using namespace ov;
using namespace ov::frontend;
@@ -55,7 +58,7 @@ NamedOutputs make_ng_node(const std::map<paddle::TensorName, Output<Node>>& node
NamedOutputs outputs;
// In case the conversion function throws exception
try {
outputs = creator_it->second(NodeContext(DecoderProto(op_place), named_inputs));
outputs = creator_it->second(paddle::NodeContext(DecoderProto(op_place), named_inputs));
} catch (std::exception& ex) {
FRONT_END_OP_CONVERSION_CHECK(false, "Fail to convert " + op_desc.type() + " Exception " + ex.what());
}
@@ -96,7 +99,7 @@ bool normalize_framework_node(const std::shared_ptr<FrameworkNode>& node,
auto creator_it = CREATORS_MAP.find(type);
FRONT_END_OP_CONVERSION_CHECK(creator_it != CREATORS_MAP.end(), "No creator found for ", type, " node.");
auto new_node_outputs = creator_it->second(NodeContext(node->get_decoder(), node->get_named_inputs()));
auto new_node_outputs = creator_it->second(paddle::NodeContext(node->get_decoder(), node->get_named_inputs()));
auto new_node = new_node_outputs.begin()->second[0].get_node_shared_ptr();
new_node->set_friendly_name(node->get_friendly_name());
auto node_outputs = node->return_named_outputs();
@@ -137,17 +140,102 @@ std::istream* variant_to_stream_ptr(const ov::Any& variant, std::ifstream& ext_s
FrontEnd::FrontEnd() : m_op_translators(paddle::get_supported_ops()) {}
std::shared_ptr<ov::Model> FrontEnd::convert_each_node(
std::vector<std::shared_ptr<ov::Model>> FrontEnd::convert_each_node(
const std::shared_ptr<ov::frontend::InputModel>& frontend_model,
std::function<std::map<std::string, OutputVector>(const std::map<std::string, Output<Node>>&,
const std::shared_ptr<OpPlace>&)> func) {
auto model = std::dynamic_pointer_cast<InputModel>(frontend_model);
FRONT_END_GENERAL_CHECK(model, "Invalid input model");
std::vector<std::shared_ptr<TensorPlace>> input_tensors;
std::vector<std::shared_ptr<TensorPlace>> output_tensors;
for (const auto& _inp_place : model->get_inputs()) {
const auto& inp_place = std::dynamic_pointer_cast<TensorPlace>(_inp_place);
input_tensors.emplace_back(inp_place);
}
for (const auto& _outp_place : model->get_outputs()) {
const auto& outp_place = std::dynamic_pointer_cast<TensorPlace>(_outp_place);
output_tensors.emplace_back(outp_place);
}
auto funcs = convert_each_node_recursive(model, 0, input_tensors, output_tensors, func);
std::vector<std::shared_ptr<Model>> funcs_vec;
for (auto&& item : funcs) {
funcs_vec.emplace_back(item.second);
}
return funcs_vec;
}
// Paddle's subblock does not has 'feed' and 'fetch' and the sub-model's parameters and results
// could not be generated just like the main model. We extract the information from 'conditional_block'
// and 'while' ops
using SubblockInfo = std::map<
int32_t,
std::tuple<std::string, std::vector<std::shared_ptr<TensorPlace>>, std::vector<std::shared_ptr<TensorPlace>>>>;
void try_update_sublock_info(const std::shared_ptr<OpPlace>& op_place, SubblockInfo& subblock_info) {
const auto& op_desc = op_place->get_desc();
if (op_desc.type() == "conditional_block") {
std::vector<std::shared_ptr<TensorPlace>> outp_tensors;
std::vector<std::shared_ptr<TensorPlace>> inp_tensors;
auto outp_ports = op_place->get_output_ports();
for (auto outp_port : outp_ports["Out"]) {
auto outp_tensor = outp_port->get_target_tensor_paddle();
outp_tensors.push_back(outp_tensor);
}
FRONT_END_GENERAL_CHECK(outp_tensors.size() > 0, "Port has no tensors connected.");
auto inp_ports = op_place->get_input_ports();
for (auto inp_port : inp_ports["Input"]) {
auto inp_tensor = inp_port->get_source_tensor_paddle();
inp_tensors.push_back(inp_tensor);
}
auto tmp_node = paddle::NodeContext(DecoderProto(op_place), paddle::NamedInputs());
auto block_idx = tmp_node.get_attribute<int32_t>("sub_block");
subblock_info[block_idx] = std::make_tuple(op_desc.type(), inp_tensors, outp_tensors);
} else if (op_desc.type() == "while") {
std::vector<std::shared_ptr<TensorPlace>> outp_tensors;
std::vector<std::shared_ptr<TensorPlace>> inp_tensors;
auto outp_ports = op_place->get_output_ports();
for (auto outp_port : outp_ports["Out"]) {
auto outp_tensor = outp_port->get_target_tensor_paddle();
outp_tensors.push_back(outp_tensor);
}
FRONT_END_GENERAL_CHECK(outp_tensors.size() > 0, "Port has no tensors connected.");
auto inp_ports = op_place->get_input_ports();
for (auto inp_port : inp_ports["X"]) {
auto inp_tensor = inp_port->get_source_tensor_paddle();
inp_tensors.push_back(inp_tensor);
}
FRONT_END_GENERAL_CHECK(inp_tensors.size() > 0, "Port has no tensors connected.");
auto tmp_node = paddle::NodeContext(DecoderProto(op_place), paddle::NamedInputs());
auto block_idx = tmp_node.get_attribute<int32_t>("sub_block");
subblock_info[block_idx] = std::make_tuple(op_desc.type(), inp_tensors, outp_tensors);
}
}
std::map<int32_t, std::shared_ptr<ov::Model>> FrontEnd::convert_each_node_recursive(
const std::shared_ptr<ov::frontend::InputModel>& frontend_model,
const int32_t block_idx,
const std::vector<std::shared_ptr<TensorPlace>>& input_tensors,
const std::vector<std::shared_ptr<TensorPlace>>& output_tensors,
std::function<std::map<std::string, OutputVector>(const std::map<std::string, Output<Node>>&,
const std::shared_ptr<OpPlace>&)> func) {
auto model = std::dynamic_pointer_cast<InputModel>(frontend_model);
FRONT_END_GENERAL_CHECK(model, "Invalid input model");
auto nodes_dict(model->get_tensor_values());
ParameterVector parameter_nodes;
ResultVector result_nodes;
OutputVector output_nodes;
for (const auto& _inp_place : model->get_inputs()) {
SubblockInfo subblock_inputs_outputs; // keep info of controlflow ops
for (const auto& _inp_place : input_tensors) {
const auto& inp_place = std::dynamic_pointer_cast<TensorPlace>(_inp_place);
const auto& var = inp_place->get_desc();
const auto& shape = inp_place->get_partial_shape();
@@ -159,13 +247,15 @@ std::shared_ptr<ov::Model> FrontEnd::convert_each_node(
parameter_nodes.push_back(param);
}
const auto& op_places = model->get_op_places();
const auto& op_places = model->get_op_places(block_idx);
for (const auto& op_place : op_places) {
const auto& op_desc = op_place->get_desc();
if (op_desc.type() == "feed" || op_desc.type() == "fetch") {
// inputs and outputs are stored in the model already
continue;
} else {
try_update_sublock_info(op_place, subblock_inputs_outputs);
paddle::NamedOutputs named_outputs = func(nodes_dict, op_place);
if (!named_outputs.empty()) {
@@ -188,8 +278,7 @@ std::shared_ptr<ov::Model> FrontEnd::convert_each_node(
ng_outputs[idx].get_tensor().set_names({var_name});
// if nodes_dict already has node mapped to this tensor name it
// usually means that it was overwritten using setTensorValue
if (!nodes_dict.count(var_name))
nodes_dict[var_name] = ng_outputs[idx];
nodes_dict[var_name] = ng_outputs[idx];
}
}
}
@@ -197,16 +286,49 @@ std::shared_ptr<ov::Model> FrontEnd::convert_each_node(
}
}
for (const auto& _outp_place : model->get_outputs()) {
for (const auto& _outp_place : output_tensors) {
const auto& outp_place = std::dynamic_pointer_cast<TensorPlace>(_outp_place);
auto var = outp_place->get_desc();
auto input_var_name = var.name();
auto result = std::make_shared<Result>(nodes_dict.at(input_var_name));
result->set_friendly_name(input_var_name + "/Result");
result_nodes.push_back(result);
output_nodes.push_back(nodes_dict.at(input_var_name));
}
return std::make_shared<ov::Model>(result_nodes, parameter_nodes);
std::shared_ptr<ov::Model> main_block_func;
if (parameter_nodes.size() > 0) {
main_block_func = std::make_shared<ov::Model>(result_nodes, parameter_nodes);
} else {
main_block_func = std::make_shared<ov::Model>(output_nodes);
}
// convert each sub block
std::map<int32_t, std::shared_ptr<ov::Model>> block_funcs;
block_funcs.insert({block_idx, main_block_func});
for (auto& item : subblock_inputs_outputs) {
auto ctl_op_info = item.second;
auto sub_block_func =
convert_each_node_recursive(model, item.first, std::get<1>(ctl_op_info), std::get<2>(ctl_op_info), func);
block_funcs.insert(sub_block_func.begin(), sub_block_func.end());
}
return block_funcs;
}
void FrontEnd::try_remove_internal_ops(const std::vector<std::shared_ptr<Model>>& models) const {
for (auto& model : models) {
ov::pass::Manager manager;
manager.register_pass<ov::frontend::paddle::pass::TransformTensorArray>(models);
manager.register_pass<ov::frontend::paddle::pass::TransformIf>(models);
manager.register_pass<ov::frontend::paddle::pass::TransformWhile>(models);
manager.run_passes(model);
}
if (models.size() > 0) {
// revalidate as child models are transformed after parent models.
models[0]->validate_nodes_and_infer_types();
}
}
bool FrontEnd::supported_impl(const std::vector<ov::Any>& variants) const {
@@ -288,7 +410,7 @@ std::shared_ptr<ov::Model> FrontEnd::convert(const InputModel::Ptr& model) const
if (!m_transformation_extensions.empty()) {
auto function = decode(model);
pass::Manager manager;
ov::pass::Manager manager;
for (const auto& transformation : m_transformation_extensions) {
transformation->register_pass(manager);
}
@@ -302,7 +424,9 @@ std::shared_ptr<ov::Model> FrontEnd::convert(const InputModel::Ptr& model) const
[&](const std::map<std::string, Output<Node>>& nodes_dict, const std::shared_ptr<OpPlace>& op_place) {
return paddle::make_ng_node(nodes_dict, op_place, m_op_translators);
});
return f;
try_remove_internal_ops(f);
return f[0];
}
void FrontEnd::convert(const std::shared_ptr<ov::Model>& partiallyConverted) const {
@@ -314,6 +438,8 @@ void FrontEnd::convert(const std::shared_ptr<ov::Model>& partiallyConverted) con
for (const auto& result : partiallyConverted->get_results()) {
result->validate_and_infer_types();
}
try_remove_internal_ops({partiallyConverted});
}
std::shared_ptr<ov::Model> FrontEnd::convert_partially(const InputModel::Ptr& model) const {
@@ -323,7 +449,7 @@ std::shared_ptr<ov::Model> FrontEnd::convert_partially(const InputModel::Ptr& mo
if (!m_transformation_extensions.empty()) {
auto function = decode(model);
pass::Manager manager;
ov::pass::Manager manager;
for (const auto& transformation : m_transformation_extensions) {
transformation->register_pass(manager);
}
@@ -343,7 +469,10 @@ std::shared_ptr<ov::Model> FrontEnd::convert_partially(const InputModel::Ptr& mo
}
return named_outputs;
});
return f;
try_remove_internal_ops(f);
return f[0];
}
std::shared_ptr<ov::Model> FrontEnd::decode(const InputModel::Ptr& model) const {
@@ -351,7 +480,8 @@ std::shared_ptr<ov::Model> FrontEnd::decode(const InputModel::Ptr& model) const
FRONT_END_GENERAL_CHECK(paddle_model != nullptr, "Invalid input model");
auto f = convert_each_node(paddle_model, paddle::make_framework_node);
return f;
FRONT_END_GENERAL_CHECK(f.size() == 1, "Input model has subblocks, currently 'decode' could not support it");
return f[0];
}
std::string FrontEnd::get_name() const {

View File

@@ -48,7 +48,7 @@ public:
void setElementType(Place::Ptr place, const ov::element::Type&);
void setTensorValue(Place::Ptr place, const void* value);
std::vector<std::shared_ptr<OpPlace>> get_op_places() const;
std::vector<std::shared_ptr<OpPlace>> get_op_places(const int32_t blck_idx) const;
std::map<std::string, std::shared_ptr<TensorPlace>> get_var_places() const {
return m_var_places;
}
@@ -60,9 +60,10 @@ private:
void loadPlaces();
template <typename T>
void loadConsts(const std::basic_string<T>& folder_with_weights, std::istream* weight_stream);
void createTempConsts();
std::vector<std::shared_ptr<OpPlace>> determine_cut_nodes() const;
std::vector<std::shared_ptr<OpPlace>> m_op_places;
std::vector<std::vector<std::shared_ptr<OpPlace>>> m_op_places;
std::map<std::string, std::shared_ptr<TensorPlace>> m_var_places;
std::shared_ptr<ProgramDesc> m_fw_ptr;
const InputModel& m_input_model;
@@ -81,6 +82,8 @@ void InputModel::InputModelImpl::loadPlaces() {
const auto& blocks = m_fw_ptr->blocks();
std::map<std::string, uint64_t> op_statistics;
m_op_places.resize(cnt_of_blocks);
for (int block_idx = 0; block_idx < cnt_of_blocks; block_idx++) {
const auto& block = blocks[block_idx];
@@ -95,7 +98,7 @@ void InputModel::InputModelImpl::loadPlaces() {
op_statistics[op.type()]++;
}
m_op_places.push_back(op_place);
m_op_places[block_idx].push_back(op_place);
for (const auto& output : op.outputs()) {
for (const auto& var_name : output.arguments()) {
@@ -215,18 +218,20 @@ std::basic_string<wchar_t> get_model_path(const std::basic_string<wchar_t>& path
#endif
} // namespace
std::vector<std::shared_ptr<OpPlace>> InputModel::InputModelImpl::get_op_places() const {
std::vector<std::shared_ptr<OpPlace>> InputModel::InputModelImpl::get_op_places(const int32_t blck_idx) const {
if (m_graph_changed) {
return determine_cut_nodes();
}
return m_op_places;
if (static_cast<size_t>(blck_idx) < m_op_places.size())
return m_op_places[blck_idx];
return {};
}
std::vector<std::shared_ptr<OpPlace>> InputModel::InputModelImpl::determine_cut_nodes() const {
std::queue<OpPlace*> q;
std::unordered_set<OpPlace*> visited;
std::vector<std::shared_ptr<OpPlace>> new_op_places;
new_op_places.reserve(m_op_places.size());
new_op_places.reserve(m_op_places[0].size());
// Marking nodes from outputs to inputs/constants
for (const auto& output : getOutputs()) {
if (!output->is_input()) {
@@ -327,6 +332,53 @@ InputModel::InputModelImpl::InputModelImpl(const std::basic_string<T>& path,
} else {
loadConsts(path, nullptr);
}
createTempConsts();
}
void InputModel::InputModelImpl::createTempConsts() {
for (const auto& item : m_var_places) {
const auto& var_place = item.second;
const auto& var_desc = var_place->get_desc();
const auto& name = item.first;
if (var_desc.persistable())
continue;
// The node with tensorarray as its input may be created before the node with this tensorarray
// as its output. e.g. the tensorarray is both the input and output of the same node.
// So we have to create a fake empty node here.
// Problem is, we have no idea which axis should be 0.
// Since the models (faster/mask rcnn) are either concating tensors in tensorarray along the dynamic
// dimension, or concating static shape tensors. So we make the dynamic dimension to be 0. In case of static
// shape, we simply the the first dimension be 0.
if (var_desc.type().has_tensor_array()) {
const auto& tensor = var_desc.type().tensor_array().tensor();
const auto& type = TYPE_MAP[tensor.data_type()];
PartialShape tensor_ps(std::vector<Dimension>(tensor.dims().cbegin(), tensor.dims().cend()));
tensor_ps.insert(tensor_ps.begin(), 1); // unsqueeze
// also update the place for following initialize the graph connection
var_place->set_element_type(type);
var_place->set_partial_shape(tensor_ps);
Shape shape(tensor_ps.size());
for (auto i = 0; i < tensor_ps.size(); i++) {
const auto& dim = tensor_ps[i];
if (dim.is_static()) {
shape[i] = dim.get_length();
}
}
if (tensor_ps.is_static()) {
shape[1] = 0;
}
auto node = opset7::Constant::create(type, shape, {0});
node->set_friendly_name(name);
node->output(0).get_tensor().add_names({name});
m_tensor_values[name] = node;
}
}
}
InputModel::InputModelImpl::InputModelImpl(const std::vector<std::istream*>& streams,
@@ -347,6 +399,7 @@ InputModel::InputModelImpl::InputModelImpl(const std::vector<std::istream*>& str
loadPlaces();
if (streams.size() > 1)
loadConsts(std::string(), streams[1]);
createTempConsts();
}
std::vector<Place::Ptr> InputModel::InputModelImpl::getInputs() const {
@@ -438,8 +491,8 @@ InputModel::InputModel(const std::wstring& path, const std::shared_ptr<Telemetry
InputModel::InputModel(const std::vector<std::istream*>& streams, const std::shared_ptr<TelemetryExtension>& telemetry)
: _impl{std::make_shared<InputModelImpl>(streams, *this, telemetry)} {}
std::vector<std::shared_ptr<OpPlace>> InputModel::get_op_places() const {
return _impl->get_op_places();
std::vector<std::shared_ptr<OpPlace>> InputModel::get_op_places(const int32_t blck_idx) const {
return _impl->get_op_places(blck_idx);
}
std::map<std::string, std::shared_ptr<TensorPlace>> InputModel::get_var_places() const {

View File

@@ -38,7 +38,7 @@ private:
class InputModelImpl;
std::shared_ptr<InputModelImpl> _impl;
std::vector<std::shared_ptr<OpPlace>> get_op_places() const;
std::vector<std::shared_ptr<OpPlace>> get_op_places(const int32_t block_idx) const;
std::map<std::string, std::shared_ptr<TensorPlace>> get_var_places() const;
std::map<std::string, Output<Node>> get_tensor_values() const;
};

View File

@@ -0,0 +1,83 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "internal/op/conditional_block.hpp"
#include <algorithm>
#include <ngraph/validation_util.hpp>
#include "ngraph/op/constant.hpp"
#include "openvino/op/util/precision_sensitive_attribute.hpp"
using namespace std;
using namespace ov;
BWDCMP_RTTI_DEFINITION(op::internal::ConditionalBlock);
op::internal::ConditionalBlock::ConditionalBlock(
const Output<Node>& cond,
bool is_scalar_condition,
int32_t sub_block_index,
const std::vector<std::pair<ov::element::Type, ov::PartialShape>>& output_infos)
: Op({cond}),
m_is_scalar_condition(is_scalar_condition),
m_sub_block_index(sub_block_index),
m_output_infos(output_infos) {
constructor_validate_and_infer_types();
}
op::internal::ConditionalBlock::ConditionalBlock(
const OutputVector& inputs,
const Output<Node>& cond,
bool is_scalar_condition,
int32_t sub_block_index,
const std::vector<std::pair<ov::element::Type, ov::PartialShape>>& output_infos)
: m_is_scalar_condition(is_scalar_condition),
m_sub_block_index(sub_block_index),
m_output_infos(output_infos) {
OutputVector new_args;
std::move(inputs.begin(), inputs.end(), std::back_inserter(new_args));
new_args.emplace_back(cond);
set_arguments(new_args);
constructor_validate_and_infer_types();
}
std::shared_ptr<Node> op::internal::ConditionalBlock::clone_with_new_inputs(const OutputVector& new_args) const {
check_new_args_count(this, new_args);
if (new_args.size() == 1) { // w/o inputs
return make_shared<ConditionalBlock>(new_args.at(0), m_is_scalar_condition, m_sub_block_index, m_output_infos);
} else {
OutputVector inputs_args;
for (auto i = 0; i < new_args.size() - 1; i++) {
inputs_args.push_back(new_args[i]);
}
return make_shared<ConditionalBlock>(inputs_args,
new_args.back(),
m_is_scalar_condition,
m_sub_block_index,
m_output_infos);
}
}
bool op::internal::ConditionalBlock::visit_attributes(AttributeVisitor& visitor) {
visitor.on_attribute("is_scalar_condition", m_is_scalar_condition);
visitor.on_attribute("sub_block_index", m_sub_block_index);
return true;
}
void op::internal::ConditionalBlock::validate_and_infer_types() {
for (auto i = 0; i < m_output_infos.size(); i++) {
set_output_type(i, m_output_infos[i].first, m_output_infos[i].second);
}
}
const OutputVector op::internal::ConditionalBlock::get_inputs_from_parent() const {
OutputVector result;
const auto& inputs = this->input_values();
for (size_t i = 0; i < inputs.size() - 1; i++) { // execpt the one at last, which is "cond".
result.push_back(inputs[i]);
}
return result;
}

View File

@@ -0,0 +1,50 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/op/op.hpp"
namespace ov {
namespace op {
namespace internal {
class ConditionalBlock : public Op {
public:
OPENVINO_OP("ConditionalBlock", "internal");
BWDCMP_RTTI_DECLARATION;
ConditionalBlock() = default;
ConditionalBlock(const OutputVector& inputs,
const Output<Node>& cond,
bool is_scalar_condition,
int32_t sub_block_index,
const std::vector<std::pair<ov::element::Type, ov::PartialShape>>& output_infos);
ConditionalBlock(const Output<Node>& cond,
bool is_scalar_condition,
int32_t sub_block_index,
const std::vector<std::pair<ov::element::Type, ov::PartialShape>>& output_infos);
void validate_and_infer_types() override;
bool visit_attributes(AttributeVisitor& visitor) override;
std::shared_ptr<Node> clone_with_new_inputs(const OutputVector& new_args) const override;
/// \return A vector containing the values for each input except "cond".
const OutputVector get_inputs_from_parent() const;
const int32_t get_subblock_index() const {
return m_sub_block_index;
}
private:
bool m_is_scalar_condition;
int32_t m_sub_block_index;
std::vector<std::pair<ov::element::Type, ov::PartialShape>> m_output_infos;
};
} // namespace internal
} // namespace op
} // namespace ov

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "internal/op/tensorarray_write.hpp"
#include <algorithm>
#include <ngraph/validation_util.hpp>
#include "ngraph/op/constant.hpp"
#include "openvino/op/util/precision_sensitive_attribute.hpp"
using namespace std;
using namespace ov;
BWDCMP_RTTI_DEFINITION(op::internal::TensorArrayWrite);
op::internal::TensorArrayWrite::TensorArrayWrite(const Output<Node>& input, const Output<Node>& index)
: Op({input, index}) {
constructor_validate_and_infer_types();
}
std::shared_ptr<Node> op::internal::TensorArrayWrite::clone_with_new_inputs(const OutputVector& new_args) const {
return make_shared<TensorArrayWrite>(new_args[0], new_args[1]);
}
bool op::internal::TensorArrayWrite::visit_attributes(AttributeVisitor& visitor) {
return true;
}
// tensorarray_write will be transformed and replaced.
// Here we simply make it to be an internal dynamic node, to make sure
// all its offsprings will validate and infer shapes from an dynamic input.
void op::internal::TensorArrayWrite::validate_and_infer_types() {
auto ps = get_input_partial_shape(0);
if (ps.rank().is_static() && ps.rank().get_length() >= 1) {
ps.insert(ps.begin(), 1); // unsqueeze in order to handyfully slice a tensorarray
// will use concat to implement tensor_write and a different dimension is enough for
// a zero-dimension const input
if (ps[1].is_static()) {
ps[1] += 1;
}
}
set_output_type(0, get_input_element_type(0), ps);
}

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/op/op.hpp"
namespace ov {
namespace op {
namespace internal {
class TensorArrayWrite : public Op {
public:
OPENVINO_OP("TensorArrayWrite", "internal");
BWDCMP_RTTI_DECLARATION;
TensorArrayWrite() = default;
TensorArrayWrite(const Output<Node>& input, const Output<Node>& index);
void validate_and_infer_types() override;
bool visit_attributes(AttributeVisitor& visitor) override;
std::shared_ptr<Node> clone_with_new_inputs(const OutputVector& new_args) const override;
private:
};
} // namespace internal
} // namespace op
} // namespace ov

View File

@@ -0,0 +1,40 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "internal/op/while.hpp"
#include <algorithm>
#include <ngraph/validation_util.hpp>
#include "ngraph/op/constant.hpp"
#include "openvino/op/util/precision_sensitive_attribute.hpp"
using namespace std;
using namespace ov;
BWDCMP_RTTI_DEFINITION(op::internal::While);
op::internal::While::While(const OutputVector& inputs,
int32_t sub_block,
const std::vector<std::pair<ov::element::Type, ov::PartialShape>>& output_infos)
: Op(inputs),
m_sub_block(sub_block),
m_output_infos(output_infos) {
constructor_validate_and_infer_types();
}
std::shared_ptr<Node> op::internal::While::clone_with_new_inputs(const OutputVector& new_args) const {
return make_shared<While>(new_args, m_sub_block, m_output_infos);
}
bool op::internal::While::visit_attributes(AttributeVisitor& visitor) {
visitor.on_attribute("sub_block", m_sub_block);
return true;
}
void op::internal::While::validate_and_infer_types() {
for (auto i = 0; i < m_output_infos.size(); i++) {
set_output_type(i, m_output_infos[i].first, m_output_infos[i].second);
}
}

View File

@@ -0,0 +1,41 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/op/op.hpp"
namespace ov {
namespace op {
namespace internal {
class While : public Op {
public:
OPENVINO_OP("While", "internal");
BWDCMP_RTTI_DECLARATION;
While() = default;
While(const OutputVector& inputs,
int32_t sub_block,
const std::vector<std::pair<ov::element::Type, ov::PartialShape>>& output_infos);
void validate_and_infer_types() override;
bool visit_attributes(AttributeVisitor& visitor) override;
std::shared_ptr<Node> clone_with_new_inputs(const OutputVector& new_args) const override;
const int32_t get_subblock_index() const {
return m_sub_block;
}
private:
int32_t m_sub_block = 0;
std::vector<std::pair<ov::element::Type, ov::PartialShape>> m_output_infos;
};
} // namespace internal
} // namespace op
} // namespace ov

View File

@@ -0,0 +1,110 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "internal/pass/transform_if.hpp"
#include <ngraph/ngraph.hpp>
#include <ngraph/pattern/matcher.hpp>
#include <ngraph/pattern/op/or.hpp>
#include <ngraph/pattern/op/wrap_type.hpp>
#include <ngraph/rt_info.hpp>
#include <ngraph/variant.hpp>
#include <transformations/common_optimizations/fold_subgraph_empty_inputs.hpp>
#include "default_opset.hpp"
#include "internal/op/conditional_block.hpp"
#include "internal/op/tensorarray_write.hpp"
#include "ngraph/op/util/op_types.hpp"
#include "openvino/frontend/paddle/exception.hpp"
#include "openvino/op/util/op_types.hpp"
#include "openvino/pass/pattern/op/label.hpp"
using namespace std;
using namespace ov;
using namespace ov::pass;
using namespace ov::frontend::paddle::op::default_opset;
// Transform Paddle "conditonal_block" to OpenVINO If op.
// The contional_block only has "then" branch, while If op requires both "then" and "else" branch the same time.
// Thus a "pass-through" model is built on purpose for "else" branch with the same outputs as "then" branch.
ov::frontend::paddle::pass::TransformIf::TransformIf(std::vector<std::shared_ptr<Model>> funcs) {
const auto cond_label = ngraph::pattern::wrap_type<ov::op::internal::ConditionalBlock>();
matcher_pass_callback callback = [funcs](pattern::Matcher& m) -> bool {
const auto conditional_block =
std::dynamic_pointer_cast<ov::op::internal::ConditionalBlock>(m.get_match_root());
const auto mask_idx = conditional_block->get_input_size() - 1;
const auto cond = conditional_block->get_input_node_shared_ptr(mask_idx);
if (!conditional_block || !cond) {
return false;
}
// build_if_node
const auto then_idx = conditional_block->get_subblock_index();
const auto& then_branch = funcs[then_idx];
const auto& then_params = then_branch->get_parameters();
// make a pass-through else branch, as
// openvino If requires both then and else branch at the same time.
ParameterVector params;
ResultVector results;
for (auto i = 0; i < then_branch->get_output_size(); i++) {
const auto param = std::make_shared<Parameter>(then_branch->get_output_element_type(i),
then_branch->get_output_partial_shape(i));
param->set_friendly_name(then_branch->get_output_op(i)->get_output_tensor(0).get_any_name());
params.push_back(param);
const auto result = std::make_shared<Result>(param);
results.push_back(result);
}
const auto else_branch = std::make_shared<Model>(results, params);
const auto& else_params = else_branch->get_parameters();
auto if_node = std::make_shared<If>(cond);
ov::pass::disable_fold_subgraph_empty_inputs(if_node);
if_node->set_then_body(then_branch);
if_node->set_else_body(else_branch);
const auto then_branch_inputs_from_parent = conditional_block->get_inputs_from_parent();
NGRAPH_CHECK(then_branch_inputs_from_parent.size() == then_params.size(),
"Number of inputs to 'then_branch' is invalid. Expected " +
std::to_string(then_branch_inputs_from_parent.size()) + ", actual " +
std::to_string(then_params.size()));
auto then_param = then_params.cbegin();
for (const auto& from_parent : then_branch_inputs_from_parent) {
if_node->set_input(from_parent, *then_param, nullptr);
then_param++;
}
for (const auto& else_param : else_params) {
bool found = false;
for (const auto& from_parent : then_branch_inputs_from_parent) {
if (from_parent.get_any_name() == else_param->get_friendly_name()) {
if_node->set_input(from_parent, nullptr, else_param);
found = true;
break;
}
}
// the output generate from the body, make a default value
if (!found) {
auto ps = else_param->get_partial_shape();
const auto placeholder = Constant::create(else_param->get_element_type(), ps.get_min_shape(), {0});
if_node->set_input(placeholder, nullptr, else_param);
}
}
auto else_results = else_branch->get_results();
auto then_results = then_branch->get_results();
for (auto i = 0; i < else_results.size(); i++) {
if_node->set_output(then_results[i], else_results[i]);
}
replace_node(conditional_block, if_node);
if_node->set_friendly_name(conditional_block->get_friendly_name());
return true;
};
auto m = std::make_shared<ngraph::pattern::Matcher>(cond_label, "condtionalblock_if");
this->register_matcher(m, callback);
}

View File

@@ -0,0 +1,27 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/pass/graph_rewrite.hpp"
#include "openvino/pass/pass.hpp"
namespace ov {
namespace frontend {
namespace paddle {
namespace pass {
class TransformIf : public ov::pass::MatcherPass {
public:
OPENVINO_RTTI("ov::frontend::paddle::pass::TransformIf");
TransformIf(std::vector<std::shared_ptr<Model>> functions);
private:
std::vector<std::shared_ptr<Model>> m_functions;
};
} // namespace pass
} // namespace paddle
} // namespace frontend
} // namespace ov

View File

@@ -0,0 +1,63 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "internal/pass/transform_tensorarray.hpp"
#include <ngraph/log.hpp>
#include <ngraph/ngraph.hpp>
#include <ngraph/pattern/matcher.hpp>
#include <ngraph/pattern/op/or.hpp>
#include <ngraph/pattern/op/wrap_type.hpp>
#include <ngraph/rt_info.hpp>
#include <ngraph/variant.hpp>
#include <transformations/common_optimizations/remove_concat_zero_dim_input.hpp>
#include "default_opset.hpp"
#include "internal/op/conditional_block.hpp"
#include "internal/op/tensorarray_write.hpp"
#include "internal/op/while.hpp"
#include "openvino/frontend/paddle/exception.hpp"
#include "openvino/op/util/op_types.hpp"
#include "openvino/pass/constant_folding.hpp"
#include "openvino/pass/pattern/op/label.hpp"
using namespace std;
using namespace ov;
using namespace ov::pass;
using namespace frontend::paddle::op::default_opset;
// Transform pattern "TensorArrayLength->TensorArrayWrite" to OV concat, which
// will append to the end of array after unsqueeze along axis 0.
ov::frontend::paddle::pass::TransformTensorArray::TransformTensorArray(std::vector<std::shared_ptr<Model>> functions) {
const auto shape_label = ngraph::pattern::wrap_type<ShapeOf>();
const auto length_label = ngraph::pattern::wrap_type<StridedSlice>(
{shape_label, ngraph::pattern::any_input(), ngraph::pattern::any_input(), ngraph::pattern::any_input()});
auto write_label =
ngraph::pattern::wrap_type<ov::op::internal::TensorArrayWrite>({ngraph::pattern::any_input(), length_label});
matcher_pass_callback callback = [=](pattern::Matcher& m) -> bool {
const auto& opsMap = m.get_pattern_value_map();
const auto& write_node = opsMap.at(write_label).get_node_shared_ptr();
const auto& shape_node = opsMap.at(shape_label).get_node_shared_ptr();
if (!write_node || !shape_node)
return false;
const auto& new_item = write_node->get_input_node_shared_ptr(0);
const auto& list = shape_node->get_input_node_shared_ptr(0);
const auto& new_item_unsqueeze = std::make_shared<Unsqueeze>(
new_item->output(0),
Constant::create(element::i32, {1}, {0})); // unsqueeze in order to handyfully slice a tensorarray
// remove TensorArrayLength->TensorArrayWrite
const auto concat = std::make_shared<Concat>(OutputVector{list->output(0), new_item_unsqueeze->output(0)}, 1);
// prevent to remove concating zero-tensor
ov::pass::disable_remove_concat_zerodim_input(concat);
replace_node(write_node, concat);
concat->set_friendly_name(write_node->get_friendly_name());
return true;
};
auto m = std::make_shared<ngraph::pattern::Matcher>(write_label, "tensorarray");
this->register_matcher(m, callback);
}

View File

@@ -0,0 +1,27 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/pass/graph_rewrite.hpp"
#include "openvino/pass/pass.hpp"
namespace ov {
namespace frontend {
namespace paddle {
namespace pass {
class TransformTensorArray : public ov::pass::MatcherPass {
public:
OPENVINO_RTTI("ov::frontend::paddle::pass::TransformTensorArray");
TransformTensorArray(std::vector<std::shared_ptr<Model>> functions);
private:
std::vector<std::shared_ptr<Model>> m_functions;
};
} // namespace pass
} // namespace paddle
} // namespace frontend
} // namespace ov

View File

@@ -0,0 +1,104 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "internal/pass/transform_while.hpp"
#include <ngraph/ngraph.hpp>
#include <ngraph/pattern/matcher.hpp>
#include <ngraph/pattern/op/or.hpp>
#include <ngraph/pattern/op/wrap_type.hpp>
#include <ngraph/rt_info.hpp>
#include <ngraph/variant.hpp>
#include <transformations/common_optimizations/fold_subgraph_empty_inputs.hpp>
#include "default_opset.hpp"
#include "internal/op/conditional_block.hpp"
#include "internal/op/tensorarray_write.hpp"
#include "internal/op/while.hpp"
#include "openvino/frontend/paddle/exception.hpp"
#include "openvino/op/util/op_types.hpp"
#include "openvino/pass/constant_folding.hpp"
#include "openvino/pass/pattern/op/label.hpp"
using namespace std;
using namespace ov;
using namespace ov::pass;
using namespace ov::frontend::paddle::op::default_opset;
// Transform Paddle "while" to OpenVINO Loop op.
// "set_merged_input" is used for possible cases like TensorArray
// when it is both the input and the output to the loop.
// The reason why not using concat the output (i.e. "get_concatenated_slices") here is that,
// it would complicate processing TensorArray.
// TensorArray could be in loop body, but it could not always append something;
// TensorArray could be a non-empty input of the loop body, which needs extra concat.
// What's more, we have to tell which output is of TensorArray type to concate.
ov::frontend::paddle::pass::TransformWhile::TransformWhile(std::vector<std::shared_ptr<Model>> functions) {
const auto while_label = ngraph::pattern::wrap_type<ov::op::internal::While>();
matcher_pass_callback callback = [functions](pattern::Matcher& m) -> bool {
const auto& while_node = std::dynamic_pointer_cast<ov::op::internal::While>(m.get_match_root());
if (!while_node)
return false;
const auto& inputs = while_node->input_values();
const auto trip_count = Constant::create(element::i64, {1}, {-1});
const auto& cond = inputs.back();
const auto cond_name = cond.get_node_shared_ptr()->get_friendly_name();
auto loop = std::make_shared<Loop>(trip_count, cond);
ov::pass::disable_fold_subgraph_empty_inputs(loop);
const auto block_idx = while_node->get_subblock_index();
const auto sub_model = functions[block_idx];
loop->set_function(sub_model);
const auto& parameters = sub_model->get_parameters();
const auto submodel_outputs = sub_model->outputs();
const auto is_exist = [&submodel_outputs](const std::string& name) {
for (const auto& out : submodel_outputs) {
if (out.get_any_name() == name)
return true;
}
return false;
};
for (size_t i = 0; i < parameters.size(); i++) {
const auto names = inputs[i].get_names();
std::string param_name;
if (!names.empty()) {
param_name = *names.begin();
}
if (!param_name.empty() && is_exist(param_name)) {
auto out_node = sub_model->output(param_name);
loop->set_merged_input(parameters[i], inputs[i], out_node);
} else {
loop->set_invariant_input(parameters[i], inputs[i]);
}
}
int64_t idx = -1;
for (size_t i = 0; i < sub_model->get_results().size(); i++) {
if (sub_model->output(i).get_tensor().get_any_name() == cond_name)
idx = static_cast<int64_t>(i);
}
FRONT_END_GENERAL_CHECK(idx != -1, "could not find condition node in outputs.");
loop->set_special_body_ports(Loop::SpecialBodyPorts{-1, idx});
// replace output
const auto& results = sub_model->get_results();
for (size_t i = 0; i < results.size(); i++) {
auto out = loop->get_iter_value(results[i], -1);
while_node->output(i).replace(out);
}
loop->add_node_control_dependents(while_node);
loop->add_node_control_dependencies(while_node);
while_node->clear_control_dependents();
loop->set_friendly_name(while_node->get_friendly_name());
copy_runtime_info(while_node, loop);
return true;
};
auto m = std::make_shared<ngraph::pattern::Matcher>(while_label, "while_loop");
this->register_matcher(m, callback);
}

View File

@@ -0,0 +1,27 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/pass/graph_rewrite.hpp"
#include "openvino/pass/pass.hpp"
namespace ov {
namespace frontend {
namespace paddle {
namespace pass {
class TransformWhile : public ov::pass::MatcherPass {
public:
OPENVINO_RTTI("ov::frontend::paddle::pass::TransformWhile");
TransformWhile(std::vector<std::shared_ptr<Model>> functions);
private:
std::vector<std::shared_ptr<Model>> m_functions;
};
} // namespace pass
} // namespace paddle
} // namespace frontend
} // namespace ov

View File

@@ -0,0 +1,50 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "internal/op/conditional_block.hpp"
#include "default_opset.hpp"
#include "internal/op/while.hpp"
#include "openvino/frontend/paddle/node_context.hpp"
namespace ov {
namespace frontend {
namespace paddle {
namespace op {
NamedOutputs conditional_block(const NodeContext& node) {
const auto cond = node.get_input("Cond");
const auto sub_block = node.get_attribute<int32_t>("sub_block");
const auto is_scalar_condition = node.get_attribute<bool>("is_scalar_condition", true);
const auto outputs_info = node.get_output_port_infos("Out");
std::shared_ptr<Node> placehodler;
if (node.has_input("Input")) {
const auto inputs = node.get_ng_inputs("Input");
placehodler = std::make_shared<ov::op::internal::ConditionalBlock>(inputs,
cond,
is_scalar_condition,
sub_block,
outputs_info);
} else {
placehodler =
std::make_shared<ov::op::internal::ConditionalBlock>(cond, is_scalar_condition, sub_block, outputs_info);
}
const auto outputs = placehodler->outputs();
auto out_names = node.get_output_names();
auto it = std::find(out_names.begin(), out_names.end(), "Out");
PADDLE_OP_CHECK(node, it != out_names.end(), "Expected output not found");
NamedOutputs named_outputs;
for (const auto& output : outputs) {
named_outputs[*it].push_back(output);
}
return named_outputs;
}
} // namespace op
} // namespace paddle
} // namespace frontend
} // namespace ov

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "default_opset.hpp"
#include "openvino/frontend/paddle/node_context.hpp"
namespace ov {
namespace frontend {
namespace paddle {
namespace op {
NamedOutputs lod_array_length(const NodeContext& node) {
using namespace default_opset;
const auto x = node.get_input("X");
const auto shape = std::make_shared<default_opset::ShapeOf>(x);
// here simply get the length along the concated axis.
// we've lost the original tensor array length actually.
// luckily it's equalent since all elements are concated.
const auto const_1_node = Constant::create(element::i64, {1}, {1});
const auto const_2_node = Constant::create(element::i64, {1}, {2});
const auto len = std::make_shared<StridedSlice>(shape,
const_1_node,
const_2_node,
std::vector<int64_t>{0},
std::vector<int64_t>{0});
return node.default_single_output_mapping({len}, {"Out"});
}
} // namespace op
} // namespace paddle
} // namespace frontend
} // namespace ov

View File

@@ -0,0 +1,68 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "default_opset.hpp"
#include "openvino/frontend/paddle/node_context.hpp"
namespace ov {
namespace frontend {
namespace paddle {
namespace op {
NamedOutputs select_input(const NodeContext& node) {
const auto x = node.get_ng_inputs("X");
const auto mask = node.get_input("Mask");
PADDLE_OP_CHECK(node, x.size() == 2, "select_input needs 2 input nodes.");
const auto cond = std::make_shared<default_opset::Convert>(mask, element::boolean);
const auto ps0 = x[0].get_partial_shape();
const auto ps1 = x[1].get_partial_shape();
int idx0 = -1;
int idx1 = -1;
if (ps0.compatible(ps1)) {
idx0 = 0;
idx1 = 1;
} else {
// paddle detection model code is wrong and will result a dynamic rank model:
// https://github.com/PaddlePaddle/PaddleDetection/blob/16e3d7408161713c765886cfb952f98d9f68713c/ppdet/modeling/layers.py#L407
// workaround: check the rank and remove the wrong condition
if (ps0.rank() != ps1.rank()) {
const auto fix_idx = [&](int idx) {
const auto ps = x[idx].get_partial_shape();
if (ps.is_static()) {
const Shape shape(ps.get_shape());
const auto size = std::accumulate(shape.begin(), shape.end(), size_t{1}, std::multiplies<size_t>());
if (size == 0)
return 1 - idx;
}
return idx;
};
idx0 = fix_idx(0);
idx1 = fix_idx(1);
}
PADDLE_OP_CHECK(node, idx0 >= 0, "input shapes should be compatible.");
}
// paddle two branch may produce dynamic shape, use 'if' to satisfy it
const auto ps0_new = x[idx0].get_partial_shape();
const auto ps1_new = x[idx1].get_partial_shape();
const auto if_node = std::make_shared<default_opset::If>(cond);
const auto then_param = std::make_shared<default_opset::Parameter>(x[idx1].get_element_type(), ps1_new);
const auto then_result = std::make_shared<default_opset::Result>(then_param);
const auto then_branch = std::make_shared<Model>(ResultVector{then_result}, ParameterVector{then_param});
const auto else_param = std::make_shared<default_opset::Parameter>(x[idx0].get_element_type(), ps0_new);
const auto else_result = std::make_shared<default_opset::Result>(else_param);
const auto else_branch = std::make_shared<Model>(ResultVector{else_result}, ParameterVector{else_param});
if_node->set_then_body(then_branch);
if_node->set_else_body(else_branch);
if_node->set_input(x[idx1], then_param, nullptr);
if_node->set_input(x[idx0], nullptr, else_param);
if_node->set_output(then_result, else_result);
return node.default_single_output_mapping({if_node}, {"Out"});
}
} // namespace op
} // namespace paddle
} // namespace frontend
} // namespace ov

View File

@@ -0,0 +1,49 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <limits.h>
#include "default_opset.hpp"
#include "openvino/frontend/paddle/node_context.hpp"
namespace ov {
namespace frontend {
namespace paddle {
namespace op {
// Paddle TensorArray is not natively supported by OpenVINO.
// Here paddle frontend only partially support following circumstances:
// 1. TensorArray could be indexed with paddle slice op.
// We only support slice along axis 0 with only 1 element in TensorArray for now,
// with unsqueezing the element along axis 0 manually.
// 2. TensoArray could be tranfered to tensor with paddle tensor_array_to_tensor op.
// The elements in it should be concated along an axis.
// We only support concat along axis 0 for now. paddle.concat always along axis 0.
// what's more, we only support the pattern of "TensorArrayLength<->TensorArrayWrite" for now, which
// is tranformed togther. That means, tensorarray are always appended at the end.
NamedOutputs tensor_array_to_tensor(const NodeContext& node) {
using namespace default_opset;
const auto x = node.get_input("X");
auto axis = node.get_attribute<int32_t>("axis", 0);
PADDLE_OP_CHECK(node, axis == 0, "axis should be 0, got: ", axis);
// All elements in TensorArray have already been unsqueezed and concated. So here
// just squeeze. We use reshape instead because the tensorarray could be empty, and
// it looks squeeze would fail with empty tensor.
const auto shape = std::make_shared<ShapeOf>(x, element::Type_t::i32);
const auto const_1_node = Constant::create(element::i32, {1}, {1});
const auto const_max_node = Constant::create(element::i32, {1}, {INT_MAX});
const auto new_shape = std::make_shared<StridedSlice>(shape,
const_1_node,
const_max_node,
std::vector<int64_t>{0},
std::vector<int64_t>{0});
auto placeholder = std::make_shared<Reshape>(x, new_shape, false);
return node.default_single_output_mapping({placeholder}, {"Out"});
}
} // namespace op
} // namespace paddle
} // namespace frontend
} // namespace ov

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "internal/op/while.hpp"
#include "default_opset.hpp"
#include "openvino/frontend/paddle/node_context.hpp"
namespace ov {
namespace frontend {
namespace paddle {
namespace op {
using namespace default_opset;
NamedOutputs while_(const NodeContext& node) {
const auto data = node.get_ng_inputs("X");
const auto cond = node.get_input("Condition");
const auto sub_block = node.get_attribute<int32_t>("sub_block");
auto outputs_info = node.get_output_port_infos("Out");
ov::OutputVector inputs = data;
inputs.push_back(cond);
NamedOutputs named_outputs;
named_outputs["Out"] = std::make_shared<ov::op::internal::While>(inputs, sub_block, outputs_info)->outputs();
return named_outputs;
}
} // namespace op
} // namespace paddle
} // namespace frontend
} // namespace ov

View File

@@ -0,0 +1,24 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "default_opset.hpp"
#include "internal/op/tensorarray_write.hpp"
#include "openvino/frontend/paddle/node_context.hpp"
namespace ov {
namespace frontend {
namespace paddle {
namespace op {
NamedOutputs write_to_array(const NodeContext& node) {
const auto x = node.get_input("X");
const auto index = node.get_input("I");
auto placehodler = std::make_shared<ov::op::internal::TensorArrayWrite>(x, index);
return node.default_single_output_mapping({placehodler}, {"Out"});
}
} // namespace op
} // namespace paddle
} // namespace frontend
} // namespace ov

View File

@@ -18,6 +18,7 @@ OP_CONVERTER(cast);
OP_CONVERTER(ceil);
OP_CONVERTER(clip);
OP_CONVERTER(concat);
OP_CONVERTER(conditional_block);
OP_CONVERTER(conv2d);
OP_CONVERTER(conv2d_transpose);
OP_CONVERTER(cumsum);
@@ -53,6 +54,7 @@ OP_CONVERTER(layer_norm);
OP_CONVERTER(leaky_relu);
OP_CONVERTER(less_than);
OP_CONVERTER(linear_interp_v2);
OP_CONVERTER(lod_array_length);
OP_CONVERTER(log);
OP_CONVERTER(logical_and);
OP_CONVERTER(logical_not);
@@ -81,6 +83,7 @@ OP_CONVERTER(reshape2);
OP_CONVERTER(rnn);
OP_CONVERTER(roi_align);
OP_CONVERTER(scale);
OP_CONVERTER(select_input);
OP_CONVERTER(shape);
OP_CONVERTER(slice);
OP_CONVERTER(softmax);
@@ -94,12 +97,15 @@ OP_CONVERTER(strided_slice);
OP_CONVERTER(sum);
OP_CONVERTER(swish);
OP_CONVERTER(tanh);
OP_CONVERTER(tensor_array_to_tensor);
OP_CONVERTER(tile);
OP_CONVERTER(top_k_v2);
OP_CONVERTER(transpose2);
OP_CONVERTER(trilinear_interp_v2);
OP_CONVERTER(unsqueeze);
OP_CONVERTER(where);
OP_CONVERTER(while_);
OP_CONVERTER(write_to_array);
OP_CONVERTER(where_index);
OP_CONVERTER(yolo_box);
OP_CONVERTER(generate_proposals_v2);
@@ -117,6 +123,7 @@ std::map<std::string, CreatorFunction> get_supported_ops() {
{"ceil", op::ceil},
{"clip", op::clip},
{"concat", op::concat},
{"conditional_block", op::conditional_block},
{"conv2d", op::conv2d},
{"conv2d_transpose", op::conv2d_transpose},
{"cumsum", op::cumsum},
@@ -145,6 +152,7 @@ std::map<std::string, CreatorFunction> get_supported_ops() {
{"gather", op::gather},
{"gather_nd", op::gather_nd},
{"gelu", op::gelu},
{"generate_proposals_v2", op::generate_proposals_v2},
{"greater_equal", op::elementwise_greater_equal},
{"greater_than", op::greater_than},
{"group_norm", op::group_norm},
@@ -154,6 +162,7 @@ std::map<std::string, CreatorFunction> get_supported_ops() {
{"leaky_relu", op::leaky_relu},
{"less_than", op::less_than},
{"linear_interp_v2", op::linear_interp_v2},
{"lod_array_length", op::lod_array_length},
{"log", op::log},
{"logical_and", op::logical_and},
{"logical_not", op::logical_not},
@@ -185,6 +194,7 @@ std::map<std::string, CreatorFunction> get_supported_ops() {
{"rnn", op::rnn},
{"roi_align", op::roi_align},
{"scale", op::scale},
{"select_input", op::select_input},
{"shape", op::shape},
{"slice", op::slice},
{"softmax", op::softmax},
@@ -199,12 +209,15 @@ std::map<std::string, CreatorFunction> get_supported_ops() {
{"swish", op::swish},
{"sync_batch_norm", op::batch_norm},
{"tanh", op::tanh},
{"tensor_array_to_tensor", op::tensor_array_to_tensor},
{"tile", op::tile},
{"top_k_v2", op::top_k_v2},
{"transpose2", op::transpose2},
{"trilinear_interp_v2", op::trilinear_interp_v2},
{"unsqueeze2", op::unsqueeze},
{"where", op::where},
{"while", op::while_},
{"write_to_array", op::write_to_array},
{"where_index", op::where_index},
{"yolo_box", op::yolo_box},
{"generate_proposals_v2", op::generate_proposals_v2}};

View File

@@ -81,6 +81,8 @@ void FrontEndFuzzyOpTest::runConvertedModel(const std::shared_ptr<ngraph::Functi
addInputOutput<int32_t>(input, testCase, true);
} else if (input_dtype == element::i64) {
addInputOutput<int64_t>(input, testCase, true);
} else if (input_dtype == element::boolean) {
addInputOutput<bool>(input, testCase, true);
} else {
throw std::runtime_error("not supported dtype in" + input_dtype.get_type_name());
}

View File

@@ -37,6 +37,46 @@ TEST_F(TransformationTestsF, RemoveConcatZeroDimInputStaticShape) {
}
}
TEST_F(TransformationTestsF, DisableRemoveConcatZeroDimInputStaticShape) {
auto input1 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1, 2, 3});
auto input2 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1, 0, 3});
auto input3 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1, 2, 3});
int64_t axis = 1;
{
auto concat = std::make_shared<ov::opset8::Concat>(ov::OutputVector{input1, input2, input3}, axis);
ov::pass::disable_remove_concat_zerodim_input(concat);
function = std::make_shared<ov::Model>(ov::NodeVector{concat}, ov::ParameterVector{input1, input2, input3});
manager.register_pass<ov::pass::RemoveConcatZeroDimInput>();
}
{
auto concat = std::make_shared<ov::opset8::Concat>(ov::OutputVector{input1, input2, input3}, axis);
function_ref = std::make_shared<ov::Model>(ov::NodeVector{concat}, ov::ParameterVector{input1, input2, input3});
}
}
TEST_F(TransformationTestsF, DisableRemoveConcatZeroDimInputPartiallyKnowShape) {
auto input1 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{-1, -1, -1});
auto input2 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{-1, 0, -1});
auto input3 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{-1, -1, -1});
int64_t axis = 1;
{
auto concat = std::make_shared<ov::opset8::Concat>(ov::OutputVector{input1, input2, input3}, axis);
ov::pass::disable_remove_concat_zerodim_input(concat);
function = std::make_shared<ov::Model>(ov::NodeVector{concat}, ov::ParameterVector{input1, input2, input3});
manager.register_pass<ov::pass::RemoveConcatZeroDimInput>();
}
{
auto concat = std::make_shared<ov::opset8::Concat>(ov::OutputVector{input1, input2, input3}, axis);
function_ref = std::make_shared<ov::Model>(ov::NodeVector{concat}, ov::ParameterVector{input1, input2, input3});
}
}
TEST_F(TransformationTestsF, RemoveConcatZeroDimInputSubgraph) {
auto input1 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1, 2, 3});
auto input3 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1, 2, 3});

View File

@@ -164,3 +164,47 @@ TEST(TransformationTests, UnrollIfWithSplitInput) {
auto res = compare_functions(f, f_ref);
ASSERT_TRUE(res.first) << res.second;
}
TEST(TransformationTests, UnrollIfCondIsTrueMultiOutput) {
std::shared_ptr<ngraph::Function> f(nullptr), f_ref(nullptr);
{
auto data = std::make_shared<ngraph::opset6::Parameter>(ngraph::element::f32, ngraph::Shape{ 3 });
auto X = std::make_shared<ngraph::opset6::VariadicSplit>(data, ngraph::opset6::Constant::create(ngraph::element::i32, {1}, {0}),
ngraph::opset6::Constant::create(ngraph::element::i32, {2}, {1, 2}));
auto cond = std::make_shared<ngraph::opset1::Constant>(ngraph::element::boolean, ngraph::Shape{ 1 }, true);
auto if_op = std::make_shared<ngraph::opset8::If>(cond);
auto Xt = std::make_shared<ngraph::opset6::Parameter>(ngraph::element::f32, ngraph::Shape{ 2 });
auto then_op_result = std::make_shared<ngraph::opset1::Result>(Xt);
auto then_body = std::make_shared<ngraph::Function>(ngraph::OutputVector{ then_op_result }, ngraph::ParameterVector{ Xt });
auto Xe = std::make_shared<ngraph::opset6::Parameter>(ngraph::element::f32, ngraph::Shape{ 2 });
auto else_op_result = std::make_shared<ngraph::opset1::Result>(Xe);
auto else_body = std::make_shared<ngraph::Function>(ngraph::OutputVector{ else_op_result }, ngraph::ParameterVector{ Xe });
if_op->set_then_body(then_body);
if_op->set_else_body(else_body);
if_op->set_input(X->output(1), Xt, Xe);
if_op->set_output(then_op_result, else_op_result);
auto if_result = std::make_shared<ngraph::opset1::Result>(if_op);
f = std::make_shared<ngraph::Function>(ngraph::NodeVector{ if_result }, ngraph::ParameterVector{ data });
ngraph::pass::Manager manager;
manager.register_pass<ngraph::pass::InitNodeInfo>();
manager.register_pass<ngraph::pass::UnrollIf>();
manager.run_passes(f);
ASSERT_NO_THROW(check_rt_info(f));
}
{
auto data = std::make_shared<ngraph::opset6::Parameter>(ngraph::element::f32, ngraph::Shape{ 3 });
auto X = std::make_shared<ngraph::opset6::VariadicSplit>(data, ngraph::opset6::Constant::create(ngraph::element::i32, {1}, {0}),
ngraph::opset6::Constant::create(ngraph::element::i32, {2}, {1, 2}));
auto if_result = std::make_shared<ngraph::opset1::Result>(X->output(1));
f_ref = std::make_shared<ngraph::Function>(ngraph::NodeVector{ if_result }, ngraph::ParameterVector{ data });
}
auto res = compare_functions(f, f_ref);
ASSERT_TRUE(res.first) << res.second;
}

View File

@@ -118,6 +118,11 @@ bool equal_type_and_partial_shape(const InOut1& lhs, const InOut2& rhs) {
return lhs.get_element_type() == rhs.get_element_type() && lhs.get_partial_shape() == rhs.get_partial_shape();
}
template <typename InOut1, typename InOut2>
bool equal_type_and_partial_shape_compatible(const InOut1& lhs, const InOut2& rhs) {
return lhs.get_element_type() == rhs.get_element_type() && lhs.get_partial_shape().compatible(rhs.get_partial_shape());
}
class NodeAndInputDescription {
public:
using SubGraphOp = ov::op::util::SubGraphOp;
@@ -187,7 +192,8 @@ public:
return true;
} else if (m_description->get_type_info() == SubGraphOp::MergedInputDescription::get_type_info_static() ||
m_description->get_type_info() == SubGraphOp::InvariantInputDescription::get_type_info_static()) {
return equal_type_and_partial_shape(*m_parameter, m_input);
// If loop op has back edge it may change the parameter to dynamic. The shape will be different with the initial op
return equal_type_and_partial_shape_compatible(*m_parameter, m_input);
}
std::stringstream ss;