From e0a3aa142205f283da852b8f12202637ffed80d3 Mon Sep 17 00:00:00 2001 From: Evgeny Kotov Date: Wed, 3 Jun 2026 12:57:41 +0200 Subject: [PATCH] [TF FE] Use Convert instead of ConvertLike for Slice stop The TF/TFLite Slice translator builds the absolute `stop` as Select(Less(size, 0), ConvertLike(ShapeOf(data), size), Add(start, size)). v8::Slice resolves `stop` through value-bounds propagation (evaluate_both_bounds), not constant folding. ConvertLike implements only constant_fold, not evaluate_lower/upper, so the bound walk breaks at ConvertLike before reaching Select, and the Slice output is left dynamic even when the data shape and start/size are fully static. This trips downstream consumers that require a static shape. Build the negative-size branch with v0::Convert (to size's element type) instead of v1::ConvertLike. Convert is value-identical here but already implements bound evaluation, so bounds propagate through the whole cascade: Select resolves its statically-false condition and Slice infers a static output during validate_and_infer_types(), with no folding pass. This also makes the size = -1 ("to end") case static whenever the data shape is static. Add a TF frontend regression test (slice_static_shape.pbtxt) that converts a static-input Slice without MOC and asserts the Slice output stays static. --- .../tests/convert_tricky_models.cpp | 24 ++++ .../models_pbtxt/slice_static_shape.pbtxt | 113 ++++++++++++++++++ .../tensorflow_common/src/op/slice.cpp | 7 +- 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 src/frontends/tensorflow/tests/test_models/models_pbtxt/slice_static_shape.pbtxt diff --git a/src/frontends/tensorflow/tests/convert_tricky_models.cpp b/src/frontends/tensorflow/tests/convert_tricky_models.cpp index 100165a4b07391..e659542f897e05 100644 --- a/src/frontends/tensorflow/tests/convert_tricky_models.cpp +++ b/src/frontends/tensorflow/tests/convert_tricky_models.cpp @@ -100,6 +100,30 @@ TEST(FrontEndConvertTrickyModels, undefined_input_shape) { } } +TEST(FrontEndConvertTrickyModels, slice_static_shape_is_static) { + shared_ptr model; + try { + model = convert_model("slice_static_shape/slice_static_shape.pbtxt"); + } catch (std::exception& ex) { + ASSERT_TRUE(false) << ex.what(); + } + + // The shared TF/TFLite Slice translator builds + // stop = Select(Less(size, 0), Convert(ShapeOf(input)), Add(start, size)). + // Convert (unlike ConvertLike) propagates value bounds, so with a static input shape the Slice `stop` + // resolves via bounds-based shape inference and the Slice output stays static after frontend conversion + // (no MOC runs here). This is the regression gate for using Convert instead of ConvertLike. + bool found_slice = false; + for (const auto& node : model->get_ordered_ops()) { + if (as_type_ptr(node)) { + found_slice = true; + EXPECT_TRUE(node->get_output_partial_shape(0).is_static()); + EXPECT_TRUE(node->get_output_partial_shape(0).same_scheme(ov::PartialShape{2, 2, 4})); + } + } + ASSERT_TRUE(found_slice); +} + TEST(FrontEndConvertTrickyModels, simple_wide_and_deep) { shared_ptr model; try { diff --git a/src/frontends/tensorflow/tests/test_models/models_pbtxt/slice_static_shape.pbtxt b/src/frontends/tensorflow/tests/test_models/models_pbtxt/slice_static_shape.pbtxt new file mode 100644 index 00000000000000..6f87e4dbd3773e --- /dev/null +++ b/src/frontends/tensorflow/tests/test_models/models_pbtxt/slice_static_shape.pbtxt @@ -0,0 +1,113 @@ +node { + name: "input" + op: "Placeholder" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + dim { + size: 3 + } + dim { + size: 4 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + dim { + size: 3 + } + dim { + size: 4 + } + } + } + } +} +node { + name: "begin" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 3 + } + } + tensor_content: "\000\000\000\000\000\000\000\000\000\000\000\000" + } + } + } +} +node { + name: "size" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 3 + } + } + tensor_content: "\002\000\000\000\002\000\000\000\004\000\000\000" + } + } + } +} +node { + name: "slice_out" + op: "Slice" + input: "input" + input: "begin" + input: "size" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} +library { +} diff --git a/src/frontends/tensorflow_common/src/op/slice.cpp b/src/frontends/tensorflow_common/src/op/slice.cpp index 044a3ba1a830ff..d2e2e53925c35e 100644 --- a/src/frontends/tensorflow_common/src/op/slice.cpp +++ b/src/frontends/tensorflow_common/src/op/slice.cpp @@ -7,7 +7,7 @@ #include "common_op_table.hpp" #include "openvino/op/add.hpp" #include "openvino/op/broadcast.hpp" -#include "openvino/op/convert_like.hpp" +#include "openvino/op/convert.hpp" #include "openvino/op/less.hpp" #include "openvino/op/select.hpp" #include "openvino/op/shape_of.hpp" @@ -37,8 +37,11 @@ OutputVector translate_slice_op(const NodeContext& node) { // compute stop values in case negative sizes // since TensorFlow supports only -1 among negative sizes // assign stop values to the data shape + // Use Convert (not ConvertLike) so the value bounds of ShapeOf propagate through this node: Convert + // implements evaluate_lower/upper while ConvertLike does not, so bounds-based Slice shape inference can + // resolve `stop` and keep the output static when the data shape is static. Output stop_neg = make_shared(input); - stop_neg = make_shared(stop_neg, size); + stop_neg = make_shared(stop_neg, size.get_element_type()); // select the correct stop value based on a sign of size value auto negative_sizes_mask = make_shared(size, const_zero);