From 2a62d450edbaf49eac6b6108b53aa1c506915eac Mon Sep 17 00:00:00 2001 From: liubo-intel Date: Wed, 15 Jul 2026 13:20:03 +0800 Subject: [PATCH] Fix FastRCNN unsupported-operation crash on (dynamic-rank Broadcast) --- src/frontends/paddle/src/op/expand_v2.cpp | 22 +++- src/frontends/paddle/tests/op_fuzzy.cpp | 1 + .../gen_scripts/generate_expand_v2.py | 109 ++++++++++++++---- 3 files changed, 109 insertions(+), 23 deletions(-) diff --git a/src/frontends/paddle/src/op/expand_v2.cpp b/src/frontends/paddle/src/op/expand_v2.cpp index b2e28f36c2cc..f8e0b9303c9e 100644 --- a/src/frontends/paddle/src/op/expand_v2.cpp +++ b/src/frontends/paddle/src/op/expand_v2.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // +#include + #include "default_opset.hpp" #include "openvino/frontend/paddle/node_context.hpp" @@ -55,7 +57,25 @@ NamedOutputs expand_v2(const NodeContext& node) { // if -1 in shape we will copy the original value from input auto zero_node = Constant::create(ov::element::i32, {1}, {0}); auto mask_node = std::make_shared(shape_expected_node, zero_node); - auto fixed_shape_node = std::make_shared(mask_node, shape_expected_node, fixed_input_shape_node); + + // The target shape vector can end up with a dynamic length (e.g. when it is assembled from a + // dynamic-length Slice inside a control-flow body). That would make the Broadcast output rank + // dynamic, which some plugins (e.g. CPU) do not support. The expand output rank is statically + // known from the Paddle op output var descriptor, so pin the target shape length to that static + // rank to keep the Broadcast output rank static without constraining the (possibly dynamic) dims. + // A Gather with constant indices [0, out_rank) is used (rather than a Reshape) so the static + // length survives offline shape-of subgraph simplification / nop elimination: the gathered axis + // length is dynamic, so GatherNopElimination cannot fold it away. + const auto output_info = node.get_output_port_infos("Out"); + if (!output_info.empty() && output_info[0].second.rank().is_static()) { + const auto out_rank = static_cast(output_info[0].second.rank().get_length()); + std::vector indices(out_rank); + std::iota(indices.begin(), indices.end(), 0); + const auto gather_indices = Constant::create(element::i32, {out_rank}, indices); + const auto gather_axis = Constant::create(element::i32, {}, {0}); + fixed_shape_node = std::make_shared(fixed_shape_node, gather_indices, gather_axis); + } return node.default_single_output_mapping({std::make_shared(x, fixed_shape_node)}, {"Out"}); } diff --git a/src/frontends/paddle/tests/op_fuzzy.cpp b/src/frontends/paddle/tests/op_fuzzy.cpp index 613e9a15515f..24b4c99515d1 100644 --- a/src/frontends/paddle/tests/op_fuzzy.cpp +++ b/src/frontends/paddle/tests/op_fuzzy.cpp @@ -201,6 +201,7 @@ static const std::vector models{ std::string("expand_v2_tensor/expand_v2_tensor.pdmodel"), std::string("expand_v2_tensor_list/expand_v2_tensor_list.pdmodel"), std::string("expand_v2_tensor_list2/expand_v2_tensor_list2.pdmodel"), + std::string("expand_v2_conditional/expand_v2_conditional.pdmodel"), std::string("expand_as_v2_1/expand_as_v2_1.pdmodel"), std::string("expand_as_v2_2/expand_as_v2_2.pdmodel"), std::string("exp_test_float32/exp_test_float32.pdmodel"), diff --git a/src/frontends/paddle/tests/test_models/gen_scripts/generate_expand_v2.py b/src/frontends/paddle/tests/test_models/gen_scripts/generate_expand_v2.py index 1f2135ffc07c..c09b400cee08 100644 --- a/src/frontends/paddle/tests/test_models/gen_scripts/generate_expand_v2.py +++ b/src/frontends/paddle/tests/test_models/gen_scripts/generate_expand_v2.py @@ -12,10 +12,11 @@ data_type = 'float32' -def expand_v2(name:str, x, shape:list): +def expand_v2(name: str, x, shape: list): paddle.enable_static() - with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()): + with paddle.static.program_guard(paddle.static.Program(), + paddle.static.Program()): node_x = paddle.static.data(name='x', shape=x.shape, dtype=data_type) out = paddle.expand(node_x, shape=shape, name='expand_v2') @@ -24,23 +25,28 @@ def expand_v2(name:str, x, shape:list): # startup program will call initializer to initialize the parameters. exe.run(paddle.static.default_startup_program()) - outs = exe.run( - feed={'x': x}, - fetch_list=[out]) + outs = exe.run(feed={'x': x}, fetch_list=[out]) - saveModel(name, exe, feed_vars=[node_x], fetchlist=[out], - inputs=[x], outputs=[outs[0]], target_dir=sys.argv[1]) + saveModel(name, + exe, + feed_vars=[node_x], + fetchlist=[out], + inputs=[x], + outputs=[outs[0]], + target_dir=sys.argv[1]) return outs[0] -def expand_v2_tensor(name:str, x, out_shape, use_tensor_in_list): +def expand_v2_tensor(name: str, x, out_shape, use_tensor_in_list): paddle.enable_static() - with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()): + with paddle.static.program_guard(paddle.static.Program(), + paddle.static.Program()): node_x = paddle.static.data(name='x', shape=x.shape, dtype=data_type) if use_tensor_in_list: - out_shape[0] = paddle.assign(np.array((out_shape[0],)).astype('int32')) + out_shape[0] = paddle.assign( + np.array((out_shape[0], )).astype('int32')) out = paddle.expand(node_x, shape=out_shape, name='expand_v2') else: out_shape = np.array(out_shape).astype('int32') @@ -52,19 +58,24 @@ def expand_v2_tensor(name:str, x, out_shape, use_tensor_in_list): # startup program will call initializer to initialize the parameters. exe.run(paddle.static.default_startup_program()) - outs = exe.run( - feed={'x': x}, - fetch_list=[out]) + outs = exe.run(feed={'x': x}, fetch_list=[out]) - saveModel(name, exe, feed_vars=[node_x], fetchlist=[out], - inputs=[x], outputs=[outs[0]], target_dir=sys.argv[1]) + saveModel(name, + exe, + feed_vars=[node_x], + fetchlist=[out], + inputs=[x], + outputs=[outs[0]], + target_dir=sys.argv[1]) return outs[0] -def expand_as_v2(name:str, x, y): + +def expand_as_v2(name: str, x, y): paddle.enable_static() - with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()): + with paddle.static.program_guard(paddle.static.Program(), + paddle.static.Program()): node_x = paddle.static.data(name='x', shape=x.shape, dtype=data_type) node_y = paddle.static.data(name='y', shape=y.shape, dtype=data_type) out = paddle.expand_as(node_x, node_y, name='expand_as_v2') @@ -74,15 +85,64 @@ def expand_as_v2(name:str, x, y): # startup program will call initializer to initialize the parameters. exe.run(paddle.static.default_startup_program()) - outs = exe.run( - feed={'x': x, 'y': y}, - fetch_list=[out]) + outs = exe.run(feed={'x': x, 'y': y}, fetch_list=[out]) - saveModel(name, exe, feed_vars=[node_x, node_y], fetchlist=[out], - inputs=[x, y], outputs=[outs[0]], target_dir=sys.argv[1]) + saveModel(name, + exe, + feed_vars=[node_x, node_y], + fetchlist=[out], + inputs=[x, y], + outputs=[outs[0]], + target_dir=sys.argv[1]) return outs[0] + +def expand_v2_conditional(name: str, x, rows): + # expand_v2 nested inside a conditional_block, mirroring the control-flow + # structure of the FastRCNN model where the expand target shape is assembled + # at runtime. Exercises the expand_v2 translator path that pins the Broadcast + # output rank to the statically-known op output rank. + paddle.enable_static() + + with paddle.static.program_guard(paddle.static.Program(), + paddle.static.Program()): + node_x = paddle.static.data(name='x', shape=x.shape, dtype=data_type) + node_im = paddle.static.data(name='im', + shape=[-1, x.shape[-1]], + dtype=data_type) + + def true_fn(): + n = paddle.shape(node_im)[0].reshape([1]).astype('int32') + tgt = paddle.concat( + [n, paddle.assign(np.array([x.shape[-1]]).astype('int32'))]) + return paddle.expand(node_x, shape=tgt, name='expand_v2') + + def false_fn(): + return paddle.expand(node_x, shape=list(x.shape), name='expand_v2') + + out = paddle.static.nn.cond( + paddle.shape(node_im)[0] > 0, true_fn, false_fn) + + cpu = paddle.static.cpu_places(1) + exe = paddle.static.Executor(cpu[0]) + # startup program will call initializer to initialize the parameters. + exe.run(paddle.static.default_startup_program()) + + im = np.random.rand(rows, x.shape[-1]).astype(data_type) + outs = exe.run(feed={'x': x, 'im': im}, fetch_list=[out]) + + saveModel(name, + exe, + feed_vars=[node_x, node_im], + fetchlist=[out], + inputs=[x, im], + outputs=[outs[0]], + target_dir=sys.argv[1]) + + return outs[0] + + def main(): data = np.random.rand(1, 1, 6).astype(data_type) @@ -98,5 +158,10 @@ def main(): expand_as_v2("expand_as_v2_1", data_x, data_y1) expand_as_v2("expand_as_v2_2", data_x, data_y2) + # expand_v2 inside control flow (FastRCNN-like) + expand_v2_conditional("expand_v2_conditional", + np.random.rand(1, 4).astype(data_type), 3) + + if __name__ == "__main__": main()