Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistant behavior of Conv2D between eager mode and tracing #57664

Open
Co1lin opened this issue Sep 11, 2022 · 10 comments
Open

Inconsistant behavior of Conv2D between eager mode and tracing #57664

Co1lin opened this issue Sep 11, 2022 · 10 comments
Assignees
Labels
comp:ops OPs related issues stat:awaiting tensorflower Status - Awaiting response from tensorflower type:bug Bug

Comments

@Co1lin
Copy link

Co1lin commented Sep 11, 2022

Click to expand!

Issue Type

Bug

Source

binary

Tensorflow Version

2.11.0-dev20220910

Custom Code

No

OS Platform and Distribution

No response

Mobile device

No response

Python version

No response

Bazel version

No response

GCC/Compiler version

No response

CUDA/cuDNN version

No response

GPU model and memory

No response

Current Behaviour?

If the input to Conv2D is too small, in graph execution mode, a ValueError will be raised when tracing. However, if we only use eager mode to run, it will output a tensor with dim = 0. So there's an inconsistency under two modes.

If we only create a conv layer and directly use it to compute something, it works well as the eager mode.

conv = layers.Conv2D(1, 2, 1, autocast=False)
x = tf.random.normal([2, 1, 2, 2])
print(conv(x)) # no error

I think the behavior should be consistent in all modes.

Standalone code to reproduce the issue

import tensorflow as tf
from keras import layers

class MyModule(tf.Module):
    def __init__(self):
        self.conv = layers.Conv2D(1, 2, 1, autocast=False)
    
    @tf.function
    def __call__(self, x):
        return self.conv(x)

if __name__ == '__main__':
    model = MyModule()

    tf.config.run_functions_eagerly(True)
    x = tf.random.normal([2, 1, 2, 2])
    print(model(x)) # tf.Tensor([], shape=(2, 0, 1, 1), dtype=float32)

    tf.config.run_functions_eagerly(False)
    x = tf.random.normal([2, 1, 2, 2])
    print(model(x)) # Error when tracing  
    model.__call__.get_concrete_function(x) # Same error if we call this instead of the last line

Relevant log output

tf.Tensor([], shape=(2, 0, 1, 1), dtype=float32) # eagerly running works fine
Traceback (most recent call last):
  File "/home/colin/code/test/conv.py", line 21, in <module>
    model.__call__.get_concrete_function(x)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/eager/def_function.py", line 1233, in get_concrete_function
    concrete = self._get_concrete_function_garbage_collected(*args, **kwargs)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/eager/def_function.py", line 1213, in _get_concrete_function_garbage_collected
    self._initialize(args, kwargs, add_initializers_to=initializers)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/eager/def_function.py", line 778, in _initialize
    self._stateful_fn._get_concrete_function_internal_garbage_collected(  # pylint: disable=protected-access
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/tracing_compiler.py", line 166, in _get_concrete_function_internal_garbage_collected
    concrete_function, _ = self._maybe_define_concrete_function(args, kwargs)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/tracing_compiler.py", line 161, in _maybe_define_concrete_function
    return self._maybe_define_function(args, kwargs)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/tracing_compiler.py", line 364, in _maybe_define_function
    concrete_function = self._create_concrete_function(args, kwargs)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/tracing_compiler.py", line 288, in _create_concrete_function
    func_graph_module.func_graph_from_py_func(
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/framework/func_graph.py", line 1281, in func_graph_from_py_func
    func_outputs = python_func(*func_args, **func_kwargs)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/eager/def_function.py", line 679, in wrapped_fn
    out = weak_wrapped_fn().__wrapped__(*args, **kwds)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/tracing_compiler.py", line 449, in bound_method_wrapper
    return wrapped_fn(*args, **kwargs)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/framework/func_graph.py", line 1267, in autograph_handler
    raise e.ag_error_metadata.to_exception(e)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/framework/func_graph.py", line 1256, in autograph_handler
    return autograph.converted_call(
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/autograph/impl/api.py", line 439, in converted_call
    result = converted_f(*effective_args, **kwargs)
  File "/tmp/__autograph_generated_file6e7qw11z.py", line 12, in tf____call__
    retval_ = ag__.converted_call(ag__.ld(self).conv, (ag__.ld(x),), None, fscope)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/autograph/impl/api.py", line 377, in converted_call
    return _call_unconverted(f, args, kwargs, options)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/autograph/impl/api.py", line 459, in _call_unconverted
    return f(*args)
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/keras/utils/traceback_utils.py", line 70, in error_handler
    raise e.with_traceback(filtered_tb) from None
  File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/tensorflow/python/framework/ops.py", line 1967, in _create_c_op
    raise ValueError(e.message)
ValueError: in user code:

    File "/home/colin/code/test/conv.py", line 10, in __call__  *
        return self.conv(x)
    File "/home/colin/miniconda3/envs/py39/lib/python3.9/site-packages/keras/utils/traceback_utils.py", line 70, in error_handler  **
        raise e.with_traceback(filtered_tb) from None

    ValueError: Exception encountered when calling layer "conv2d" "                 f"(type Conv2D).
    
    Negative dimension size caused by subtracting 2 from 1 for '{{node conv2d/Conv2D}} = Conv2D[T=DT_FLOAT, data_format="NHWC", dilations=[1, 1, 1, 1], explicit_paddings=[], padding="VALID", strides=[1, 1, 1, 1], use_cudnn_on_gpu=true](x, conv2d/Conv2D/ReadVariableOp)' with input shapes: [2,1,2,2], [2,2,2,1].
    
    Call arguments received by layer "conv2d" "                 f"(type Conv2D):
      • inputs=tf.Tensor(shape=(2, 1, 2, 2), dtype=float32)
@google-ml-butler google-ml-butler bot added the type:bug Bug label Sep 11, 2022
@tiruk007 tiruk007 added the comp:keras Keras related issues label Sep 12, 2022
@tiruk007
Copy link
Contributor

@Co1lin
This issue seems to be Keras issue. Please post this issue on keras-team/keras repo. All issues and PRs related to keras will be addressed in that repo.
To know more see;
https://discuss.tensorflow.org/t/keras-project-moved-to-new-repository-in-https-github-com-keras-team-keras/1999.

Thank you!

@tiruk007 tiruk007 added the stat:awaiting response Status - Awaiting response from author label Sep 12, 2022
@Co1lin
Copy link
Author

Co1lin commented Sep 12, 2022

Hi! Thanks for the reply!

Although I used keras, the issue seems to be in the tracing part of TensorFlow. Because I can get the same issue without keras by the following code.

import tensorflow as tf

class MyModule(tf.Module):
    def __init__(self):
        self.kernel = tf.random.normal((2,2,2,1))
    
    @tf.function
    def __call__(self, x):
        return tf.raw_ops.Conv2D(input=x, filter=self.kernel, strides=[1,1,1,1], padding='VALID')

if __name__ == '__main__':
    model = MyModule()

    tf.config.run_functions_eagerly(True)
    x = tf.random.normal([2, 1, 2, 2])
    print(model(x)) # tf.Tensor([], shape=(2, 0, 1, 1), dtype=float32)

    tf.config.run_functions_eagerly(False)
    x = tf.random.normal([2, 1, 2, 2])
    print(model(x)) # ValueError when tracing  
    model.__call__.get_concrete_function(x) # Same error if we call this instead of the last line

Raising this ValueError in tracing / graph execution mode may be reasonable, but I think the behavior of eager running should be the same.

@google-ml-butler google-ml-butler bot removed the stat:awaiting response Status - Awaiting response from author label Sep 12, 2022
@tiruk007 tiruk007 added comp:ops OPs related issues and removed comp:keras Keras related issues labels Sep 12, 2022
@tiruk007
Copy link
Contributor

@Co1lin
I was able to reproduce the issue in the latest TF2.10. Could you please find the gist here and confirm the same?
Please check the workaround here. Please let us know if it helps.
Thank you!

@tiruk007 tiruk007 added the stat:awaiting response Status - Awaiting response from author label Sep 14, 2022
@Co1lin
Copy link
Author

Co1lin commented Sep 14, 2022

@tiruk007 I know if we use SAME instead of VALID, it can always produce valid outputs. But in some already designed model, we have to use VALID padding, which matches the following operators. In this way, the behavior of eager mode and graph execution with tracing should be same. They should do the same shape checking.

@google-ml-butler google-ml-butler bot removed the stat:awaiting response Status - Awaiting response from author label Sep 14, 2022
@tiruk007 tiruk007 assigned gadagashwini and unassigned tiruk007 Sep 14, 2022
@sachinprasadhs
Copy link
Contributor

If you remove the tf.function when you use run_functions_eagerly(False), both tracing produces the same result.

Below is the modified code and the output.

import tensorflow as tf


class MyModule(tf.Module):
    def __init__(self):
        self.kernel = tf.random.normal((2, 2, 2, 1))

    def __call__(self, x):
        return tf.raw_ops.Conv2D(input=x, filter=self.kernel, strides=[1, 1, 1, 1], padding='VALID')


if __name__ == '__main__':
    model = MyModule()

    tf.config.run_functions_eagerly(True)
    x = tf.random.normal([2, 1, 2, 2])
    print(model(x))  # tf.Tensor([], shape=(2, 0, 1, 1), dtype=float32)

    tf.config.run_functions_eagerly(False)
    x = tf.random.normal([2, 1, 2, 2])
    print(model(x))  # ValueError when tracing

tf.Tensor([], shape=(2, 0, 1, 1), dtype=float32)
tf.Tensor([], shape=(2, 0, 1, 1), dtype=float32)

@sachinprasadhs sachinprasadhs added the stat:awaiting response Status - Awaiting response from author label Nov 1, 2022
@google-ml-butler
Copy link

This issue has been automatically marked as stale because it has no recent activity. It will be closed if no further activity occurs. Thank you.

@google-ml-butler google-ml-butler bot added the stale This label marks the issue/pr stale - to be closed automatically if no activity label Nov 9, 2022
@google-ml-butler
Copy link

Closing as stale. Please reopen if you'd like to work on this further.

@google-ml-butler
Copy link

Are you satisfied with the resolution of your issue?
Yes
No

@Co1lin
Copy link
Author

Co1lin commented Dec 27, 2022

Hi @sachinprasadhs ! I understand I can use the workaround for this inconsistency, but for development, I think whether setting running functions eagerly or not should have consistent and expected behaviors because it's better to let the graph execution mode work the same as the eager mode, so that it's transparent for users. In this case, it's quite common to add tf.function decorator for the function the user wants to run in graph execution mode to improve efficiency, like this:

import tensorflow as tf

class MyModule(tf.Module):
    def __init__(self):
        self.kernel = tf.random.normal((2,2,2,1))
    
    @tf.function
    def __call__(self, x):
        print(f'{tf.config.functions_run_eagerly() = }') # False
        return tf.raw_ops.Conv2D(input=x, filter=self.kernel, strides=[1,1,1,1], padding='VALID')

if __name__ == '__main__':
    model = MyModule()
    x = tf.random.normal([2, 1, 2, 2])
    print(model(x)) # ValueError

But this will throw the ValueError that will not appear in eager mode, so I said this is an inconsistency. Switching the mode by tf.config.run_functions_eagerly is just for comparison in one program.

In short, I think the two modes should have the same behavior, so please reopen this issue, and I'm sorry for the late reply.

@sachinprasadhs sachinprasadhs removed stat:awaiting response Status - Awaiting response from author stale This label marks the issue/pr stale - to be closed automatically if no activity labels Dec 30, 2022
@sachinprasadhs sachinprasadhs added the stat:awaiting tensorflower Status - Awaiting response from tensorflower label Dec 30, 2022
@cantonios
Copy link
Contributor

This is likely because we do shape inference in graph mode (but not eager mode), which is attempting to compute the output shape, catching the negative dimension, and sending the error.

In eager mode, we skip shape inference and jump straight to the kernel, which was written generically to handle empty outputs.

We could either change the shape inference function to produce an empty tensor instead of erroring on negative output shape, or we can change the kernel to check the shape and throw the same error as the shape inference function. My preference is probably the first option. But then we need to ensure all backends/devices (XLA, CPU, GPU, oneDNN) can consistently handle empty outputs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
comp:ops OPs related issues stat:awaiting tensorflower Status - Awaiting response from tensorflower type:bug Bug
Projects
None yet
Development

No branches or pull requests

6 participants