Visitar URL original
Fix ops.tile shape inference issue on TensorFlow backend (#21860) · keras-team/keras@0771c80 · GitHub
Skip to content

Commit 0771c80

Browse files
authored
Fix ops.tile shape inference issue on TensorFlow backend (#21860)
* Fix ops.tile shape inference issue on TensorFlow backend When ops.tile is called inside a Layer's call method with concrete integer repeats, the TensorFlow backend was converting those repeats to a tensor, which prevented TensorFlow's shape inference from properly determining the output shape. This resulted in all-None shapes. Changes: 1. Modified TensorFlow backend's tile() to detect when repeats contains only concrete integer values and pass them directly to tf.tile as a Python list/tuple instead of converting to a tensor. This allows TensorFlow's shape inference to work correctly. 2. Enhanced ops.numpy.Tile.compute_output_spec() to handle symbolic repeat values more gracefully by checking if each repeat is a concrete integer before attempting multiplication. 3. Added regression tests to verify shape inference works correctly both in direct ops.tile calls and when used inside Layer.call(). Fixes #20914 Signed-off-by: Samaresh Kumar Singh <ssam3003@gmail.com> * Fix code formatting 1. Fix line length issues in tensorflow/numpy.py 2. Remove extra blank lines 3. Use specific exception types instead of bare except 4. Improve docstring formatting * Regenerate API files with proper formatting. * Apply proper formatting via shell/api_gen.sh * Address review comments: simplify tile implementation and refactor tests * Remove unnecessary dynamic rank check * Fix isinstance check to use x_size instead of repeat * Skip tile shape inference test for OpenVINO backend * Handle int repeats in tile function per review feedback --------- Signed-off-by: Samaresh Kumar Singh <ssam3003@gmail.com>
1 parent 024c96d commit 0771c80

3 files changed

Lines changed: 54 additions & 16 deletions

File tree

‎keras/src/backend/tensorflow/numpy.py‎

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2755,19 +2755,33 @@ def round(x, decimals=0):
27552755

27562756
def tile(x, repeats):
27572757
x = convert_to_tensor(x)
2758-
repeats = tf.reshape(convert_to_tensor(repeats, dtype="int32"), [-1])
2759-
repeats_size = tf.size(repeats)
2760-
repeats = tf.pad(
2761-
repeats,
2762-
[[tf.maximum(x.shape.rank - repeats_size, 0), 0]],
2763-
constant_values=1,
2764-
)
2765-
x_shape = tf.pad(
2766-
tf.shape(x),
2767-
[[tf.maximum(repeats_size - x.shape.rank, 0), 0]],
2768-
constant_values=1,
2769-
)
2770-
x = tf.reshape(x, x_shape)
2758+
2759+
# Convert repeats to a list (works for both sequences and 1D tensors)
2760+
if isinstance(repeats, int):
2761+
repeats = [repeats]
2762+
else:
2763+
repeats = [v for v in repeats]
2764+
2765+
# Process list elements: convert concrete scalar tensors to Python ints
2766+
processed_repeats = []
2767+
for r in repeats:
2768+
if hasattr(r, "numpy") and r.shape == ():
2769+
processed_repeats.append(int(r.numpy()))
2770+
else:
2771+
processed_repeats.append(r)
2772+
repeats = processed_repeats
2773+
2774+
# Get x rank
2775+
x_rank = x.shape.rank
2776+
2777+
# Pad repeats if needed
2778+
if len(repeats) < x_rank:
2779+
repeats = [1] * (x_rank - len(repeats)) + repeats
2780+
2781+
# Add dimensions to x if needed using tf.expand_dims
2782+
while len(repeats) > x.shape.rank:
2783+
x = tf.expand_dims(x, 0)
2784+
27712785
return tf.tile(x, repeats)
27722786

27732787

‎keras/src/ops/numpy.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6451,17 +6451,20 @@ def compute_output_spec(self, x):
64516451
repeats = self.repeats
64526452
if isinstance(repeats, int):
64536453
repeats = [repeats]
6454+
else:
6455+
repeats = list(repeats)
6456+
64546457
if len(x_shape) > len(repeats):
64556458
repeats = [1] * (len(x_shape) - len(repeats)) + repeats
64566459
else:
64576460
x_shape = [1] * (len(repeats) - len(x_shape)) + x_shape
64586461

64596462
output_shape = []
64606463
for x_size, repeat in zip(x_shape, repeats):
6461-
if x_size is None:
6462-
output_shape.append(None)
6463-
else:
6464+
if isinstance(x_size, int):
64646465
output_shape.append(x_size * repeat)
6466+
else:
6467+
output_shape.append(None)
64656468
return KerasTensor(output_shape, dtype=x.dtype)
64666469

64676470

‎keras/src/ops/numpy_test.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,6 +1840,10 @@ def test_tile(self):
18401840
self.assertEqual(knp.tile(x, [1, 2]).shape, (None, 6))
18411841
self.assertEqual(knp.tile(x, [2, 1, 2]).shape, (2, None, 6))
18421842

1843+
# Test with multi-dimensional input
1844+
x = KerasTensor((None, 3, 2, 2))
1845+
self.assertEqual(knp.tile(x, [1, 2, 1, 1]).shape, (None, 6, 2, 2))
1846+
18431847
def test_trace(self):
18441848
x = KerasTensor((None, 3, None, 5))
18451849
self.assertEqual(knp.trace(x).shape, (None, 5))
@@ -9614,3 +9618,20 @@ def call(self, x):
96149618
model.compile(jit_compile=jit_compile)
96159619

96169620
model.predict(np.random.randn(1, 8))
9621+
9622+
9623+
class TileTest(testing.TestCase):
9624+
@pytest.mark.skipif(
9625+
keras.config.backend() == "openvino",
9626+
reason="`tile` is not supported with openvino backend",
9627+
)
9628+
def test_tile_shape_inference_in_layer(self):
9629+
class TileLayer(keras.layers.Layer):
9630+
def call(self, x):
9631+
repeats = [1, 2, 1, 1]
9632+
return knp.tile(x, repeats)
9633+
9634+
inputs = keras.Input(shape=(3, 2, 2))
9635+
output = TileLayer()(inputs)
9636+
9637+
self.assertEqual(output.shape, (None, 6, 2, 2))

0 commit comments

Comments
 (0)