Repository: rwth-i6/returnn

Issue:
CompareLayer: allow_broadcast_all_sources missing
The `CompareLayer` does not yet set the `allow_broadcast_all_sources` parameter of `Data.get_common_data` but instead leaves it on the default `NotSpecified` regardless of whether `out_shape` or `out_type` are set. The `CompareLayer` uses `Data.get_common_data` both in `__init__` and in `get_out_data_from_opts`. For the latter, we could use the same code as the `CombineLayer`: 

 ```
   if sources:
      allow_broadcast_all_sources = NotSpecified
      if out_shape is not None:
        allow_broadcast_all_sources = True
      elif out_type and isinstance(out_type, dict) and ("shape" in out_type or "dim_tags" in out_type):
        allow_broadcast_all_sources = True
      out_type_.update(
        Data.get_common_data(
          [s.output for s in sources], allow_broadcast_all_sources=allow_broadcast_all_sources).get_kwargs())
```

For the former, however, we currently cannot check whether `out_shape` is set because `out_shape` is not an attribute of `CompareLayer`. Should we set `self.out_shape` in `LayerBase`? What do you think? @albertz @Zettelkasten 


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
@@ -6892,11 +6892,11 @@ def __init__(self, kind="equal", value=None, **kwargs):
       assert len(self.sources) >= 2, "{} requires at least two elements to compare".format(self)
     op = getattr(tf, kind)  # e.g. tf.equal
     from returnn.tf.util.basic import opt_logical_and
-    common_data = Data.get_common_data([s.output for s in self.sources])
-    x = self.sources[0].output.copy_compatible_to(common_data).placeholder
+    common_data = self.output
+    x = self.sources[0].output.copy_compatible_to(common_data, check_dtype=False).placeholder
     r_last = True
     for source in self.sources[1:]:
-      x2 = source.output.copy_compatible_to(common_data).placeholder
+      x2 = source.output.copy_compatible_to(common_data, check_dtype=False).placeholder
       r_last = opt_logical_and(r_last, op(x, x2))
       x = x2
     if value is not None:
@@ -6904,16 +6904,24 @@ def __init__(self, kind="equal", value=None, **kwargs):
     self.output.placeholder = r_last
 
   @classmethod
-  def get_out_data_from_opts(cls, n_out=NotSpecified, out_type=None, sources=(), **kwargs):
+  def get_out_data_from_opts(cls, n_out=NotSpecified, out_type=None, out_shape=None, sources=(), **kwargs):
     """
     :param int|None|NotSpecified n_out:
     :param dict[str]|None out_type:
+    :param dict[str]|None out_shape:
     :param list[LayerBase] sources:
     :rtype: Data
     """
     out_type_ = {}
     if sources:
-      out_type_.update(Data.get_common_data([s.output for s in sources]).get_kwargs())
+      allow_broadcast_all_sources = NotSpecified
+      if out_shape is not None:
+        allow_broadcast_all_sources = True
+      elif out_type and isinstance(out_type, dict) and ("shape" in out_type or "dim_tags" in out_type):
+        allow_broadcast_all_sources = True
+      out_type_.update(
+        Data.get_common_data(
+          [s.output for s in sources], allow_broadcast_all_sources=allow_broadcast_all_sources).get_kwargs())
     if n_out is not NotSpecified:
       out_type_["dim"] = n_out
     elif out_type_.get("sparse", False):


Test patch:
diff --git a/tests/test_TFNetworkLayer.py b/tests/test_TFNetworkLayer.py
--- a/tests/test_TFNetworkLayer.py
+++ b/tests/test_TFNetworkLayer.py
@@ -1246,6 +1246,26 @@ def _eval_seq_lens(source, **_kwargs):
     session.run((out_data.placeholder, out_data.get_sequence_lengths()), feed_dict=feed_dict)
 
 
+def test_CompareLayer_allow_broadcast_all_sources():
+  from returnn.tf.util.data import BatchDim, DimensionTag
+  time_tag = DimensionTag(DimensionTag.Types.Spatial, description="time")
+  with make_scope():
+    n_out = 5
+    config = Config({
+      "debug_print_layer_output_template": True,
+      "extern_data": {
+        "data": {"dim": n_out, "same_dim_tags_as": {"t": time_tag}}
+      }})
+    net = TFNetwork(config=config, train_flag=True)
+    net.construct_from_dict({
+      "range0": {"class": "range_in_axis", "from": "data", "axis": "b"},
+      "range1": {"class": "range_in_axis", "from": "data", "axis": "t"},
+      "compare": {
+        "class": "compare", "from": ["range0", "range1"], "kind": "equal", "is_output_layer": True,
+        "out_shape": {BatchDim, time_tag}}
+    })
+
+
 def test_dot_layer_shuffled_remaining_dims_static():
   with make_scope() as session:
     import numpy as np