Repository: rwth-i6/returnn

Issue:
PadLayer exception for no-op
`data` e.g. of shape [B,D,F].
```
{'class': 'pad', 'mode': 'constant', 'axes': 'spatial', 'padding': [(0, 0)], 'from': 'data', 'value': 0}
```
Exception:
```
  File "/home/az/Programmierung/returnn/returnn/tf/network.py", line 1001, in TFNetwork._create_layer
    line: layer.output.sanity_check()
    locals:
      layer = <local> <PadLayer 'output' out_type=Data{[B,F|F'feature:data'(5),T|'output_pad0'[?]]}>
      layer.output = <local> Data{'output_output', [B,F|F'feature:data'(5),T|'output_pad0'[?]]}
      layer.output.sanity_check = <local> <bound method Data.sanity_check of Data{'output_output', [B,F|F'feature:data'(5),T|'output_pad0'[?]]}>
  File "/home/az/Programmierung/returnn/returnn/tf/util/data.py", line 1670, in Data.sanity_check
    line: assert tag.dyn_size is not None
    locals:
      tag = <local> DimensionTag{'output_pad0'[?]}
      tag.dyn_size = <local> None
AssertionError: 
```



Discussion:

Resolving patch:
diff --git a/returnn/tf/layers/basic.py b/returnn/tf/layers/basic.py
--- a/returnn/tf/layers/basic.py
+++ b/returnn/tf/layers/basic.py
@@ -2554,7 +2554,9 @@ def __init__(self, axes, padding, value=0, mode="constant", **kwargs):
     for i, a in enumerate(axes):
       paddings[a] = padding[i]
     mode = mode.upper()
-    if mode == "REPLICATION":
+    if all(sum(p) == 0 for p in padding):
+      self.output.placeholder = self.input_data.placeholder
+    elif mode == "REPLICATION":
       self.output.placeholder = tf_util.pad_replicate(self.input_data.placeholder, axes, padding)
     else:
       self.output.placeholder = tf.pad(self.input_data.placeholder, paddings=paddings, mode=mode, constant_values=value)
@@ -2612,6 +2614,8 @@ def get_out_data_from_opts(cls, name, axes, padding, sources=(), **kwargs):
     padding = cls._transform_padding(padding=padding, axes=axes)
     dim_tags = data.dim_tags
     for i, a in enumerate(axes):
+      if sum(padding[i]) == 0:
+        continue
       tag = dim_tags[a]
       dim = None if tag.dimension is None else (tag.dimension + sum(padding[i]))
       tag = DimensionTag(kind=tag.kind, description="%s_pad%i" % (name, i), dimension=dim, derived_from_tag=tag)


Test patch:
diff --git a/tests/test_TFNetworkLayer.py b/tests/test_TFNetworkLayer.py
--- a/tests/test_TFNetworkLayer.py
+++ b/tests/test_TFNetworkLayer.py
@@ -165,6 +165,28 @@ def test_PadLayer_feature():
       numpy.testing.assert_array_equal(padded_left_ref - out_v[:, :, -padding[1]:], 0)
 
 
+def test_PadLayer_no_op():
+  # https://github.com/rwth-i6/returnn/issues/687
+  n_batch, n_time, n_in = 7, 3, 5
+  config = Config({
+    "extern_data": {"data": {"shape": (n_in, None)}},  # [B,D,T]
+    "debug_print_layer_output_template": True,
+  })
+  with make_scope() as session:
+    net = TFNetwork(config=config)
+    net.construct_from_dict({
+      "output": {'class': 'pad', 'mode': 'constant', 'axes': 'spatial', 'padding': [(0, 0)], 'from': 'data', 'value': 0}
+    })
+    out = net.get_default_output_layer().output
+    out_t = out.placeholder
+    assert out_t.shape.as_list() == [None, n_in, None]
+    in_v = numpy.arange(0, n_batch * n_time * n_in).astype("float32").reshape((n_batch, n_in, n_time))
+    out_v = session.run(out_t, feed_dict={net.extern_data.data["data"].placeholder: in_v})
+    assert isinstance(out_v, numpy.ndarray)
+    assert out_v.shape == (n_batch, n_in, n_time)
+    numpy.testing.assert_array_equal(in_v, out_v)
+
+
 def test_concat_sources():
   with make_scope() as session:
     network = TFNetwork(train_flag=True, extern_data=ExternData())