TensorFlow tf.group ignoring dependencies?

ghz 10hours ago ⋅ 1 views

Following on from an earlier question, it seems tf.group is indeed ignoring dependencies. Here's a simple stand-alone example (I have run it on Python 2.7 with TensorFlow 1.1):

import tensorflow as tf
from tensorflow.python.ops import control_flow_ops

xs = [tf.constant(x) for x in range(10)]
xs = [tf.Print(x, [x]) for x in xs]
dependency = None
dxs = []

for x in xs:
    if dependency is None:
        dependency = x
    else:
        dependency = control_flow_ops.with_dependencies([dependency], x)

    dxs.append(dependency)

print_all_op = tf.group(*dxs)

with tf.Session() as session:
    session.run(print_all_op)

Expected output:

2017-05-29 15:11:53.961221: I tensorflow/core/kernels/logging_ops.cc:79] [0]
2017-05-29 15:11:53.961236: I tensorflow/core/kernels/logging_ops.cc:79] [1]
2017-05-29 15:11:53.961255: I tensorflow/core/kernels/logging_ops.cc:79] [2]
2017-05-29 15:11:53.961237: I tensorflow/core/kernels/logging_ops.cc:79] [3]
2017-05-29 15:11:53.961262: I tensorflow/core/kernels/logging_ops.cc:79] [4]
2017-05-29 15:11:53.961263: I tensorflow/core/kernels/logging_ops.cc:79] [5]
2017-05-29 15:11:53.961268: I tensorflow/core/kernels/logging_ops.cc:79] [6]
2017-05-29 15:11:53.961272: I tensorflow/core/kernels/logging_ops.cc:79] [7]
2017-05-29 15:11:53.961274: I tensorflow/core/kernels/logging_ops.cc:79] [8]
2017-05-29 15:11:53.961221: I tensorflow/core/kernels/logging_ops.cc:79] [9]

Actual output (different each time the code is run):

2017-05-29 15:16:26.279655: I tensorflow/core/kernels/logging_ops.cc:79] [0]
2017-05-29 15:16:26.279655: I tensorflow/core/kernels/logging_ops.cc:79] [9]
2017-05-29 15:16:26.279697: I tensorflow/core/kernels/logging_ops.cc:79] [3]
2017-05-29 15:16:26.279660: I tensorflow/core/kernels/logging_ops.cc:79] [1]
2017-05-29 15:16:26.279711: I tensorflow/core/kernels/logging_ops.cc:79] [8]
2017-05-29 15:16:26.279713: I tensorflow/core/kernels/logging_ops.cc:79] [4]
2017-05-29 15:16:26.279723: I tensorflow/core/kernels/logging_ops.cc:79] [5]
2017-05-29 15:16:26.279663: I tensorflow/core/kernels/logging_ops.cc:79] [2]
2017-05-29 15:16:26.279724: I tensorflow/core/kernels/logging_ops.cc:79] [7]
2017-05-29 15:16:26.279728: I tensorflow/core/kernels/logging_ops.cc:79] [6]

There's nothing in the tf.group documentation to indicate why dependencies are ignored.

Is there an alternative to tf.group that does consider dependencies?

Switching to use tf.control_dependencies instead of tensorflow.python.ops.control_flow_ops.with_dependencies doesn't help:

import tensorflow as tf

xs = [tf.constant(x) for x in range(10)]
xs = [tf.Print(x, [x]) for x in xs]
dependency = None
dxs = []

for x in xs:
    if dependency is None:
        dependency = x
    else:
        with tf.control_dependencies([dependency]):
            dependency = x

    dxs.append(dependency)

print_all_op = tf.group(*dxs)

with tf.Session() as session:
    session.run(print_all_op)

Answer

The issue you're encountering stems from how tf.group behaves in TensorFlow. tf.group creates an operation that groups a list of operations to be executed at the same time, but it does not enforce dependencies between them. As you've observed, dependencies are ignored because tf.group doesn't respect the order in which operations are added to the group.

In TensorFlow, the control_dependencies context manager, which is used to enforce the execution order of operations, doesn’t work directly inside tf.group. This is because tf.group doesn’t manage dependencies; it's only for grouping multiple operations together.

To achieve the desired sequential behavior (i.e., ensuring that each tf.Print operation happens after its predecessor), you should use a combination of tf.control_dependencies directly in the loop or outside of the tf.group context. Here's how you can fix it:

Updated Solution

You can directly control the flow of dependencies using tf.control_dependencies in a way that ensures each operation is executed after the previous one.

import tensorflow as tf

xs = [tf.constant(x) for x in range(10)]
xs = [tf.Print(x, [x]) for x in xs]
dependency = None
dxs = []

# Create dependencies using tf.control_dependencies in a proper sequence
for x in xs:
    if dependency is None:
        dependency = x
    else:
        # Ensure each operation depends on the previous one
        with tf.control_dependencies([dependency]):
            dependency = x
    
    dxs.append(dependency)

# The final print operation (for all x) is not grouped here.
with tf.control_dependencies(dxs):
    print_all_op = tf.no_op()  # Placeholder for the operation

# Run the session
with tf.Session() as session:
    session.run(print_all_op)

Key Modifications:

  1. Control Dependencies: Instead of using tf.group, you should use tf.control_dependencies directly to enforce sequential execution. You build up the list of dependencies with tf.control_dependencies([dependency]) for each step.

  2. tf.no_op(): After all dependencies are defined, you can use tf.no_op() as the operation that effectively runs after all dependent operations have been executed. This will ensure that the operations are run sequentially.

Explanation:

  • Control Flow in TensorFlow: tf.control_dependencies ensures that operations are executed in the specified order. By wrapping each dependency operation in tf.control_dependencies, you're ensuring that each operation waits for the previous one to complete before starting.
  • Why tf.group doesn't work as expected: tf.group runs operations in parallel, without respect to the dependencies between them. So when you want ordered execution, you must manage dependencies explicitly.

Output:

When you run this updated version, you should see the print statements in sequential order, as expected.

0
1
2
3
4
5
6
7
8
9

This approach ensures that the operations respect the execution order and should behave as expected, even when using multiple dependencies.