aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/python/eager
diff options
context:
space:
mode:
authorGravatar Allen Lavoie <allenl@google.com>2018-09-11 11:47:14 -0700
committerGravatar TensorFlower Gardener <gardener@tensorflow.org>2018-09-11 11:50:38 -0700
commit29c3c08f23e14eaff1dbd7a3c66139314c045574 (patch)
tree3e870781ce65e648ec4af0dd47a9f00655f0273a /tensorflow/python/eager
parent9b8c30fb0abf42f34c17050ff455d36166fa0e24 (diff)
Convert NumPy arrays to Tensors when they're arguments to a defun
Previously they were counted in the cache key as if they were Tensors, but were not fed as placeholders, leading to stale values when the trace was reused. There is an 8%ish performance impact from the tuple comprehension on the defun no-signature-call microbenchmarks. I don't see a much faster way to do this without rewriting it in C, but I'm open to ideas. I've avoided re-packing the input tuple unless there's actually a numpy array, so this CL will slow down NumPy defun calls more (in addition to the convert_to_tensor overhead). After: entry { name: "MicroBenchmarks.benchmark_defun_with_signature" iters: 30000 wall_time: 134.219272931 extras { key: "examples_per_sec" value { double_value: 7450.49483699 } } } entry { name: "MicroBenchmarks.benchmark_defun_with_signature_and_kwargs" iters: 30000 wall_time: 142.88717111 extras { key: "examples_per_sec" value { double_value: 6998.52892485 } } } entry { name: "MicroBenchmarks.benchmark_defun_without_signature" iters: 30000 wall_time: 76.2096961339 extras { key: "examples_per_sec" value { double_value: 13121.6898994 } } } entry { name: "MicroBenchmarks.benchmark_defun_without_signature_and_with_kwargs" iters: 30000 wall_time: 81.8309704463 extras { key: "examples_per_sec" value { double_value: 12220.3121208 } } } Before: entry { name: "MicroBenchmarks.benchmark_defun_with_signature" iters: 30000 wall_time: 129.392266273 extras { key: "examples_per_sec" value { double_value: 7728.43716862 } } } entry { name: "MicroBenchmarks.benchmark_defun_with_signature_and_kwargs" iters: 30000 wall_time: 141.65956974 extras { key: "examples_per_sec" value { double_value: 7059.1771656 } } } entry { name: "MicroBenchmarks.benchmark_defun_without_signature" iters: 30000 wall_time: 70.6333637238 extras { key: "examples_per_sec" value { double_value: 14157.6154282 } } } entry { name: "MicroBenchmarks.benchmark_defun_without_signature_and_with_kwargs" iters: 30000 wall_time: 78.4090677897 extras { key: "examples_per_sec" value { double_value: 12753.6269489 } } } PiperOrigin-RevId: 212491803
Diffstat (limited to 'tensorflow/python/eager')
-rw-r--r--tensorflow/python/eager/function.py21
-rw-r--r--tensorflow/python/eager/function_test.py9
2 files changed, 26 insertions, 4 deletions
diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py
index 03f12139f6..8c30550708 100644
--- a/tensorflow/python/eager/function.py
+++ b/tensorflow/python/eager/function.py
@@ -34,6 +34,7 @@ from tensorflow.python.eager import execute
from tensorflow.python.eager import tape
from tensorflow.python.eager.graph_only_ops import graph_placeholder
from tensorflow.python.framework import c_api_util
+from tensorflow.python.framework import constant_op
from tensorflow.python.framework import device as pydev
from tensorflow.python.framework import dtypes as dtypes_module
from tensorflow.python.framework import ops
@@ -879,9 +880,6 @@ def _encode_arg(arg):
_TensorType(arg.values.dtype, arg.values._shape_tuple()),
_TensorType(arg.indices.dtype, arg.indices._shape_tuple()),
])
- elif isinstance(arg, np.ndarray):
- tensor = ops.convert_to_tensor(arg)
- return _TensorType(tensor.dtype, tensor._shape_tuple())
# pylint: enable=protected-access
elif isinstance(arg, (list, tuple)):
return tuple([_encode_arg(elem) for elem in arg])
@@ -1089,6 +1087,17 @@ class PolymorphicFunction(object):
# opposed to named arguments called in a keyword-like fashion.
kwds.pop(arg)
inputs = args + _deterministic_dict_values(arg_indices_to_values)
+ flat_inputs = nest.flatten(inputs)
+
+ # Check for NumPy arrays in arguments and convert them to Tensors.
+ need_packing = False
+ for index, value in enumerate(flat_inputs):
+ if isinstance(value, np.ndarray):
+ flat_inputs[index] = constant_op.constant(value)
+ need_packing = True
+ if need_packing:
+ inputs = nest.pack_sequence_as(structure=inputs,
+ flat_sequence=flat_inputs)
if self._input_signature is None:
return inputs, kwds
else:
@@ -1098,7 +1107,6 @@ class PolymorphicFunction(object):
except (ValueError, TypeError):
raise ValueError("Structure of Python function inputs does not match "
"input_signature.")
- flat_inputs = nest.flatten(inputs)
if any(not isinstance(arg, ops.Tensor) for arg in flat_inputs):
raise ValueError("When input_signature is provided, all inputs to "
"the Python function must be Tensors.")
@@ -1271,6 +1279,11 @@ def defun(func=None, input_signature=None):
tracing the execution of `f(*args, **kwargs)`; this graph is bound to an
input signature inferred from `(*args, **kwargs)` and cached for future reuse.
+ NumPy arrays passed as inputs to `F` are converted to `tf.Tensor` objects
+ before being passed to `f`, and are treated as Tensors for caching. This
+ allows a function to be called multiple times with NumPy arrays having
+ different values but the same shape and dtype without re-tracing each time.
+
`tf.contrib.eager.defun` caches graphs for your convenience, letting you
define TensorFlow functions without explicitly specifying their signatures.
However, this policy is conservative and potentially expensive; for example,
diff --git a/tensorflow/python/eager/function_test.py b/tensorflow/python/eager/function_test.py
index 92254a2c00..6507bc6d71 100644
--- a/tensorflow/python/eager/function_test.py
+++ b/tensorflow/python/eager/function_test.py
@@ -22,6 +22,8 @@ import functools
from multiprocessing.pool import ThreadPool
import sys
+import numpy
+
from tensorflow.core.protobuf import config_pb2
from tensorflow.python.data.ops import iterator_ops
from tensorflow.python.eager import backprop
@@ -314,6 +316,7 @@ class FunctionTest(test.TestCase):
def testDefunNumpyArraysConvertedToTensors(self):
def f(x):
+ self.assertIsInstance(x, ops.Tensor)
return x
x = random_ops.random_uniform([2, 2]).numpy()
@@ -327,6 +330,12 @@ class FunctionTest(test.TestCase):
# shouldn't trigger another function definition.
self.assertEqual(len(defined._function_cache), 1)
+ # Test that the numpy array is properly an argument to the graph function.
+ self.assertEqual(1., defined(numpy.ones([])).numpy())
+ self.assertEqual(0., defined(numpy.zeros([])).numpy())
+ self.assertEqual(1., defined(array_ops.ones([])).numpy())
+ self.assertEqual(0., defined(array_ops.zeros([])).numpy())
+
def testDefunCapturedInt32(self):
x = constant_op.constant(1, dtype=dtypes.int32)