diff options
author | Allen Lavoie <allenl@google.com> | 2018-09-11 11:47:14 -0700 |
---|---|---|
committer | TensorFlower Gardener <gardener@tensorflow.org> | 2018-09-11 11:50:38 -0700 |
commit | 29c3c08f23e14eaff1dbd7a3c66139314c045574 (patch) | |
tree | 3e870781ce65e648ec4af0dd47a9f00655f0273a /tensorflow/python/eager | |
parent | 9b8c30fb0abf42f34c17050ff455d36166fa0e24 (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.py | 21 | ||||
-rw-r--r-- | tensorflow/python/eager/function_test.py | 9 |
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) |