Some code I'm using is trying but failing to copy.deepcopy a @tf.function-decorated function (as part of pickling). Is there a correct way to do this?
Simplified reproduction
Define any simple @tf.function-decorated function:
>>> import tensorflow as tf
>>> @tf.function
... def foo(x): return x * 2
>>> foo
<tensorflow.python.eager.def_function.Function object at 0x7fc98bec0950>
Deepcopying it before initialisation works fine:
>>> import copy
>>> copy.deepcopy(foo)
<tensorflow.python.eager.def_function.Function object at 0x7fc98bec8b50>
However, it fails after initialisation:
>>> foo(tf.constant([3.]))
<tf.Tensor: shape=(1,), dtype=float32, numpy=array([6.], dtype=float32)>
>>> copy.deepcopy(foo)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/user/.pyenv/versions/3.7.13/lib/python3.7/copy.py", line 180, in deepcopy
y = _reconstruct(x, memo, *rv)
....
File "/home/user/.pyenv/versions/3.7.13/lib/python3.7/copy.py", line 169, in deepcopy
rv = reductor(4)
TypeError: can't pickle _thread.RLock objects
Comments
- I don't mind if the copied version is uninitialized and so has to be recompiled on first use, but I don't want to have to recompile the original function every time I copy it.
- The
tensorflow.python.eager.def_function.Functionclass has a__getstate__method that omits the_lockattribute and other "unpickleable objects". However, while this might permit shallow copies it doesn't handle deep ones, since the class objects also contain attributes of the following types, which themselves contain locks somewhere inside them:
_stateful_fn: <class 'tensorflow.python.eager.function.Function'>
_stateless_fn: <class 'tensorflow.python.eager.function.Function'>
_lifted_initializer_graph: <class 'tensorflow.python.framework.func_graph.FuncGraph'>
_graph_deleter: <class 'tensorflow.python.eager.def_function.FunctionDeleter'>
_concrete_stateful_fn: <class 'tensorflow.python.eager.function.ConcreteFunction'>
Environment
Tested in Python 3.7.13 on Ubuntu with both TF 2.8 and TF 2.4.