Skip to content

Commit 8980ab1

Browse files
arnoegwtensorflower-gardener
authored andcommitted
Switch tfgnn.broadcast() to implementation v2 and remove v1.
This adds support for broadcasting to multiple edge sets (or node sets). Matching changes are planned for tfgnn.pool() and the Keras wrappers of both. PiperOrigin-RevId: 533987359
1 parent b01902f commit 8980ab1

4 files changed

Lines changed: 26 additions & 85 deletions

File tree

tensorflow_gnn/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@
152152
broadcast_node_to_edges = broadcast_ops.broadcast_node_to_edges
153153
broadcast_context_to_nodes = broadcast_ops.broadcast_context_to_nodes
154154
broadcast_context_to_edges = broadcast_ops.broadcast_context_to_edges
155-
broadcast = broadcast_ops.broadcast_v1 # TODO(b/265760014): Switch to v2.
155+
broadcast = broadcast_ops.broadcast_v2
156156
pool_edges_to_node = pool_ops_v1.pool_edges_to_node
157157
pool_nodes_to_context = pool_ops_v1.pool_nodes_to_context
158158
pool_edges_to_context = pool_ops_v1.pool_edges_to_context

tensorflow_gnn/graph/broadcast_ops.py

Lines changed: 9 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,8 @@ def _broadcast_context(graph_tensor: GraphTensor,
178178
repeats_sum_hint=node_or_edge_set.spec.total_size)
179179

180180

181-
# TODO(b/265760014): Export as tfgnn.broadcast() and remove broadcast_v1().
182-
# The difference is that v2 supports multiple node/edge sets and v1 does not,
183-
# mirroring the difference between pool_v2() and pool_v1().
184181
def broadcast_v2(
185-
graph: GraphTensor,
182+
graph_tensor: GraphTensor,
186183
from_tag: IncidentNodeOrContextTag,
187184
*,
188185
edge_set_name: Union[Sequence[EdgeSetName], EdgeSetName, None] = None,
@@ -207,7 +204,7 @@ def broadcast_v2(
207204
image of `tfgnn.pool()`, which comes in handy for some algorithms.
208205
209206
Args:
210-
graph: A scalar GraphTensor.
207+
graph_tensor: A scalar GraphTensor.
211208
from_tag: Values are broadcast from context if this is `tfgnn.CONTEXT` or
212209
from the incident node on each edge with this tag.
213210
edge_set_name: The name of the edge set to which values are broadcast, or
@@ -230,10 +227,10 @@ def broadcast_v2(
230227
If a list of names was specified, the result is a list of tensors,
231228
with parallel indices.
232229
"""
233-
gt.check_scalar_graph_tensor(graph, "broadcast()")
230+
gt.check_scalar_graph_tensor(graph_tensor, "broadcast()")
234231
edge_set_names, node_set_names, got_sequence_args = (
235232
tag_utils.get_edge_or_node_set_name_args_for_tag(
236-
graph.spec, from_tag,
233+
graph_tensor.spec, from_tag,
237234
edge_set_name=edge_set_name, node_set_name=node_set_name,
238235
function_name="broadcast()"))
239236
del edge_set_name, node_set_name # Replaced by their cleaned-up versions.
@@ -244,82 +241,18 @@ def broadcast_v2(
244241

245242
if from_tag == const.CONTEXT:
246243
if edge_set_names is not None:
247-
result = [broadcast_context_to_edges(graph, name, **feature_kwargs)
244+
result = [broadcast_context_to_edges(graph_tensor, name, **feature_kwargs)
248245
for name in edge_set_names]
249246
else:
250-
result = [broadcast_context_to_nodes(graph, name, **feature_kwargs)
247+
result = [broadcast_context_to_nodes(graph_tensor, name, **feature_kwargs)
251248
for name in node_set_names]
252249
else:
253-
result = [broadcast_node_to_edges(graph, name, from_tag, **feature_kwargs)
254-
for name in edge_set_names]
250+
result = [
251+
broadcast_node_to_edges(graph_tensor, name, from_tag, **feature_kwargs)
252+
for name in edge_set_names]
255253

256254
if got_sequence_args:
257255
return result
258256
else:
259257
assert len(result) == 1
260258
return result[0]
261-
262-
263-
# TODO(b/265760014): Remove in favor of broadcast_v2().
264-
# The difference is that v2 supports multiple node/edge sets and v1 does not,
265-
# mirroring the difference between pool_v2() and pool_v1().
266-
def broadcast_v1(graph_tensor: GraphTensor,
267-
from_tag: const.IncidentNodeOrContextTag,
268-
*,
269-
edge_set_name: Optional[EdgeSetName] = None,
270-
node_set_name: Optional[NodeSetName] = None,
271-
feature_value: Optional[Field] = None,
272-
feature_name: Optional[FieldName] = None) -> Field:
273-
"""Broadcasts values from nodes to edges, or from context to nodes or edges.
274-
275-
This function broadcasts from context if `from_tag=tfgnn.CONTEXT` and
276-
broadcasts from incident nodes to edges if `from_tag` is an ordinary node tag
277-
like `tfgnn.SOURCE` or `tfgnn.TARGET`. Most user code will not need this
278-
flexibility and can directly call one of the underlying functions
279-
`broadcast_node_to_edges()`, `broadcast_context_to_nodes()`, or
280-
`broadcast_context_to_edges()`.
281-
282-
Args:
283-
graph_tensor: A scalar GraphTensor.
284-
from_tag: Values are broadcast from context if this is `tfgnn.CONTEXT` or
285-
from the incident node on each edge with this tag.
286-
edge_set_name: The name of the edge set to which values are broadcast.
287-
node_set_name: The name of the node set to which values are broadcast.
288-
Can only be set with `from_tag=tfgnn.CONTEXT`. Either edge_set_name or
289-
node_set_name must be set.
290-
feature_value: As for the underlying broadcast_*() function.
291-
feature_name: As for the underlying broadcast_*() function.
292-
Exactly one of feature_name or feature_value must be set.
293-
294-
Returns:
295-
The result of the underlying broadcast_*() function.
296-
"""
297-
_validate_names_and_tag(
298-
from_tag, edge_set_name=edge_set_name, node_set_name=node_set_name)
299-
if from_tag == const.CONTEXT:
300-
if node_set_name is not None:
301-
return broadcast_context_to_nodes(
302-
graph_tensor, node_set_name=node_set_name,
303-
feature_value=feature_value, feature_name=feature_name)
304-
else:
305-
return broadcast_context_to_edges(
306-
graph_tensor, edge_set_name=edge_set_name,
307-
feature_value=feature_value, feature_name=feature_name)
308-
else:
309-
return broadcast_node_to_edges(
310-
graph_tensor, edge_set_name=edge_set_name, node_tag=from_tag,
311-
feature_value=feature_value, feature_name=feature_name)
312-
313-
314-
# TODO(b/265760014): Remove along with broadcast_v1().
315-
def _validate_names_and_tag(tag, *, edge_set_name, node_set_name):
316-
"""Helper for broadcast_v1()."""
317-
if tag == const.CONTEXT:
318-
num_names = bool(edge_set_name is None) + bool(node_set_name is None)
319-
if num_names != 1:
320-
raise ValueError("With tag CONTEXT, must pass exactly 1 of "
321-
f"edge_set_name, node_set_name; got {num_names}.")
322-
else:
323-
if edge_set_name is None or node_set_name is not None:
324-
raise ValueError("Must pass edge_set_name but not node_set_name "
325-
"for a tag other than CONTEXT.")

tensorflow_gnn/graph/broadcast_ops_test.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@
2828
as_ragged = tf.ragged.constant
2929

3030

31-
class BroadcastingTest(tf.test.TestCase, parameterized.TestCase):
32-
"""Tests for broadcasting operations."""
31+
class BroadcastXToYTest(tf.test.TestCase, parameterized.TestCase):
32+
"""Tests for basic broadcasting operations broadcast_*_to_*().
33+
34+
For consistency, some tests run the corresponging call to the generic
35+
broadcast_v2() function as well, but see BroadcastV2Test for more on that.
36+
"""
3337

3438
@parameterized.named_parameters(
3539
("WithAdjacency", False),
@@ -73,7 +77,7 @@ def testEdgeFieldFromNode(self, use_hyper_adjacency=False):
7377
graph, "edge", const.SOURCE, feature_name=fname))
7478
self.assertAllEqual(
7579
expected,
76-
broadcast_ops.broadcast_v1(
80+
broadcast_ops.broadcast_v2(
7781
graph, const.SOURCE, edge_set_name="edge", feature_name=fname))
7882
for fname, expected in expected_target_fields.items():
7983
self.assertAllEqual(
@@ -82,7 +86,7 @@ def testEdgeFieldFromNode(self, use_hyper_adjacency=False):
8286
graph, "edge", const.TARGET, feature_name=fname))
8387
self.assertAllEqual(
8488
expected,
85-
broadcast_ops.broadcast_v1(
89+
broadcast_ops.broadcast_v2(
8690
graph, const.TARGET, edge_set_name="edge", feature_name=fname))
8791

8892
@parameterized.parameters([
@@ -138,7 +142,7 @@ def testNodeFieldFromContext(self, description: str, context: gt.Context,
138142
graph, "node", feature_name=fname))
139143
self.assertAllEqual(
140144
expected,
141-
broadcast_ops.broadcast_v1(
145+
broadcast_ops.broadcast_v2(
142146
graph, const.CONTEXT, node_set_name="node", feature_name=fname))
143147

144148
@parameterized.parameters([
@@ -204,12 +208,16 @@ def testEdgeFieldFromContext(self, description: str, context: gt.Context,
204208
graph, "edge", feature_name=fname))
205209
self.assertAllEqual(
206210
expected,
207-
broadcast_ops.broadcast_v1(
211+
broadcast_ops.broadcast_v2(
208212
graph, const.CONTEXT, edge_set_name="edge", feature_name=fname))
209213

210214

211215
class BroadcastV2Test(tf.test.TestCase, parameterized.TestCase):
212-
"""Tests for generic broadcast_v2(), on top of already-tested basic ops."""
216+
"""Tests for generic broadcast_v2() wrapper.
217+
218+
These tests assume correctness of the underlying broadcast_*_to_*() ops;
219+
see BroadcastXtoYTest for these.
220+
"""
213221

214222
def testOneEdgeSetFromTag(self):
215223
input_graph = _get_test_graph_broadcast()

tensorflow_gnn/keras/layers/convolution_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ def bind_receiver_args(fn):
305305
return lambda feature_value, **kwargs: fn(
306306
graph, receiver_tag, **name_kwarg,
307307
feature_value=feature_value, **kwargs)
308-
broadcast_from_receiver = bind_receiver_args(broadcast_ops.broadcast_v1)
308+
broadcast_from_receiver = bind_receiver_args(broadcast_ops.broadcast_v2)
309309
pool_to_receiver = bind_receiver_args(pool_ops_v1.pool_v1)
310310
if self._extra_receiver_ops is None:
311311
extra_receiver_ops_kwarg = {} # Pass no argument for this.

0 commit comments

Comments
 (0)