From 25c48f50fcc332ff7b882806a5dad26d44b00451 Mon Sep 17 00:00:00 2001 From: Gaurav Jain <gjn@google.com> Date: Mon, 18 Mar 2019 16:22:37 -0700 Subject: [PATCH] Expose optimizer options in tf.config PiperOrigin-RevId: 239084110 --- tensorflow/python/BUILD | 2 + tensorflow/python/eager/context.py | 97 +++++++++++++ tensorflow/python/framework/config.py | 78 ++++++++++- tensorflow/python/framework/config_test.py | 131 +++++++++++++++++- .../tools/api/generator/api_init_files.bzl | 1 + .../tools/api/generator/api_init_files_v1.bzl | 1 + .../v1/tensorflow.config.optimizer.pbtxt | 19 +++ .../api/golden/v1/tensorflow.config.pbtxt | 4 + .../v2/tensorflow.config.optimizer.pbtxt | 19 +++ .../api/golden/v2/tensorflow.config.pbtxt | 4 + 10 files changed, 354 insertions(+), 2 deletions(-) create mode 100644 tensorflow/tools/api/golden/v1/tensorflow.config.optimizer.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.config.optimizer.pbtxt diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index e3c026c81c5..8d658faa268 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -1020,8 +1020,10 @@ cuda_py_test( ":constant_op", ":client_testlib", ":platform", + ":test_ops", ":util", ], + tags = ["no_pip"], # test_ops are not available in pip. xla_enable_strict_auto_jit = True, ) diff --git a/tensorflow/python/eager/context.py b/tensorflow/python/eager/context.py index 0092ab54303..a31d22f68e1 100644 --- a/tensorflow/python/eager/context.py +++ b/tensorflow/python/eager/context.py @@ -25,6 +25,7 @@ import random import threading from tensorflow.core.protobuf import config_pb2 +from tensorflow.core.protobuf import rewriter_config_pb2 from tensorflow.python import pywrap_tensorflow from tensorflow.python import tf2 from tensorflow.python.framework import c_api_util @@ -754,6 +755,102 @@ class Context(object): self._config.gpu_options.allow_growth = enabled + @property + def optimizer_jit(self): + level = self._config.graph_options.optimizer_options.global_jit_level + return (level == config_pb2.OptimizerOptions.ON_1 or + level == config_pb2.OptimizerOptions.ON_2) + + @optimizer_jit.setter + def optimizer_jit(self, enabled): + self._config.graph_options.optimizer_options.global_jit_level = ( + config_pb2.OptimizerOptions.ON_1 + if enabled else config_pb2.OptimizerOptions.OFF) + + self._thread_local_data.function_call_options = None + + def get_optimizer_experimental_options(self): + """Get experimental options for the optimizer. + + Returns: + Dictionary of current option values + """ + rewrite_options = self._config.graph_options.rewrite_options + options = {} + + def rewriter_toggle(option): + attr = getattr(rewrite_options, option) + if attr != 0: + options[option] = (attr == rewriter_config_pb2.RewriterConfig.ON) + + def rewriter_bool(option): + options[option] = getattr(rewrite_options, option) + + rewriter_toggle("layout_optimizer") + rewriter_toggle("constant_folding") + rewriter_toggle("shape_optimization") + rewriter_toggle("remapping") + rewriter_toggle("arithmetic_optimization") + rewriter_toggle("dependency_optimization") + rewriter_toggle("loop_optimization") + rewriter_toggle("function_optimization") + rewriter_toggle("debug_stripper") + rewriter_bool("disable_model_pruning") + rewriter_toggle("scoped_allocator_optimization") + rewriter_toggle("pin_to_host_optimization") + rewriter_toggle("implementation_selector") + rewriter_bool("disable_meta_optimizer") + + if rewrite_options.min_graph_nodes != 0: + options["min_graph_nodes"] = rewrite_options.min_graph_nodes + + return options + + def set_optimizer_experimental_options(self, options): + """Set experimental options for the optimizer. + + Args: + options: Dictionary of options to modify + """ + def rewriter_toggle(option): + toggle = options.get(option, None) + if toggle is None: + return + + setattr(self._config.graph_options.rewrite_options, + option, + (rewriter_config_pb2.RewriterConfig.ON + if toggle else rewriter_config_pb2.RewriterConfig.OFF)) + + def rewriter_bool(option): + toggle = options.get(option, None) + if toggle is None: + return + + setattr(self._config.graph_options.rewrite_options, + option, + toggle) + + rewriter_toggle("layout_optimizer") + rewriter_toggle("constant_folding") + rewriter_toggle("shape_optimization") + rewriter_toggle("remapping") + rewriter_toggle("arithmetic_optimization") + rewriter_toggle("dependency_optimization") + rewriter_toggle("loop_optimization") + rewriter_toggle("function_optimization") + rewriter_toggle("debug_stripper") + rewriter_bool("disable_model_pruning") + rewriter_toggle("scoped_allocator_optimization") + rewriter_toggle("pin_to_host_optimization") + rewriter_toggle("implementation_selector") + rewriter_bool("disable_meta_optimizer") + nodes = options.get("min_graph_nodes", None) + if nodes is not None: + self._config.graph_options.rewrite_options.min_graph_nodes = nodes + + self._thread_local_data.function_call_options = None + @property def intra_op_parallelism_threads(self): return self._config.intra_op_parallelism_threads diff --git a/tensorflow/python/framework/config.py b/tensorflow/python/framework/config.py index de79ca3b2ab..7190959f77c 100644 --- a/tensorflow/python/framework/config.py +++ b/tensorflow/python/framework/config.py @@ -122,6 +122,82 @@ def set_inter_op_parallelism_threads(num_threads): context.context().inter_op_parallelism_threads = num_threads +@tf_export('config.optimizer.get_jit') +def get_optimizer_jit(): + """Get if JIT compilation is enabled. + + Note that optimizations are only applied in graph mode, (within tf.function). + + Returns: + If JIT compilation is enabled. + """ + return context.context().optimizer_jit + + +@tf_export('config.optimizer.set_jit') +def set_optimizer_jit(enabled): + """Set if JIT compilation is enabled. + + Args: + enabled: Whether to enable JIT compilation. + """ + context.context().optimizer_jit = enabled + + +@tf_export('config.optimizer.get_experimental_options') +def get_optimizer_experimental_options(): + """Get experimental optimizer options. + + Refer to tf.config.optimizer.set_experimental_options for a list of current + options. + + Note that optimizations are only applied in graph mode, (within tf.function). + In addition, as these are experimental options, the list is subject to change. + + Returns: + Dictionary of configured experimental optimizer options + """ + return context.context().get_optimizer_experimental_options() + + +@tf_export('config.optimizer.set_experimental_options') +def set_optimizer_experimental_options(options): + """Set experimental optimizer options. + + Note that optimizations are only applied in graph mode, (within tf.function). + In addition, as these are experimental options, the list is subject to change. + + Args: + options: Dictionary of experimental optimizer options to configure. + Valid keys: + - layout_optimizer: Optimize tensor layouts + e.g. This will try to use NCHW layout on GPU which is faster. + - constant_folding: Fold constants + Statically infer the value of tensors when possible, and materialize the + result using constants. + - shape_optimization: Simplify computations made on shapes. + - remapping: Remap subgraphs onto more efficient implementations. + - arithmetic_optimization: Simplify arithmetic ops with common + sub-expression elimination and arithmetic simplification. + - dependency_optimization: Control dependency optimizations. Remove + redundant control dependencies, which may enable other optimization. + This optimizer is also essential for pruning Identity and NoOp nodes. + - loop_optimization: Loop optimizations. + - function_optimization: Function optimizations and inlining. + - debug_stripper: Strips debug-related nodes from the graph. + - disable_model_pruning: Disable removal of unnecessary ops from the graph + - scoped_allocator_optimization: Try to allocate some independent Op + outputs contiguously in order to merge or eliminate downstream Ops. + - pin_to_host_optimization: Force small ops onto the CPU. + - implementation_selector: Enable the swap of kernel implementations based + on the device placement. + - disable_meta_optimizer: Disable the entire meta optimizer. + - min_graph_nodes: The minimum number of nodes in a graph to optimizer. + For smaller graphs, optimization is skipped. + """ + context.context().set_optimizer_experimental_options(options) + + @tf_export('config.get_soft_device_placement') def get_soft_device_placement(): """Get if soft device placement is enabled. @@ -147,7 +223,7 @@ def set_soft_device_placement(enabled): 3. need to co-locate with reftype input(s) which are from CPU Args: - enabled: Whether to enabled soft placement. + enabled: Whether to enable soft placement. """ context.context().soft_device_placement = enabled diff --git a/tensorflow/python/framework/config_test.py b/tensorflow/python/framework/config_test.py index e7287c84dbb..9721a67bf1e 100644 --- a/tensorflow/python/framework/config_test.py +++ b/tensorflow/python/framework/config_test.py @@ -18,6 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +from absl.testing import parameterized + from tensorflow.python.eager import context from tensorflow.python.eager import def_function from tensorflow.python.framework import config @@ -25,9 +27,11 @@ from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors from tensorflow.python.framework import ops +from tensorflow.python.framework import test_ops from tensorflow.python.framework import test_util from tensorflow.python.ops import math_ops from tensorflow.python.platform import test +from tensorflow.python.util import compat def reset_eager(fn): @@ -42,7 +46,7 @@ def reset_eager(fn): return wrapper -class ConfigTest(test.TestCase): +class ConfigTest(test.TestCase, parameterized.TestCase): @test_util.run_gpu_only @reset_eager @@ -223,6 +227,131 @@ class ConfigTest(test.TestCase): with self.assertRaises(RuntimeError): context.set_log_device_placement(False) + @test_util.run_gpu_only + @reset_eager + def testJit(self): + self.assertEqual(config.get_optimizer_jit(), False) + + # the following function should cause Op fusion to occur. However, there is + # unfortunately no straightforward way to ensure this. We will just have to + # settle for creating a test that can trigger JIT. + @def_function.function + def fun(a, b): + c = a * b + d = c + a + return d + + a = constant_op.constant([2., 2.]) + b = constant_op.constant([2., 2.]) + + self.evaluate(fun(a, b)) + + config.set_optimizer_jit(True) + self.assertEqual(config.get_optimizer_jit(), True) + self.assertEqual(config.get_optimizer_jit(), + context.context().optimizer_jit) + + self.evaluate(fun(a, b)) + + config.set_optimizer_jit(False) + self.assertEqual(config.get_optimizer_jit(), False) + self.assertEqual(config.get_optimizer_jit(), + context.context().optimizer_jit) + + self.evaluate(fun(a, b)) + + @parameterized.named_parameters( + ('LayoutOptimizer', 'layout_optimizer'), + ('ConstantFolding', 'constant_folding'), + ('ShapeOptimization', 'shape_optimization'), + ('Remapping', 'remapping'), + ('ArithmeticOptimization', 'arithmetic_optimization'), + ('DependencyOptimization', 'dependency_optimization'), + ('LoopOptimization', 'loop_optimization'), + ('FunctionOptimization', 'function_optimization'), + ('DebugStripper', 'debug_stripper'), + ('ScopedAllocatorOptimization', 'scoped_allocator_optimization'), + ('ImplementationSelector', 'implementation_selector')) + @reset_eager + def testOptimizerToggleOption(self, field): + # TODO(b/128531235): Improve testing of option + options = config.get_optimizer_experimental_options() + self.assertIsNone(options.get(field)) + + config.set_optimizer_experimental_options({field: True}) + options[field] = True + self.assertDictEqual(config.get_optimizer_experimental_options(), options) + self.assertDictEqual( + context.context().get_optimizer_experimental_options(), options) + + config.set_optimizer_experimental_options({field: False}) + options[field] = False + self.assertDictEqual(config.get_optimizer_experimental_options(), options) + self.assertDictEqual( + context.context().get_optimizer_experimental_options(), options) + + @parameterized.named_parameters( + ('DisableModelPruning', 'disable_model_pruning'), + ('DisableMetaOptimizer', 'disable_meta_optimizer')) + @reset_eager + def testOptimizerBoolOption(self, field): + # TODO(b/128531235): Improve testing of option + options = config.get_optimizer_experimental_options() + self.assertFalse(options.get(field)) + + config.set_optimizer_experimental_options({field: True}) + options[field] = True + self.assertDictEqual(config.get_optimizer_experimental_options(), options) + self.assertDictEqual( + context.context().get_optimizer_experimental_options(), options) + + config.set_optimizer_experimental_options({field: False}) + options[field] = False + self.assertDictEqual(config.get_optimizer_experimental_options(), options) + self.assertDictEqual( + context.context().get_optimizer_experimental_options(), options) + + @test_util.run_gpu_only + @reset_eager + def testOptimizerToggleOptionPinToHost(self): + options = config.get_optimizer_experimental_options() + self.assertIsNone(options.get('pin_to_host_optimization')) + + @def_function.function + def fun(): + op = test_ops.device_placement_op() + return op + + # Force optimizer to run for all graphs + config.set_optimizer_experimental_options({'min_graph_nodes': -1}) + options['min_graph_nodes'] = -1 + + # Since pin to host is disabled, the operation should go on GPU + gpu = self.evaluate(fun()) + self.assertIn(compat.as_bytes('GPU'), gpu) + + config.set_optimizer_experimental_options( + {'pin_to_host_optimization': True}) + options['pin_to_host_optimization'] = True + self.assertDictEqual(config.get_optimizer_experimental_options(), options) + self.assertDictEqual( + context.context().get_optimizer_experimental_options(), options) + + # Since pin to host is enabled, the operation should go on CPU + cpu = self.evaluate(fun()) + self.assertIn(compat.as_bytes('CPU'), cpu) + + config.set_optimizer_experimental_options( + {'pin_to_host_optimization': False}) + options['pin_to_host_optimization'] = False + self.assertDictEqual(config.get_optimizer_experimental_options(), options) + self.assertDictEqual( + context.context().get_optimizer_experimental_options(), options) + + # Since pin to host is disabled again, the operation should go on GPU + gpu2 = self.evaluate(fun()) + self.assertIn(compat.as_bytes('GPU'), gpu2) + if __name__ == '__main__': ops.enable_eager_execution() diff --git a/tensorflow/python/tools/api/generator/api_init_files.bzl b/tensorflow/python/tools/api/generator/api_init_files.bzl index e4289a3951d..6fd60545dce 100644 --- a/tensorflow/python/tools/api/generator/api_init_files.bzl +++ b/tensorflow/python/tools/api/generator/api_init_files.bzl @@ -12,6 +12,7 @@ TENSORFLOW_API_INIT_FILES = [ "config/__init__.py", "config/experimental/__init__.py", "config/gpu/__init__.py", + "config/optimizer/__init__.py", "config/threading/__init__.py", "data/__init__.py", "data/experimental/__init__.py", diff --git a/tensorflow/python/tools/api/generator/api_init_files_v1.bzl b/tensorflow/python/tools/api/generator/api_init_files_v1.bzl index e65043532d9..66703c0f99e 100644 --- a/tensorflow/python/tools/api/generator/api_init_files_v1.bzl +++ b/tensorflow/python/tools/api/generator/api_init_files_v1.bzl @@ -13,6 +13,7 @@ TENSORFLOW_API_INIT_FILES_V1 = [ "config/__init__.py", "config/experimental/__init__.py", "config/gpu/__init__.py", + "config/optimizer/__init__.py", "config/threading/__init__.py", "data/__init__.py", "data/experimental/__init__.py", diff --git a/tensorflow/tools/api/golden/v1/tensorflow.config.optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.config.optimizer.pbtxt new file mode 100644 index 00000000000..10485affc57 --- /dev/null +++ b/tensorflow/tools/api/golden/v1/tensorflow.config.optimizer.pbtxt @@ -0,0 +1,19 @@ +path: "tensorflow.config.optimizer" +tf_module { + member_method { + name: "get_experimental_options" + argspec: "args=[], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_jit" + argspec: "args=[], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "set_experimental_options" + argspec: "args=[\'options\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "set_jit" + argspec: "args=[\'enabled\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v1/tensorflow.config.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.config.pbtxt index 41e61ac683c..d92d5f9610d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.config.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.config.pbtxt @@ -8,6 +8,10 @@ tf_module { name: "gpu" mtype: "<type \'module\'>" } + member { + name: "optimizer" + mtype: "<type \'module\'>" + } member { name: "threading" mtype: "<type \'module\'>" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.config.optimizer.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.config.optimizer.pbtxt new file mode 100644 index 00000000000..10485affc57 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.config.optimizer.pbtxt @@ -0,0 +1,19 @@ +path: "tensorflow.config.optimizer" +tf_module { + member_method { + name: "get_experimental_options" + argspec: "args=[], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_jit" + argspec: "args=[], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "set_experimental_options" + argspec: "args=[\'options\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "set_jit" + argspec: "args=[\'enabled\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.config.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.config.pbtxt index 41e61ac683c..d92d5f9610d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.config.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.config.pbtxt @@ -8,6 +8,10 @@ tf_module { name: "gpu" mtype: "<type \'module\'>" } + member { + name: "optimizer" + mtype: "<type \'module\'>" + } member { name: "threading" mtype: "<type \'module\'>"